[automerger skipped] [DO NOT MERGE] Enable APP_SEARCH_IMPROVEMENTS flag. am: 6b6dbc3442 -s ours
am skip reason: subject contains skip directive
Change-Id: I36aaad811f906d9245302192c62c8931dfc4fd45
diff --git a/Android.bp b/Android.bp
index 4c38205..e132854 100644
--- a/Android.bp
+++ b/Android.bp
@@ -30,3 +30,31 @@
manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
}
+
+java_library_static {
+ name: "launcher_log_protos_lite",
+ srcs: [
+ "protos/*.proto",
+ "proto_overrides/*.proto",
+ ],
+ sdk_version: "current",
+ proto: {
+ type: "lite",
+ local_include_dirs:[
+ "protos",
+ "proto_overrides",
+ ],
+ },
+ static_libs: ["libprotobuf-java-lite"],
+}
+
+java_library {
+ name: "LauncherPluginLib",
+
+ static_libs: ["PluginCoreLib"],
+
+ srcs: ["src_plugins/**/*.java"],
+
+ sdk_version: "current",
+ min_sdk_version: "28",
+}
diff --git a/Android.mk b/Android.mk
index 78ea02a..9cfcf17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,24 +17,6 @@
LOCAL_PATH := $(call my-dir)
#
-# Build rule for plugin lib (needed to write a plugin).
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src_plugins)
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 28
-LOCAL_MODULE := LauncherPluginLib
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#
# Build rule for Launcher3 dependencies lib.
#
include $(CLEAR_VARS)
@@ -48,7 +30,9 @@
androidx.preference_preference \
iconloader_base
-LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ LauncherPluginLib \
+ launcher_log_protos_lite
LOCAL_SRC_FILES := \
$(call all-proto-files-under, protos) \
@@ -78,14 +62,12 @@
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- Launcher3CommonDepsLib \
- SecondaryDisplayLauncherLib
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_shortcuts_overrides) \
- $(call all-java-files-under, src_ui_overrides) \
- $(call all-java-files-under, src_flags)
+ $(call all-java-files-under, src_ui_overrides)
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
# Proguard is disable for testing. Derivarive prjects to keep proguard enabled
@@ -95,7 +77,7 @@
LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_PRODUCT_MODULE := true
+LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
@@ -126,7 +108,7 @@
LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3Go
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_PRODUCT_MODULE := true
+LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
@@ -146,7 +128,10 @@
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ SystemUISharedLib \
+ launcherprotosnano \
+ launcher_log_protos_lite
ifneq (,$(wildcard frameworks/base))
LOCAL_PRIVATE_PLATFORM_APIS := true
else
@@ -155,15 +140,12 @@
endif
LOCAL_MODULE := Launcher3QuickStepLib
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- Launcher3CommonDepsLib \
- SecondaryDisplayLauncherLib
+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, quickstep/recents_ui_overrides/src) \
- $(call all-java-files-under, src_flags) \
$(call all-java-files-under, src_shortcuts_overrides)
LOCAL_RESOURCE_DIR := \
@@ -193,7 +175,7 @@
endif
LOCAL_PACKAGE_NAME := Launcher3QuickStep
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_PRODUCT_MODULE := true
+LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
@@ -202,7 +184,7 @@
$(LOCAL_PATH)/quickstep/recents_ui_overrides/res
LOCAL_FULL_LIBS_MANIFEST_FILES := \
- $(LOCAL_PATH)/AndroidManifest.xml \
+ $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
$(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
@@ -218,7 +200,10 @@
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ SystemUISharedLib \
+ launcherprotosnano \
+ launcher_log_protos_lite
ifneq (,$(wildcard frameworks/base))
LOCAL_PRIVATE_PLATFORM_APIS := true
else
@@ -243,59 +228,13 @@
LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_PRODUCT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3GoIconRecents
+LOCAL_SYSTEM_EXT_MODULE := true
+LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/go/AndroidManifest.xml \
- $(LOCAL_PATH)/AndroidManifest.xml \
- $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-include $(BUILD_PACKAGE)
-
-#
-# Build rule for Launcher3 Go app with quickstep and Go-specific
-# version of recents for Android Go devices.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
-ifneq (,$(wildcard frameworks/base))
- LOCAL_PRIVATE_PLATFORM_APIS := true
-else
- LOCAL_SDK_VERSION := system_current
- LOCAL_MIN_SDK_VERSION := 26
-endif
-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) \
- $(call all-java-files-under, go/quickstep/src)
-
-LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/quickstep/res \
- $(LOCAL_PATH)/go/res \
- $(LOCAL_PATH)/go/quickstep/res
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_PROGUARD_ENABLED := full
-
-LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_PRODUCT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := \
- $(LOCAL_PATH)/go/AndroidManifest.xml \
- $(LOCAL_PATH)/AndroidManifest.xml \
+ $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
$(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 555cc73..c7a0253 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -44,8 +44,11 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
+ <!-- TODO(b/150802536): Enabled only for ENABLE_FIXED_ROTATION_TRANSFORM feature flag -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
<!--
Permissions required for read/write access to the workspace data. These permission name
should not conflict with that defined in other apps, as such an app should embed its package
@@ -70,6 +73,7 @@
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:fullBackupOnly="true"
+ android:backupInForeground="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher_home"
@@ -184,5 +188,20 @@
android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
android:exported="true"
android:enabled="false" />
+
+ <!--
+ Launcher activity for secondary display
+ -->
+ <activity
+ android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
+ android:theme="@style/AppTheme"
+ android:launchMode="singleTop"
+ android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SECONDARY_HOME" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b2c5266..f7bda94 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -53,6 +53,16 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3Go)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3QuickStep)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3QuickStepGo)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3GoIconRecents)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3Go)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3QuickStep)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3QuickStepGo)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3GoIconRecents)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/OWNERS b/OWNERS
index 538ca33..3069afa 100644
--- a/OWNERS
+++ b/OWNERS
@@ -8,8 +8,26 @@
hyunyoungs@google.com
mrcasey@google.com
sunnygoyal@google.com
+awickham@google.com
twickham@google.com
winsonc@google.com
+zakcohen@google.com
+santie@google.com
+vadimt@google.com
+mett@google.com
+jonmiranda@google.com
+pinyaoting@google.com
+sfufa@google.com
+gwasserman@google.com
+jamesoleary@google.com
+joshtrask@google.com
+mrenouf@google.com
+mkephart@google.com
+hwwang@google.com
+tracyzhou@google.com
+peanutbutter@google.com
+xuqiu@google.com
+sreyasr@google.com
-per-file FeatureFlags.java = sunnygoyal@google.com, adamcohen@google.com
-per-file BaseFlags.java = sunnygoyal@google.com, adamcohen@google.com
+per-file FeatureFlags.java, globs = set noparent
+per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, zakcohen@google.com, mrcasey@google.com, adamcohen@google.com, hyunyoungs@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f3db20e..9123959 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,2 @@
[Hook Scripts]
-checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
diff --git a/SecondaryDisplayLauncher/Android.mk b/SecondaryDisplayLauncher/Android.mk
deleted file mode 100644
index 7f305bb..0000000
--- a/SecondaryDisplayLauncher/Android.mk
+++ /dev/null
@@ -1,39 +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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := com.google.android.material_material
-
-LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_MODULE := SecondaryDisplayLauncherLib
-LOCAL_PRIVILEGED_MODULE := true
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/SecondaryDisplayLauncher/AndroidManifest.xml b/SecondaryDisplayLauncher/AndroidManifest.xml
deleted file mode 100644
index ebf6b02..0000000
--- a/SecondaryDisplayLauncher/AndroidManifest.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 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.
-*/
--->
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.launcher3">
-
- <application>
-
- <activity
- android:name="com.android.launcher3.SecondaryDisplayLauncher"
- android:theme="@style/SecondaryLauncherTheme"
- android:launchMode="singleTop"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
- android:enabled="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.SECONDARY_HOME" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
- </application>
-</manifest>
diff --git a/SecondaryDisplayLauncher/res/drawable/ic_settings.xml b/SecondaryDisplayLauncher/res/drawable/ic_settings.xml
deleted file mode 100644
index c269c3b..0000000
--- a/SecondaryDisplayLauncher/res/drawable/ic_settings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
- -->
-
-<vector android:height="24dp" android:tint="#FFFFFF"
- android:viewportHeight="24.0" android:viewportWidth="24.0"
- android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#FF000000" android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
-</vector>
diff --git a/SecondaryDisplayLauncher/res/layout-sw600dp/secondary_display_launcher.xml b/SecondaryDisplayLauncher/res/layout-sw600dp/secondary_display_launcher.xml
deleted file mode 100644
index 46f1674..0000000
--- a/SecondaryDisplayLauncher/res/layout-sw600dp/secondary_display_launcher.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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.
- -->
-
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/RootView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/launcher_bg_color"
- android:fitsSystemWindows="true" >
-
- <GridView
- android:id="@+id/pinned_app_grid"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/app_grid_margin_top"
- android:layout_marginStart="@dimen/app_grid_margin_left"
- android:layout_marginEnd="@dimen/app_grid_margin_right"
- android:columnWidth="@dimen/app_list_col_width"
- android:verticalSpacing="@dimen/app_list_horizontal_spacing"
- android:horizontalSpacing="@dimen/app_list_vertical_spacing"
- android:numColumns="auto_fit" />
-
- <ImageButton
- android:id="@+id/OptionsButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|start"
- android:layout_marginStart="@dimen/options_button_margin"
- android:layout_marginBottom="@dimen/options_button_margin"
- android:src="@drawable/ic_settings"
- android:background="@null"/>
-
- <FrameLayout
- android:layout_width="@dimen/app_picker_width"
- android:layout_height="@dimen/app_picker_height"
- android:layout_gravity="bottom|end"
- android:layout_margin="@dimen/app_picker_fab_margin">
-
- <include layout="@layout/app_picker_layout"/>
- </FrameLayout>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/SecondaryDisplayLauncher/res/layout-sw720dp/secondary_display_launcher.xml b/SecondaryDisplayLauncher/res/layout-sw720dp/secondary_display_launcher.xml
deleted file mode 100644
index 6653a77..0000000
--- a/SecondaryDisplayLauncher/res/layout-sw720dp/secondary_display_launcher.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?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.
- -->
-
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/RootView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/launcher_bg_color"
- android:fitsSystemWindows="true" >
-
- <GridView
- android:id="@+id/pinned_app_grid"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/app_grid_margin_top"
- android:layout_marginStart="@dimen/app_grid_margin_left"
- android:layout_marginEnd="@dimen/app_grid_margin_right"
- android:columnWidth="@dimen/app_list_col_width"
- android:verticalSpacing="@dimen/app_list_horizontal_spacing"
- android:horizontalSpacing="@dimen/app_list_vertical_spacing"
- android:numColumns="auto_fit" />
-
- <ImageButton
- android:id="@+id/OptionsButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|start"
- android:layout_marginStart="@dimen/options_button_margin"
- android:layout_marginBottom="@dimen/options_button_margin"
- android:src="@drawable/ic_settings"
- android:background="@null"/>
-
- <FrameLayout
- android:layout_width="@dimen/app_picker_width"
- android:layout_height="@dimen/app_picker_height"
- android:layout_gravity="bottom|end"
- android:layout_marginEnd="@dimen/app_picker_fab_margin"
- android:layout_marginBottom="@dimen/app_picker_fab_margin">
-
- <include layout="@layout/app_picker_layout"/>
- </FrameLayout>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/SecondaryDisplayLauncher/res/layout/app_grid_item.xml b/SecondaryDisplayLauncher/res/layout/app_grid_item.xml
deleted file mode 100644
index ee5158a..0000000
--- a/SecondaryDisplayLauncher/res/layout/app_grid_item.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center">
-
- <ImageView
- android:id="@+id/app_icon"
- android:layout_width="@dimen/app_icon_width"
- android:layout_height="@dimen/app_icon_height" />
-
- <TextView
- android:id="@+id/app_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:maxLines="1" />
-</LinearLayout>
diff --git a/SecondaryDisplayLauncher/res/layout/app_picker_dialog.xml b/SecondaryDisplayLauncher/res/layout/app_picker_dialog.xml
deleted file mode 100644
index 563a1af..0000000
--- a/SecondaryDisplayLauncher/res/layout/app_picker_dialog.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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.
- -->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <GridView
- android:id="@+id/picker_app_grid"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/app_grid_margin_top"
- android:layout_marginStart="@dimen/app_grid_margin_left"
- android:layout_marginEnd="@dimen/app_grid_margin_right"
- android:columnWidth="@dimen/app_list_col_width"
- android:verticalSpacing="@dimen/app_list_horizontal_spacing"
- android:horizontalSpacing="@dimen/app_list_vertical_spacing"
- android:numColumns="auto_fit" />
-</FrameLayout>
diff --git a/SecondaryDisplayLauncher/res/layout/app_picker_layout.xml b/SecondaryDisplayLauncher/res/layout/app_picker_layout.xml
deleted file mode 100644
index 20f85b1..0000000
--- a/SecondaryDisplayLauncher/res/layout/app_picker_layout.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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.
- -->
-
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <com.google.android.material.circularreveal.cardview.CircularRevealCardView
- android:id="@+id/FloatingSheet"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible">
-
- <GridView
- android:id="@+id/app_grid"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/app_grid_margin_top"
- android:columnWidth="@dimen/app_list_col_width"
- android:verticalSpacing="@dimen/app_list_horizontal_spacing"
- android:horizontalSpacing="@dimen/app_list_vertical_spacing"
- android:numColumns="auto_fit" />
- </com.google.android.material.circularreveal.cardview.CircularRevealCardView>
-
- <com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/FloatingActionButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|end"
- android:src="@drawable/ic_apps"/>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/SecondaryDisplayLauncher/res/layout/secondary_display_launcher.xml b/SecondaryDisplayLauncher/res/layout/secondary_display_launcher.xml
deleted file mode 100644
index 49cd499..0000000
--- a/SecondaryDisplayLauncher/res/layout/secondary_display_launcher.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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.
- -->
-
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/RootView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/launcher_bg_color"
- android:fitsSystemWindows="true" >
-
- <GridView
- android:id="@+id/pinned_app_grid"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/app_grid_margin_top"
- android:layout_marginStart="@dimen/app_grid_margin_left"
- android:layout_marginEnd="@dimen/app_grid_margin_right"
- android:columnWidth="@dimen/app_list_col_width"
- android:verticalSpacing="@dimen/app_list_horizontal_spacing"
- android:horizontalSpacing="@dimen/app_list_vertical_spacing"
- android:numColumns="auto_fit" />
-
- <ImageButton
- android:id="@+id/OptionsButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|start"
- android:layout_marginStart="@dimen/options_button_margin"
- android:layout_marginBottom="@dimen/options_button_margin"
- android:src="@drawable/ic_settings"
- android:background="@null"/>
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="bottom|end"
- android:layout_margin="@dimen/app_picker_fab_margin">
-
- <include layout="@layout/app_picker_layout"/>
- </FrameLayout>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/SecondaryDisplayLauncher/res/menu/context_menu.xml b/SecondaryDisplayLauncher/res/menu/context_menu.xml
deleted file mode 100644
index 6263842..0000000
--- a/SecondaryDisplayLauncher/res/menu/context_menu.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
- -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/add_app_shortcut"
- android:title="@string/add_app_shortcut" />
- <item android:id="@+id/set_wallpaper"
- android:title="@string/set_wallpaper" />
-</menu>
diff --git a/SecondaryDisplayLauncher/res/values-af/strings.xml b/SecondaryDisplayLauncher/res/values-af/strings.xml
deleted file mode 100644
index b544be7a..0000000
--- a/SecondaryDisplayLauncher/res/values-af/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Kon nie die aktiwiteit begin nie"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Voeg programkortpad by"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Stel muurpapier"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-am/strings.xml b/SecondaryDisplayLauncher/res/values-am/strings.xml
deleted file mode 100644
index 71854ad..0000000
--- a/SecondaryDisplayLauncher/res/values-am/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"እንቅስቃሴውን ማስጀመር አልተቻለም"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"የመተግበሪያ አቋራጭ ያክሉ"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ልጣፍ አዘጋጅ"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ar/strings.xml b/SecondaryDisplayLauncher/res/values-ar/strings.xml
deleted file mode 100644
index 9a8adf5..0000000
--- a/SecondaryDisplayLauncher/res/values-ar/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"تعذَّر تفعيل النشاط."</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"إضافة اختصار التطبيق"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ضبط الخلفية"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-as/strings.xml b/SecondaryDisplayLauncher/res/values-as/strings.xml
deleted file mode 100644
index d199a26..0000000
--- a/SecondaryDisplayLauncher/res/values-as/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"কাৰ্যকলাপটো লঞ্চ কৰিব পৰা নগ’ল"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"এপৰ শ্বর্টকাট যোগ কৰক"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ৱালপেপাৰ ছেট কৰক"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-az/strings.xml b/SecondaryDisplayLauncher/res/values-az/strings.xml
deleted file mode 100644
index cee70a0..0000000
--- a/SecondaryDisplayLauncher/res/values-az/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Fəaliyyəti başlatmaq mümkün olmadı"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Tətbiq qısayolu əlavə edin"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Divar kağızı ayarlayın"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-b+sr+Latn/strings.xml b/SecondaryDisplayLauncher/res/values-b+sr+Latn/strings.xml
deleted file mode 100644
index a8859d9..0000000
--- a/SecondaryDisplayLauncher/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspelo"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj prečicu za aplikaciju"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Podesite pozadinu"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-be/strings.xml b/SecondaryDisplayLauncher/res/values-be/strings.xml
deleted file mode 100644
index 3df3760..0000000
--- a/SecondaryDisplayLauncher/res/values-be/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Не ўдалося запусціць дзеянне"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Дадаць ярлык праграмы"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Устанавіць шпалеры"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-bg/strings.xml b/SecondaryDisplayLauncher/res/values-bg/strings.xml
deleted file mode 100644
index 4474815..0000000
--- a/SecondaryDisplayLauncher/res/values-bg/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Активността не можа да бъде стартирана"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Добавяне на пряк път към приложението"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Задаване на тапет"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-bn/strings.xml b/SecondaryDisplayLauncher/res/values-bn/strings.xml
deleted file mode 100644
index 7322691..0000000
--- a/SecondaryDisplayLauncher/res/values-bn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"অ্যাক্টিভিটি চালু করা যায়নি"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"অ্যাপ শর্টকাট যোগ করুন"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ওয়ালপেপার সেট করুন"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-bs/strings.xml b/SecondaryDisplayLauncher/res/values-bs/strings.xml
deleted file mode 100644
index 1e59d33..0000000
--- a/SecondaryDisplayLauncher/res/values-bs/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspjelo"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj prečicu aplikacije"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Postavi pozadinsku sliku"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ca/strings.xml b/SecondaryDisplayLauncher/res/values-ca/strings.xml
deleted file mode 100644
index c0274d1..0000000
--- a/SecondaryDisplayLauncher/res/values-ca/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"No s\'ha pogut iniciar l\'activitat"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Afegeix una drecera d\'aplicació"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Estableix el fons de pantalla"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-cs/strings.xml b/SecondaryDisplayLauncher/res/values-cs/strings.xml
deleted file mode 100644
index 92ed5fa..0000000
--- a/SecondaryDisplayLauncher/res/values-cs/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Aktivitu nelze zahájit"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Přidat zkratku aplikace"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Nastavení tapety"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-da/strings.xml b/SecondaryDisplayLauncher/res/values-da/strings.xml
deleted file mode 100644
index 16bdb33..0000000
--- a/SecondaryDisplayLauncher/res/values-da/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Aktiviteten kunne ikke startes"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Tilføj appgenvej"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Angiv baggrund"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-de/strings.xml b/SecondaryDisplayLauncher/res/values-de/strings.xml
deleted file mode 100644
index 3617a5b..0000000
--- a/SecondaryDisplayLauncher/res/values-de/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Aktivität konnte nicht gestartet werden"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"App-Verknüpfung hinzufügen"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Hintergrund festlegen"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-el/strings.xml b/SecondaryDisplayLauncher/res/values-el/strings.xml
deleted file mode 100644
index 8d19d09..0000000
--- a/SecondaryDisplayLauncher/res/values-el/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Δεν ήταν δυνατή η εκκίνηση της δραστηριότητας"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Προσθήκη συντόμευσης εφαρμογής"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Ορισμός ταπετσαρίας"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rAU/strings.xml b/SecondaryDisplayLauncher/res/values-en-rAU/strings.xml
deleted file mode 100644
index 8d8c419..0000000
--- a/SecondaryDisplayLauncher/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml b/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml
deleted file mode 100644
index 8d8c419..0000000
--- a/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rGB/strings.xml b/SecondaryDisplayLauncher/res/values-en-rGB/strings.xml
deleted file mode 100644
index 8d8c419..0000000
--- a/SecondaryDisplayLauncher/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rIN/strings.xml b/SecondaryDisplayLauncher/res/values-en-rIN/strings.xml
deleted file mode 100644
index 8d8c419..0000000
--- a/SecondaryDisplayLauncher/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml b/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml
deleted file mode 100644
index da69193..0000000
--- a/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-es-rUS/strings.xml b/SecondaryDisplayLauncher/res/values-es-rUS/strings.xml
deleted file mode 100644
index ff6772b..0000000
--- a/SecondaryDisplayLauncher/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"No se pudo iniciar la actividad"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Agregar acceso directo a app"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Establecer fondo de pantalla"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-es/strings.xml b/SecondaryDisplayLauncher/res/values-es/strings.xml
deleted file mode 100644
index 0654dcb..0000000
--- a/SecondaryDisplayLauncher/res/values-es/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"No se ha podido iniciar la acción"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Añadir acceso directo a la aplicación"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Establecer fondo de pantalla"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-et/strings.xml b/SecondaryDisplayLauncher/res/values-et/strings.xml
deleted file mode 100644
index 3410fd4..0000000
--- a/SecondaryDisplayLauncher/res/values-et/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Tegevust ei saanud käivitada"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Lisa rakenduse otsetee"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Määra taustapilt"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-eu/strings.xml b/SecondaryDisplayLauncher/res/values-eu/strings.xml
deleted file mode 100644
index d7abe33..0000000
--- a/SecondaryDisplayLauncher/res/values-eu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Ezin izan da abiarazi jarduera"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Gehitu aplikaziorako lasterbidea"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Ezarri horma-papera"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-fa/strings.xml b/SecondaryDisplayLauncher/res/values-fa/strings.xml
deleted file mode 100644
index 4d3ec4d..0000000
--- a/SecondaryDisplayLauncher/res/values-fa/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"فعالیت راهاندازی نشد"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"افزودن میانبر برنامه"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"تنظیم کاغذدیواری"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-fi/strings.xml b/SecondaryDisplayLauncher/res/values-fi/strings.xml
deleted file mode 100644
index e56f67a..0000000
--- a/SecondaryDisplayLauncher/res/values-fi/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Käynnistäminen epäonnistui"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Lisää sovelluksen pikakuvake"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Aseta taustakuva"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-fr-rCA/strings.xml b/SecondaryDisplayLauncher/res/values-fr-rCA/strings.xml
deleted file mode 100644
index f5c9ba5..0000000
--- a/SecondaryDisplayLauncher/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Impossible de lancer l\'activité"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Ajouter un raccourci vers l\'application"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Définir le fond d\'écran"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-fr/strings.xml b/SecondaryDisplayLauncher/res/values-fr/strings.xml
deleted file mode 100644
index daa186b..0000000
--- a/SecondaryDisplayLauncher/res/values-fr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Impossible de lancer l\'activité"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Ajouter un raccourci vers l\'application"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Configurer le fond d\'écran"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-gl/strings.xml b/SecondaryDisplayLauncher/res/values-gl/strings.xml
deleted file mode 100644
index 0bcf969..0000000
--- a/SecondaryDisplayLauncher/res/values-gl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Non se puido iniciar a actividade"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Engadir atallo da aplicación"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Definir fondo de pantalla"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-gu/strings.xml b/SecondaryDisplayLauncher/res/values-gu/strings.xml
deleted file mode 100644
index 82b4444..0000000
--- a/SecondaryDisplayLauncher/res/values-gu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"પ્રવૃત્તિ લૉન્ચ કરી શકાઈ નથી"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ઍપ શૉર્ટકટ ઉમેરો"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"વૉલપેપર સેટ કરો"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-hi/strings.xml b/SecondaryDisplayLauncher/res/values-hi/strings.xml
deleted file mode 100644
index 8adb519..0000000
--- a/SecondaryDisplayLauncher/res/values-hi/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"गतिविधि लॉन्च नहीं हो सकी"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ऐप्लिकेशन शॉर्टकट जोड़ें"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"वॉलपेपर सेट करें"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-hr/strings.xml b/SecondaryDisplayLauncher/res/values-hr/strings.xml
deleted file mode 100644
index 87ac874..0000000
--- a/SecondaryDisplayLauncher/res/values-hr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspjelo"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Dodajte aplikacijski prečac"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Postavljanje pozadine"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-hu/strings.xml b/SecondaryDisplayLauncher/res/values-hu/strings.xml
deleted file mode 100644
index a8870fc..0000000
--- a/SecondaryDisplayLauncher/res/values-hu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Nem sikerült elindítani a tevékenységet"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Alkalmazás parancsikonjának hozzáadása"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Háttérkép beállítása"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-hy/strings.xml b/SecondaryDisplayLauncher/res/values-hy/strings.xml
deleted file mode 100644
index a64233f..0000000
--- a/SecondaryDisplayLauncher/res/values-hy/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Չհաջողվեց գործարկել գործողությունը"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Ավելացնել հավելվածի դյուրանցումը"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Դարձնել պաստառ"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-in/strings.xml b/SecondaryDisplayLauncher/res/values-in/strings.xml
deleted file mode 100644
index f51d238..0000000
--- a/SecondaryDisplayLauncher/res/values-in/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Tidak dapat meluncurkan aktivitas"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Tambahkan pintasan app"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Setel wallpaper"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-is/strings.xml b/SecondaryDisplayLauncher/res/values-is/strings.xml
deleted file mode 100644
index e8b3e97..0000000
--- a/SecondaryDisplayLauncher/res/values-is/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Ekki tókst að ræsa aðgerðina"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Bæta við flýtileið forrita"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Velja veggfóður"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-it/strings.xml b/SecondaryDisplayLauncher/res/values-it/strings.xml
deleted file mode 100644
index 4941515..0000000
--- a/SecondaryDisplayLauncher/res/values-it/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Impossibile avviare l\'attività"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Aggiungi scorciatoia app"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Imposta sfondo"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-iw/strings.xml b/SecondaryDisplayLauncher/res/values-iw/strings.xml
deleted file mode 100644
index 06b0c42..0000000
--- a/SecondaryDisplayLauncher/res/values-iw/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"לא ניתן היה להפעיל את הפעילות"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"הוספת קיצור דרך של אפליקציה"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"הגדרת טפט"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ja/strings.xml b/SecondaryDisplayLauncher/res/values-ja/strings.xml
deleted file mode 100644
index 3ed7b2b..0000000
--- a/SecondaryDisplayLauncher/res/values-ja/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"アクティビティを開始できませんでした"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"アプリのショートカットを追加"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"壁紙を設定"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ka/strings.xml b/SecondaryDisplayLauncher/res/values-ka/strings.xml
deleted file mode 100644
index ac85f70..0000000
--- a/SecondaryDisplayLauncher/res/values-ka/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"აქტივობის გაშვება ვერ მოხერხდა"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"აპის მალსახმობის დამატება"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ფონის დაყენება"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-kk/strings.xml b/SecondaryDisplayLauncher/res/values-kk/strings.xml
deleted file mode 100644
index f9ac455..0000000
--- a/SecondaryDisplayLauncher/res/values-kk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Әрекет іске қосылмады"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Қолданба таңбашасын енгізу"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Тұсқағаз орнату"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-km/strings.xml b/SecondaryDisplayLauncher/res/values-km/strings.xml
deleted file mode 100644
index afc050f..0000000
--- a/SecondaryDisplayLauncher/res/values-km/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"មិនអាចចាប់ផ្តើមសកម្មភាពទេ"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"បញ្ចូលផ្លូវកាត់កម្មវិធី"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"កំណត់ផ្ទាំងរូបភាព"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-kn/strings.xml b/SecondaryDisplayLauncher/res/values-kn/strings.xml
deleted file mode 100644
index 09c327f..0000000
--- a/SecondaryDisplayLauncher/res/values-kn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"ಚಟುವಟಿಕೆಯನ್ನು ಲಾಂಚ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ಆ್ಯಪ್ ಶಾರ್ಟ್ಕಟ್ ಸೇರಿಸಿ"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ವಾಲ್ಪೇಪರ್ ಹೊಂದಿಸಿ"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ko/strings.xml b/SecondaryDisplayLauncher/res/values-ko/strings.xml
deleted file mode 100644
index 6a02ac0..0000000
--- a/SecondaryDisplayLauncher/res/values-ko/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"활동을 실행할 수 없음"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"앱 바로가기 추가"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"배경화면 설정"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ky/strings.xml b/SecondaryDisplayLauncher/res/values-ky/strings.xml
deleted file mode 100644
index 56185fa..0000000
--- a/SecondaryDisplayLauncher/res/values-ky/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Аракет аткарылган жок"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Колдонмого кыска жол кошуу"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Тушкагаз орнотуу"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-lo/strings.xml b/SecondaryDisplayLauncher/res/values-lo/strings.xml
deleted file mode 100644
index 36a6275..0000000
--- a/SecondaryDisplayLauncher/res/values-lo/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"ບໍ່ສາມາດເປີດໃຊ້ການເຄື່ອນໄຫວໄດ້"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ເພີ່ມທາງລັດແອັບ"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ຕັ້ງເປັນຮູບພື້ນຫຼັງ"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-lt/strings.xml b/SecondaryDisplayLauncher/res/values-lt/strings.xml
deleted file mode 100644
index 8113eb6..0000000
--- a/SecondaryDisplayLauncher/res/values-lt/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Nepavyko paleisti veiklos"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Pridėti programos šaukinį"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Nustatyti ekrano foną"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-lv/strings.xml b/SecondaryDisplayLauncher/res/values-lv/strings.xml
deleted file mode 100644
index e267933..0000000
--- a/SecondaryDisplayLauncher/res/values-lv/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Nevarēja palaist darbību"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Pievienot lietotnes saīsni"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Iestatīt fona tapeti"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-mk/strings.xml b/SecondaryDisplayLauncher/res/values-mk/strings.xml
deleted file mode 100644
index e2cca03..0000000
--- a/SecondaryDisplayLauncher/res/values-mk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Не можеше да се стартува активноста"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Додајте кратенка за апликација"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Поставете го тапетот"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ml/strings.xml b/SecondaryDisplayLauncher/res/values-ml/strings.xml
deleted file mode 100644
index 864245b..0000000
--- a/SecondaryDisplayLauncher/res/values-ml/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"ആക്റ്റിവിറ്റി പ്രകാശിപ്പിക്കാനായില്ല"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ആപ്പ് കുറുക്കുവഴികൾ ചേർക്കുക"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"വാൾപേപ്പർ സജ്ജീകരിക്കുക"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-mn/strings.xml b/SecondaryDisplayLauncher/res/values-mn/strings.xml
deleted file mode 100644
index 85fb020..0000000
--- a/SecondaryDisplayLauncher/res/values-mn/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Үйл ажиллагааг эхлүүж чадсангүй"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Аппын товчлол нэмэх"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Ханын зураг тохируулах"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-mr/strings.xml b/SecondaryDisplayLauncher/res/values-mr/strings.xml
deleted file mode 100644
index 6e92a2f..0000000
--- a/SecondaryDisplayLauncher/res/values-mr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"अॅक्टिव्हिटी लाँच करता आली नाही"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"अॅप शॉर्टकट जोडा"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"वॉलपेपर सेट करा"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ms/strings.xml b/SecondaryDisplayLauncher/res/values-ms/strings.xml
deleted file mode 100644
index fd78053..0000000
--- a/SecondaryDisplayLauncher/res/values-ms/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Tidak dapat melancarkan aktiviti"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Tambah pintasan apl"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Tetapkan kertas dinding"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-my/strings.xml b/SecondaryDisplayLauncher/res/values-my/strings.xml
deleted file mode 100644
index 1521402..0000000
--- a/SecondaryDisplayLauncher/res/values-my/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"လုပ်ဆောင်ချက်ကို စတင်၍မရပါ"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"အက်ပ်ဖြတ်လမ်းလင့်ခ်ထည့်ရန်"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"နောက်ခံ သတ်မှတ်ရန်"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-nb/strings.xml b/SecondaryDisplayLauncher/res/values-nb/strings.xml
deleted file mode 100644
index 945c87b..0000000
--- a/SecondaryDisplayLauncher/res/values-nb/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Kunne ikke starte aktiviteten"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Legg til en appsnarvei"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Angi bakgrunn"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ne/strings.xml b/SecondaryDisplayLauncher/res/values-ne/strings.xml
deleted file mode 100644
index 9a5b0a0..0000000
--- a/SecondaryDisplayLauncher/res/values-ne/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"उक्त क्रियाकलाप सुरु गर्न सकिएन"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"अनुप्रयोगको सर्टकट थप्नुहोस्"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"वालपेपर सेट गर्नुहोस्"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-nl/strings.xml b/SecondaryDisplayLauncher/res/values-nl/strings.xml
deleted file mode 100644
index 8767708..0000000
--- a/SecondaryDisplayLauncher/res/values-nl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Kan de activiteit niet starten"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"App-snelkoppeling toevoegen"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Achtergrond instellen"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-or/strings.xml b/SecondaryDisplayLauncher/res/values-or/strings.xml
deleted file mode 100644
index 9bc5725..0000000
--- a/SecondaryDisplayLauncher/res/values-or/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"ଗତିବିଧିକୁ ଲଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ଆପ୍ ସର୍ଟକଟ୍ ଯୋଗ କରନ୍ତୁ"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ୱାଲ୍ପେପର୍କୁ ସେଟ୍ କରନ୍ତୁ"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-pa/strings.xml b/SecondaryDisplayLauncher/res/values-pa/strings.xml
deleted file mode 100644
index c5dd582..0000000
--- a/SecondaryDisplayLauncher/res/values-pa/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"ਸਰਗਰਮੀ ਨੂੰ ਲਾਂਚ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ਐਪ ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ਵਾਲਪੇਪਰ ਸੈੱਟ ਕਰੋ"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-pl/strings.xml b/SecondaryDisplayLauncher/res/values-pl/strings.xml
deleted file mode 100644
index e8efaed..0000000
--- a/SecondaryDisplayLauncher/res/values-pl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Nie udało się uruchomić aktywności"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj skrót do aplikacji"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Ustaw tapetę"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-pt-rPT/strings.xml b/SecondaryDisplayLauncher/res/values-pt-rPT/strings.xml
deleted file mode 100644
index 67c7557..0000000
--- a/SecondaryDisplayLauncher/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Não foi possível iniciar a atividade."</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Adicionar atalho de aplicação"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Definir imagem de fundo"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-pt/strings.xml b/SecondaryDisplayLauncher/res/values-pt/strings.xml
deleted file mode 100644
index 201fc07..0000000
--- a/SecondaryDisplayLauncher/res/values-pt/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Não foi possível abrir a atividade"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Adicionar atalho de apps"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Definir plano de fundo"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ro/strings.xml b/SecondaryDisplayLauncher/res/values-ro/strings.xml
deleted file mode 100644
index e2e21c5..0000000
--- a/SecondaryDisplayLauncher/res/values-ro/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Nu s-a putut lansa activitatea"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Adăugați comanda rapidă pentru aplicație"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Setați imaginea de fundal"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ru/strings.xml b/SecondaryDisplayLauncher/res/values-ru/strings.xml
deleted file mode 100644
index 64ba00e..0000000
--- a/SecondaryDisplayLauncher/res/values-ru/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Не удалось запустить объект activity"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Добавить ярлык приложения"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Установить обои"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-si/strings.xml b/SecondaryDisplayLauncher/res/values-si/strings.xml
deleted file mode 100644
index ac492eb..0000000
--- a/SecondaryDisplayLauncher/res/values-si/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"ක්රියාකාරකම දියත් කිරීමට නොහැකි විය"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"යෙදුම් කෙටිමඟ එක් කරන්න"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"බිතුපත සකසන්න"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sk/strings.xml b/SecondaryDisplayLauncher/res/values-sk/strings.xml
deleted file mode 100644
index 5e6fa7a..0000000
--- a/SecondaryDisplayLauncher/res/values-sk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Aktivitu sa nepodarilo spustiť"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Pridať odkaz do aplikácie"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Nastaviť tapetu"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sl/strings.xml b/SecondaryDisplayLauncher/res/values-sl/strings.xml
deleted file mode 100644
index f54dec9..0000000
--- a/SecondaryDisplayLauncher/res/values-sl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Dejavnosti ni bilo mogoče zagnati"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj bližnjico do aplikacije"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Nastavi ozadje"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sq/strings.xml b/SecondaryDisplayLauncher/res/values-sq/strings.xml
deleted file mode 100644
index e626dd1..0000000
--- a/SecondaryDisplayLauncher/res/values-sq/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Aktiviteti nuk mund të hapej"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Shto shkurtoren e aplikacionit"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Cakto imazhin e sfondit"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sr/strings.xml b/SecondaryDisplayLauncher/res/values-sr/strings.xml
deleted file mode 100644
index 94214f1..0000000
--- a/SecondaryDisplayLauncher/res/values-sr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Покретање активности није успело"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Додај пречицу за апликацију"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Подесите позадину"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sv/strings.xml b/SecondaryDisplayLauncher/res/values-sv/strings.xml
deleted file mode 100644
index 53e17ef..0000000
--- a/SecondaryDisplayLauncher/res/values-sv/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Det gick inte att starta aktiviteten"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Lägg till appgenväg"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Ange bakgrund"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sw/strings.xml b/SecondaryDisplayLauncher/res/values-sw/strings.xml
deleted file mode 100644
index 490561a..0000000
--- a/SecondaryDisplayLauncher/res/values-sw/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Imeshindwa kuanzisha shughuli"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Ongeza njia ya mkato ya programu"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Weka mandhari"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sw600dp/dimens.xml b/SecondaryDisplayLauncher/res/values-sw600dp/dimens.xml
deleted file mode 100644
index f33a8db..0000000
--- a/SecondaryDisplayLauncher/res/values-sw600dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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="app_picker_width">400dp</dimen>
- <dimen name="app_picker_height">400dp</dimen>
- <dimen name="app_picker_fab_margin">60dp</dimen>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-sw720dp/dimens.xml b/SecondaryDisplayLauncher/res/values-sw720dp/dimens.xml
deleted file mode 100644
index 524e52a..0000000
--- a/SecondaryDisplayLauncher/res/values-sw720dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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="app_picker_width">660dp</dimen>
- <dimen name="app_picker_height">660dp</dimen>
- <dimen name="app_picker_fab_margin">70dp</dimen>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ta/strings.xml b/SecondaryDisplayLauncher/res/values-ta/strings.xml
deleted file mode 100644
index 6bb054a..0000000
--- a/SecondaryDisplayLauncher/res/values-ta/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"நடவடிக்கையைத் துவக்க இயலவில்லை"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ஆப்ஸ் ஷார்ட்கட்டைச் சேர்"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"வால்பேப்பரை அமை"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-te/strings.xml b/SecondaryDisplayLauncher/res/values-te/strings.xml
deleted file mode 100644
index 3dd3c9b..0000000
--- a/SecondaryDisplayLauncher/res/values-te/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"కార్యకలాపాన్ని ప్రారంభించడం సాధ్యం కాలేదు"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"యాప్ షార్ట్కట్ని జోడించు"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"వాల్పేపర్ను సెట్ చేయండి"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-th/strings.xml b/SecondaryDisplayLauncher/res/values-th/strings.xml
deleted file mode 100644
index 6368950..0000000
--- a/SecondaryDisplayLauncher/res/values-th/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"เปิดกิจกรรมไม่ได้"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"เพิ่มทางลัดของแอป"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"ตั้งวอลเปเปอร์"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-tl/strings.xml b/SecondaryDisplayLauncher/res/values-tl/strings.xml
deleted file mode 100644
index 192e5c4..0000000
--- a/SecondaryDisplayLauncher/res/values-tl/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Hindi mailunsad ang aktibidad"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Magdagdag ng shortcut ng app"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Magtakda ng wallpaper"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-tr/strings.xml b/SecondaryDisplayLauncher/res/values-tr/strings.xml
deleted file mode 100644
index e7ed998..0000000
--- a/SecondaryDisplayLauncher/res/values-tr/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"İşlem başlatılamadı"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Uygulama kısayolu ekle"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Duvar kağıdı ayarla"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-uk/strings.xml b/SecondaryDisplayLauncher/res/values-uk/strings.xml
deleted file mode 100644
index e465995..0000000
--- a/SecondaryDisplayLauncher/res/values-uk/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Не вдалося запустити активність"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Розмістити ярлик додатка"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Вибрати фоновий малюнок"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-ur/strings.xml b/SecondaryDisplayLauncher/res/values-ur/strings.xml
deleted file mode 100644
index e4c8641..0000000
--- a/SecondaryDisplayLauncher/res/values-ur/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"سرگرمی کو شروع نہیں کیا جا سکا"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"ایپ شارٹ کٹ شامل کریں"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"وال پیپر سیٹ کریں"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-uz/strings.xml b/SecondaryDisplayLauncher/res/values-uz/strings.xml
deleted file mode 100644
index 585739d..0000000
--- a/SecondaryDisplayLauncher/res/values-uz/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Faollik ishga tushmadi"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Yorliq yaratish"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Fonga rasm oʻrnatish"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-vi/strings.xml b/SecondaryDisplayLauncher/res/values-vi/strings.xml
deleted file mode 100644
index 15a1a44..0000000
--- a/SecondaryDisplayLauncher/res/values-vi/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Không thể chạy hoạt động"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Thêm lối tắt ứng dụng"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Đặt hình nền"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rCN/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 3358499..0000000
--- a/SecondaryDisplayLauncher/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"无法启动该操作组件"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"添加应用快捷方式"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"设置壁纸"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rHK/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rHK/strings.xml
deleted file mode 100644
index bf76f29..0000000
--- a/SecondaryDisplayLauncher/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"無法啟動活動"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"新增應用程式捷徑"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"設定桌布"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
deleted file mode 100644
index c02fe2c..0000000
--- a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"無法啟動活動"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"新增應用程式捷徑"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"套用桌布"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values-zu/strings.xml b/SecondaryDisplayLauncher/res/values-zu/strings.xml
deleted file mode 100644
index ad2f6b9..0000000
--- a/SecondaryDisplayLauncher/res/values-zu/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch" msgid="7873588052226763866">"Ayikwazanga ukuqalisa umsebenzi"</string>
- <string name="add_app_shortcut" msgid="2756755330707509435">"Engeza isinqamuleli sohlelo lokusebenza"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"Setha isithombe sangemuva"</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values/colors.xml b/SecondaryDisplayLauncher/res/values/colors.xml
deleted file mode 100644
index 66b41a2..0000000
--- a/SecondaryDisplayLauncher/res/values/colors.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/colors.xml
-**
-** Copyright 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>
- <color name="launcher_bg_color">#884e8391</color>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values/dimens.xml b/SecondaryDisplayLauncher/res/values/dimens.xml
deleted file mode 100644
index 7cca607..0000000
--- a/SecondaryDisplayLauncher/res/values/dimens.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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="app_list_col_width">72dp</dimen>
- <dimen name="app_list_horizontal_spacing">24dp</dimen>
- <dimen name="app_list_vertical_spacing">24dp</dimen>
- <dimen name="app_icon_width">64dp</dimen>
- <dimen name="app_icon_height">64dp</dimen>
- <dimen name="app_grid_margin_top">24dp</dimen>
- <dimen name="app_grid_margin_left">8dp</dimen>
- <dimen name="app_grid_margin_right">8dp</dimen>
-
- <dimen name="app_picker_width">300dp</dimen>
- <dimen name="app_picker_height">300dp</dimen>
- <dimen name="app_picker_fab_margin">20dp</dimen>
- <dimen name="options_button_margin">20dp</dimen>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values/strings.xml b/SecondaryDisplayLauncher/res/values/strings.xml
deleted file mode 100644
index b68918a..0000000
--- a/SecondaryDisplayLauncher/res/values/strings.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="couldnt_launch">Couldn\'t launch the activity</string>
- <string name="add_app_shortcut">Add app shortcut</string>
- <string name="set_wallpaper">Set wallpaper</string>
-</resources>
diff --git a/SecondaryDisplayLauncher/res/values/styles.xml b/SecondaryDisplayLauncher/res/values/styles.xml
deleted file mode 100644
index 4e41a98..0000000
--- a/SecondaryDisplayLauncher/res/values/styles.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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>
- <style name="SecondaryLauncherTheme" parent="Theme.MaterialComponents.NoActionBar" >
- <item name="android:windowShowWallpaper">true</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowTranslucentStatus">true</item>
- <item name="android:windowTranslucentNavigation">true</item>
- <item name="android:colorBackgroundCacheHint">@null</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- </style>
-</resources>
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java
deleted file mode 100644
index 3017b81..0000000
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Copyright (c) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-
-/** An entry that represents a single activity that can be launched. */
-public class AppEntry {
-
- private String mLabel;
- private Drawable mIcon;
- private Intent mLaunchIntent;
-
- AppEntry(ResolveInfo info, PackageManager packageManager) {
- mLabel = info.loadLabel(packageManager).toString();
- mIcon = info.loadIcon(packageManager);
- mLaunchIntent = new Intent();
- mLaunchIntent.setComponent(new ComponentName(info.activityInfo.packageName,
- info.activityInfo.name));
- }
-
- String getLabel() {
- return mLabel;
- }
-
- Drawable getIcon() {
- return mIcon;
- }
-
- Intent getLaunchIntent() { return mLaunchIntent; }
-
- ComponentName getComponentName() {
- return mLaunchIntent.getComponent();
- }
-
- @Override
- public String toString() {
- return mLabel;
- }
-}
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java
deleted file mode 100644
index aa115cb..0000000
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright (c) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.launcher3.R;
-
-import java.util.List;
-
-/** Adapter for available apps list. */
-public class AppListAdapter extends ArrayAdapter<AppEntry> {
- private final LayoutInflater mInflater;
-
- AppListAdapter(Context context) {
- super(context, android.R.layout.simple_list_item_2);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- void setData(List<AppEntry> data) {
- clear();
- if (data != null) {
- addAll(data);
- }
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view;
-
- if (convertView == null) {
- view = mInflater.inflate(R.layout.app_grid_item, parent, false);
- } else {
- view = convertView;
- }
-
- AppEntry item = getItem(position);
- ((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable(item.getIcon());
- ((TextView)view.findViewById(R.id.app_name)).setText(item.getLabel());
-
- return view;
- }
-}
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java
deleted file mode 100644
index 914fd5e..0000000
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * Copyright (c) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.app.Application;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
-
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.LiveData;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A view model that provides a list of activities that can be launched.
- */
-public class AppListViewModel extends AndroidViewModel {
-
- private final AppListLiveData mLiveData;
- private final PackageIntentReceiver
- mPackageIntentReceiver;
-
- public AppListViewModel(Application application) {
- super(application);
- mLiveData = new AppListLiveData(application);
- mPackageIntentReceiver = new PackageIntentReceiver(mLiveData, application);
- }
-
- public LiveData<List<AppEntry>> getAppList() {
- return mLiveData;
- }
-
- protected void onCleared() {
- getApplication().unregisterReceiver(mPackageIntentReceiver);
- }
-}
-
-class AppListLiveData extends LiveData<List<AppEntry>> {
-
- private final PackageManager mPackageManager;
- private int mCurrentDataVersion;
-
- public AppListLiveData(Context context) {
- mPackageManager = context.getPackageManager();
- loadData();
- }
-
- void loadData() {
- final int loadDataVersion = ++mCurrentDataVersion;
-
- new AsyncTask<Void, Void, List<AppEntry>>() {
- @Override
- protected List<AppEntry> doInBackground(Void... voids) {
- Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
- List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent,
- PackageManager.GET_META_DATA);
-
- List<AppEntry> entries = new ArrayList<>();
- if (apps != null) {
- for (ResolveInfo app : apps) {
- AppEntry entry = new AppEntry(app, mPackageManager);
- entries.add(entry);
- }
- }
- return entries;
- }
-
- @Override
- protected void onPostExecute(List<AppEntry> data) {
- if (mCurrentDataVersion == loadDataVersion) {
- setValue(data);
- }
- }
- }.execute();
- }
-}
-
-/**
- * Receiver used to notify live data about app list changes.
- */
-class PackageIntentReceiver extends BroadcastReceiver {
-
- private final AppListLiveData mLiveData;
-
- public PackageIntentReceiver(AppListLiveData liveData, Context context) {
- mLiveData = liveData;
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addDataScheme("package");
- context.registerReceiver(this, filter);
-
- // Register for events related to sdcard installation.
- IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- context.registerReceiver(this, sdFilter);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mLiveData.loadData();
- }
-}
\ No newline at end of file
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java
deleted file mode 100644
index 4f92038..0000000
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * Copyright (c) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY;
-
-import android.app.Application;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
-
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.LiveData;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A view model that provides a list of activities that were pinned by user to always display on
- * home screen.
- * The pinned activities are stored in {@link SharedPreferences} to keep the sample simple :).
- */
-public class PinnedAppListViewModel extends AndroidViewModel {
-
- final static String PINNED_APPS_KEY = "pinned_apps";
-
- private final PinnedAppListLiveData mLiveData;
-
- public PinnedAppListViewModel(Application application) {
- super(application);
- mLiveData = new PinnedAppListLiveData(application);
- }
-
- public LiveData<List<AppEntry>> getPinnedAppList() {
- return mLiveData;
- }
-}
-
-class PinnedAppListLiveData extends LiveData<List<AppEntry>> {
-
- private final Context mContext;
- private final PackageManager mPackageManager;
- // Store listener reference, so it won't be GC-ed.
- private final SharedPreferences.OnSharedPreferenceChangeListener mChangeListener;
- private int mCurrentDataVersion;
-
- public PinnedAppListLiveData(Context context) {
- mContext = context;
- mPackageManager = context.getPackageManager();
-
- final SharedPreferences prefs = context.getSharedPreferences(PINNED_APPS_KEY, 0);
- mChangeListener = (preferences, key) -> {
- loadData();
- };
- prefs.registerOnSharedPreferenceChangeListener(mChangeListener);
-
- loadData();
- }
-
- private void loadData() {
- final int loadDataVersion = ++mCurrentDataVersion;
-
- new AsyncTask<Void, Void, List<AppEntry>>() {
- @Override
- protected List<AppEntry> doInBackground(Void... voids) {
- List<AppEntry> entries = new ArrayList<>();
-
- final SharedPreferences sp = mContext.getSharedPreferences(PINNED_APPS_KEY, 0);
- final Set<String> pinnedAppsComponents = sp.getStringSet(PINNED_APPS_KEY, null);
- if (pinnedAppsComponents == null) {
- return null;
- }
-
- for (String componentString : pinnedAppsComponents) {
- final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.setComponent(ComponentName.unflattenFromString(componentString));
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
- final List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent,
- PackageManager.GET_META_DATA);
-
- if (apps != null) {
- for (ResolveInfo app : apps) {
- final AppEntry entry = new AppEntry(app, mPackageManager);
- entries.add(entry);
- }
- }
- }
-
- return entries;
- }
-
- @Override
- protected void onPostExecute(List<AppEntry> data) {
- if (mCurrentDataVersion == loadDataVersion) {
- setValue(data);
- }
- }
- }.execute();
- }
-}
\ No newline at end of file
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java
deleted file mode 100644
index 02e6e4a..0000000
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Copyright (c) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.GridView;
-
-import androidx.fragment.app.DialogFragment;
-
-import com.android.launcher3.R;
-
-/**
- * Callback to be invoked when an app was picked.
- */
-interface AppPickedCallback {
- void onAppPicked(AppEntry appEntry);
-}
-
-/**
- * Dialog that provides the user with a list of available apps to pin to the home screen.
- */
-public class PinnedAppPickerDialog extends DialogFragment {
-
- private AppListAdapter mAppListAdapter;
- private AppPickedCallback mAppPickerCallback;
-
- public PinnedAppPickerDialog() {
- }
-
- public static PinnedAppPickerDialog newInstance(AppListAdapter appListAdapter,
- AppPickedCallback callback) {
- PinnedAppPickerDialog
- frag = new PinnedAppPickerDialog();
- frag.mAppListAdapter = appListAdapter;
- frag.mAppPickerCallback = callback;
- return frag;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.app_picker_dialog, container);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- GridView appGridView = view.findViewById(R.id.picker_app_grid);
- appGridView.setAdapter(mAppListAdapter);
- appGridView.setOnItemClickListener((adapterView, itemView, position, id) -> {
- final AppEntry entry = mAppListAdapter.getItem(position);
- mAppPickerCallback.onAppPicked(entry);
- dismiss();
- });
- }
-}
\ No newline at end of file
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
deleted file mode 100644
index 0a2f18f..0000000
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/**
- * Copyright (c) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.app.AlertDialog;
-import android.app.Application;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.GridView;
-import android.widget.ImageButton;
-import android.widget.PopupMenu;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
-
-import com.google.android.material.circularreveal.cardview.CircularRevealCardView;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Secondary launcher activity. It's launch mode is configured as "singleTop" to allow showing on
- * multiple displays and to ensure a single instance per each display.
- */
-public class SecondaryDisplayLauncher extends FragmentActivity implements AppPickedCallback,
- PopupMenu.OnMenuItemClickListener {
-
- private AppListAdapter mAppListAdapter;
- private AppListAdapter mPinnedAppListAdapter;
- private CircularRevealCardView mAppDrawerView;
- private FloatingActionButton mFab;
-
- private boolean mAppDrawerShown;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.secondary_display_launcher);
-
- mAppDrawerView = findViewById(R.id.FloatingSheet);
- mFab = findViewById(R.id.FloatingActionButton);
-
- mFab.setOnClickListener((View v) -> {
- showAppDrawer(true);
- });
-
- final ViewModelProvider viewModelProvider = new ViewModelProvider(getViewModelStore(),
- new AndroidViewModelFactory((Application) getApplicationContext()));
-
- mPinnedAppListAdapter = new AppListAdapter(this);
- final GridView pinnedAppGridView = findViewById(R.id.pinned_app_grid);
- pinnedAppGridView.setAdapter(mPinnedAppListAdapter);
- pinnedAppGridView.setOnItemClickListener((adapterView, view, position, id) -> {
- final AppEntry entry = mPinnedAppListAdapter.getItem(position);
- launch(entry.getLaunchIntent());
- });
- final PinnedAppListViewModel pinnedAppListViewModel =
- viewModelProvider.get(PinnedAppListViewModel.class);
- pinnedAppListViewModel.getPinnedAppList().observe(this, data -> {
- mPinnedAppListAdapter.setData(data);
- });
-
- mAppListAdapter = new AppListAdapter(this);
- final GridView appGridView = findViewById(R.id.app_grid);
- appGridView.setAdapter(mAppListAdapter);
- appGridView.setOnItemClickListener((adapterView, view, position, id) -> {
- final AppEntry entry = mAppListAdapter.getItem(position);
- launch(entry.getLaunchIntent());
- });
- final AppListViewModel appListViewModel = viewModelProvider.get(AppListViewModel.class);
- appListViewModel.getAppList().observe(this, data -> {
- mAppListAdapter.setData(data);
- });
-
- ImageButton optionsButton = findViewById(R.id.OptionsButton);
- optionsButton.setOnClickListener((View v) -> {
- PopupMenu popup = new PopupMenu(this,v);
- popup.setOnMenuItemClickListener(this);
- MenuInflater inflater = popup.getMenuInflater();
- inflater.inflate(R.menu.context_menu, popup.getMenu());
- popup.show();
- });
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // Respond to picking one of the popup menu items.
- final int id = item.getItemId();
- if (id == R.id.add_app_shortcut) {
- FragmentManager fm = getSupportFragmentManager();
- PinnedAppPickerDialog pickerDialogFragment =
- PinnedAppPickerDialog.newInstance(mAppListAdapter, this);
- pickerDialogFragment.show(fm, "fragment_app_picker");
- return true;
- } else if (id == R.id.set_wallpaper) {
- Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
- startActivity(Intent.createChooser(intent, getString(R.string.set_wallpaper)));
- return true;
- }
-
- return true;
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- showAppDrawer(false);
- }
-
- public void onBackPressed() {
- // If the app drawer was shown - hide it. Otherwise, not doing anything since we don't want
- // to close the launcher.
- showAppDrawer(false);
- }
-
- public void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
-
- if (Intent.ACTION_MAIN.equals(intent.getAction())) {
- // Hide keyboard.
- final View v = getWindow().peekDecorView();
- if (v != null && v.getWindowToken() != null) {
- getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
- v.getWindowToken(), 0);
- }
- }
-
- // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
- showAppDrawer(false);
- }
-
- void launch(Intent launchIntent) {
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- startActivity(launchIntent);
- } catch (Exception e) {
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert);
- builder.setTitle(R.string.couldnt_launch)
- .setMessage(e.getLocalizedMessage())
- .setIcon(android.R.drawable.ic_dialog_alert)
- .show();
- }
- }
-
- /**
- * Store the picked app to persistent pinned list and update the loader.
- */
- @Override
- public void onAppPicked(AppEntry appEntry) {
- final SharedPreferences sp = getSharedPreferences(PINNED_APPS_KEY, 0);
- Set<String> pinnedApps = sp.getStringSet(PINNED_APPS_KEY, null);
- if (pinnedApps == null) {
- pinnedApps = new HashSet<String>();
- } else {
- // Always need to create a new object to make sure that the changes are persisted.
- pinnedApps = new HashSet<String>(pinnedApps);
- }
- pinnedApps.add(appEntry.getComponentName().flattenToString());
-
- final SharedPreferences.Editor editor = sp.edit();
- editor.putStringSet(PINNED_APPS_KEY, pinnedApps);
- editor.apply();
- }
-
- /**
- * Show/hide app drawer card with animation.
- */
- private void showAppDrawer(boolean show) {
- if (show == mAppDrawerShown) {
- return;
- }
-
- final Animator animator = revealAnimator(mAppDrawerView, show);
- if (show) {
- mAppDrawerShown = true;
- mAppDrawerView.setVisibility(View.VISIBLE);
- mFab.setVisibility(View.INVISIBLE);
- } else {
- mAppDrawerShown = false;
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mAppDrawerView.setVisibility(View.INVISIBLE);
- mFab.setVisibility(View.VISIBLE);
- }
- });
- }
- animator.start();
- }
-
- /**
- * Create reveal/hide animator for app list card.
- */
- private Animator revealAnimator(View view, boolean open) {
- final int radius = (int) Math.hypot((double) view.getWidth(), (double) view.getHeight());
- return ViewAnimationUtils.createCircularReveal(view, view.getRight(), view.getBottom(),
- open ? 0 : radius, open ? radius : 0);
- }
-}
diff --git a/SharedLibWrapper/build.gradle b/SharedLibWrapper/build.gradle
new file mode 100644
index 0000000..674e38a
--- /dev/null
+++ b/SharedLibWrapper/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'java'
+
+final String ANDROID_TOP = "${rootDir}/../../.."
+final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
+
+sourceSets {
+ main {
+ java.srcDirs = ["${ANDROID_TOP}/frameworks/lib/systemui/SharedLibWrapper/src"]
+ }
+}
+
+sourceCompatibility = 1.8
+
+dependencies {
+ implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
+ compileOnly fileTree(dir: "$ANDROID_TOP/prebuilts/fullsdk-${org.gradle.internal.os.OperatingSystem.current().isMacOsX() ? "darwin" : "linux"}/platforms/${COMPILE_SDK}", include: 'android.jar')
+}
diff --git a/build.gradle b/build.gradle
index e296455..534ca65 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@
repositories {
mavenCentral()
google()
+ jcenter()
}
dependencies {
classpath GRADLE_CLASS_PATH
@@ -62,12 +63,6 @@
minSdkVersion 28
}
- withQuickstepIconRecents {
- dimension "recents"
-
- minSdkVersion 28
- }
-
withoutQuickstep {
dimension "recents"
}
@@ -78,11 +73,6 @@
if (variant.buildType.name.endsWith('release')) {
variant.setIgnore(true)
}
-
- // Icon recents is Go only
- if (name.contains("WithQuickstepIconRecents") && !name.contains("l3go")) {
- variant.setIgnore(true)
- }
}
sourceSets {
@@ -96,10 +86,6 @@
}
}
- debug {
- manifest.srcFile "AndroidManifest.xml"
- }
-
androidTest {
res.srcDirs = ['tests/res']
java.srcDirs = ['tests/src', 'tests/tapl']
@@ -112,15 +98,30 @@
aosp {
java.srcDirs = ['src_flags', 'src_shortcuts_overrides']
+ }
+
+ aospWithoutQuickstep {
manifest.srcFile "AndroidManifest.xml"
}
+ aospWithQuickstep {
+ manifest.srcFile "quickstep/AndroidManifest-launcher.xml"
+ }
+
l3go {
res.srcDirs = ['go/res']
java.srcDirs = ['go/src']
manifest.srcFile "go/AndroidManifest.xml"
}
+ l3goWithoutQuickstepDebug {
+ manifest.srcFile "AndroidManifest.xml"
+ }
+
+ l3goWithQuickstepDebug {
+ manifest.srcFile "quickstep/AndroidManifest-launcher.xml"
+ }
+
withoutQuickstep {
java.srcDirs = ['src_ui_overrides']
}
@@ -130,20 +131,17 @@
java.srcDirs = ['quickstep/src', 'quickstep/recents_ui_overrides/src']
manifest.srcFile "quickstep/AndroidManifest.xml"
}
-
- withQuickstepIconRecents {
- res.srcDirs = ['quickstep/res', 'go/quickstep/res']
- java.srcDirs = ['quickstep/src', 'go/quickstep/src']
- manifest.srcFile "quickstep/AndroidManifest.xml"
- }
}
}
-repositories {
- maven { url "../../../prebuilts/fullsdk-darwin/extras/android/m2repository" }
- maven { url "../../../prebuilts/fullsdk-linux/extras/android/m2repository" }
- mavenCentral()
- google()
+allprojects {
+ repositories {
+ maven { url "../../../prebuilts/sdk/current/androidx/m2repository" }
+ maven { url "../../../prebuilts/fullsdk-darwin/extras/android/m2repository" }
+ maven { url "../../../prebuilts/fullsdk-linux/extras/android/m2repository" }
+ mavenCentral()
+ google()
+ }
}
dependencies {
@@ -151,14 +149,12 @@
implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
implementation project(':IconLoader')
+ withQuickstepImplementation project(':SharedLibWrapper')
implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'launcher_protos.jar')
// Recents lib dependency
withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
- // Recents lib dependency for Go
- withQuickstepIconRecentsImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
-
// Required for AOSP to compile. This is already included in the sysui_shared.jar
withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'plugin_core.jar')
@@ -175,7 +171,7 @@
protobuf {
// Configure the protoc executable
protoc {
- artifact = 'com.google.protobuf:protoc:3.0.0-alpha-3'
+ artifact = 'com.google.protobuf:protoc:3.0.0'
generateProtoTasks {
all().each { task ->
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index fae1eff..f84a82e 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3" >
- <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"/>
+ <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
diff --git a/go/quickstep/res/drawable/default_thumbnail.xml b/go/quickstep/res/drawable/default_thumbnail.xml
deleted file mode 100644
index ab22dcf..0000000
--- a/go/quickstep/res/drawable/default_thumbnail.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@android:color/darker_gray"/>
- <corners android:radius="@dimen/task_thumbnail_corner_radius"/>
-</shape>
diff --git a/go/quickstep/res/layout/clear_all_button.xml b/go/quickstep/res/layout/clear_all_button.xml
deleted file mode 100644
index eef66ad..0000000
--- a/go/quickstep/res/layout/clear_all_button.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/clear_all_item_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/clear_all_item_view_height">
- <Button
- android:id="@+id/clear_all_button"
- android:layout_width="@dimen/clear_all_button_width"
- android:layout_height="match_parent"
- android:layout_gravity="center_horizontal"
- android:background="@drawable/clear_all_button"
- android:gravity="center"
- android:text="@string/recents_clear_all"
- android:textAllCaps="false"
- android:textColor="@color/clear_all_button_text"
- android:textSize="14sp"
- style="@style/TextTitle"/>
-</FrameLayout>
diff --git a/go/quickstep/res/layout/fallback_recents_activity.xml b/go/quickstep/res/layout/fallback_recents_activity.xml
deleted file mode 100644
index 653f463..0000000
--- a/go/quickstep/res/layout/fallback_recents_activity.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.quickstep.fallback.GoRecentsActivityRootView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/drag_layer"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fitsSystemWindows="true">
-
- <include
- android:id="@+id/overview_panel"
- layout="@layout/overview_panel"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:outlineProvider="none"
- android:theme="@style/HomeScreenElementTheme" />
-</com.android.quickstep.fallback.GoRecentsActivityRootView>
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
deleted file mode 100644
index 8381ebc..0000000
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.quickstep.views.IconRecentsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:clipChildren="false">
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/recent_task_recycler_view"
- android:layout_width="@dimen/recents_list_width"
- android:layout_height="match_parent"
- android:layout_gravity="center_horizontal"
- android:scrollbars="none"
- android:clipToPadding="false"
- android:clipChildren="false"/>
- <TextView
- android:id="@+id/recent_task_empty_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:text="@string/recents_empty_message"
- android:textColor="@android:color/white"
- android:textSize="25sp"
- style="@style/TextTitle"
- android:visibility="gone"/>
-</com.android.quickstep.views.IconRecentsView>
\ No newline at end of file
diff --git a/go/quickstep/res/layout/overview_panel.xml b/go/quickstep/res/layout/overview_panel.xml
deleted file mode 100644
index 601edce..0000000
--- a/go/quickstep/res/layout/overview_panel.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<fragment android:name="com.android.quickstep.IconRecentsFragment"
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/low_ram_recents_fragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml
deleted file mode 100644
index aeac477..0000000
--- a/go/quickstep/res/layout/task_item_view.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.quickstep.views.TaskItemView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/task_item_height"
- android:orientation="horizontal"
- android:clipChildren="false">
- <com.android.quickstep.views.TaskThumbnailIconView
- android:id="@+id/task_icon_and_thumbnail"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginHorizontal="@dimen/task_thumbnail_icon_horiz_margin">
- <ImageView
- android:id="@+id/task_thumbnail"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- <ImageView
- android:id="@+id/task_icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- </com.android.quickstep.views.TaskThumbnailIconView>
- <TextView
- android:id="@+id/task_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:singleLine="true"
- android:textColor="@android:color/white"
- android:textSize="24sp"
- style="@style/TextTitle"/>
-</com.android.quickstep.views.TaskItemView>
diff --git a/go/quickstep/res/values-sw480dp/dimens.xml b/go/quickstep/res/values-sw480dp/dimens.xml
deleted file mode 100644
index 571b8a1..0000000
--- a/go/quickstep/res/values-sw480dp/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources>
- <dimen name="recents_list_width">480dp</dimen>
-
- <dimen name="task_item_height">90dp</dimen>
- <dimen name="task_item_top_margin">24dp</dimen>
- <dimen name="task_thumbnail_icon_horiz_margin">24dp</dimen>
-
- <dimen name="task_thumbnail_corner_radius">4dp</dimen>
-
- <dimen name="clear_all_item_view_height">52dp</dimen>
- <dimen name="clear_all_item_view_top_margin">28dp</dimen>
- <dimen name="clear_all_item_view_bottom_margin">28dp</dimen>
- <dimen name="clear_all_button_width">160dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
deleted file mode 100644
index 91040f2..0000000
--- a/go/quickstep/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources>
- <dimen name="recents_list_width">320dp</dimen>
-
- <dimen name="task_item_height">60dp</dimen>
- <dimen name="task_item_top_margin">16dp</dimen>
- <dimen name="task_thumbnail_icon_horiz_margin">16dp</dimen>
-
- <dimen name="task_thumbnail_corner_radius">3dp</dimen>
-
- <dimen name="clear_all_item_view_height">36dp</dimen>
- <dimen name="clear_all_item_view_top_margin">20dp</dimen>
- <dimen name="clear_all_item_view_bottom_margin">20dp</dimen>
- <dimen name="clear_all_button_width">106dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/go/quickstep/res/values/override.xml b/go/quickstep/res/values/override.xml
deleted file mode 100644
index bb267a3..0000000
--- a/go/quickstep/res/values/override.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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.
--->
-
-<!-- Class overrides for Go version of launcher with Go recents. -->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_transition_manager_class" translatable="false">com.android.launcher3.GoLauncherAppTransitionManagerImpl</string>
-
- <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
-
- <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
-
- <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
-</resources>
-
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
deleted file mode 100644
index bcb1f5c..0000000
--- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.android.launcher3;
-
-import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
-import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.os.Handler;
-import android.view.View;
-
-import com.android.quickstep.views.IconRecentsView;
-import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * A {@link QuickstepAppTransitionManagerImpl} with recents-specific app transitions based off
- * {@link com.android.quickstep.views.IconRecentsView}.
- */
-public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
-
- public GoLauncherAppTransitionManagerImpl(Context context) {
- super(context);
- }
-
- @Override
- protected boolean isLaunchingFromRecents(View v, RemoteAnimationTargetCompat[] targets) {
- return mLauncher.getStateManager().getState().overviewUi;
- }
-
- @Override
- RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
- return new GoWallpaperOpenLauncherAnimationRunner(mHandler,
- false /* startAtFrontOfQueue */, fromUnlock);
- }
-
- @Override
- protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
- RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
- // Stubbed. Recents launch animation will come from the recents view itself and will not
- // use remote animations.
- }
-
- @Override
- protected Runnable composeViewContentAnimator(AnimatorSet anim, float[] alphas, float[] trans) {
- IconRecentsView overview = mLauncher.getOverviewPanel();
- ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
- CONTENT_ALPHA, alphas);
- alpha.setDuration(CONTENT_ALPHA_DURATION);
- alpha.setInterpolator(LINEAR);
- anim.play(alpha);
-
- ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
- transY.setInterpolator(AGGRESSIVE_EASE);
- transY.setDuration(CONTENT_TRANSLATION_DURATION);
- anim.play(transY);
-
- return mLauncher.getStateManager()::reapplyState;
- }
-
- /**
- * Remote animation runner for animation from app to Launcher. For Go, when going to recents,
- * we need to ensure that the recents view is ready for remote animation before starting.
- */
- private final class GoWallpaperOpenLauncherAnimationRunner extends
- WallpaperOpenLauncherAnimationRunner {
- public GoWallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
- boolean fromUnlock) {
- super(handler, startAtFrontOfQueue, fromUnlock);
- }
-
- @Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
- AnimationResult result) {
- boolean isGoingToRecents =
- taskIsATargetWithMode(targetCompats, mLauncher.getTaskId(), MODE_OPENING)
- && (mLauncher.getStateManager().getState() == LauncherState.OVERVIEW);
- if (isGoingToRecents) {
- IconRecentsView recentsView = mLauncher.getOverviewPanel();
- if (!recentsView.isReadyForRemoteAnim()) {
- recentsView.setOnReadyForRemoteAnimCallback(() ->
- postAsyncCallback(mHandler, () -> onCreateAnimation(targetCompats, result))
- );
- return;
- }
- }
- super.onCreateAnimation(targetCompats, result);
- }
- }
-}
diff --git a/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java b/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java
deleted file mode 100644
index c12da94..0000000
--- a/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-
-import com.android.quickstep.RecentsToActivityHelper;
-
-/**
- * {@link RecentsToActivityHelper} for when the recents implementation is contained in
- * {@link Launcher}.
- */
-public final class LauncherRecentsToActivityHelper implements RecentsToActivityHelper {
-
- private final Launcher mLauncher;
-
- public LauncherRecentsToActivityHelper(Launcher launcher) {
- mLauncher = launcher;
- }
-
- @Override
- public void leaveRecents() {
- mLauncher.getStateManager().goToState(NORMAL);
- }
-}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
deleted file mode 100644
index cbc77d2..0000000
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
-import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
-import com.android.launcher3.util.TouchController;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.views.IconRecentsView;
-
-import java.util.ArrayList;
-
-/**
- * Provides recents-related {@link UiFactory} logic and classes.
- */
-public abstract class RecentsUiFactory {
-
- public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
-
- public static TouchController[] createTouchControllers(Launcher launcher) {
- ArrayList<TouchController> list = new ArrayList<>();
- list.add(launcher.getDragController());
-
- if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- list.add(new LandscapeStatesTouchController(launcher));
- list.add(new LandscapeEdgeSwipeController(launcher));
- } else {
- boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher)
- .getMode().hasGestures;
- list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
- }
- if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
- && !launcher.getDeviceProfile().isMultiWindowMode
- && !launcher.getDeviceProfile().isVerticalBarLayout()) {
- list.add(new StatusBarTouchController(launcher));
- }
- return list.toArray(new TouchController[list.size()]);
- }
-
- /**
- * Creates and returns the controller responsible for recents view state transitions.
- *
- * @param launcher the launcher activity
- * @return state handler for recents
- */
- public static StateHandler createRecentsViewStateController(Launcher launcher) {
- return new RecentsViewStateController(launcher);
- }
-
- /**
- * Clean-up logic that occurs when recents is no longer in use/visible.
- *
- * @param launcher the launcher activity
- */
- public static void resetOverview(Launcher launcher) {
- IconRecentsView recentsView = launcher.getOverviewPanel();
- recentsView.setTransitionedFromApp(false);
- }
-
- /**
- * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
- *
- * @param launcher the launcher activity
- */
- public static void onLauncherStateOrResumeChanged(Launcher launcher) {}
-
- public static RotationMode getRotationMode(DeviceProfile dp) {
- return RotationMode.NORMAL;
- }
-
- public static void clearSwipeSharedState(boolean finishAnimation) {}
-}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
deleted file mode 100644
index 0b12ab0..0000000
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
-
-import android.util.FloatProperty;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherRecentsToActivityHelper;
-import com.android.quickstep.views.IconRecentsView;
-
-import androidx.annotation.NonNull;
-
-/**
- * State handler for Go's {@link IconRecentsView}.
- */
-public final class RecentsViewStateController extends
- BaseRecentsViewStateController<IconRecentsView> {
-
- public RecentsViewStateController(@NonNull Launcher launcher) {
- super(launcher);
- launcher.<IconRecentsView>getOverviewPanel().setRecentsToActivityHelper(
- new LauncherRecentsToActivityHelper(launcher));
- }
-
- @Override
- FloatProperty<IconRecentsView> getContentAlphaProperty() {
- return CONTENT_ALPHA;
- }
-}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
deleted file mode 100644
index 212ce9b..0000000
--- a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.states;
-
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
-import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.views.IconRecentsView;
-
-/**
- * Definition for overview state
- */
-public class OverviewState extends LauncherState {
-
- // Scale recents takes before animating in
- private static final float RECENTS_PREPARE_SCALE = 1.33f;
-
- private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
- | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
-
- public OverviewState(int id) {
- this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
- }
-
- protected OverviewState(int id, int transitionDuration, int stateFlags) {
- super(id, LauncherLogProto.ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
- }
-
- @Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1f, 0f, 0f);
- }
-
- @Override
- public void onStateEnabled(Launcher launcher) {
- IconRecentsView recentsView = launcher.getOverviewPanel();
- recentsView.onBeginTransitionToOverview();
- recentsView.setShowStatusBarForegroundScrim(true);
- // Request orientation be set to unspecified, letting the system decide the best
- // orientation.
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
- }
-
- @Override
- public void onStateDisabled(Launcher launcher) {
- IconRecentsView recentsView = launcher.getOverviewPanel();
- recentsView.setShowStatusBarForegroundScrim(false);
- }
-
- @Override
- public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
- return new PageAlphaProvider(DEACCEL_2) {
- @Override
- public float getPageAlpha(int pageIndex) {
- return 0;
- }
- };
- }
-
- @Override
- public int getVisibleElements(Launcher launcher) {
- return NONE;
- }
-
- @Override
- public float getWorkspaceScrimAlpha(Launcher launcher) {
- return 0.5f;
- }
-
- @Override
- public String getDescription(Launcher launcher) {
- return launcher.getString(R.string.accessibility_desc_recent_apps);
- }
-
- @Override
- public void onBackPressed(Launcher launcher) {
- // TODO: Add logic to go back to task if coming from a currently running task.
- super.onBackPressed(launcher);
- }
-
- public static float getDefaultSwipeHeight(Launcher launcher) {
- return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
- }
-
- public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
- return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
- }
-
- @Override
- public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
- AnimatorSetBuilder builder) {
- if (fromState == NORMAL && this == OVERVIEW) {
- if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
- builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
- builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
- } else {
- builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
- }
- builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
- builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
-
- View overview = launcher.getOverviewPanel();
- if (overview.getVisibility() != VISIBLE) {
- SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
- }
- }
- }
-
- public static OverviewState newBackgroundState(int id) {
- return new OverviewState(id);
- }
-
- public static OverviewState newPeekState(int id) {
- return new OverviewState(id);
- }
-
- public static OverviewState newSwitchState(int id) {
- return new OverviewState(id);
- }
-}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
deleted file mode 100644
index 66aec40..0000000
--- a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides.touchcontrollers;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-
-import android.view.MotionEvent;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-
-/**
- * Touch controller for landscape mode.
- */
-public final class LandscapeStatesTouchController extends PortraitStatesTouchController {
-
- public LandscapeStatesTouchController(Launcher l) {
- super(l, true /* allowDragToOverview */);
- }
-
- @Override
- protected boolean canInterceptTouch(MotionEvent ev) {
- if (mCurrentAnimation != null) {
- // If we are already animating from a previous state, we can intercept.
- return true;
- }
- if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
- return false;
- }
- if (mLauncher.isInState(ALL_APPS)) {
- // In all-apps only listen if the container cannot scroll itself
- return mLauncher.getAppsView().shouldContainerScroll(ev);
- } else if (mLauncher.isInState(NORMAL)) {
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- if (fromState == ALL_APPS && !isDragTowardPositive) {
- return NORMAL;
- } else if (isDragTowardPositive) {
- return ALL_APPS;
- }
- return fromState;
- }
-
- @Override
- protected int getLogContainerTypeForNormalState(MotionEvent ev) {
- return LauncherLogProto.ContainerType.WORKSPACE;
- }
-}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
deleted file mode 100644
index 011a4e7..0000000
--- a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.touchcontrollers;
-
-import android.view.MotionEvent;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.util.PendingAnimation;
-
-/**
- * Helper class for {@link PortraitStatesTouchController} that determines swipeable regions and
- * animations on the overview state that depend on the recents implementation.
- */
-public final class PortraitOverviewStateTouchHelper {
-
- public PortraitOverviewStateTouchHelper(Launcher launcher) {}
-
- /**
- * Whether or not {@link PortraitStatesTouchController} should intercept the touch when on the
- * overview state.
- *
- * @param ev the motion event
- * @return true if we should intercept the motion event
- */
- boolean canInterceptTouch(MotionEvent ev) {
- // Go does not support swiping to all-apps from recents.
- return false;
- }
-
- /**
- * Whether or not swiping down to leave overview state should return to the currently running
- * task app.
- *
- * @return true if going back should take the user to the currently running task
- */
- boolean shouldSwipeDownReturnToApp() {
- // Go does not support swiping tasks down to launch tasks from recents.
- return false;
- }
-
- /**
- * Create the animation for going from overview to the task app via swiping.
- *
- * @param duration how long the animation should be
- * @return the animation
- */
- PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
- // Go does not support swiping tasks down to launch tasks from recents.
- return null;
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
deleted file mode 100644
index 92900f2..0000000
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.quickstep.views.IconRecentsView.REMOTE_APP_TO_OVERVIEW_DURATION;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
-
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.LauncherAnimationRunner;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.views.IconRecentsView;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * Provider for the atomic remote window animation from the app to the overview.
- *
- * @param <T> activity that contains the overview
- */
-final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
- RemoteAnimationProvider {
- private static final String TAG = "AppToOverviewAnimationProvider";
-
- private final ActivityControlHelper<T> mHelper;
- private final int mTargetTaskId;
- private IconRecentsView mRecentsView;
- private AppToOverviewAnimationListener mAnimationReadyListener;
-
- AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
- mHelper = helper;
- mTargetTaskId = targetTaskId;
- }
-
- /**
- * Set listener to various points in the animation preparing to animate.
- *
- * @param listener listener
- */
- void setAnimationListener(AppToOverviewAnimationListener listener) {
- mAnimationReadyListener = listener;
- }
-
- /**
- * Callback for when the activity is ready/initialized.
- *
- * @param activity the activity that is ready
- * @param wasVisible true if it was visible before
- */
- boolean onActivityReady(T activity, Boolean wasVisible) {
- if (mAnimationReadyListener != null) {
- mAnimationReadyListener.onActivityReady(activity);
- }
- ActivityControlHelper.AnimationFactory factory =
- mHelper.prepareRecentsUI(activity, wasVisible,
- false /* animate activity */, (controller) -> {
- controller.dispatchOnStart();
- ValueAnimator anim = controller.getAnimationPlayer()
- .setDuration(getRecentsLaunchDuration());
- anim.setInterpolator(FAST_OUT_SLOW_IN);
- anim.start();
- });
- factory.onRemoteAnimationReceived(null);
- factory.createActivityController(getRecentsLaunchDuration());
- mRecentsView = activity.getOverviewPanel();
- return false;
- }
-
- /**
- * Create remote window animation from the currently running app to the overview panel. Should
- * be called after {@link #onActivityReady}.
- *
- * @param targetCompats the target apps
- * @return animation from app to overview
- */
- @Override
- public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
- if (mAnimationReadyListener != null) {
- mAnimationReadyListener.onWindowAnimationCreated();
- }
- AnimatorSet anim = new AnimatorSet();
- if (mRecentsView == null) {
- if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "No recents view. Using stub animation.");
- }
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
- return anim;
- }
-
- RemoteAnimationTargetSet targetSet =
- new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
- mRecentsView.setTransitionedFromApp(!targetSet.isAnimatingHome());
-
- RemoteAnimationTargetCompat recentsTarget = null;
- RemoteAnimationTargetCompat closingAppTarget = null;
-
- for (RemoteAnimationTargetCompat target : targetCompats) {
- if (target.mode == MODE_OPENING) {
- recentsTarget = target;
- } else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) {
- closingAppTarget = target;
- }
- }
-
- if (closingAppTarget == null) {
- if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "No closing app target. Using stub animation.");
- }
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
- return anim;
- }
- if (recentsTarget == null) {
- if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "No recents target. Using stub animation.");
- }
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
- return anim;
- }
-
- if (closingAppTarget.activityType == ACTIVITY_TYPE_HOME) {
- mRecentsView.playRemoteHomeToRecentsAnimation(anim, closingAppTarget, recentsTarget);
- } else {
- mRecentsView.playRemoteAppToRecentsAnimation(anim, closingAppTarget, recentsTarget);
- }
-
- return anim;
- }
-
- @Override
- public ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
- LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
- false /* startAtFrontOfQueue */) {
-
- @Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
- AnimationResult result) {
- IconRecentsView recentsView = mRecentsView;
- if (!recentsView.isReadyForRemoteAnim()) {
- recentsView.setOnReadyForRemoteAnimCallback(() -> postAsyncCallback(handler,
- () -> onCreateAnimation(targetCompats, result))
- );
- return;
- }
- result.setAnimation(createWindowAnimation(targetCompats), context);
- }
- };
- return ActivityOptionsCompat.makeRemoteAnimation(
- new RemoteAnimationAdapterCompat(runner, duration,
- 0 /* statusBarTransitionDelay */));
- }
-
- /**
- * Get duration of animation from app to overview.
- *
- * @return duration of animation
- */
- long getRecentsLaunchDuration() {
- return REMOTE_APP_TO_OVERVIEW_DURATION;
- }
-
- /**
- * Listener for various points in the app to overview animation preparing to animate.
- */
- interface AppToOverviewAnimationListener {
- /**
- * Logic for when activity we're animating to is ready
- *
- * @param activity activity to animate to
- */
- void onActivityReady(BaseDraggingActivity activity);
-
- /**
- * Logic for when we've created the app to recents animation.
- */
- void onWindowAnimationCreated();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/ClearAllHolder.java b/go/quickstep/src/com/android/quickstep/ClearAllHolder.java
deleted file mode 100644
index ce87171..0000000
--- a/go/quickstep/src/com/android/quickstep/ClearAllHolder.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-/**
- * Holder for clear all button view in task recycler view.
- */
-final class ClearAllHolder extends ViewHolder {
- public ClearAllHolder(@NonNull View itemView) {
- super(itemView);
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
deleted file mode 100644
index 808cd72..0000000
--- a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static android.view.View.ALPHA;
-
-import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
-import static com.android.quickstep.views.TaskItemView.CONTENT_TRANSITION_PROGRESS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import androidx.recyclerview.widget.SimpleItemAnimator;
-
-import com.android.quickstep.views.TaskItemView;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * An item animator that is only set and used for the transition from the empty loading UI to
- * the filled task content UI. The animation starts from the bottom to top, changing all valid
- * empty item views to be filled and removing all extra empty views.
- */
-public final class ContentFillItemAnimator extends SimpleItemAnimator {
-
- private static final class PendingAnimation {
- ViewHolder viewHolder;
- int animType;
-
- PendingAnimation(ViewHolder vh, int type) {
- viewHolder = vh;
- animType = type;
- }
- }
-
- private static final int ANIM_TYPE_REMOVE = 0;
- private static final int ANIM_TYPE_CHANGE = 1;
-
- private static final int ITEM_BETWEEN_DELAY = 40;
- private static final int ITEM_CHANGE_DURATION = 150;
- private static final int ITEM_REMOVE_DURATION = 150;
-
- /**
- * Animations that have been registered to occur together at the next call of
- * {@link #runPendingAnimations()} but have not started.
- */
- private final ArrayList<PendingAnimation> mPendingAnims = new ArrayList<>();
-
- /**
- * Animations that have started and are running.
- */
- private final ArrayList<ObjectAnimator> mRunningAnims = new ArrayList<>();
-
- private Runnable mOnFinishRunnable;
-
- /**
- * Set runnable to run after the content fill animation is fully completed.
- *
- * @param runnable runnable to run on end
- */
- public void setOnAnimationFinishedRunnable(Runnable runnable) {
- mOnFinishRunnable = runnable;
- }
-
- @Override
- public void setChangeDuration(long changeDuration) {
- throw new UnsupportedOperationException("Cascading item animator cannot have animation "
- + "duration changed.");
- }
-
- @Override
- public void setRemoveDuration(long removeDuration) {
- throw new UnsupportedOperationException("Cascading item animator cannot have animation "
- + "duration changed.");
- }
-
- @Override
- public boolean animateRemove(ViewHolder holder) {
- PendingAnimation pendAnim = new PendingAnimation(holder, ANIM_TYPE_REMOVE);
- mPendingAnims.add(pendAnim);
- return true;
- }
-
- private void animateRemoveImpl(ViewHolder holder, long startDelay) {
- final View view = holder.itemView;
- if (holder.itemView.getAlpha() == 0) {
- // View is already visually removed. We can just get rid of it now.
- view.setAlpha(1.0f);
- dispatchRemoveFinished(holder);
- dispatchFinishedWhenDone();
- return;
- }
- final ObjectAnimator anim = ObjectAnimator.ofFloat(
- holder.itemView, ALPHA, holder.itemView.getAlpha(), 0.0f);
- anim.setDuration(ITEM_REMOVE_DURATION).setStartDelay(startDelay);
- anim.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- dispatchRemoveStarting(holder);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setAlpha(1);
- dispatchRemoveFinished(holder);
- mRunningAnims.remove(anim);
- dispatchFinishedWhenDone();
- }
- }
- );
- anim.start();
- mRunningAnims.add(anim);
- }
-
- @Override
- public boolean animateAdd(ViewHolder holder) {
- dispatchAddFinished(holder);
- return false;
- }
-
- @Override
- public boolean animateMove(ViewHolder holder, int fromX, int fromY, int toX,
- int toY) {
- dispatchMoveFinished(holder);
- return false;
- }
-
- @Override
- public boolean animateChange(ViewHolder oldHolder,
- ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
- // Only support changes where the holders are the same
- if (oldHolder == newHolder) {
- PendingAnimation pendAnim = new PendingAnimation(oldHolder, ANIM_TYPE_CHANGE);
- mPendingAnims.add(pendAnim);
- return true;
- }
- dispatchChangeFinished(oldHolder, true /* oldItem */);
- dispatchChangeFinished(newHolder, false /* oldItem */);
- return false;
- }
-
- private void animateChangeImpl(ViewHolder viewHolder, long startDelay) {
- TaskItemView itemView = (TaskItemView) viewHolder.itemView;
- if (itemView.getAlpha() == 0) {
- // View is still not visible, so we can finish the change immediately.
- CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
- dispatchChangeFinished(viewHolder, true /* oldItem */);
- dispatchFinishedWhenDone();
- return;
- }
- final ObjectAnimator anim =
- ObjectAnimator.ofFloat(itemView, CONTENT_TRANSITION_PROGRESS, 0.0f, 1.0f);
- anim.setDuration(ITEM_CHANGE_DURATION).setStartDelay(startDelay);
- anim.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- dispatchChangeStarting(viewHolder, true /* oldItem */);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
- dispatchChangeFinished(viewHolder, true /* oldItem */);
- mRunningAnims.remove(anim);
- dispatchFinishedWhenDone();
- }
- }
- );
- anim.start();
- mRunningAnims.add(anim);
- }
-
- @Override
- public void runPendingAnimations() {
- // Run animations bottom to top.
- mPendingAnims.sort(Comparator.comparingInt(o -> -o.viewHolder.itemView.getBottom()));
- int delay = 0;
- while (!mPendingAnims.isEmpty()) {
- PendingAnimation curAnim = mPendingAnims.remove(0);
- ViewHolder vh = curAnim.viewHolder;
- switch (curAnim.animType) {
- case ANIM_TYPE_REMOVE:
- animateRemoveImpl(vh, delay);
- break;
- case ANIM_TYPE_CHANGE:
- animateChangeImpl(vh, delay);
- break;
- default:
- break;
- }
- delay += ITEM_BETWEEN_DELAY;
- }
- }
-
- @Override
- public void endAnimation(@NonNull ViewHolder item) {
- for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
- endPendingAnimation(mPendingAnims.get(i));
- mPendingAnims.remove(i);
- }
- dispatchFinishedWhenDone();
- }
-
- @Override
- public void endAnimations() {
- if (!isRunning()) {
- return;
- }
- for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
- endPendingAnimation(mPendingAnims.get(i));
- mPendingAnims.remove(i);
- }
- for (int i = mRunningAnims.size() - 1; i >= 0; i--) {
- ObjectAnimator anim = mRunningAnims.get(i);
- // This calls the on end animation callback which will set values to their end target.
- anim.cancel();
- }
- dispatchFinishedWhenDone();
- }
-
- private void endPendingAnimation(PendingAnimation pendAnim) {
- ViewHolder item = pendAnim.viewHolder;
- switch (pendAnim.animType) {
- case ANIM_TYPE_REMOVE:
- item.itemView.setAlpha(1.0f);
- dispatchRemoveFinished(item);
- break;
- case ANIM_TYPE_CHANGE:
- CONTENT_TRANSITION_PROGRESS.set(item.itemView, 1.0f);
- dispatchChangeFinished(item, true /* oldItem */);
- break;
- default:
- break;
- }
- }
-
- @Override
- public boolean isRunning() {
- return !mPendingAnims.isEmpty() || !mRunningAnims.isEmpty();
- }
-
- @Override
- public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
- @NonNull List<Object> payloads) {
- if (!payloads.isEmpty()
- && (int) payloads.get(0) == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) {
- return true;
- }
- return super.canReuseUpdatedViewHolder(viewHolder, payloads);
- }
-
- private void dispatchFinishedWhenDone() {
- if (!isRunning()) {
- dispatchAnimationsFinished();
- if (mOnFinishRunnable != null) {
- mOnFinishRunnable.run();
- }
- }
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
deleted file mode 100644
index 057b48b..0000000
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.graphics.Rect;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.views.IconRecentsView;
-
-import java.util.function.BiPredicate;
-import java.util.function.Consumer;
-
-/**
- * {@link ActivityControlHelper} for recents when the default launcher is different than the
- * currently running one and apps should interact with the {@link RecentsActivity} as opposed
- * to the in-launcher one.
- */
-public final class FallbackActivityControllerHelper extends
- GoActivityControlHelper<RecentsActivity> {
-
- public FallbackActivityControllerHelper() { }
-
- @Override
- public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
- boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
- if (activityVisible) {
- return (transitionLength) -> { };
- }
-
- IconRecentsView rv = activity.getOverviewPanel();
- rv.setUsingRemoteAnimation(true);
- rv.setAlpha(0);
-
- return new AnimationFactory() {
-
- boolean isAnimatingToRecents = false;
-
- @Override
- public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
- isAnimatingToRecents = targets != null && targets.isAnimatingHome();
- if (!isAnimatingToRecents) {
- rv.setAlpha(1);
- }
- createActivityController(getSwipeUpDestinationAndLength(
- activity.getDeviceProfile(), activity, new Rect()));
- }
-
- @Override
- public void createActivityController(long transitionLength) {
- if (!isAnimatingToRecents) {
- return;
- }
-
- ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
- anim.setDuration(transitionLength).setInterpolator(LINEAR);
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.play(anim);
- callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength));
- }
- };
- }
-
- @Override
- public ActivityInitListener createActivityInitListener(
- BiPredicate<RecentsActivity, Boolean> onInitListener) {
- return new RecentsActivityTracker(onInitListener);
- }
-
- @Nullable
- @Override
- public RecentsActivity getCreatedActivity() {
- return RecentsActivityTracker.getCurrentActivity();
- }
-
- @Nullable
- @Override
- public IconRecentsView getVisibleRecentsView() {
- RecentsActivity activity = getCreatedActivity();
- if (activity != null && activity.hasWindowFocus()) {
- return activity.getOverviewPanel();
- }
- return null;
- }
-
- @Override
- public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
- return false;
- }
-
- @Override
- public int getContainerType() {
- return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
- }
-
- @Override
- public void onLaunchTaskSuccess(RecentsActivity activity) { }
-}
diff --git a/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java b/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java
deleted file mode 100644
index a845f93..0000000
--- a/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-/**
- * {@link RecentsToActivityHelper} for when we are using the fallback recents in
- * {@link BaseRecentsActivity}.
- */
-public final class FallbackRecentsToActivityHelper implements RecentsToActivityHelper {
-
- BaseRecentsActivity mActivity;
-
- public FallbackRecentsToActivityHelper(BaseRecentsActivity activity) {
- mActivity = activity;
- }
-
- @Override
- public void leaveRecents() {
- mActivity.startHome();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java b/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
deleted file mode 100644
index 274a347..0000000
--- a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.android.quickstep;
-
-import android.content.Context;
-import android.graphics.Rect;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * Base activity control helper for Go that stubs out most of the functionality that is not needed
- * for Go.
- *
- * @param <T> activity that contains the overview
- */
-public abstract class GoActivityControlHelper<T extends BaseDraggingActivity> implements
- ActivityControlHelper<T> {
-
- @Override
- public void onTransitionCancelled(T activity, boolean activityVisible) {
- // Go transitions to overview are all atomic.
- }
-
- @Override
- public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
- // TODO Implement outRect depending on where the task should animate to.
- // Go does not support swipe up gesture.
- return 0;
- }
-
- @Override
- public void onSwipeUpToRecentsComplete(T activity) {
- // Go does not support swipe up gesture.
- }
-
- @Override
- public void onAssistantVisibilityChanged(float visibility) {
- // Go does not support assistant visibility transitions.
- }
-
- @Override
- public HomeAnimationFactory prepareHomeUI(T activity) {
- // Go does not support gestures from app to home.
- return null;
- }
-
- @Override
- public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
- // Go does not support gestures to overview.
- return null;
- }
-
- @Override
- public boolean shouldMinimizeSplitScreen() {
- // Go does not support split screen.
- return true;
- }
-
- @Override
- public boolean isInLiveTileMode() {
- // Go does not support live tiles.
- return false;
- }
-
- @Override
- public void onLaunchTaskFailed(T activity) {
- // Go does not support gestures from one task to another.
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java b/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
deleted file mode 100644
index facf0d2..0000000
--- a/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-
-public class IconRecentsFragment extends Fragment {
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.icon_recents_root_view, container, false);
- }
-}
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
deleted file mode 100644
index b0d9cda..0000000
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep;
-
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherInitListener;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.views.IconRecentsView;
-
-import java.util.function.BiPredicate;
-import java.util.function.Consumer;
-
-/**
- * {@link ActivityControlHelper} for the in-launcher recents.
- * TODO: Implement the app to overview animation functionality
- */
-public final class LauncherActivityControllerHelper extends GoActivityControlHelper<Launcher> {
-
- @Override
- public AnimationFactory prepareRecentsUI(Launcher activity,
- boolean activityVisible, boolean animateActivity,
- Consumer<AnimatorPlaybackController> callback) {
- LauncherState fromState = activity.getStateManager().getState();
- activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
- //TODO: Implement this based off where the recents view needs to be for app => recents anim.
- return new AnimationFactory() {
- @Override
- public void createActivityController(long transitionLength) {
- callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
- fromState, OVERVIEW, transitionLength));
- }
-
- @Override
- public void onTransitionCancelled() {}
- };
- }
-
- @Override
- public ActivityInitListener createActivityInitListener(
- BiPredicate<Launcher, Boolean> onInitListener) {
- return new LauncherInitListener(onInitListener);
- }
-
- @Override
- public Launcher getCreatedActivity() {
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app == null) {
- return null;
- }
- return (Launcher) app.getModel().getCallback();
- }
-
- private Launcher getVisibleLauncher() {
- Launcher launcher = getCreatedActivity();
- return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
- launcher : null;
- }
-
- @Override
- public IconRecentsView getVisibleRecentsView() {
- Launcher launcher = getVisibleLauncher();
- return launcher != null && launcher.getStateManager().getState().overviewUi
- ? launcher.getOverviewPanel() : null;
- }
-
- @Override
- public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
- Launcher launcher = getVisibleLauncher();
- if (launcher == null) {
- return false;
- }
- launcher.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(false);
- launcher.getUserEventDispatcher().logActionCommand(
- LauncherLogProto.Action.Command.RECENTS_BUTTON,
- getContainerType(),
- LauncherLogProto.ContainerType.TASKSWITCHER);
- launcher.getStateManager().goToState(OVERVIEW,
- launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
- return true;
- }
-
- @Override
- public int getContainerType() {
- final Launcher launcher = getVisibleLauncher();
- return launcher != null ? launcher.getStateManager().getState().containerType
- : LauncherLogProto.ContainerType.APP;
- }
-
- @Override
- public void onLaunchTaskSuccess(Launcher launcher) {
- launcher.getStateManager().moveToRestState();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
deleted file mode 100644
index 216972c..0000000
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.SystemClock;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
-import com.android.quickstep.AppToOverviewAnimationProvider.AppToOverviewAnimationListener;
-import com.android.quickstep.views.IconRecentsView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
-
-/**
- * Helper class to handle various atomic commands for switching between Overview.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class OverviewCommandHelper {
-
- private final Context mContext;
- private final ActivityManagerWrapper mAM;
- private final RecentsModel mRecentsModel;
- private final OverviewComponentObserver mOverviewComponentObserver;
-
- private long mLastToggleTime;
-
- public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
- mContext = context;
- mAM = ActivityManagerWrapper.getInstance();
- mRecentsModel = RecentsModel.INSTANCE.get(mContext);
- mOverviewComponentObserver = observer;
- }
-
- public void onOverviewToggle() {
- // If currently screen pinning, do not enter overview
- if (mAM.isScreenPinningActive()) {
- return;
- }
-
- mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
- }
-
- public void onOverviewShown(boolean triggeredFromAltTab) {
- MAIN_EXECUTOR.execute(new ShowRecentsCommand());
- }
-
- public void onOverviewHidden() {
- MAIN_EXECUTOR.execute(new HideRecentsCommand());
- }
-
- public void onTip(int actionType, int viewType) {
- MAIN_EXECUTOR.execute(() ->
- UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
- }
-
- private class ShowRecentsCommand extends RecentsActivityCommand {
-
- @Override
- protected boolean handleCommand(long elapsedTime) {
- return mHelper.getVisibleRecentsView() != null;
- }
- }
-
- private class HideRecentsCommand extends RecentsActivityCommand {
-
- @Override
- protected boolean handleCommand(long elapsedTime) {
- IconRecentsView recents = (IconRecentsView) mHelper.getVisibleRecentsView();
- if (recents == null) {
- return false;
- }
- recents.handleOverviewCommand();
- return true;
- }
- }
-
- private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
-
- protected final ActivityControlHelper<T> mHelper;
- private final long mCreateTime;
-
- private final long mToggleClickedTime = SystemClock.uptimeMillis();
- private boolean mUserEventLogged;
- private ActivityInitListener mListener;
-
- public RecentsActivityCommand() {
- mHelper = mOverviewComponentObserver.getActivityControlHelper();
- mCreateTime = SystemClock.elapsedRealtime();
-
- // Preload the plan
- mRecentsModel.getTasks(null);
- }
-
- @Override
- public void run() {
- long elapsedTime = mCreateTime - mLastToggleTime;
- mLastToggleTime = mCreateTime;
-
- if (handleCommand(elapsedTime)) {
- // Command already handled.
- return;
- }
-
- if (mHelper.switchToRecentsIfVisible(null /* onCompleteCallback */)) {
- // If successfully switched, then return
- return;
- }
-
- AppToOverviewAnimationProvider<T> provider =
- new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId());
- provider.setAnimationListener(
- new AppToOverviewAnimationListener() {
- @Override
- public void onActivityReady(BaseDraggingActivity activity) {
- if (!mUserEventLogged) {
- activity.getUserEventDispatcher().logActionCommand(
- LauncherLogProto.Action.Command.RECENTS_BUTTON,
- mHelper.getContainerType(),
- LauncherLogProto.ContainerType.TASKSWITCHER);
- mUserEventLogged = true;
- }
- }
-
- @Override
- public void onWindowAnimationCreated() {
- if (LatencyTrackerCompat.isEnabled(mContext)) {
- LatencyTrackerCompat.logToggleRecents(
- (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
- }
-
- mListener.unregister();
- }
- });
-
- // Otherwise, start overview.
- mListener = mHelper.createActivityInitListener(provider::onActivityReady);
- mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
- provider, mContext, MAIN_EXECUTOR.getHandler(),
- provider.getRecentsLaunchDuration());
- }
-
- protected boolean handleCommand(long elapsedTime) {
- IconRecentsView recents = mHelper.getVisibleRecentsView();
- if (recents != null) {
- recents.handleOverviewCommand();
- return true;
- } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
- // The user tried to launch back into overview too quickly, either after
- // launching an app, or before overview has actually shown, just ignore for now
- return true;
- }
- return false;
- }
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
deleted file mode 100644
index 7f813ce..0000000
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.app.ActivityOptions;
-import android.view.View;
-
-import com.android.launcher3.R;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.fallback.GoRecentsActivityRootView;
-import com.android.quickstep.views.IconRecentsView;
-
-/**
- * A recents activity that displays recent tasks with an icon and small snapshot.
- */
-public final class RecentsActivity extends BaseRecentsActivity {
-
- private GoRecentsActivityRootView mRecentsRootView;
- private IconRecentsView mIconRecentsView;
-
- @Override
- protected void initViews() {
- setContentView(R.layout.fallback_recents_activity);
- mRecentsRootView = findViewById(R.id.drag_layer);
- mIconRecentsView = findViewById(R.id.overview_panel);
- mIconRecentsView.setRecentsToActivityHelper(new FallbackRecentsToActivityHelper(this));
- mIconRecentsView.setShowStatusBarForegroundScrim(true);
- }
-
- @Override
- protected void reapplyUi() {
- // No-op. Insets are automatically re-applied in the root view.
- }
-
- @Override
- public BaseDragLayer getDragLayer() {
- return mRecentsRootView;
- }
-
- @Override
- public View getRootView() {
- return mRecentsRootView;
- }
-
- @Override
- public <T extends View> T getOverviewPanel() {
- return (T) mIconRecentsView;
- }
-
- @Override
- public ActivityOptions getActivityLaunchOptions(View v) {
- // Stubbed. Recents launch animation will come from the recents view itself.
- return null;
- }
-
- @Override
- protected void onResume() {
- mIconRecentsView.onBeginTransitionToOverview();
- super.onResume();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java b/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java
deleted file mode 100644
index 8f3b707..0000000
--- a/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-/**
- * Generic interface providing methods to the recents implementation that allow it to callback to
- * the containing activity.
- */
-public interface RecentsToActivityHelper {
-
- /**
- * The default action to take when leaving/closing recents. In general, this should be used to
- * go to the appropriate home state.
- */
- void leaveRecents();
-}
diff --git a/go/quickstep/src/com/android/quickstep/TaskActionController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java
deleted file mode 100644
index f49fa3e..0000000
--- a/go/quickstep/src/com/android/quickstep/TaskActionController.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
-import static com.android.quickstep.TaskUtils.getLaunchComponentKeyForTask;
-
-import android.app.ActivityOptions;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.logging.StatsLogManager;
-import com.android.quickstep.views.TaskItemView;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-/**
- * Controller that provides logic for task-related commands on recents and updating the model/view
- * as appropriate.
- */
-public final class TaskActionController {
-
- private final TaskListLoader mLoader;
- private final TaskAdapter mAdapter;
- private final StatsLogManager mStatsLogManager;
-
- public TaskActionController(TaskListLoader loader, TaskAdapter adapter,
- StatsLogManager logManager) {
- mLoader = loader;
- mAdapter = adapter;
- mStatsLogManager = logManager;
- }
-
- /**
- * Launch the task associated with the task holder, animating into the app from the task view.
- *
- * @param viewHolder the task view holder to launch
- */
- public void launchTaskFromView(@NonNull TaskHolder viewHolder) {
- if (!viewHolder.getTask().isPresent()) {
- return;
- }
- TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
- View v = itemView.getThumbnailView();
- int left = 0;
- int top = 0;
- int width = v.getMeasuredWidth();
- int height = v.getMeasuredHeight();
-
- TaskKey key = viewHolder.getTask().get().key;
- ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
- ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts,
- null /* resultCallback */, null /* resultCallbackHandler */);
- mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(key));
- }
-
- /**
- * Launch the task directly with a basic animation.
- *
- * @param task the task to launch
- */
- public void launchTask(@NonNull Task task) {
- ActivityOptions opts = ActivityOptions.makeBasic();
- ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
- null /* resultCallback */, null /* resultCallbackHandler */);
- mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(task.key));
- }
-
- /**
- * Removes the task holder and the task, updating the model and the view.
- *
- * @param viewHolder the task view holder to remove
- */
- public void removeTask(TaskHolder viewHolder) {
- if (!viewHolder.getTask().isPresent()) {
- return;
- }
- int position = viewHolder.getAdapterPosition();
- Task task = viewHolder.getTask().get();
- ActivityManagerWrapper.getInstance().removeTask(task.key.id);
- mLoader.removeTask(task);
- mAdapter.notifyItemRemoved(position);
- // TODO(b/131840601): Add logging point for removal.
- }
-
- /**
- * Clears all tasks and updates the model and view.
- */
- public void clearAllTasks() {
- int count = mAdapter.getItemCount();
- ActivityManagerWrapper.getInstance().removeAllRecentTasks();
- mLoader.clearAllTasks();
- mAdapter.notifyItemRangeRemoved(TASKS_START_POSITION /* positionStart */, count);
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
deleted file mode 100644
index 509bf29..0000000
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-import com.android.launcher3.R;
-import com.android.quickstep.views.TaskItemView;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
- * appropriate {@link Task} from the recents task list.
- */
-public final class TaskAdapter extends Adapter<ViewHolder> {
-
- public static final int CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT = 0;
- public static final int MAX_TASKS_TO_DISPLAY = 6;
- public static final int TASKS_START_POSITION = 1;
-
- public static final int ITEM_TYPE_TASK = 0;
- public static final int ITEM_TYPE_CLEAR_ALL = 1;
-
- private static final String TAG = "TaskAdapter";
- private final TaskListLoader mLoader;
- private TaskActionController mTaskActionController;
- private OnClickListener mClearAllListener;
- private boolean mIsShowingLoadingUi;
-
- public TaskAdapter(@NonNull TaskListLoader loader) {
- mLoader = loader;
- }
-
- public void setActionController(TaskActionController taskActionController) {
- mTaskActionController = taskActionController;
- }
-
- public void setOnClearAllClickListener(OnClickListener listener) {
- mClearAllListener = listener;
- }
-
- /**
- * Sets all positions in the task adapter to loading views, binding new views if necessary.
- * This changes the task adapter's view of the data, so the appropriate notify events should be
- * called in addition to this method to reflect the changes.
- *
- * @param isShowingLoadingUi true to bind loading task views to all positions, false to return
- * to the real data
- */
- public void setIsShowingLoadingUi(boolean isShowingLoadingUi) {
- mIsShowingLoadingUi = isShowingLoadingUi;
- }
-
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- switch (viewType) {
- case ITEM_TYPE_TASK:
- TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
- .inflate(R.layout.task_item_view, parent, false);
- TaskHolder taskHolder = new TaskHolder(itemView);
- itemView.setOnClickListener(
- view -> mTaskActionController.launchTaskFromView(taskHolder));
- return taskHolder;
- case ITEM_TYPE_CLEAR_ALL:
- View clearView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.clear_all_button, parent, false);
- ClearAllHolder clearAllHolder = new ClearAllHolder(clearView);
- Button clearViewButton = clearView.findViewById(R.id.clear_all_button);
- clearViewButton.setOnClickListener(mClearAllListener);
- return clearAllHolder;
- default:
- throw new IllegalArgumentException("No known holder for item type: " + viewType);
- }
- }
-
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- onBindViewHolderInternal(holder, position, false /* willAnimate */);
- }
-
- @Override
- public void onBindViewHolder(@NonNull ViewHolder holder, int position,
- @NonNull List<Object> payloads) {
- if (payloads.isEmpty()) {
- super.onBindViewHolder(holder, position, payloads);
- return;
- }
- int changeType = (int) payloads.get(0);
- if (changeType == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) {
- // Bind in preparation for animation
- onBindViewHolderInternal(holder, position, true /* willAnimate */);
- } else {
- throw new IllegalArgumentException("Payload content is not a valid change event type: "
- + changeType);
- }
- }
-
- private void onBindViewHolderInternal(@NonNull ViewHolder holder, int position,
- boolean willAnimate) {
- int itemType = getItemViewType(position);
- switch (itemType) {
- case ITEM_TYPE_TASK:
- TaskHolder taskHolder = (TaskHolder) holder;
- if (mIsShowingLoadingUi) {
- taskHolder.bindEmptyUi();
- return;
- }
- List<Task> tasks = mLoader.getCurrentTaskList();
- int taskPos = position - TASKS_START_POSITION;
- if (taskPos >= tasks.size()) {
- // Task list has updated.
- return;
- }
- Task task = tasks.get(taskPos);
- taskHolder.bindTask(task, willAnimate /* willAnimate */);
- mLoader.loadTaskIconAndLabel(task, () -> {
- // Ensure holder still has the same task.
- if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
- taskHolder.getTaskItemView().setIcon(task.icon);
- taskHolder.getTaskItemView().setLabel(task.titleDescription);
- }
- });
- mLoader.loadTaskThumbnail(task, () -> {
- if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
- taskHolder.getTaskItemView().setThumbnail(task.thumbnail);
- }
- });
- break;
- case ITEM_TYPE_CLEAR_ALL:
- // Nothing to bind.
- break;
- default:
- throw new IllegalArgumentException("No known holder for item type: " + itemType);
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- // Bottom is always clear all button.
- return (position == 0) ? ITEM_TYPE_CLEAR_ALL : ITEM_TYPE_TASK;
- }
-
- @Override
- public int getItemCount() {
- int itemCount = TASKS_START_POSITION;
- if (mIsShowingLoadingUi) {
- // Show loading version of all items.
- itemCount += MAX_TASKS_TO_DISPLAY;
- } else {
- itemCount += Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
- }
- return itemCount;
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
deleted file mode 100644
index 49b6aaa..0000000
--- a/go/quickstep/src/com/android/quickstep/TaskHolder.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-import com.android.quickstep.views.TaskItemView;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.Optional;
-
-/**
- * A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
- * etc.) to the view.
- */
-public final class TaskHolder extends ViewHolder {
-
- private final TaskItemView mTaskItemView;
- private Task mTask;
-
- public TaskHolder(TaskItemView itemView) {
- super(itemView);
- mTaskItemView = itemView;
- }
-
- public TaskItemView getTaskItemView() {
- return mTaskItemView;
- }
-
- /**
- * Bind the task model to the holder. This will take the current task content in the task
- * object (i.e. icon, thumbnail, label) and either apply the content immediately or simply bind
- * the content to animate to at a later time. If the task does not have all its content loaded,
- * the view will prepare appropriate default placeholders and it is the callers responsibility
- * to change them at a later time.
- *
- * Regardless of whether it is animating, input handlers will be bound immediately (see
- * {@link TaskActionController}).
- *
- * @param task the task to bind to the view
- * @param willAnimate true if UI should animate in later, false if it should apply immediately
- */
- public void bindTask(@NonNull Task task, boolean willAnimate) {
- mTask = task;
- if (willAnimate) {
- mTaskItemView.startContentAnimation(task.icon, task.thumbnail, task.titleDescription);
- } else {
- mTaskItemView.setIcon(task.icon);
- mTaskItemView.setThumbnail(task.thumbnail);
- mTaskItemView.setLabel(task.titleDescription);
- }
- }
-
- /**
- * Bind a generic empty UI to the holder to make it clear that the item is loading/unbound and
- * should not be expected to react to user input.
- */
- public void bindEmptyUi() {
- mTask = null;
- mTaskItemView.resetToEmptyUi();
- }
-
- /**
- * Gets the task currently bound to this view. May be null if task holder is in a loading state.
- *
- * @return the current task
- */
- public Optional<Task> getTask() {
- return Optional.ofNullable(mTask);
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
deleted file mode 100644
index 1335cac..0000000
--- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * This class is responsible for maintaining the list of tasks and the task content. The list must
- * be updated explicitly with {@link #loadTaskList} whenever the list needs to be
- * up-to-date.
- */
-public final class TaskListLoader {
-
- private final RecentsModel mRecentsModel;
-
- private ArrayList<Task> mTaskList = new ArrayList<>();
- private int mTaskListChangeId;
-
- public TaskListLoader(Context context) {
- mRecentsModel = RecentsModel.INSTANCE.get(context);
- }
-
- /**
- * Returns the current task list as of the last completed load (see {@link #loadTaskList}) as a
- * read-only list. This list of tasks is not guaranteed to have all content loaded.
- *
- * @return the current list of tasks
- */
- public List<Task> getCurrentTaskList() {
- return Collections.unmodifiableList(mTaskList);
- }
-
- /**
- * Whether or not the loader needs to load data to be up to date. This can return true if the
- * task list is already up to date OR there is already a load in progress for the task list to
- * become up to date.
- *
- * @return true if already up to date or load in progress, false otherwise
- */
- public boolean needsToLoad() {
- return !mRecentsModel.isTaskListValid(mTaskListChangeId);
- }
-
- /**
- * Fetches the most recent tasks and updates the task list asynchronously. This call does not
- * provide guarantees the task content (icon, thumbnail, label) are loaded but will fill in
- * what it has. May run the callback immediately if there have been no changes in the task
- * list since the start of the last load.
- *
- * @param onLoadedCallback callback to run when task list is loaded
- */
- public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onLoadedCallback) {
- if (!needsToLoad()) {
- if (onLoadedCallback != null) {
- onLoadedCallback.accept(mTaskList);
- }
- return;
- }
- // TODO: Look into error checking / more robust handling for when things go wrong.
- mTaskListChangeId = mRecentsModel.getTasks(loadedTasks -> {
- ArrayList<Task> tasks = new ArrayList<>(loadedTasks);
- // Reverse tasks to put most recent at the bottom of the view
- Collections.reverse(tasks);
- // Load task content
- for (Task task : tasks) {
- int loadedPos = mTaskList.indexOf(task);
- if (loadedPos == -1) {
- continue;
- }
- Task loadedTask = mTaskList.get(loadedPos);
- task.icon = loadedTask.icon;
- task.titleDescription = loadedTask.titleDescription;
- task.thumbnail = loadedTask.thumbnail;
- }
- mTaskList = tasks;
- onLoadedCallback.accept(tasks);
- });
- }
-
- /**
- * Load task icon and label asynchronously if it is not already loaded in the task. If the task
- * already has an icon, this calls the callback immediately.
- *
- * @param task task to update with icon + label
- * @param onLoadedCallback callback to run when task has icon and label
- */
- public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) {
- mRecentsModel.getIconCache().updateIconInBackground(task,
- loadedTask -> onLoadedCallback.run());
- }
-
- /**
- * Load thumbnail asynchronously if not already loaded in the task. If the task already has a
- * thumbnail or if the thumbnail is cached, this calls the callback immediately.
- *
- * @param task task to update with the thumbnail
- * @param onLoadedCallback callback to run when task has thumbnail
- */
- public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) {
- mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
- thumbnail -> onLoadedCallback.run());
- }
-
- /**
- * Removes the task from the current task list.
- */
- void removeTask(Task task) {
- mTaskList.remove(task);
- }
-
- /**
- * Clears the current task list.
- */
- void clearAllTasks() {
- mTaskList.clear();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
deleted file mode 100644
index 57f49d6..0000000
--- a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
-
-import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
-
-import android.graphics.Canvas;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.ItemTouchHelper;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-import java.util.function.Consumer;
-
-/**
- * Callback for swipe input on {@link TaskHolder} views in the recents view.
- */
-public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
-
- private final Consumer<TaskHolder> mOnTaskSwipeCallback;
-
- public TaskSwipeCallback(Consumer<TaskHolder> onTaskSwipeCallback) {
- super(0 /* dragDirs */, RIGHT);
- mOnTaskSwipeCallback = onTaskSwipeCallback;
- }
-
- @Override
- public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder,
- ViewHolder target) {
- return false;
- }
-
- @Override
- public void onSwiped(ViewHolder viewHolder, int direction) {
- if (direction == RIGHT) {
- mOnTaskSwipeCallback.accept((TaskHolder) viewHolder);
- }
- }
-
- @Override
- public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
- @NonNull ViewHolder viewHolder, float dX, float dY, int actionState,
- boolean isCurrentlyActive) {
- if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
- float alpha = 1.0f - dX / (float) viewHolder.itemView.getWidth();
- viewHolder.itemView.setAlpha(alpha);
- }
- super.onChildDraw(c, recyclerView, viewHolder, dX, dY,
- actionState, isCurrentlyActive);
- }
-
- @Override
- public int getSwipeDirs(@NonNull RecyclerView recyclerView,
- @NonNull ViewHolder viewHolder) {
- if (viewHolder.getItemViewType() == ITEM_TYPE_CLEAR_ALL) {
- // Clear all button should not be swipable.
- return 0;
- }
- return super.getSwipeDirs(recyclerView, viewHolder);
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java b/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
deleted file mode 100644
index 922e68a..0000000
--- a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep;
-
-import static android.graphics.Shader.TileMode.CLAMP;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.R;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-
-/**
- * Bitmap backed drawable that supports rotating the thumbnail bitmap depending on if the
- * orientation the thumbnail was taken in matches the desired orientation. In addition, the
- * thumbnail always fills into the containing bounds.
- */
-public final class ThumbnailDrawable extends Drawable {
-
- private final Paint mPaint = new Paint();
- private final Matrix mMatrix = new Matrix();
- private final ThumbnailData mThumbnailData;
- private final BitmapShader mShader;
- private final RectF mDestRect = new RectF();
- private final int mCornerRadius;
- private int mRequestedOrientation;
-
- public ThumbnailDrawable(Resources res, @NonNull ThumbnailData thumbnailData,
- int requestedOrientation) {
- mThumbnailData = thumbnailData;
- mRequestedOrientation = requestedOrientation;
- mCornerRadius = (int) res.getDimension(R.dimen.task_thumbnail_corner_radius);
- mShader = new BitmapShader(mThumbnailData.thumbnail, CLAMP, CLAMP);
- mPaint.setShader(mShader);
- mPaint.setAntiAlias(true);
- updateMatrix();
- }
-
- /**
- * Set the requested orientation.
- *
- * @param orientation the orientation we want the thumbnail to be in
- */
- public void setRequestedOrientation(int orientation) {
- if (mRequestedOrientation != orientation) {
- mRequestedOrientation = orientation;
- updateMatrix();
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mThumbnailData.thumbnail == null) {
- return;
- }
- canvas.drawRoundRect(mDestRect, mCornerRadius, mCornerRadius, mPaint);
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- mDestRect.set(bounds);
- updateMatrix();
- }
-
- @Override
- public void setAlpha(int alpha) {
- final int oldAlpha = mPaint.getAlpha();
- if (alpha != oldAlpha) {
- mPaint.setAlpha(alpha);
- invalidateSelf();
- }
- }
-
- @Override
- public int getAlpha() {
- return mPaint.getAlpha();
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- mPaint.setColorFilter(colorFilter);
- invalidateSelf();
- }
-
- @Override
- public ColorFilter getColorFilter() {
- return mPaint.getColorFilter();
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- private void updateMatrix() {
- if (mThumbnailData.thumbnail == null) {
- return;
- }
- mMatrix.reset();
- float scaleX;
- float scaleY;
- Rect bounds = getBounds();
- Bitmap thumbnail = mThumbnailData.thumbnail;
- if (mRequestedOrientation != mThumbnailData.orientation) {
- // Rotate and translate so that top left is the same.
- mMatrix.postRotate(90, 0, 0);
- mMatrix.postTranslate(thumbnail.getHeight(), 0);
-
- scaleX = (float) bounds.width() / thumbnail.getHeight();
- scaleY = (float) bounds.height() / thumbnail.getWidth();
- } else {
- scaleX = (float) bounds.width() / thumbnail.getWidth();
- scaleY = (float) bounds.height() / thumbnail.getHeight();
- }
- // Scale to fill.
- mMatrix.postScale(scaleX, scaleY);
- mShader.setLocalMatrix(mMatrix);
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
deleted file mode 100644
index 19dd82f..0000000
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
-
-import android.annotation.TargetApi;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Region;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-
-/**
- * Service connected by system-UI for handling touch interaction.
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class TouchInteractionService extends Service {
-
- private static final String TAG = "GoTouchInteractionService";
- private boolean mIsUserUnlocked;
- private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- initWhenUserUnlocked();
- }
- }
- };
-
- private final IBinder mMyBinder = new IOverviewProxy.Stub() {
-
- @Override
- public void onActiveNavBarRegionChanges(Region region) throws RemoteException { }
-
- @Override
- public void onInitialize(Bundle bundle) throws RemoteException {
- ISystemUiProxy iSystemUiProxy = ISystemUiProxy.Stub
- .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
- mRecentsModel.setSystemUiProxy(iSystemUiProxy);
- }
-
- @Override
- public void onOverviewToggle() {
- if (mIsUserUnlocked) {
- mOverviewCommandHelper.onOverviewToggle();
- }
- }
-
- @Override
- public void onOverviewShown(boolean triggeredFromAltTab) {
- if (mIsUserUnlocked) {
- mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
- }
- }
-
- @Override
- public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (mIsUserUnlocked && triggeredFromAltTab && !triggeredFromHomeKey) {
- // onOverviewShownFromAltTab hides the overview and ends at the target app
- mOverviewCommandHelper.onOverviewHidden();
- }
- }
-
- @Override
- public void onTip(int actionType, int viewType) {
- if (mIsUserUnlocked) {
- mOverviewCommandHelper.onTip(actionType, viewType);
- }
- }
-
- @Override
- public void onAssistantAvailable(boolean available) {
- // TODO handle assistant
- }
-
- @Override
- public void onAssistantVisibilityChanged(float visibility) {
- // TODO handle assistant
- }
-
- public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) {
- }
-
- public void onSystemUiStateChanged(int stateFlags) {
- // To be implemented
- }
-
- /** Deprecated methods **/
- public void onQuickStep(MotionEvent motionEvent) { }
-
- public void onQuickScrubEnd() { }
-
- public void onQuickScrubProgress(float progress) { }
-
- public void onQuickScrubStart() { }
-
- public void onPreMotionEvent(int downHitTarget) { }
-
- public void onMotionEvent(MotionEvent ev) { }
-
- public void onBind(ISystemUiProxy iSystemUiProxy) {
- mRecentsModel.setSystemUiProxy(iSystemUiProxy);
- }
- };
-
- private static boolean sConnected = false;;
-
- public static boolean isConnected() {
- return sConnected;
- }
-
- private RecentsModel mRecentsModel;
- private OverviewComponentObserver mOverviewComponentObserver;
- private OverviewCommandHelper mOverviewCommandHelper;
-
- @Override
- public void onCreate() {
- super.onCreate();
- if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
- initWhenUserUnlocked();
- } else {
- mIsUserUnlocked = false;
- registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
- }
-
- sConnected = true;
- }
-
- private void initWhenUserUnlocked() {
- mRecentsModel = RecentsModel.INSTANCE.get(this);
- mOverviewComponentObserver = new OverviewComponentObserver(this);
- mOverviewCommandHelper = new OverviewCommandHelper(this,
- mOverviewComponentObserver);
- mIsUserUnlocked = true;
- Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
- }
-
- @Override
- public void onDestroy() {
- if (mIsUserUnlocked) {
- mOverviewComponentObserver.onDestroy();
- }
- Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
- sConnected = false;
- super.onDestroy();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Touch service connected");
- }
- return mMyBinder;
- }
-
- public static boolean isInitialized() {
- return true;
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
deleted file mode 100644
index b550011..0000000
--- a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.fallback;
-
-import android.content.Context;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsActivity;
-
-/**
- * Minimal implementation of {@link BaseDragLayer} for Go's fallback recents activity.
- */
-public final class GoRecentsActivityRootView extends BaseDragLayer<RecentsActivity> {
- public GoRecentsActivityRootView(Context context, AttributeSet attrs) {
- super(context, attrs, 1 /* alphaChannelCount */);
- // Go leaves touch control to the view itself.
- mControllers = new TouchController[0];
- setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | SYSTEM_UI_FLAG_LAYOUT_STABLE);
- }
-
- @Override
- public void setInsets(Rect insets) {
- if (insets.equals(mInsets)) {
- return;
- }
- super.setInsets(insets);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- Insets sysInsets = insets.getSystemWindowInsets();
- setInsets(new Rect(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom));
- return insets.consumeSystemWindowInsets();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
deleted file mode 100644
index 87b4d4e..0000000
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ /dev/null
@@ -1,942 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.views;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-
-import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
-
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
-import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
-import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK;
-import static com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY;
-import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
-import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewTreeObserver;
-import android.view.animation.PathInterpolator;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
-import androidx.recyclerview.widget.DefaultItemAnimator;
-import androidx.recyclerview.widget.ItemTouchHelper;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
-import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
-import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-import com.android.quickstep.ContentFillItemAnimator;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RecentsToActivityHelper;
-import com.android.quickstep.TaskActionController;
-import com.android.quickstep.TaskAdapter;
-import com.android.quickstep.TaskHolder;
-import com.android.quickstep.TaskListLoader;
-import com.android.quickstep.TaskSwipeCallback;
-import com.android.quickstep.util.MultiValueUpdateListener;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
- * base.
- */
-public final class IconRecentsView extends FrameLayout implements Insettable {
-
- public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
- new FloatProperty<IconRecentsView>("contentAlpha") {
- @Override
- public void setValue(IconRecentsView view, float v) {
- ALPHA.set(view, v);
- if (view.getVisibility() != VISIBLE && v > 0) {
- view.setVisibility(VISIBLE);
- } else if (view.getVisibility() != GONE && v == 0){
- view.setVisibility(GONE);
- }
- }
-
- @Override
- public Float get(IconRecentsView view) {
- return ALPHA.get(view);
- }
- };
- private static final long CROSSFADE_DURATION = 300;
- private static final long LAYOUT_ITEM_ANIMATE_IN_DURATION = 150;
- private static final long LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN = 40;
- private static final long ITEM_ANIMATE_OUT_DURATION = 150;
- private static final long ITEM_ANIMATE_OUT_DELAY_BETWEEN = 40;
- private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
- private static final long CLEAR_ALL_FADE_DELAY = 120;
-
- private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300;
- private static final long REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION = 400;
- private static final long REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY = 200;
- private static final long REMOTE_TO_RECENTS_ITEM_FADE_DURATION = 217;
- private static final long REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY = 33;
-
- private static final PathInterpolator FAST_OUT_SLOW_IN_1 =
- new PathInterpolator(.4f, 0f, 0f, 1f);
- private static final PathInterpolator FAST_OUT_SLOW_IN_2 =
- new PathInterpolator(.5f, 0f, 0f, 1f);
- private static final LinearOutSlowInInterpolator OUT_SLOW_IN =
- new LinearOutSlowInInterpolator();
-
- public static final long REMOTE_APP_TO_OVERVIEW_DURATION =
- REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION;
-
- /**
- * A ratio representing the view's relative placement within its padded space. For example, 0
- * is top aligned and 0.5 is centered vertically.
- */
- @ViewDebug.ExportedProperty(category = "launcher")
-
- private final Context mContext;
- private final TaskListLoader mTaskLoader;
- private final TaskAdapter mTaskAdapter;
- private final LinearLayoutManager mTaskLayoutManager;
- private final TaskActionController mTaskActionController;
- private final DefaultItemAnimator mDefaultItemAnimator = new DefaultItemAnimator();
- private final ContentFillItemAnimator mLoadingContentItemAnimator =
- new ContentFillItemAnimator();
- private final BaseActivity mActivity;
- private final Drawable mStatusBarForegroundScrim;
-
- private RecentsToActivityHelper mActivityHelper;
- private RecyclerView mTaskRecyclerView;
- private View mShowingContentView;
- private View mEmptyView;
- private View mContentView;
- private boolean mTransitionedFromApp;
- private boolean mUsingRemoteAnimation;
- private boolean mStartedEnterAnimation;
- private boolean mShowStatusBarForegroundScrim;
- private AnimatorSet mLayoutAnimation;
- private final ArraySet<View> mLayingOutViews = new ArraySet<>();
- private Rect mInsets;
- private final RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> {
- ArrayList<TaskItemView> itemViews = getTaskViews();
- for (int i = 0, size = itemViews.size(); i < size; i++) {
- TaskItemView taskView = itemViews.get(i);
- TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView);
- Optional<Task> optTask = taskHolder.getTask();
- if (optTask.filter(task -> task.key.id == taskId).isPresent()) {
- Task task = optTask.get();
- // Update thumbnail on the task.
- task.thumbnail = thumbnailData;
- taskView.setThumbnail(thumbnailData);
- return task;
- }
- }
- return null;
- };
-
- public IconRecentsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mActivity = BaseActivity.fromContext(context);
- mContext = context;
- mStatusBarForegroundScrim =
- Themes.getAttrDrawable(mContext, R.attr.workspaceStatusBarScrim);
- mTaskLoader = new TaskListLoader(mContext);
- mTaskAdapter = new TaskAdapter(mTaskLoader);
- mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks());
- mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter,
- mActivity.getStatsLogManager());
- mTaskAdapter.setActionController(mTaskActionController);
- mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */);
- RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- if (mTaskRecyclerView == null) {
- mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
- mTaskRecyclerView.setAdapter(mTaskAdapter);
- mTaskRecyclerView.setLayoutManager(mTaskLayoutManager);
- ItemTouchHelper helper = new ItemTouchHelper(
- new TaskSwipeCallback(holder -> {
- mTaskActionController.removeTask(holder);
- if (mTaskLoader.getCurrentTaskList().isEmpty()) {
- mActivityHelper.leaveRecents();
- }
- }));
- helper.attachToRecyclerView(mTaskRecyclerView);
- mTaskRecyclerView.addOnChildAttachStateChangeListener(
- new OnChildAttachStateChangeListener() {
- @Override
- public void onChildViewAttachedToWindow(@NonNull View view) {
- if (mLayoutAnimation != null && !mLayingOutViews.contains(view)) {
- // Child view was added that is not part of current layout animation
- // so restart the animation.
- animateFadeInLayoutAnimation();
- }
- }
-
- @Override
- public void onChildViewDetachedFromWindow(@NonNull View view) { }
- });
- mTaskRecyclerView.setItemAnimator(mDefaultItemAnimator);
- mLoadingContentItemAnimator.setOnAnimationFinishedRunnable(
- () -> mTaskRecyclerView.setItemAnimator(new DefaultItemAnimator()));
- ItemDecoration marginDecorator = new ItemDecoration() {
- @Override
- public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
- @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
- // TODO: Determine if current margins cause off screen item to be fully off
- // screen and if so, modify them so that it is partially off screen.
- int itemType = parent.getChildViewHolder(view).getItemViewType();
- Resources res = getResources();
- switch (itemType) {
- case ITEM_TYPE_CLEAR_ALL:
- outRect.top = (int) res.getDimension(
- R.dimen.clear_all_item_view_top_margin);
- outRect.bottom = (int) res.getDimension(
- R.dimen.clear_all_item_view_bottom_margin);
- break;
- case ITEM_TYPE_TASK:
- int desiredTopMargin = (int) res.getDimension(
- R.dimen.task_item_top_margin);
- if (mTaskRecyclerView.getChildAdapterPosition(view) ==
- state.getItemCount() - 1) {
- // Only add top margin to top task view if insets aren't enough.
- if (mInsets.top < desiredTopMargin) {
- outRect.top = desiredTopMargin - mInsets.bottom;
- }
- return;
- }
- outRect.top = desiredTopMargin;
- break;
- default:
- }
- }
- };
- mTaskRecyclerView.addItemDecoration(marginDecorator);
-
- mEmptyView = findViewById(R.id.recent_task_empty_view);
- mContentView = mTaskRecyclerView;
- mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
- @Override
- public void onChanged() {
- updateContentViewVisibility();
- }
-
- @Override
- public void onItemRangeRemoved(int positionStart, int itemCount) {
- updateContentViewVisibility();
- }
- });
- }
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
- int childCount = mTaskRecyclerView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- mTaskRecyclerView.getChildAt(i).setEnabled(enabled);
- }
- }
-
- /**
- * Set activity helper for the view to callback to.
- *
- * @param helper the activity helper
- */
- public void setRecentsToActivityHelper(@NonNull RecentsToActivityHelper helper) {
- mActivityHelper = helper;
- }
-
- /**
- * Logic for when we know we are going to overview/recents and will be putting up the recents
- * view. This should be used to prepare recents (e.g. load any task data, etc.) before it
- * becomes visible.
- */
- public void onBeginTransitionToOverview() {
- mStartedEnterAnimation = false;
- if (mContext.getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- // Scroll to bottom of task in landscape mode. This is a non-issue in portrait mode as
- // all tasks should be visible to fill up the screen in portrait mode and the view will
- // not be scrollable.
- mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */);
- }
- if (!mUsingRemoteAnimation) {
- scheduleFadeInLayoutAnimation();
- }
- // Load any task changes
- if (!mTaskLoader.needsToLoad()) {
- return;
- }
- mTaskAdapter.setIsShowingLoadingUi(true);
- mTaskAdapter.notifyDataSetChanged();
- mTaskLoader.loadTaskList(tasks -> {
- int numEmptyItems = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
- mTaskAdapter.setIsShowingLoadingUi(false);
- int numActualItems = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
- if (numEmptyItems < numActualItems) {
- throw new IllegalStateException("There are less empty item views than the number "
- + "of items to animate to.");
- }
- // Possible that task list loads faster than adapter changes propagate to layout so
- // only start content fill animation if there aren't any pending adapter changes and
- // we've started the on enter layout animation.
- boolean needsContentFillAnimation =
- !mTaskRecyclerView.hasPendingAdapterUpdates() && mStartedEnterAnimation;
- if (needsContentFillAnimation) {
- // Set item animator for content filling animation. The item animator will switch
- // back to the default on completion
- mTaskRecyclerView.setItemAnimator(mLoadingContentItemAnimator);
- mTaskAdapter.notifyItemRangeRemoved(TASKS_START_POSITION + numActualItems,
- numEmptyItems - numActualItems);
- mTaskAdapter.notifyItemRangeChanged(TASKS_START_POSITION, numActualItems,
- CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT);
- } else {
- // Notify change without animating.
- mTaskAdapter.notifyDataSetChanged();
- }
- });
- }
-
- /**
- * Set whether we transitioned to recents from the most recent app.
- *
- * @param transitionedFromApp true if transitioned from the most recent app, false otherwise
- */
- public void setTransitionedFromApp(boolean transitionedFromApp) {
- mTransitionedFromApp = transitionedFromApp;
- }
-
- /**
- * Set whether we're using a custom remote animation. If so, we will not do the default layout
- * animation when entering recents and instead wait for the remote app surface to be ready to
- * use.
- *
- * @param usingRemoteAnimation true if doing a remote animation, false o/w
- */
- public void setUsingRemoteAnimation(boolean usingRemoteAnimation) {
- mUsingRemoteAnimation = usingRemoteAnimation;
- }
-
- /**
- * Handles input from the overview button. Launch the most recent task unless we just came from
- * the app. In that case, we launch the next most recent.
- */
- public void handleOverviewCommand() {
- List<Task> tasks = mTaskLoader.getCurrentTaskList();
- int tasksSize = tasks.size();
- if (tasksSize == 0) {
- // Do nothing
- return;
- }
- Task taskToLaunch;
- if (mTransitionedFromApp && tasksSize > 1) {
- // Launch the next most recent app
- taskToLaunch = tasks.get(1);
- } else {
- // Launch the most recent app
- taskToLaunch = tasks.get(0);
- }
-
- // See if view for this task is attached, and if so, animate launch from that view.
- ArrayList<TaskItemView> itemViews = getTaskViews();
- for (int i = 0, size = itemViews.size(); i < size; i++) {
- TaskItemView taskView = itemViews.get(i);
- TaskHolder holder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView);
- if (Objects.equals(holder.getTask(), Optional.of(taskToLaunch))) {
- mTaskActionController.launchTaskFromView(holder);
- return;
- }
- }
-
- // Otherwise, just use a basic launch animation.
- mTaskActionController.launchTask(taskToLaunch);
- }
-
- /**
- * Set whether or not to show the scrim in between the view and the top insets. This only works
- * if the view is being insetted in the first place.
- *
- * The scrim is added to the activity's root view to prevent animations on this view
- * affecting the scrim. As a result, it is the activity's responsibility to show/hide this
- * scrim as appropriate.
- *
- * @param showStatusBarForegroundScrim true to show the scrim, false to hide
- */
- public void setShowStatusBarForegroundScrim(boolean showStatusBarForegroundScrim) {
- mShowStatusBarForegroundScrim = showStatusBarForegroundScrim;
- if (mShowStatusBarForegroundScrim != showStatusBarForegroundScrim) {
- updateStatusBarScrim();
- }
- }
-
- private void updateStatusBarScrim() {
- boolean shouldShow = mInsets.top != 0 && mShowStatusBarForegroundScrim;
- mActivity.getDragLayer().setForeground(shouldShow ? mStatusBarForegroundScrim : null);
- }
-
- /**
- * Get the bottom most task view to animate to.
- *
- * @return the task view
- */
- private @Nullable TaskItemView getBottomTaskView() {
- int childCount = mTaskRecyclerView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View view = mTaskRecyclerView.getChildAt(i);
- if (mTaskRecyclerView.getChildViewHolder(view).getItemViewType() == ITEM_TYPE_TASK) {
- return (TaskItemView) view;
- }
- }
- return null;
- }
-
- /**
- * Whether this view has processed all data changes and is ready to animate from the app to
- * the overview.
- *
- * @return true if ready to animate app to overview, false otherwise
- */
- public boolean isReadyForRemoteAnim() {
- return !mTaskRecyclerView.hasPendingAdapterUpdates();
- }
-
- /**
- * Set a callback for whenever this view is ready to do a remote animation from the app to
- * overview. See {@link #isReadyForRemoteAnim()}.
- *
- * @param callback callback to run when view is ready to animate
- */
- public void setOnReadyForRemoteAnimCallback(onReadyForRemoteAnimCallback callback) {
- mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (isReadyForRemoteAnim()) {
- callback.onReadyForRemoteAnim();
- mTaskRecyclerView.getViewTreeObserver().
- removeOnGlobalLayoutListener(this);
- }
- }
- });
- }
-
- /**
- * Clear all tasks and animate out.
- */
- private void animateClearAllTasks() {
- setEnabled(false);
- ArrayList<TaskItemView> itemViews = getTaskViews();
-
- AnimatorSet clearAnim = new AnimatorSet();
- long currentDelay = 0;
-
- // Animate each item view to the right and fade out.
- for (int i = 0, size = itemViews.size(); i < size; i++) {
- TaskItemView itemView = itemViews.get(i);
- PropertyValuesHolder transXproperty = PropertyValuesHolder.ofFloat(TRANSLATION_X,
- 0, itemView.getWidth() * ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO);
- PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat(ALPHA, 1.0f, 0f);
- ObjectAnimator itemAnim = ObjectAnimator.ofPropertyValuesHolder(itemView,
- transXproperty, alphaProperty);
- itemAnim.setDuration(ITEM_ANIMATE_OUT_DURATION);
- itemAnim.setStartDelay(currentDelay);
-
- clearAnim.play(itemAnim);
- currentDelay += ITEM_ANIMATE_OUT_DELAY_BETWEEN;
- }
-
- // Animate view fading and leave recents when faded enough.
- ValueAnimator contentAlpha = ValueAnimator.ofFloat(1.0f, 0f)
- .setDuration(CROSSFADE_DURATION);
- contentAlpha.setStartDelay(CLEAR_ALL_FADE_DELAY);
- contentAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- private boolean mLeftRecents = false;
-
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- mContentView.setAlpha((float) valueAnimator.getAnimatedValue());
- // Leave recents while fading out.
- if ((float) valueAnimator.getAnimatedValue() < .5f && !mLeftRecents) {
- mActivityHelper.leaveRecents();
- mLeftRecents = true;
- }
- }
- });
-
- clearAnim.play(contentAlpha);
- clearAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- for (int i = 0, size = itemViews.size(); i < size; i++) {
- TaskItemView itemView = itemViews.get(i);
- itemView.setTranslationX(0);
- itemView.setAlpha(1.0f);
- }
- setEnabled(true);
- mContentView.setVisibility(GONE);
- mTaskActionController.clearAllTasks();
- }
- });
- clearAnim.start();
- }
-
- /**
- * Get attached task item views ordered by most recent.
- *
- * @return array list of attached task item views
- */
- private ArrayList<TaskItemView> getTaskViews() {
- int taskCount = mTaskRecyclerView.getChildCount();
- ArrayList<TaskItemView> itemViews = new ArrayList<>();
- for (int i = 0; i < taskCount; i ++) {
- View child = mTaskRecyclerView.getChildAt(i);
- if (child instanceof TaskItemView) {
- itemViews.add((TaskItemView) child);
- }
- }
- return itemViews;
- }
-
- /**
- * Update the content view so that the appropriate view is shown based off the current list
- * of tasks.
- */
- private void updateContentViewVisibility() {
- int taskListSize = mTaskAdapter.getItemCount() - TASKS_START_POSITION;
- if (mShowingContentView != mEmptyView && taskListSize == 0) {
- mShowingContentView = mEmptyView;
- crossfadeViews(mEmptyView, mContentView);
- }
- if (mShowingContentView != mContentView && taskListSize > 0) {
- mShowingContentView = mContentView;
- crossfadeViews(mContentView, mEmptyView);
- }
- }
-
- /**
- * Animate views so that one view fades in while the other fades out.
- *
- * @param fadeInView view that should fade in
- * @param fadeOutView view that should fade out
- */
- private void crossfadeViews(View fadeInView, View fadeOutView) {
- fadeInView.animate().cancel();
- fadeInView.setVisibility(VISIBLE);
- fadeInView.setAlpha(0f);
- fadeInView.animate()
- .alpha(1f)
- .setDuration(CROSSFADE_DURATION)
- .setListener(null);
-
- fadeOutView.animate().cancel();
- fadeOutView.animate()
- .alpha(0f)
- .setDuration(CROSSFADE_DURATION)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- fadeOutView.setVisibility(GONE);
- }
- });
- }
-
- /**
- * Schedule a one-shot layout animation on the next layout. Separate from
- * {@link #scheduleLayoutAnimation()} as the animation is {@link Animator} based and acts on the
- * view properties themselves, allowing more controllable behavior and making it easier to
- * manage when the animation conflicts with another animation.
- */
- private void scheduleFadeInLayoutAnimation() {
- mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- animateFadeInLayoutAnimation();
- mTaskRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- });
- }
-
- /**
- * Start animating the layout animation where items fade in.
- */
- private void animateFadeInLayoutAnimation() {
- if (mLayoutAnimation != null) {
- // If layout animation still in progress, cancel and restart.
- mLayoutAnimation.cancel();
- }
- ArrayList<TaskItemView> views = getTaskViews();
- int delay = 0;
- mLayoutAnimation = new AnimatorSet();
- for (int i = 0, size = views.size(); i < size; i++) {
- TaskItemView view = views.get(i);
- view.setAlpha(0.0f);
- Animator alphaAnim = ObjectAnimator.ofFloat(view, ALPHA, 0.0f, 1.0f);
- alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION).setStartDelay(delay);
- alphaAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setAlpha(1.0f);
- mLayingOutViews.remove(view);
- }
- });
- delay += LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN;
- mLayoutAnimation.play(alphaAnim);
- mLayingOutViews.add(view);
- }
- mLayoutAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLayoutAnimation = null;
- }
- });
- mLayoutAnimation.start();
- mStartedEnterAnimation = true;
- }
-
- /**
- * Play remote app to recents animation when the app is the home activity. We use a simple
- * cross-fade here. Note this is only used if the home activity is a separate app than the
- * recents activity.
- *
- * @param anim animator set
- * @param homeTarget the home surface thats closing
- * @param recentsTarget the surface containing recents
- */
- public void playRemoteHomeToRecentsAnimation(@NonNull AnimatorSet anim,
- @NonNull RemoteAnimationTargetCompat homeTarget,
- @NonNull RemoteAnimationTargetCompat recentsTarget) {
- SyncRtSurfaceTransactionApplierCompat surfaceApplier =
- new SyncRtSurfaceTransactionApplierCompat(this);
-
- SurfaceParams[] params = new SurfaceParams[2];
- int boostedMode = MODE_CLOSING;
-
- ValueAnimator remoteHomeAnim = ValueAnimator.ofFloat(0, 1);
- remoteHomeAnim.setDuration(REMOTE_APP_TO_OVERVIEW_DURATION);
-
- remoteHomeAnim.addUpdateListener(valueAnimator -> {
- float val = (float) valueAnimator.getAnimatedValue();
- float alpha;
- RemoteAnimationTargetCompat visibleTarget;
- RemoteAnimationTargetCompat invisibleTarget;
- if (val < .5f) {
- visibleTarget = homeTarget;
- invisibleTarget = recentsTarget;
- alpha = 1 - (val * 2);
- } else {
- visibleTarget = recentsTarget;
- invisibleTarget = homeTarget;
- alpha = (val - .5f) * 2;
- }
- params[0] = new SurfaceParams(visibleTarget.leash, alpha, null /* matrix */,
- null /* windowCrop */, getLayer(visibleTarget, boostedMode),
- 0 /* cornerRadius */);
- params[1] = new SurfaceParams(invisibleTarget.leash, 0.0f, null /* matrix */,
- null /* windowCrop */, getLayer(invisibleTarget, boostedMode),
- 0 /* cornerRadius */);
- surfaceApplier.scheduleApply(params);
- });
- anim.play(remoteHomeAnim);
- animateFadeInLayoutAnimation();
- }
-
- /**
- * Play remote animation from app to recents. This should scale the currently closing app down
- * to the recents thumbnail.
- *
- * @param anim animator set
- * @param appTarget the app surface thats closing
- * @param recentsTarget the surface containing recents
- */
- public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim,
- @NonNull RemoteAnimationTargetCompat appTarget,
- @NonNull RemoteAnimationTargetCompat recentsTarget) {
- TaskItemView bottomView = getBottomTaskView();
- if (bottomView == null) {
- // This can be null if there were previously 0 tasks and the recycler view has not had
- // enough time to take in the data change, bind a new view, and lay out the new view.
- // TODO: Have a fallback to animate to
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION));
- return;
- }
- final Matrix appMatrix = new Matrix();
- playRemoteTransYAnim(anim, appMatrix);
- playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget,
- bottomView.getThumbnailView());
- playRemoteTaskListFadeIn(anim, bottomView);
- mStartedEnterAnimation = true;
- }
-
- /**
- * Play translation Y animation for the remote app to recents animation. Animates over all task
- * views as well as the closing app, easing them into their final vertical positions.
- *
- * @param anim animator set to play on
- * @param appMatrix transformation matrix for the closing app surface
- */
- private void playRemoteTransYAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix) {
- final ArrayList<TaskItemView> views = getTaskViews();
-
- // Start Y translation from about halfway through the tasks list to the bottom thumbnail.
- float taskHeight = getResources().getDimension(R.dimen.task_item_height);
- float totalTransY = -(MAX_TASKS_TO_DISPLAY / 2.0f - 1) * taskHeight;
- for (int i = 0, size = views.size(); i < size; i++) {
- views.get(i).setTranslationY(totalTransY);
- }
-
- ValueAnimator transYAnim = ValueAnimator.ofFloat(totalTransY, 0);
- transYAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
- transYAnim.setInterpolator(FAST_OUT_SLOW_IN_2);
- transYAnim.addUpdateListener(valueAnimator -> {
- float transY = (float) valueAnimator.getAnimatedValue();
- for (int i = 0, size = views.size(); i < size; i++) {
- views.get(i).setTranslationY(transY);
- }
- appMatrix.postTranslate(0, transY - totalTransY);
- });
- transYAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- for (int i = 0, size = views.size(); i < size; i++) {
- views.get(i).setTranslationY(0);
- }
- }
- });
- anim.play(transYAnim);
- }
-
- /**
- * Play the scale down animation for the remote app to recents animation where the app surface
- * scales down to where the thumbnail is.
- *
- * @param anim animator set to play on
- * @param appMatrix transformation matrix for the app surface
- * @param appTarget closing app target
- * @param recentsTarget opening recents target
- * @param thumbnailView thumbnail view to animate to
- */
- private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix,
- @NonNull RemoteAnimationTargetCompat appTarget,
- @NonNull RemoteAnimationTargetCompat recentsTarget,
- @NonNull View thumbnailView) {
- // Identify where the entering remote app should animate to.
- Rect endRect = new Rect();
- thumbnailView.getGlobalVisibleRect(endRect);
- Rect appBounds = appTarget.sourceContainerBounds;
- RectF currentAppRect = new RectF();
-
- SyncRtSurfaceTransactionApplierCompat surfaceApplier =
- new SyncRtSurfaceTransactionApplierCompat(this);
-
- // Keep recents visible throughout the animation.
- SurfaceParams[] params = new SurfaceParams[2];
- // Closing app should stay on top.
- int boostedMode = MODE_CLOSING;
- params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
- null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
-
- ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1);
- remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
- remoteAppAnim.addUpdateListener(new MultiValueUpdateListener() {
- private final FloatProp mScaleX;
- private final FloatProp mScaleY;
- private final FloatProp mTranslationX;
- private final FloatProp mTranslationY;
- private final FloatProp mAlpha;
-
- {
- // Scale down and move to view location.
- float endScaleX = ((float) endRect.width()) / appBounds.width();
- mScaleX = new FloatProp(1f, endScaleX, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
- FAST_OUT_SLOW_IN_1);
- float endScaleY = ((float) endRect.height()) / appBounds.height();
- mScaleY = new FloatProp(1f, endScaleY, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
- FAST_OUT_SLOW_IN_1);
- float endTranslationX = endRect.left -
- (appBounds.width() - thumbnailView.getWidth()) / 2.0f;
- mTranslationX = new FloatProp(0, endTranslationX, 0,
- REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_1);
- float endTranslationY = endRect.top -
- (appBounds.height() - thumbnailView.getHeight()) / 2.0f;
- mTranslationY = new FloatProp(0, endTranslationY, 0,
- REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_2);
- mAlpha = new FloatProp(1.0f, 0, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
- ACCEL_2);
- }
-
- @Override
- public void onUpdate(float percent) {
- Matrix m = new Matrix();
- m.preScale(mScaleX.value, mScaleY.value,
- appBounds.width() / 2.0f, appBounds.height() / 2.0f);
- m.postTranslate(mTranslationX.value, mTranslationY.value);
- appMatrix.preConcat(m);
- params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix,
- null /* windowCrop */, getLayer(appTarget, boostedMode),
- 0 /* cornerRadius */);
- surfaceApplier.scheduleApply(params);
-
- m.mapRect(currentAppRect, new RectF(appBounds));
- setViewToRect(thumbnailView, new RectF(endRect), currentAppRect);
- appMatrix.reset();
- }
- });
- remoteAppAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- thumbnailView.setTranslationY(0);
- thumbnailView.setTranslationX(0);
- thumbnailView.setScaleX(1);
- thumbnailView.setScaleY(1);
- }
- });
- anim.play(remoteAppAnim);
- }
-
- /**
- * Play task list fade in animation as part of remote app to recents animation. This animation
- * ensures that the task views in the recents list fade in from bottom to top.
- *
- * @param anim animator set to play on
- * @param appTaskView the task view associated with the remote app closing
- */
- private void playRemoteTaskListFadeIn(@NonNull AnimatorSet anim,
- @NonNull TaskItemView appTaskView) {
- long delay = REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY;
- int childCount = mTaskRecyclerView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- ValueAnimator fadeAnim = ValueAnimator.ofFloat(0, 1.0f);
- fadeAnim.setDuration(REMOTE_TO_RECENTS_ITEM_FADE_DURATION).setInterpolator(OUT_SLOW_IN);
- fadeAnim.setStartDelay(delay);
- View view = mTaskRecyclerView.getChildAt(i);
- if (Objects.equals(view, appTaskView)) {
- // Only animate icon and text for the view with snapshot animating in
- final View icon = appTaskView.getIconView();
- final View label = appTaskView.getLabelView();
-
- icon.setAlpha(0.0f);
- label.setAlpha(0.0f);
-
- fadeAnim.addUpdateListener(alphaVal -> {
- float val = alphaVal.getAnimatedFraction();
-
- icon.setAlpha(val);
- label.setAlpha(val);
- });
- fadeAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- icon.setAlpha(1.0f);
- label.setAlpha(1.0f);
- }
- });
- } else {
- // Otherwise, fade in the entire view.
- view.setAlpha(0.0f);
- fadeAnim.addUpdateListener(alphaVal -> {
- float val = alphaVal.getAnimatedFraction();
- view.setAlpha(val);
- });
- fadeAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setAlpha(1.0f);
- }
- });
- }
- anim.play(fadeAnim);
-
- int itemType = mTaskRecyclerView.getChildViewHolder(view).getItemViewType();
- if (itemType == ITEM_TYPE_CLEAR_ALL) {
- // Don't add delay. Clear all should animate at same time as next view.
- continue;
- }
- delay += REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY;
- }
- }
-
- /**
- * Set view properties so that the view fits to the target rect.
- *
- * @param view view to set
- * @param origRect original rect that view was located
- * @param targetRect rect to set to
- */
- private void setViewToRect(View view, RectF origRect, RectF targetRect) {
- float dX = targetRect.left - origRect.left;
- float dY = targetRect.top - origRect.top;
- view.setTranslationX(dX);
- view.setTranslationY(dY);
-
- float scaleX = targetRect.width() / origRect.width();
- float scaleY = targetRect.height() / origRect.height();
- view.setPivotX(0);
- view.setPivotY(0);
- view.setScaleX(scaleX);
- view.setScaleY(scaleY);
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets = insets;
- mTaskRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
- mTaskRecyclerView.invalidateItemDecorations();
- if (mInsets.top != 0) {
- updateStatusBarScrim();
- }
- }
-
- /**
- * Callback for when this view is ready for a remote animation from app to overview.
- */
- public interface onReadyForRemoteAnimCallback {
-
- void onReadyForRemoteAnim();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
deleted file mode 100644
index f184ad0..0000000
--- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.views;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.quickstep.ThumbnailDrawable;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-
-/**
- * View representing an individual task item with the icon + thumbnail adjacent to the task label.
- */
-public final class TaskItemView extends LinearLayout {
-
- private static final String EMPTY_LABEL = "";
- private static final String DEFAULT_LABEL = "...";
- private final Drawable mDefaultIcon;
- private final Drawable mDefaultThumbnail;
- private final TaskLayerDrawable mIconDrawable;
- private final TaskLayerDrawable mThumbnailDrawable;
- private View mTaskIconThumbnailView;
- private TextView mLabelView;
- private ImageView mIconView;
- private ImageView mThumbnailView;
- private float mContentTransitionProgress;
- private int mDisplayedOrientation;
-
- /**
- * Property representing the content transition progress of the view. 1.0f represents that the
- * currently bound icon, thumbnail, and label are fully animated in and visible.
- */
- public static FloatProperty CONTENT_TRANSITION_PROGRESS =
- new FloatProperty<TaskItemView>("taskContentTransitionProgress") {
- @Override
- public void setValue(TaskItemView view, float progress) {
- view.setContentTransitionProgress(progress);
- }
-
- @Override
- public Float get(TaskItemView view) {
- return view.mContentTransitionProgress;
- }
- };
-
- public TaskItemView(Context context, AttributeSet attrs) {
- super(context, attrs);
- Resources res = context.getResources();
- mDefaultIcon = res.getDrawable(android.R.drawable.sym_def_app_icon, context.getTheme());
- mDefaultThumbnail = res.getDrawable(R.drawable.default_thumbnail, context.getTheme());
- mIconDrawable = new TaskLayerDrawable(context);
- mThumbnailDrawable = new TaskLayerDrawable(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mLabelView = findViewById(R.id.task_label);
- mTaskIconThumbnailView = findViewById(R.id.task_icon_and_thumbnail);
- mThumbnailView = findViewById(R.id.task_thumbnail);
- mIconView = findViewById(R.id.task_icon);
-
- mThumbnailView.setImageDrawable(mThumbnailDrawable);
- mIconView.setImageDrawable(mIconDrawable);
-
- resetToEmptyUi();
- CONTENT_TRANSITION_PROGRESS.setValue(this, 1.0f);
- }
-
- /**
- * Resets task item view to empty, loading UI.
- */
- public void resetToEmptyUi() {
- mIconDrawable.resetDrawable();
- mThumbnailDrawable.resetDrawable();
- setLabel(EMPTY_LABEL);
- }
-
- /**
- * Set the label for the task item. Sets to a default label if null.
- *
- * @param label task label
- */
- public void setLabel(@Nullable String label) {
- mLabelView.setText(getSafeLabel(label));
- // TODO: Animation for label
- }
-
- /**
- * Set the icon for the task item. Sets to a default icon if null.
- *
- * @param icon task icon
- */
- public void setIcon(@Nullable Drawable icon) {
- // TODO: Scale the icon up based off the padding on the side
- // The icon proper is actually smaller than the drawable and has "padding" on the side for
- // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the
- // view if we want the icon to be flush with the bottom of the thumbnail.
- mIconDrawable.setCurrentDrawable(getSafeIcon(icon));
- }
-
- /**
- * Set the task thumbnail for the task. Sets to a default thumbnail if null.
- *
- * @param thumbnailData task thumbnail data for the task
- */
- public void setThumbnail(@Nullable ThumbnailData thumbnailData) {
- mThumbnailDrawable.setCurrentDrawable(getSafeThumbnail(thumbnailData));
- }
-
- public View getThumbnailView() {
- return mThumbnailView;
- }
-
- public View getIconView() {
- return mIconView;
- }
-
- public View getLabelView() {
- return mLabelView;
- }
-
- /**
- * Start a new animation from the current task content to the specified new content. The caller
- * is responsible for the actual animation control via the property
- * {@link #CONTENT_TRANSITION_PROGRESS}.
- *
- * @param endIcon the icon to animate to
- * @param endThumbnail the thumbnail to animate to
- * @param endLabel the label to animate to
- */
- public void startContentAnimation(@Nullable Drawable endIcon,
- @Nullable ThumbnailData endThumbnail, @Nullable String endLabel) {
- mIconDrawable.startNewTransition(getSafeIcon(endIcon));
- mThumbnailDrawable.startNewTransition(getSafeThumbnail(endThumbnail));
- // TODO: Animation for label
-
- setContentTransitionProgress(0.0f);
- }
-
- private void setContentTransitionProgress(float progress) {
- mContentTransitionProgress = progress;
- mIconDrawable.setTransitionProgress(progress);
- mThumbnailDrawable.setTransitionProgress(progress);
- // TODO: Animation for label
- }
-
- private @NonNull Drawable getSafeIcon(@Nullable Drawable icon) {
- return (icon != null) ? icon : mDefaultIcon;
- }
-
- private @NonNull Drawable getSafeThumbnail(@Nullable ThumbnailData thumbnailData) {
- if (thumbnailData == null || thumbnailData.thumbnail == null) {
- return mDefaultThumbnail;
- }
- int orientation = getResources().getConfiguration().orientation;
- return new ThumbnailDrawable(getResources(), thumbnailData,
- orientation /* requestedOrientation */);
- }
-
- private @NonNull String getSafeLabel(@Nullable String label) {
- return (label != null) ? label : DEFAULT_LABEL;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- onOrientationChanged(getResources().getConfiguration().orientation);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- onOrientationChanged(newConfig.orientation);
- }
-
- private void onOrientationChanged(int newOrientation) {
- if (mDisplayedOrientation == newOrientation) {
- return;
- }
- mDisplayedOrientation = newOrientation;
- int layerCount = mThumbnailDrawable.getNumberOfLayers();
- for (int i = 0; i < layerCount; i++) {
- Drawable drawable = mThumbnailDrawable.getDrawable(i);
- if (drawable instanceof ThumbnailDrawable) {
- ((ThumbnailDrawable) drawable).setRequestedOrientation(newOrientation);
- }
- }
- mTaskIconThumbnailView.forceLayout();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java b/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java
deleted file mode 100644
index 98b66b9..0000000
--- a/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.R;
-
-/**
- * A layer drawable for task content that transitions between two drawables by crossfading. Similar
- * to {@link android.graphics.drawable.TransitionDrawable} but allows callers to control transition
- * progress and provides a default, empty drawable.
- */
-public final class TaskLayerDrawable extends LayerDrawable {
- private final Drawable mEmptyDrawable;
- private float mProgress;
-
- public TaskLayerDrawable(Context context) {
- super(new Drawable[0]);
-
- // Use empty drawable for both layers initially.
- mEmptyDrawable = context.getResources().getDrawable(
- R.drawable.empty_content_box, context.getTheme());
- addLayer(mEmptyDrawable);
- addLayer(mEmptyDrawable);
- setTransitionProgress(1.0f);
- }
-
- /**
- * Immediately set the front-most drawable layer.
- *
- * @param drawable drawable to set
- */
- public void setCurrentDrawable(@NonNull Drawable drawable) {
- setDrawable(0, drawable);
- applyTransitionProgress(mProgress);
- }
-
- /**
- * Immediately reset the drawable to showing the empty drawable.
- */
- public void resetDrawable() {
- setCurrentDrawable(mEmptyDrawable);
- }
-
- /**
- * Prepare to start animating the transition by pushing the current drawable to the back and
- * setting a new drawable to the front layer and making it invisible.
- *
- * @param endDrawable drawable to animate to
- */
- public void startNewTransition(@NonNull Drawable endDrawable) {
- Drawable oldDrawable = getDrawable(0);
- setDrawable(1, oldDrawable);
- setDrawable(0, endDrawable);
- setTransitionProgress(0.0f);
- }
-
- /**
- * Set the progress of the transition animation to crossfade the two drawables.
- *
- * @param progress current transition progress between 0 (front view invisible) and 1
- * (front view visible)
- */
- public void setTransitionProgress(float progress) {
- if (progress > 1 || progress < 0) {
- throw new IllegalArgumentException("Transition progress should be between 0 and 1");
- }
- mProgress = progress;
- applyTransitionProgress(progress);
- }
-
- private void applyTransitionProgress(float progress) {
- int drawableAlpha = (int) (progress * 255);
- getDrawable(0).setAlpha(drawableAlpha);
- if (getDrawable(0) != getDrawable(1)) {
- // Only do this if it's a different drawable so that it fades out.
- // Otherwise, we'd just be overwriting the front drawable's alpha.
- getDrawable(1).setAlpha(255 - drawableAlpha);
- }
- invalidateSelf();
- }
-}
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java b/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
deleted file mode 100644
index eaefa21..0000000
--- a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.launcher3.R;
-
-/**
- * Square view that holds thumbnail and icon and shrinks them appropriately so that both fit nicely
- * within the view. Side length is determined by height.
- */
-final class TaskThumbnailIconView extends ViewGroup {
- private final Rect mTmpFrameRect = new Rect();
- private final Rect mTmpChildRect = new Rect();
- private View mThumbnailView;
- private View mIconView;
- private static final float SUBITEM_FRAME_RATIO = .6f;
-
- public TaskThumbnailIconView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mThumbnailView = findViewById(R.id.task_thumbnail);
- mIconView = findViewById(R.id.task_icon);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
- int width = height;
- setMeasuredDimension(width, height);
-
- int subItemSize = (int) (SUBITEM_FRAME_RATIO * height);
- if (mThumbnailView.getVisibility() != GONE) {
- boolean isPortrait =
- (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT);
- int thumbnailHeightSpec =
- makeMeasureSpec(isPortrait ? height : subItemSize, MeasureSpec.EXACTLY);
- int thumbnailWidthSpec =
- makeMeasureSpec(isPortrait ? subItemSize : width, MeasureSpec.EXACTLY);
- measureChild(mThumbnailView, thumbnailWidthSpec, thumbnailHeightSpec);
- }
- if (mIconView.getVisibility() != GONE) {
- int iconHeightSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
- int iconWidthSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY);
- measureChild(mIconView, iconWidthSpec, iconHeightSpec);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- mTmpFrameRect.left = getPaddingLeft();
- mTmpFrameRect.right = right - left - getPaddingRight();
- mTmpFrameRect.top = getPaddingTop();
- mTmpFrameRect.bottom = bottom - top - getPaddingBottom();
-
- // Layout the thumbnail to the top-start corner of the view
- if (mThumbnailView.getVisibility() != GONE) {
- final int width = mThumbnailView.getMeasuredWidth();
- final int height = mThumbnailView.getMeasuredHeight();
-
- final int thumbnailGravity = Gravity.TOP | Gravity.START;
- Gravity.apply(thumbnailGravity, width, height, mTmpFrameRect, mTmpChildRect);
-
- mThumbnailView.layout(mTmpChildRect.left, mTmpChildRect.top,
- mTmpChildRect.right, mTmpChildRect.bottom);
- }
-
- // Layout the icon to the bottom-end corner of the view
- if (mIconView.getVisibility() != GONE) {
- final int width = mIconView.getMeasuredWidth();
- final int height = mIconView.getMeasuredHeight();
-
- int thumbnailGravity = Gravity.BOTTOM | Gravity.END;
- Gravity.apply(thumbnailGravity, width, height, mTmpFrameRect, mTmpChildRect);
-
- mIconView.layout(mTmpChildRect.left, mTmpChildRect.top,
- mTmpChildRect.right, mTmpChildRect.bottom);
- }
- }
-}
diff --git a/go/src/com/android/launcher3/config/FeatureFlags.java b/go/src/com/android/launcher3/config/FeatureFlags.java
deleted file mode 100644
index a90808c..0000000
--- a/go/src/com/android/launcher3/config/FeatureFlags.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.config;
-
-import android.content.Context;
-
-/**
- * Defines a set of flags used to control various launcher behaviors
- */
-public final class FeatureFlags extends BaseFlags {
- private FeatureFlags() {
- // Prevent instantiation
- }
-
- // Features to control Launcher3Go behavior
- public static final boolean GO_DISABLE_WIDGETS = true;
- public static final boolean LAUNCHER3_SPRING_ICONS = false;
-}
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java
index 26c3313..7130531 100644
--- a/go/src/com/android/launcher3/model/LoaderResults.java
+++ b/go/src/com/android/launcher3/model/LoaderResults.java
@@ -16,10 +16,11 @@
package com.android.launcher3.model;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.model.BgDataModel.Callbacks;
-
-import java.lang.ref.WeakReference;
+import com.android.launcher3.util.LooperExecutor;
/**
* Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
@@ -27,8 +28,13 @@
public class LoaderResults extends BaseLoaderResults {
public LoaderResults(LauncherAppState app, BgDataModel dataModel,
- AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
- super(app, dataModel, allAppsList, pageToBindFirst, callbacks);
+ AllAppsList allAppsList, Callbacks[] callbacks) {
+ this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
+ }
+
+ public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+ AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
+ super(app, dataModel, allAppsList, callbacks, executor);
}
@Override
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 18f3f9d..3b3dc01 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -16,11 +16,14 @@
package com.android.launcher3.model;
+import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
-import com.android.launcher3.icons.ComponentWithLabel;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetListRowEntry;
@@ -29,14 +32,16 @@
import java.util.List;
import java.util.Set;
-import androidx.annotation.Nullable;
-
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
*
* <p> The widgets and shortcuts are organized using package name as its index.
*/
public class WidgetsModel {
+
+ // True is the widget support is disabled.
+ public static final boolean GO_DISABLE_WIDGETS = true;
+
private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
/**
@@ -55,7 +60,7 @@
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
*/
- public List<ComponentWithLabel> update(LauncherAppState app,
+ public List<ComponentWithLabelAndIcon> update(LauncherAppState app,
@Nullable PackageUserKey packageUser) {
return Collections.emptyList();
}
@@ -64,4 +69,9 @@
public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
LauncherAppState app) {
}
+
+ public WidgetItem getWidgetProviderInfoByProviderName(
+ ComponentName providerName) {
+ return null;
+ }
}
\ No newline at end of file
diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
deleted file mode 100644
index 42b1194..0000000
--- a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shortcuts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.notification.NotificationKeyData;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
- */
-public class DeepShortcutManager {
-
- private static final DeepShortcutManager sInstance = new DeepShortcutManager();
-
- public static DeepShortcutManager getInstance(Context context) {
- return sInstance;
- }
-
- private final QueryResult mFailure = new QueryResult();
-
- private DeepShortcutManager() { }
-
- /**
- * Queries for the shortcuts with the package name and provided ids.
- *
- * This method is intended to get the full details for shortcuts when they are added or updated,
- * because we only get "key" fields in onShortcutsChanged().
- */
- public QueryResult queryForFullDetails(String packageName,
- List<String> shortcutIds, UserHandle user) {
- return mFailure;
- }
-
- /**
- * Gets all the manifest and dynamic shortcuts associated with the given package and user,
- * to be displayed in the shortcuts container on long press.
- */
- public QueryResult queryForShortcutsContainer(ComponentName activity,
- UserHandle user) {
- return mFailure;
- }
-
- /**
- * Removes the given shortcut from the current list of pinned shortcuts.
- * (Runs on background thread)
- */
- public void unpinShortcut(final ShortcutKey key) {
- }
-
- /**
- * Adds the given shortcut to the current list of pinned shortcuts.
- * (Runs on background thread)
- */
- public void pinShortcut(final ShortcutKey key) {
- }
-
- public void startShortcut(String packageName, String id, Rect sourceBounds,
- Bundle startActivityOptions, UserHandle user) {
- }
-
- public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
- return null;
- }
-
- /**
- * Returns the id's of pinned shortcuts associated with the given package and user.
- *
- * If packageName is null, returns all pinned shortcuts regardless of package.
- */
- public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
- return mFailure;
- }
-
- public QueryResult queryForPinnedShortcuts(String packageName,
- List<String> shortcutIds, UserHandle user) {
- return mFailure;
- }
-
- public QueryResult queryForAllShortcuts(UserHandle user) {
- return mFailure;
- }
-
- public boolean hasHostPermission() {
- return false;
- }
-
-
- public static class QueryResult extends ArrayList<ShortcutInfo> {
-
- public boolean wasSuccess() {
- return true;
- }
- }
-}
diff --git a/gradle.properties b/gradle.properties
index 5b90f08..7a51375 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,12 +2,12 @@
android.useAndroidX = true
android.enableJetifier = true
-ANDROID_X_VERSION=1.0.0-beta01
+ANDROID_X_VERSION=1+
-GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.3.0
+GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.5.1
-PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.6
+PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.8
PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7
BUILD_TOOLS_VERSION=28.0.3
-COMPILE_SDK=android-Q
\ No newline at end of file
+COMPILE_SDK=android-R
diff --git a/iconloaderlib/Android.bp b/iconloaderlib/Android.bp
deleted file mode 100644
index f12d16e..0000000
--- a/iconloaderlib/Android.bp
+++ /dev/null
@@ -1,44 +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.
-
-android_library {
- name: "iconloader_base",
- sdk_version: "28",
- min_sdk_version: "21",
- static_libs: [
- "androidx.core_core",
- ],
- resource_dirs: [
- "res",
- ],
- srcs: [
- "src/**/*.java",
- ],
-}
-
-android_library {
- name: "iconloader",
- sdk_version: "system_current",
- min_sdk_version: "21",
- static_libs: [
- "androidx.core_core",
- ],
- resource_dirs: [
- "res",
- ],
- srcs: [
- "src/**/*.java",
- "src_full_lib/**/*.java",
- ],
-}
diff --git a/iconloaderlib/AndroidManifest.xml b/iconloaderlib/AndroidManifest.xml
deleted file mode 100644
index b30258d..0000000
--- a/iconloaderlib/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.launcher3.icons">
-</manifest>
diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle
deleted file mode 100644
index 8a4d2b7..0000000
--- a/iconloaderlib/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion COMPILE_SDK
- buildToolsVersion BUILD_TOOLS_VERSION
- publishNonDefault true
-
- defaultConfig {
- minSdkVersion 25
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
- }
-
- sourceSets {
- main {
- java.srcDirs = ['src', 'src_full_lib']
- manifest.srcFile 'AndroidManifest.xml'
- res.srcDirs = ['res']
- }
- }
-
- lintOptions {
- abortOnError false
- }
-
- tasks.withType(JavaCompile) {
- options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- implementation "androidx.core:core:${ANDROID_X_VERSION}"
-}
diff --git a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
deleted file mode 100644
index 9f13cf5..0000000
--- a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@color/legacy_icon_background"/>
- <foreground>
- <com.android.launcher3.icons.FixedScaleDrawable />
- </foreground>
-</adaptive-icon>
diff --git a/iconloaderlib/res/drawable/ic_instant_app_badge.xml b/iconloaderlib/res/drawable/ic_instant_app_badge.xml
deleted file mode 100644
index b74317e..0000000
--- a/iconloaderlib/res/drawable/ic_instant_app_badge.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/profile_badge_size"
- android:height="@dimen/profile_badge_size"
- android:viewportWidth="18"
- android:viewportHeight="18">
-
- <path
- android:fillColor="@android:color/black"
- android:strokeWidth="1"
- android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
- <path
- android:fillColor="@android:color/white"
- android:strokeWidth="1"
- android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
- <path
- android:fillColor="@android:color/white"
- android:strokeWidth="1"
- android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
- <path
- android:fillColor="@android:color/black"
- android:fillAlpha="0.87"
- android:strokeWidth="1"
- android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
-</vector>
diff --git a/iconloaderlib/res/values/colors.xml b/iconloaderlib/res/values/colors.xml
deleted file mode 100644
index 873b2fc..0000000
--- a/iconloaderlib/res/values/colors.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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>
- <color name="legacy_icon_background">#FFFFFF</color>
-</resources>
diff --git a/iconloaderlib/res/values/config.xml b/iconloaderlib/res/values/config.xml
deleted file mode 100644
index 68c2d2e..0000000
--- a/iconloaderlib/res/values/config.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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>
-
- <!-- Various configurations to control the simple cache implementation -->
-
- <dimen name="default_icon_bitmap_size">56dp</dimen>
- <bool name="simple_cache_enable_im_memory">false</bool>
- <string name="cache_db_name" translatable="false">app_icons.db</string>
-
-</resources>
\ No newline at end of file
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
deleted file mode 100644
index 5c4f37c..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ /dev/null
@@ -1,365 +0,0 @@
-package com.android.launcher3.icons;
-
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-/**
- * This class will be moved to androidx library. There shouldn't be any dependency outside
- * this package.
- */
-public class BaseIconFactory implements AutoCloseable {
-
- private static final String TAG = "BaseIconFactory";
- private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
- static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
- static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
-
- private static final float ICON_BADGE_SCALE = 0.444f;
-
- private final Rect mOldBounds = new Rect();
- protected final Context mContext;
- private final Canvas mCanvas;
- private final PackageManager mPm;
- private final ColorExtractor mColorExtractor;
- private boolean mDisableColorExtractor;
-
- protected final int mFillResIconDpi;
- protected final int mIconBitmapSize;
-
- private IconNormalizer mNormalizer;
- private ShadowGenerator mShadowGenerator;
- private final boolean mShapeDetection;
-
- private Drawable mWrapperIcon;
- private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
-
- protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
- boolean shapeDetection) {
- mContext = context.getApplicationContext();
- mShapeDetection = shapeDetection;
- mFillResIconDpi = fillResIconDpi;
- mIconBitmapSize = iconBitmapSize;
-
- mPm = mContext.getPackageManager();
- mColorExtractor = new ColorExtractor();
-
- mCanvas = new Canvas();
- mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
- clear();
- }
-
- protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
- this(context, fillResIconDpi, iconBitmapSize, false);
- }
-
- protected void clear() {
- mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
- mDisableColorExtractor = false;
- }
-
- public ShadowGenerator getShadowGenerator() {
- if (mShadowGenerator == null) {
- mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
- }
- return mShadowGenerator;
- }
-
- public IconNormalizer getNormalizer() {
- if (mNormalizer == null) {
- mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
- }
- return mNormalizer;
- }
-
- @SuppressWarnings("deprecation")
- public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
- try {
- Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
- if (resources != null) {
- final int id = resources.getIdentifier(iconRes.resourceName, null, null);
- // do not stamp old legacy shortcuts as the app may have already forgotten about it
- return createBadgedIconBitmap(
- resources.getDrawableForDensity(id, mFillResIconDpi),
- Process.myUserHandle() /* only available on primary user */,
- false /* do not apply legacy treatment */);
- }
- } catch (Exception e) {
- // Icon not found.
- }
- return null;
- }
-
- public BitmapInfo createIconBitmap(Bitmap icon) {
- if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {
- icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
- }
-
- return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- boolean shrinkNonAdaptiveIcons) {
- return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- int iconAppTargetSdk) {
- return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- int iconAppTargetSdk, boolean isInstantApp) {
- return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
- boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
- (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
- return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
- }
-
- public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
- boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
- (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
- return createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
- }
-
- /**
- * Creates bitmap using the source drawable and various parameters.
- * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
- *
- * @param icon source of the icon
- * @param user info can be used for a badge
- * @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated
- * @param isInstantApp info can be used for a badge
- * @param scale returns the scale result from normalization
- * @return a bitmap suitable for disaplaying as an icon at various system UIs.
- */
- public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
- boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
- if (scale == null) {
- scale = new float[1];
- }
- icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
- Bitmap bitmap = createIconBitmap(icon, scale[0]);
- if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
- mCanvas.setBitmap(bitmap);
- getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
- mCanvas.setBitmap(null);
- }
-
- if (isInstantApp) {
- badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
- }
- if (user != null) {
- BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
- Drawable badged = mPm.getUserBadgedIcon(drawable, user);
- if (badged instanceof BitmapDrawable) {
- bitmap = ((BitmapDrawable) badged).getBitmap();
- } else {
- bitmap = createIconBitmap(badged, 1f);
- }
- }
- return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor);
- }
-
- public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
- RectF iconBounds = new RectF();
- float[] scale = new float[1];
- icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
- return createIconBitmap(icon,
- Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
- }
-
- /**
- * Sets the background color used for wrapped adaptive icon
- */
- public void setWrapperBackgroundColor(int color) {
- mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
- }
-
- /**
- * Disables the dominant color extraction for all icons loaded.
- */
- public void disableColorExtraction() {
- mDisableColorExtractor = true;
- }
-
- private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
- boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
- if (icon == null) {
- return null;
- }
- float scale = 1f;
-
- if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
- if (mWrapperIcon == null) {
- mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
- .mutate();
- }
- AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
- dr.setBounds(0, 0, 1, 1);
- boolean[] outShape = new boolean[1];
- scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
- if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
- FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
- fsd.setDrawable(icon);
- fsd.setScale(scale);
- icon = dr;
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
-
- ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
- }
- } else {
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
- }
-
- outScale[0] = scale;
- return icon;
- }
-
- /**
- * Adds the {@param badge} on top of {@param target} using the badge dimensions.
- */
- public void badgeWithDrawable(Bitmap target, Drawable badge) {
- mCanvas.setBitmap(target);
- badgeWithDrawable(mCanvas, badge);
- mCanvas.setBitmap(null);
- }
-
- /**
- * Adds the {@param badge} on top of {@param target} using the badge dimensions.
- */
- public void badgeWithDrawable(Canvas target, Drawable badge) {
- int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
- badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
- mIconBitmapSize, mIconBitmapSize);
- badge.draw(target);
- }
-
- private Bitmap createIconBitmap(Drawable icon, float scale) {
- return createIconBitmap(icon, scale, mIconBitmapSize);
- }
-
- /**
- * @param icon drawable that should be flattened to a bitmap
- * @param scale the scale to apply before drawing {@param icon} on the canvas
- */
- public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
- Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- if (icon == null) {
- return bitmap;
- }
- mCanvas.setBitmap(bitmap);
- mOldBounds.set(icon.getBounds());
-
- if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
- int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
- Math.round(size * (1 - scale) / 2 ));
- icon.setBounds(offset, offset, size - offset, size - offset);
- icon.draw(mCanvas);
- } else {
- if (icon instanceof BitmapDrawable) {
- BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
- Bitmap b = bitmapDrawable.getBitmap();
- if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
- bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
- }
- }
- int width = size;
- int height = size;
-
- int intrinsicWidth = icon.getIntrinsicWidth();
- int intrinsicHeight = icon.getIntrinsicHeight();
- if (intrinsicWidth > 0 && intrinsicHeight > 0) {
- // Scale the icon proportionally to the icon dimensions
- final float ratio = (float) intrinsicWidth / intrinsicHeight;
- if (intrinsicWidth > intrinsicHeight) {
- height = (int) (width / ratio);
- } else if (intrinsicHeight > intrinsicWidth) {
- width = (int) (height * ratio);
- }
- }
- final int left = (size - width) / 2;
- final int top = (size - height) / 2;
- icon.setBounds(left, top, left + width, top + height);
- mCanvas.save();
- mCanvas.scale(scale, scale, size / 2, size / 2);
- icon.draw(mCanvas);
- mCanvas.restore();
-
- }
- icon.setBounds(mOldBounds);
- mCanvas.setBitmap(null);
- return bitmap;
- }
-
- @Override
- public void close() {
- clear();
- }
-
- public BitmapInfo makeDefaultIcon(UserHandle user) {
- return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi),
- user, Build.VERSION.SDK_INT);
- }
-
- public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
- return Resources.getSystem().getDrawableForDensity(
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
- iconDpi);
- }
-
- /**
- * Returns the correct badge size given an icon size
- */
- public static int getBadgeSizeForIconSize(int iconSize) {
- return (int) (ICON_BADGE_SCALE * iconSize);
- }
-
- /**
- * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
- * This allows the badging to be done based on the action bitmap size rather than
- * the scaled bitmap size.
- */
- private static class FixedSizeBitmapDrawable extends BitmapDrawable {
-
- public FixedSizeBitmapDrawable(Bitmap bitmap) {
- super(null, bitmap);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return getBitmap().getWidth();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return getBitmap().getWidth();
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
deleted file mode 100644
index 245561e..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-
-public class BitmapInfo {
-
- public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
-
- public Bitmap icon;
- public int color;
-
- public void applyTo(BitmapInfo info) {
- info.icon = icon;
- info.color = color;
- }
-
- public final boolean isLowRes() {
- return LOW_RES_ICON == icon;
- }
-
- public static BitmapInfo fromBitmap(Bitmap bitmap) {
- return fromBitmap(bitmap, null);
- }
-
- public static BitmapInfo fromBitmap(Bitmap bitmap, ColorExtractor dominantColorExtractor) {
- BitmapInfo info = new BitmapInfo();
- info.icon = bitmap;
- info.color = dominantColorExtractor != null
- ? dominantColorExtractor.findDominantColorByHue(bitmap)
- : 0;
- return info;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
deleted file mode 100644
index a66b929..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import android.annotation.TargetApi;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Picture;
-import android.os.Build;
-
-/**
- * Interface representing a bitmap draw operation.
- */
-public interface BitmapRenderer {
-
- boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
-
- static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
- Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- renderer.draw(new Canvas(result));
- return result;
- }
-
- @TargetApi(Build.VERSION_CODES.P)
- static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) {
- if (!USE_HARDWARE_BITMAP) {
- return createSoftwareBitmap(width, height, renderer);
- }
-
- Picture picture = new Picture();
- renderer.draw(picture.beginRecording(width, height));
- picture.endRecording();
- return Bitmap.createBitmap(picture);
- }
-
- void draw(Canvas out);
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java b/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
deleted file mode 100644
index 87bda82..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.util.SparseArray;
-import java.util.Arrays;
-
-/**
- * Utility class for extracting colors from a bitmap.
- */
-public class ColorExtractor {
-
- private final int NUM_SAMPLES = 20;
- private final float[] mTmpHsv = new float[3];
- private final float[] mTmpHueScoreHistogram = new float[360];
- private final int[] mTmpPixels = new int[NUM_SAMPLES];
- private final SparseArray<Float> mTmpRgbScores = new SparseArray<>();
-
- /**
- * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
- * @param bitmap The bitmap to scan
- */
- public int findDominantColorByHue(Bitmap bitmap) {
- return findDominantColorByHue(bitmap, NUM_SAMPLES);
- }
-
- /**
- * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
- * @param bitmap The bitmap to scan
- */
- public int findDominantColorByHue(Bitmap bitmap, int samples) {
- final int height = bitmap.getHeight();
- final int width = bitmap.getWidth();
- int sampleStride = (int) Math.sqrt((height * width) / samples);
- if (sampleStride < 1) {
- sampleStride = 1;
- }
-
- // This is an out-param, for getting the hsv values for an rgb
- float[] hsv = mTmpHsv;
- Arrays.fill(hsv, 0);
-
- // First get the best hue, by creating a histogram over 360 hue buckets,
- // where each pixel contributes a score weighted by saturation, value, and alpha.
- float[] hueScoreHistogram = mTmpHueScoreHistogram;
- Arrays.fill(hueScoreHistogram, 0);
- float highScore = -1;
- int bestHue = -1;
-
- int[] pixels = mTmpPixels;
- Arrays.fill(pixels, 0);
- int pixelCount = 0;
-
- for (int y = 0; y < height; y += sampleStride) {
- for (int x = 0; x < width; x += sampleStride) {
- int argb = bitmap.getPixel(x, y);
- int alpha = 0xFF & (argb >> 24);
- if (alpha < 0x80) {
- // Drop mostly-transparent pixels.
- continue;
- }
- // Remove the alpha channel.
- int rgb = argb | 0xFF000000;
- Color.colorToHSV(rgb, hsv);
- // Bucket colors by the 360 integer hues.
- int hue = (int) hsv[0];
- if (hue < 0 || hue >= hueScoreHistogram.length) {
- // Defensively avoid array bounds violations.
- continue;
- }
- if (pixelCount < samples) {
- pixels[pixelCount++] = rgb;
- }
- float score = hsv[1] * hsv[2];
- hueScoreHistogram[hue] += score;
- if (hueScoreHistogram[hue] > highScore) {
- highScore = hueScoreHistogram[hue];
- bestHue = hue;
- }
- }
- }
-
- SparseArray<Float> rgbScores = mTmpRgbScores;
- rgbScores.clear();
- int bestColor = 0xff000000;
- highScore = -1;
- // Go back over the RGB colors that match the winning hue,
- // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
- // The highest-scoring RGB color wins.
- for (int i = 0; i < pixelCount; i++) {
- int rgb = pixels[i];
- Color.colorToHSV(rgb, hsv);
- int hue = (int) hsv[0];
- if (hue == bestHue) {
- float s = hsv[1];
- float v = hsv[2];
- int bucket = (int) (s * 100) + (int) (v * 10000);
- // Score by cumulative saturation * value.
- float score = s * v;
- Float oldTotal = rgbScores.get(bucket);
- float newTotal = oldTotal == null ? score : oldTotal + score;
- rgbScores.put(bucket, newTotal);
- if (newTotal > highScore) {
- highScore = newTotal;
- // All the colors in the winning bucket are very similar. Last in wins.
- bestColor = rgb;
- }
- }
- }
- return bestColor;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
deleted file mode 100644
index 97a0fd3..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PathMeasure;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.Log;
-import android.view.ViewDebug;
-
-/**
- * Used to draw a notification dot on top of an icon.
- */
-public class DotRenderer {
-
- private static final String TAG = "DotRenderer";
-
- // The dot size is defined as a percentage of the app icon size.
- private static final float SIZE_PERCENTAGE = 0.228f;
-
- private final float mCircleRadius;
- private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
-
- private final Bitmap mBackgroundWithShadow;
- private final float mBitmapOffset;
-
- // Stores the center x and y position as a percentage (0 to 1) of the icon size
- private final float[] mRightDotPosition;
- private final float[] mLeftDotPosition;
-
- public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
- int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
- ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
- builder.ambientShadowAlpha = 88;
- mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
- mCircleRadius = builder.radius;
-
- mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
-
- // Find the points on the path that are closest to the top left and right corners.
- mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
- mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
- }
-
- private static float[] getPathPoint(Path path, float size, float direction) {
- float halfSize = size / 2;
- // Small delta so that we don't get a zero size triangle
- float delta = 1;
-
- float x = halfSize + direction * halfSize;
- Path trianglePath = new Path();
- trianglePath.moveTo(halfSize, halfSize);
- trianglePath.lineTo(x + delta * direction, 0);
- trianglePath.lineTo(x, -delta);
- trianglePath.close();
-
- trianglePath.op(path, Path.Op.INTERSECT);
- float[] pos = new float[2];
- new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
-
- pos[0] = pos[0] / size;
- pos[1] = pos[1] / size;
- return pos;
- }
-
- public float[] getLeftDotPosition() {
- return mLeftDotPosition;
- }
-
- public float[] getRightDotPosition() {
- return mRightDotPosition;
- }
-
- /**
- * Draw a circle on top of the canvas according to the given params.
- */
- public void draw(Canvas canvas, DrawParams params) {
- if (params == null) {
- Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
- return;
- }
- canvas.save();
-
- Rect iconBounds = params.iconBounds;
- float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
- float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
- float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
-
- // Ensure dot fits entirely in canvas clip bounds.
- Rect canvasBounds = canvas.getClipBounds();
- float offsetX = params.leftAlign
- ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
- : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
- float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));
-
- // We draw the dot relative to its center.
- canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
- canvas.scale(params.scale, params.scale);
-
- mCirclePaint.setColor(Color.BLACK);
- canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
- mCirclePaint.setColor(params.color);
- canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
- canvas.restore();
- }
-
- public static class DrawParams {
- /** The color (possibly based on the icon) to use for the dot. */
- @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
- public int color;
- /** The bounds of the icon that the dot is drawn on top of. */
- @ViewDebug.ExportedProperty(category = "notification dot")
- public Rect iconBounds = new Rect();
- /** The progress of the animation, from 0 to 1. */
- @ViewDebug.ExportedProperty(category = "notification dot")
- public float scale;
- /** Whether the dot should align to the top left of the icon rather than the top right. */
- @ViewDebug.ExportedProperty(category = "notification dot")
- public boolean leftAlign;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
deleted file mode 100644
index 516965e..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.android.launcher3.icons;
-
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.graphics.Canvas;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.DrawableWrapper;
-import android.util.AttributeSet;
-
-import org.xmlpull.v1.XmlPullParser;
-
-/**
- * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
- */
-public class FixedScaleDrawable extends DrawableWrapper {
-
- // TODO b/33553066 use the constant defined in MaskableIconDrawable
- private static final float LEGACY_ICON_SCALE = .7f * .6667f;
- private float mScaleX, mScaleY;
-
- public FixedScaleDrawable() {
- super(new ColorDrawable());
- mScaleX = LEGACY_ICON_SCALE;
- mScaleY = LEGACY_ICON_SCALE;
- }
-
- @Override
- public void draw(Canvas canvas) {
- int saveCount = canvas.save();
- canvas.scale(mScaleX, mScaleY,
- getBounds().exactCenterX(), getBounds().exactCenterY());
- super.draw(canvas);
- canvas.restoreToCount(saveCount);
- }
-
- @Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
-
- @Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
-
- public void setScale(float scale) {
- float h = getIntrinsicHeight();
- float w = getIntrinsicWidth();
- mScaleX = scale * LEGACY_ICON_SCALE;
- mScaleY = scale * LEGACY_ICON_SCALE;
- if (h > w && w > 0) {
- mScaleX *= w / h;
- } else if (w > h && h > 0) {
- mScaleY *= h / w;
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
deleted file mode 100644
index 3e818a5..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.RegionIterator;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-import androidx.annotation.ColorInt;
-
-public class GraphicsUtils {
-
- private static final String TAG = "GraphicsUtils";
-
- /**
- * Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version,
- * it bounds the alpha in valid range instead of throwing an exception to allow for safer
- * interpolation of color animations
- */
- @ColorInt
- public static int setColorAlphaBound(int color, int alpha) {
- if (alpha < 0) {
- alpha = 0;
- } else if (alpha > 255) {
- alpha = 255;
- }
- return (color & 0x00ffffff) | (alpha << 24);
- }
-
- /**
- * Compresses the bitmap to a byte array for serialization.
- */
- public static byte[] flattenBitmap(Bitmap bitmap) {
- // Try go guesstimate how much space the icon will take when serialized
- // to avoid unnecessary allocations/copies during the write (4 bytes per pixel).
- int size = bitmap.getWidth() * bitmap.getHeight() * 4;
- ByteArrayOutputStream out = new ByteArrayOutputStream(size);
- try {
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
- out.flush();
- out.close();
- return out.toByteArray();
- } catch (IOException e) {
- Log.w(TAG, "Could not write bitmap");
- return null;
- }
- }
-
- public static int getArea(Region r) {
- RegionIterator itr = new RegionIterator(r);
- int area = 0;
- Rect tempRect = new Rect();
- while (itr.next(tempRect)) {
- area += tempRect.width() * tempRect.height();
- }
- return area;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
deleted file mode 100644
index de39e79..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
+++ /dev/null
@@ -1,411 +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.icons;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class IconNormalizer {
-
- private static final String TAG = "IconNormalizer";
- private static final boolean DEBUG = false;
- // Ratio of icon visible area to full icon size for a square shaped icon
- private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
- // Ratio of icon visible area to full icon size for a circular shaped icon
- private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
-
- private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
-
- // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
- private static final float LINEAR_SCALE_SLOPE =
- (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
-
- private static final int MIN_VISIBLE_ALPHA = 40;
-
- // Shape detection related constants
- private static final float BOUND_RATIO_MARGIN = .05f;
- private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
- private static final float SCALE_NOT_INITIALIZED = 0;
-
- // Ratio of the diameter of an normalized circular icon to the actual icon size.
- public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;
-
- private final int mMaxSize;
- private final Bitmap mBitmap;
- private final Canvas mCanvas;
- private final Paint mPaintMaskShape;
- private final Paint mPaintMaskShapeOutline;
- private final byte[] mPixels;
-
- private final RectF mAdaptiveIconBounds;
- private float mAdaptiveIconScale;
-
- private boolean mEnableShapeDetection;
-
- // for each y, stores the position of the leftmost x and the rightmost x
- private final float[] mLeftBorder;
- private final float[] mRightBorder;
- private final Rect mBounds;
- private final Path mShapePath;
- private final Matrix mMatrix;
-
- /** package private **/
- IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) {
- // Use twice the icon size as maximum size to avoid scaling down twice.
- mMaxSize = iconBitmapSize * 2;
- mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
- mCanvas = new Canvas(mBitmap);
- mPixels = new byte[mMaxSize * mMaxSize];
- mLeftBorder = new float[mMaxSize];
- mRightBorder = new float[mMaxSize];
- mBounds = new Rect();
- mAdaptiveIconBounds = new RectF();
-
- mPaintMaskShape = new Paint();
- mPaintMaskShape.setColor(Color.RED);
- mPaintMaskShape.setStyle(Paint.Style.FILL);
- mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
-
- mPaintMaskShapeOutline = new Paint();
- mPaintMaskShapeOutline.setStrokeWidth(
- 2 * context.getResources().getDisplayMetrics().density);
- mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
- mPaintMaskShapeOutline.setColor(Color.BLACK);
- mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
- mShapePath = new Path();
- mMatrix = new Matrix();
- mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
- mEnableShapeDetection = shapeDetection;
- }
-
- private static float getScale(float hullArea, float boundingArea, float fullArea) {
- float hullByRect = hullArea / boundingArea;
- float scaleRequired;
- if (hullByRect < CIRCLE_AREA_BY_RECT) {
- scaleRequired = MAX_CIRCLE_AREA_FACTOR;
- } else {
- scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
- }
-
- float areaScale = hullArea / fullArea;
- // Use sqrt of the final ratio as the images is scaled across both width and height.
- return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
- }
-
- /**
- * @param d Should be AdaptiveIconDrawable
- * @param size Canvas size to use
- */
- @TargetApi(Build.VERSION_CODES.O)
- public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {
- Rect tmpBounds = new Rect(d.getBounds());
- d.setBounds(0, 0, size, size);
-
- Path path = ((AdaptiveIconDrawable) d).getIconMask();
- Region region = new Region();
- region.setPath(path, new Region(0, 0, size, size));
-
- Rect hullBounds = region.getBounds();
- int hullArea = GraphicsUtils.getArea(region);
-
- if (outBounds != null) {
- float sizeF = size;
- outBounds.set(
- hullBounds.left / sizeF,
- hullBounds.top / sizeF,
- 1 - (hullBounds.right / sizeF),
- 1 - (hullBounds.bottom / sizeF));
- }
- d.setBounds(tmpBounds);
- return getScale(hullArea, hullArea, size * size);
- }
-
- /**
- * Returns if the shape of the icon is same as the path.
- * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
- */
- private boolean isShape(Path maskPath) {
- // Condition1:
- // If width and height of the path not close to a square, then the icon shape is
- // not same as the mask shape.
- float iconRatio = ((float) mBounds.width()) / mBounds.height();
- if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
- if (DEBUG) {
- Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
- }
- return false;
- }
-
- // Condition 2:
- // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
- // should generate transparent image, if the actual icon is equivalent to the shape.
-
- // Fit the shape within the icon's bounding box
- mMatrix.reset();
- mMatrix.setScale(mBounds.width(), mBounds.height());
- mMatrix.postTranslate(mBounds.left, mBounds.top);
- maskPath.transform(mMatrix, mShapePath);
-
- // XOR operation
- mCanvas.drawPath(mShapePath, mPaintMaskShape);
-
- // DST_OUT operation around the mask path outline
- mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
-
- // Check if the result is almost transparent
- return isTransparentBitmap();
- }
-
- /**
- * Used to determine if certain the bitmap is transparent.
- */
- private boolean isTransparentBitmap() {
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mBitmap.copyPixelsToBuffer(buffer);
-
- int y = mBounds.top;
- // buffer position
- int index = y * mMaxSize;
- // buffer shift after every row, width of buffer = mMaxSize
- int rowSizeDiff = mMaxSize - mBounds.right;
-
- int sum = 0;
- for (; y < mBounds.bottom; y++) {
- index += mBounds.left;
- for (int x = mBounds.left; x < mBounds.right; x++) {
- if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
- sum++;
- }
- index++;
- }
- index += rowSizeDiff;
- }
-
- float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
- return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
- }
-
- /**
- * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
- * matches the design guidelines for a launcher icon.
- *
- * We first calculate the convex hull of the visible portion of the icon.
- * This hull then compared with the bounding rectangle of the hull to find how closely it
- * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
- * ideal solution but it gives satisfactory result without affecting the performance.
- *
- * This closeness is used to determine the ratio of hull area to the full icon size.
- * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
- *
- * @param outBounds optional rect to receive the fraction distance from each edge.
- */
- public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
- @Nullable Path path, @Nullable boolean[] outMaskShape) {
- if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
- if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
- mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
- }
- if (outBounds != null) {
- outBounds.set(mAdaptiveIconBounds);
- }
- return mAdaptiveIconScale;
- }
- int width = d.getIntrinsicWidth();
- int height = d.getIntrinsicHeight();
- if (width <= 0 || height <= 0) {
- width = width <= 0 || width > mMaxSize ? mMaxSize : width;
- height = height <= 0 || height > mMaxSize ? mMaxSize : height;
- } else if (width > mMaxSize || height > mMaxSize) {
- int max = Math.max(width, height);
- width = mMaxSize * width / max;
- height = mMaxSize * height / max;
- }
-
- mBitmap.eraseColor(Color.TRANSPARENT);
- d.setBounds(0, 0, width, height);
- d.draw(mCanvas);
-
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mBitmap.copyPixelsToBuffer(buffer);
-
- // Overall bounds of the visible icon.
- int topY = -1;
- int bottomY = -1;
- int leftX = mMaxSize + 1;
- int rightX = -1;
-
- // Create border by going through all pixels one row at a time and for each row find
- // the first and the last non-transparent pixel. Set those values to mLeftBorder and
- // mRightBorder and use -1 if there are no visible pixel in the row.
-
- // buffer position
- int index = 0;
- // buffer shift after every row, width of buffer = mMaxSize
- int rowSizeDiff = mMaxSize - width;
- // first and last position for any row.
- int firstX, lastX;
-
- for (int y = 0; y < height; y++) {
- firstX = lastX = -1;
- for (int x = 0; x < width; x++) {
- if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
- if (firstX == -1) {
- firstX = x;
- }
- lastX = x;
- }
- index++;
- }
- index += rowSizeDiff;
-
- mLeftBorder[y] = firstX;
- mRightBorder[y] = lastX;
-
- // If there is at least one visible pixel, update the overall bounds.
- if (firstX != -1) {
- bottomY = y;
- if (topY == -1) {
- topY = y;
- }
-
- leftX = Math.min(leftX, firstX);
- rightX = Math.max(rightX, lastX);
- }
- }
-
- if (topY == -1 || rightX == -1) {
- // No valid pixels found. Do not scale.
- return 1;
- }
-
- convertToConvexArray(mLeftBorder, 1, topY, bottomY);
- convertToConvexArray(mRightBorder, -1, topY, bottomY);
-
- // Area of the convex hull
- float area = 0;
- for (int y = 0; y < height; y++) {
- if (mLeftBorder[y] <= -1) {
- continue;
- }
- area += mRightBorder[y] - mLeftBorder[y] + 1;
- }
-
- mBounds.left = leftX;
- mBounds.right = rightX;
-
- mBounds.top = topY;
- mBounds.bottom = bottomY;
-
- if (outBounds != null) {
- outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
- 1 - ((float) mBounds.right) / width,
- 1 - ((float) mBounds.bottom) / height);
- }
- if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {
- outMaskShape[0] = isShape(path);
- }
- // Area of the rectangle required to fit the convex hull
- float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
- return getScale(area, rectArea, width * height);
- }
-
- /**
- * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
- * (except on either ends) with appropriate values.
- * @param xCoordinates map of x coordinate per y.
- * @param direction 1 for left border and -1 for right border.
- * @param topY the first Y position (inclusive) with a valid value.
- * @param bottomY the last Y position (inclusive) with a valid value.
- */
- private static void convertToConvexArray(
- float[] xCoordinates, int direction, int topY, int bottomY) {
- int total = xCoordinates.length;
- // The tangent at each pixel.
- float[] angles = new float[total - 1];
-
- int first = topY; // First valid y coordinate
- int last = -1; // Last valid y coordinate which didn't have a missing value
-
- float lastAngle = Float.MAX_VALUE;
-
- for (int i = topY + 1; i <= bottomY; i++) {
- if (xCoordinates[i] <= -1) {
- continue;
- }
- int start;
-
- if (lastAngle == Float.MAX_VALUE) {
- start = first;
- } else {
- float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
- start = last;
- // If this position creates a concave angle, keep moving up until we find a
- // position which creates a convex angle.
- if ((currentAngle - lastAngle) * direction < 0) {
- while (start > first) {
- start --;
- currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
- if ((currentAngle - angles[start]) * direction >= 0) {
- break;
- }
- }
- }
- }
-
- // Reset from last check
- lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
- // Update all the points from start.
- for (int j = start; j < i; j++) {
- angles[j] = lastAngle;
- xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
- }
- last = i;
- }
- }
-
- /**
- * @return The diameter of the normalized circle that fits inside of the square (size x size).
- */
- public static int getNormalizedCircleSize(int size) {
- float area = size * size * MAX_CIRCLE_AREA_FACTOR;
- return (int) Math.round(Math.sqrt((4 * area) / Math.PI));
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
deleted file mode 100644
index 5df8043..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BlurMaskFilter;
-import android.graphics.BlurMaskFilter.Blur;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.RectF;
-
-/**
- * Utility class to add shadows to bitmaps.
- */
-public class ShadowGenerator {
- public static final float BLUR_FACTOR = 0.5f/48;
-
- // Percent of actual icon size
- public static final float KEY_SHADOW_DISTANCE = 1f/48;
- private static final int KEY_SHADOW_ALPHA = 61;
- // Percent of actual icon size
- private static final float HALF_DISTANCE = 0.5f;
- private static final int AMBIENT_SHADOW_ALPHA = 30;
-
- private final int mIconSize;
-
- private final Paint mBlurPaint;
- private final Paint mDrawPaint;
- private final BlurMaskFilter mDefaultBlurMaskFilter;
-
- public ShadowGenerator(int iconSize) {
- mIconSize = iconSize;
- mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
- }
-
- public synchronized void recreateIcon(Bitmap icon, Canvas out) {
- recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
- }
-
- public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
- int ambientAlpha, int keyAlpha, Canvas out) {
- int[] offset = new int[2];
- mBlurPaint.setMaskFilter(blurMaskFilter);
- Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
-
- // Draw ambient shadow
- mDrawPaint.setAlpha(ambientAlpha);
- out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
-
- // Draw key shadow
- mDrawPaint.setAlpha(keyAlpha);
- out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
-
- // Draw the icon
- mDrawPaint.setAlpha(255);
- out.drawBitmap(icon, 0, 0, mDrawPaint);
- }
-
- /**
- * Returns the minimum amount by which an icon with {@param bounds} should be scaled
- * so that the shadows do not get clipped.
- */
- public static float getScaleForBounds(RectF bounds) {
- float scale = 1;
-
- // For top, left & right, we need same space.
- float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top);
- if (minSide < BLUR_FACTOR) {
- scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide);
- }
-
- float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE;
- if (bounds.bottom < bottomSpace) {
- scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom));
- }
- return scale;
- }
-
- public static class Builder {
-
- public final RectF bounds = new RectF();
- public final int color;
-
- public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA;
-
- public float shadowBlur;
-
- public float keyShadowDistance;
- public int keyShadowAlpha = KEY_SHADOW_ALPHA;
- public float radius;
-
- public Builder(int color) {
- this.color = color;
- }
-
- public Builder setupBlurForSize(int height) {
- shadowBlur = height * 1f / 24;
- keyShadowDistance = height * 1f / 16;
- return this;
- }
-
- public Bitmap createPill(int width, int height) {
- return createPill(width, height, height / 2f);
- }
-
- public Bitmap createPill(int width, int height, float r) {
- radius = r;
-
- int centerX = Math.round(width / 2f + shadowBlur);
- int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
- int center = Math.max(centerX, centerY);
- bounds.set(0, 0, width, height);
- bounds.offsetTo(center - width / 2f, center - height / 2f);
-
- int size = center * 2;
- Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
- drawShadow(new Canvas(result));
- return result;
- }
-
- public void drawShadow(Canvas c) {
- Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- p.setColor(color);
-
- // Key shadow
- p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
- setColorAlphaBound(Color.BLACK, keyShadowAlpha));
- c.drawRoundRect(bounds, radius, radius, p);
-
- // Ambient shadow
- p.setShadowLayer(shadowBlur, 0, 0,
- setColorAlphaBound(Color.BLACK, ambientShadowAlpha));
- c.drawRoundRect(bounds, radius, radius, p);
-
- if (Color.alpha(color) < 255) {
- // Clear any content inside the pill-rect for translucent fill.
- p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- p.clearShadowLayer();
- p.setColor(Color.BLACK);
- c.drawRoundRect(bounds, radius, radius, p);
-
- p.setXfermode(null);
- p.setColor(color);
- c.drawRoundRect(bounds, radius, radius, p);
- }
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
deleted file mode 100644
index a886c0a..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ /dev/null
@@ -1,576 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons.cache;
-
-import static com.android.launcher3.icons.BaseIconFactory.getFullResDefaultActivityIcon;
-import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Handler;
-import android.os.LocaleList;
-import android.os.Looper;
-import android.os.Process;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SQLiteCacheHelper;
-
-import java.util.AbstractMap;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-
-public abstract class BaseIconCache {
-
- private static final String TAG = "BaseIconCache";
- private static final boolean DEBUG = false;
-
- private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
-
- // Empty class name is used for storing package default entry.
- public static final String EMPTY_CLASS_NAME = ".";
-
- public static class CacheEntry extends BitmapInfo {
- public CharSequence title = "";
- public CharSequence contentDescription = "";
- }
-
- private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
-
- protected final Context mContext;
- protected final PackageManager mPackageManager;
-
- private final Map<ComponentKey, CacheEntry> mCache;
- protected final Handler mWorkerHandler;
-
- protected int mIconDpi;
- protected IconDB mIconDb;
- protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
- protected String mSystemState = "";
-
- private final String mDbFileName;
- private final BitmapFactory.Options mDecodeOptions;
- private final Looper mBgLooper;
-
- public BaseIconCache(Context context, String dbFileName, Looper bgLooper,
- int iconDpi, int iconPixelSize, boolean inMemoryCache) {
- mContext = context;
- mDbFileName = dbFileName;
- mPackageManager = context.getPackageManager();
- mBgLooper = bgLooper;
- mWorkerHandler = new Handler(mBgLooper);
-
- if (inMemoryCache) {
- mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
- } else {
- // Use a dummy cache
- mCache = new AbstractMap<ComponentKey, CacheEntry>() {
- @Override
- public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
- return Collections.emptySet();
- }
-
- @Override
- public CacheEntry put(ComponentKey key, CacheEntry value) {
- return value;
- }
- };
- }
-
- if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- mDecodeOptions = new BitmapFactory.Options();
- mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
- } else {
- mDecodeOptions = null;
- }
-
- updateSystemState();
- mIconDpi = iconDpi;
- mIconDb = new IconDB(context, dbFileName, iconPixelSize);
- }
-
- /**
- * Returns the persistable serial number for {@param user}. Subclass should implement proper
- * caching strategy to avoid making binder call every time.
- */
- protected abstract long getSerialNumberForUser(UserHandle user);
-
- /**
- * Return true if the given app is an instant app and should be badged appropriately.
- */
- protected abstract boolean isInstantApp(ApplicationInfo info);
-
- /**
- * Opens and returns an icon factory. The factory is recycled by the caller.
- */
- protected abstract BaseIconFactory getIconFactory();
-
- public void updateIconParams(int iconDpi, int iconPixelSize) {
- mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize));
- }
-
- private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) {
- mIconDpi = iconDpi;
- mDefaultIcons.clear();
- mIconDb.clear();
- mIconDb.close();
- mIconDb = new IconDB(mContext, mDbFileName, iconPixelSize);
- mCache.clear();
- }
-
- private Drawable getFullResIcon(Resources resources, int iconId) {
- if (resources != null && iconId != 0) {
- try {
- return resources.getDrawableForDensity(iconId, mIconDpi);
- } catch (Resources.NotFoundException e) { }
- }
- return getFullResDefaultActivityIcon(mIconDpi);
- }
-
- public Drawable getFullResIcon(String packageName, int iconId) {
- try {
- return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId);
- } catch (PackageManager.NameNotFoundException e) { }
- return getFullResDefaultActivityIcon(mIconDpi);
- }
-
- public Drawable getFullResIcon(ActivityInfo info) {
- try {
- return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo),
- info.getIconResource());
- } catch (PackageManager.NameNotFoundException e) { }
- return getFullResDefaultActivityIcon(mIconDpi);
- }
-
- private BitmapInfo makeDefaultIcon(UserHandle user) {
- try (BaseIconFactory li = getIconFactory()) {
- return li.makeDefaultIcon(user);
- }
- }
-
- /**
- * Remove any records for the supplied ComponentName.
- */
- public synchronized void remove(ComponentName componentName, UserHandle user) {
- mCache.remove(new ComponentKey(componentName, user));
- }
-
- /**
- * Remove any records for the supplied package name from memory.
- */
- private void removeFromMemCacheLocked(String packageName, UserHandle user) {
- HashSet<ComponentKey> forDeletion = new HashSet<>();
- for (ComponentKey key: mCache.keySet()) {
- if (key.componentName.getPackageName().equals(packageName)
- && key.user.equals(user)) {
- forDeletion.add(key);
- }
- }
- for (ComponentKey condemned: forDeletion) {
- mCache.remove(condemned);
- }
- }
-
- /**
- * Removes the entries related to the given package in memory and persistent DB.
- */
- public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
- removeFromMemCacheLocked(packageName, user);
- long userSerial = getSerialNumberForUser(user);
- mIconDb.delete(
- IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[]{packageName + "/%", Long.toString(userSerial)});
- }
-
- public IconCacheUpdateHandler getUpdateHandler() {
- updateSystemState();
- return new IconCacheUpdateHandler(this);
- }
-
- /**
- * Refreshes the system state definition used to check the validity of the cache. It
- * incorporates all the properties that can affect the cache like the list of enabled locale
- * and system-version.
- */
- private void updateSystemState() {
- mLocaleList = mContext.getResources().getConfiguration().getLocales();
- mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
- }
-
- protected String getIconSystemState(String packageName) {
- return mSystemState;
- }
-
- /**
- * Adds an entry into the DB and the in-memory cache.
- * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
- * the memory. This is useful then the previous bitmap was created using
- * old data.
- * package private
- */
- protected synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic,
- PackageInfo info, long userSerial, boolean replaceExisting) {
- UserHandle user = cachingLogic.getUser(object);
- ComponentName componentName = cachingLogic.getComponent(object);
-
- final ComponentKey key = new ComponentKey(componentName, user);
- CacheEntry entry = null;
- if (!replaceExisting) {
- entry = mCache.get(key);
- // We can't reuse the entry if the high-res icon is not present.
- if (entry == null || entry.icon == null || entry.isLowRes()) {
- entry = null;
- }
- }
- if (entry == null) {
- entry = new CacheEntry();
- cachingLogic.loadIcon(mContext, object, entry);
- }
- // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
- // (e.g. fallback icon, default icon). So we drop here since there's no point in caching
- // an empty entry.
- if (entry.icon == null) return;
- entry.title = cachingLogic.getLabel(object);
- entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
- if (cachingLogic.addToMemCache()) mCache.put(key, entry);
-
- ContentValues values = newContentValues(entry, entry.title.toString(),
- componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
- addIconToDB(values, componentName, info, userSerial);
- }
-
- /**
- * Updates {@param values} to contain versioning information and adds it to the DB.
- * @param values {@link ContentValues} containing icon & title
- */
- private void addIconToDB(ContentValues values, ComponentName key,
- PackageInfo info, long userSerial) {
- values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
- values.put(IconDB.COLUMN_USER, userSerial);
- values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
- values.put(IconDB.COLUMN_VERSION, info.versionCode);
- mIconDb.insertOrReplace(values);
- }
-
- public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
- if (!mDefaultIcons.containsKey(user)) {
- mDefaultIcons.put(user, makeDefaultIcon(user));
- }
- return mDefaultIcons.get(user);
- }
-
- public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
- return getDefaultIcon(user).icon == icon;
- }
-
- /**
- * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
- * This method is not thread safe, it must be called from a synchronized method.
- */
- protected <T> CacheEntry cacheLocked(
- @NonNull ComponentName componentName, @NonNull UserHandle user,
- @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
- boolean usePackageIcon, boolean useLowResIcon) {
- assertWorkerThread();
- ComponentKey cacheKey = new ComponentKey(componentName, user);
- CacheEntry entry = mCache.get(cacheKey);
- if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
- entry = new CacheEntry();
- if (cachingLogic.addToMemCache()) {
- mCache.put(cacheKey, entry);
- }
-
- // Check the DB first.
- T object = null;
- boolean providerFetchedOnce = false;
-
- if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
- object = infoProvider.get();
- providerFetchedOnce = true;
-
- if (object != null) {
- cachingLogic.loadIcon(mContext, object, entry);
- } else {
- if (usePackageIcon) {
- CacheEntry packageEntry = getEntryForPackageLocked(
- componentName.getPackageName(), user, false);
- if (packageEntry != null) {
- if (DEBUG) Log.d(TAG, "using package default icon for " +
- componentName.toShortString());
- packageEntry.applyTo(entry);
- entry.title = packageEntry.title;
- entry.contentDescription = packageEntry.contentDescription;
- }
- }
- if (entry.icon == null) {
- if (DEBUG) Log.d(TAG, "using default icon for " +
- componentName.toShortString());
- getDefaultIcon(user).applyTo(entry);
- }
- }
- }
-
- if (TextUtils.isEmpty(entry.title)) {
- if (object == null && !providerFetchedOnce) {
- object = infoProvider.get();
- providerFetchedOnce = true;
- }
- if (object != null) {
- entry.title = cachingLogic.getLabel(object);
- entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
- }
- }
- }
- return entry;
- }
-
- public synchronized void clear() {
- assertWorkerThread();
- mIconDb.clear();
- }
-
- /**
- * Adds a default package entry in the cache. This entry is not persisted and will be removed
- * when the cache is flushed.
- */
- public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
- Bitmap icon, CharSequence title) {
- removeFromMemCacheLocked(packageName, user);
-
- ComponentKey cacheKey = getPackageKey(packageName, user);
- CacheEntry entry = mCache.get(cacheKey);
-
- // For icon caching, do not go through DB. Just update the in-memory entry.
- if (entry == null) {
- entry = new CacheEntry();
- }
- if (!TextUtils.isEmpty(title)) {
- entry.title = title;
- }
- if (icon != null) {
- BaseIconFactory li = getIconFactory();
- li.createIconBitmap(icon).applyTo(entry);
- li.close();
- }
- if (!TextUtils.isEmpty(title) && entry.icon != null) {
- mCache.put(cacheKey, entry);
- }
- }
-
- private static ComponentKey getPackageKey(String packageName, UserHandle user) {
- ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
- return new ComponentKey(cn, user);
- }
-
- /**
- * Gets an entry for the package, which can be used as a fallback entry for various components.
- * This method is not thread safe, it must be called from a synchronized method.
- */
- protected CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
- boolean useLowResIcon) {
- assertWorkerThread();
- ComponentKey cacheKey = getPackageKey(packageName, user);
- CacheEntry entry = mCache.get(cacheKey);
-
- if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
- entry = new CacheEntry();
- boolean entryUpdated = true;
-
- // Check the DB first.
- if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
- try {
- int flags = Process.myUserHandle().equals(user) ? 0 :
- PackageManager.GET_UNINSTALLED_PACKAGES;
- PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
- ApplicationInfo appInfo = info.applicationInfo;
- if (appInfo == null) {
- throw new NameNotFoundException("ApplicationInfo is null");
- }
-
- BaseIconFactory li = getIconFactory();
- // Load the full res icon for the application, but if useLowResIcon is set, then
- // only keep the low resolution icon instead of the larger full-sized icon
- BitmapInfo iconInfo = li.createBadgedIconBitmap(
- appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
- isInstantApp(appInfo));
- li.close();
-
- entry.title = appInfo.loadLabel(mPackageManager);
- entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
- entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
- entry.color = iconInfo.color;
-
- // Add the icon in the DB here, since these do not get written during
- // package updates.
- ContentValues values = newContentValues(
- iconInfo, entry.title.toString(), packageName, null);
- addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user));
-
- } catch (NameNotFoundException e) {
- if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
- entryUpdated = false;
- }
- }
-
- // Only add a filled-out entry to the cache
- if (entryUpdated) {
- mCache.put(cacheKey, entry);
- }
- }
- return entry;
- }
-
- private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
- Cursor c = null;
- try {
- c = mIconDb.query(
- lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
- IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[]{
- cacheKey.componentName.flattenToString(),
- Long.toString(getSerialNumberForUser(cacheKey.user))});
- if (c.moveToNext()) {
- // Set the alpha to be 255, so that we never have a wrong color
- entry.color = setColorAlphaBound(c.getInt(0), 255);
- entry.title = c.getString(1);
- if (entry.title == null) {
- entry.title = "";
- entry.contentDescription = "";
- } else {
- entry.contentDescription = mPackageManager.getUserBadgedLabel(
- entry.title, cacheKey.user);
- }
-
- if (lowRes) {
- entry.icon = LOW_RES_ICON;
- } else {
- byte[] data = c.getBlob(2);
- try {
- entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
- mDecodeOptions);
- } catch (Exception e) { }
- }
- return true;
- }
- } catch (SQLiteException e) {
- Log.d(TAG, "Error reading icon cache", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return false;
- }
-
- /**
- * Returns a cursor for an arbitrary query to the cache db
- */
- public synchronized Cursor queryCacheDb(String[] columns, String selection,
- String[] selectionArgs) {
- return mIconDb.query(columns, selection, selectionArgs);
- }
-
- /**
- * Cache class to store the actual entries on disk
- */
- public static final class IconDB extends SQLiteCacheHelper {
- private static final int RELEASE_VERSION = 27;
-
- public static final String TABLE_NAME = "icons";
- public static final String COLUMN_ROWID = "rowid";
- public static final String COLUMN_COMPONENT = "componentName";
- public static final String COLUMN_USER = "profileId";
- public static final String COLUMN_LAST_UPDATED = "lastUpdated";
- public static final String COLUMN_VERSION = "version";
- public static final String COLUMN_ICON = "icon";
- public static final String COLUMN_ICON_COLOR = "icon_color";
- public static final String COLUMN_LABEL = "label";
- public static final String COLUMN_SYSTEM_STATE = "system_state";
- public static final String COLUMN_KEYWORDS = "keywords";
-
- public static final String[] COLUMNS_HIGH_RES = new String[] {
- IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
- public static final String[] COLUMNS_LOW_RES = new String[] {
- IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
-
- public IconDB(Context context, String dbFileName, int iconPixelSize) {
- super(context, dbFileName, (RELEASE_VERSION << 16) + iconPixelSize, TABLE_NAME);
- }
-
- @Override
- protected void onCreateTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
- + COLUMN_COMPONENT + " TEXT NOT NULL, "
- + COLUMN_USER + " INTEGER NOT NULL, "
- + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_ICON + " BLOB, "
- + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_LABEL + " TEXT, "
- + COLUMN_SYSTEM_STATE + " TEXT, "
- + COLUMN_KEYWORDS + " TEXT, "
- + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") "
- + ");");
- }
- }
-
- private ContentValues newContentValues(BitmapInfo bitmapInfo, String label,
- String packageName, @Nullable String keywords) {
- ContentValues values = new ContentValues();
- values.put(IconDB.COLUMN_ICON,
- bitmapInfo.isLowRes() ? null : GraphicsUtils.flattenBitmap(bitmapInfo.icon));
- values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color);
-
- values.put(IconDB.COLUMN_LABEL, label);
- values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName));
- values.put(IconDB.COLUMN_KEYWORDS, keywords);
- return values;
- }
-
- private void assertWorkerThread() {
- if (Looper.myLooper() != mBgLooper) {
- throw new IllegalStateException("Cache accessed on wrong thread " + Looper.myLooper());
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
deleted file mode 100644
index e40a9c2..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons.cache;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.LocaleList;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.icons.BitmapInfo;
-
-public interface CachingLogic<T> {
-
- ComponentName getComponent(T object);
-
- UserHandle getUser(T object);
-
- CharSequence getLabel(T object);
-
- void loadIcon(Context context, T object, BitmapInfo target);
-
- /**
- * Provides a option list of keywords to associate with this object
- */
- @Nullable
- default String getKeywords(T object, LocaleList localeList) {
- return null;
- }
-
- /**
- * Returns true the object should be added to mem cache; otherwise returns false.
- */
- default boolean addToMemCache() {
- return true;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java b/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java
deleted file mode 100644
index ee52934..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons.cache;
-
-import android.os.Handler;
-
-/**
- * A runnable that can be posted to a {@link Handler} which can be canceled.
- */
-public abstract class HandlerRunnable implements Runnable {
-
- private final Handler mHandler;
- private final Runnable mEndRunnable;
-
- private boolean mEnded = false;
- private boolean mCanceled = false;
-
- public HandlerRunnable(Handler handler, Runnable endRunnable) {
- mHandler = handler;
- mEndRunnable = endRunnable;
- }
-
- /**
- * Cancels this runnable from being run, only if it has not already run.
- */
- public void cancel() {
- mHandler.removeCallbacks(this);
- // TODO: This can actually cause onEnd to be called twice if the handler is already running
- // this runnable
- // NOTE: This is currently run on whichever thread the caller is run on.
- mCanceled = true;
- onEnd();
- }
-
- /**
- * @return whether this runnable was canceled.
- */
- protected boolean isCanceled() {
- return mCanceled;
- }
-
- /**
- * To be called by the implemention of this runnable. The end callback is done on whichever
- * thread the caller is calling from.
- */
- public void onEnd() {
- if (!mEnded) {
- mEnded = true;
- if (mEndRunnable != null) {
- mEndRunnable.run();
- }
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index 8224966..7b8070a 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -1,3 +1,4 @@
+<<<<<<< HEAD (cc6caf Merge "Let launcher to provide its own OverscrollPlugin" int)
/*
* Copyright (C) 2018 The Android Open Source Project
*
@@ -24,6 +25,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseBooleanArray;
@@ -61,7 +63,7 @@
private final HashMap<String, PackageInfo> mPkgInfoMap;
private final BaseIconCache mIconCache;
- private final HashMap<UserHandle, Set<String>> mPackagesToIgnore = new HashMap<>();
+ private final ArrayMap<UserHandle, Set<String>> mPackagesToIgnore = new ArrayMap<>();
private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray();
private boolean mFilterMode = MODE_SET_INVALID_ITEMS;
@@ -77,8 +79,16 @@
createPackageInfoMap();
}
- public void setPackagesToIgnore(UserHandle userHandle, Set<String> packages) {
- mPackagesToIgnore.put(userHandle, packages);
+ /**
+ * Sets a package to ignore for processing
+ */
+ public void addPackagesToIgnore(UserHandle userHandle, String packageName) {
+ Set<String> packages = mPackagesToIgnore.get(userHandle);
+ if (packages == null) {
+ packages = new HashSet<>();
+ mPackagesToIgnore.put(userHandle, packages);
+ }
+ packages.add(packageName);
}
private void createPackageInfoMap() {
@@ -171,7 +181,8 @@
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
T app = componentMap.remove(component);
- if (version == info.versionCode && updateTime == info.lastUpdateTime
+ if (version == info.versionCode
+ && updateTime == cachingLogic.getLastUpdatedTime(app, info)
&& TextUtils.equals(c.getString(systemStateIndex),
mIconCache.getIconSystemState(info.packageName))) {
@@ -180,6 +191,7 @@
}
continue;
}
+
if (app == null) {
if (mFilterMode == MODE_SET_INVALID_ITEMS) {
mIconCache.remove(component, user);
@@ -231,7 +243,6 @@
}
}
-
/**
* A runnable that updates invalid icons and adds missing icons in the DB for the provided
* LauncherActivityInfo list. Items are updated/added one at a time, so that the
@@ -263,6 +274,7 @@
T app = mAppsToUpdate.pop();
String pkg = mCachingLogic.getComponent(app).getPackageName();
PackageInfo info = mPkgInfoMap.get(pkg);
+
mIconCache.addIconToDBAndMemCache(
app, mCachingLogic, info, mUserSerial, true /*replace existing*/);
mUpdatedPackages.add(pkg);
@@ -301,3 +313,5 @@
void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user);
}
}
+=======
+>>>>>>> CHANGE (805b52 Removes iconloaderlib from Launcher3.)
diff --git a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java b/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java
deleted file mode 100644
index 34bed94..0000000
--- a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.android.launcher3.util;
-
-/**
- * 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.
- */
-
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-import java.util.Arrays;
-
-public class ComponentKey {
-
- public final ComponentName componentName;
- public final UserHandle user;
-
- private final int mHashCode;
-
- public ComponentKey(ComponentName componentName, UserHandle user) {
- if (componentName == null || user == null) {
- throw new NullPointerException();
- }
- this.componentName = componentName;
- this.user = user;
- mHashCode = Arrays.hashCode(new Object[] {componentName, user});
-
- }
-
- @Override
- public int hashCode() {
- return mHashCode;
- }
-
- @Override
- public boolean equals(Object o) {
- ComponentKey other = (ComponentKey) o;
- return other.componentName.equals(componentName) && other.user.equals(user);
- }
-
- /**
- * Encodes a component key as a string of the form [flattenedComponentString#userId].
- */
- @Override
- public String toString() {
- return componentName.flattenToString() + "#" + user;
- }
-}
\ No newline at end of file
diff --git a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java b/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
deleted file mode 100644
index fe864a2..0000000
--- a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import static android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.database.DatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteDatabase.OpenParams;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Build;
-
-/**
- * Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by
- * A context wrapper which creates databases without support for localized collators.
- */
-public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper {
-
- private static final boolean ATLEAST_P =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
-
- public NoLocaleSQLiteHelper(Context context, String name, int version) {
- super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version);
- if (ATLEAST_P) {
- setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build());
- }
- }
-
- private static class NoLocalContext extends ContextWrapper {
- public NoLocalContext(Context base) {
- super(base);
- }
-
- @Override
- public SQLiteDatabase openOrCreateDatabase(
- String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
- return super.openOrCreateDatabase(
- name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java b/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java
deleted file mode 100644
index 49de4bd..0000000
--- a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package com.android.launcher3.util;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteFullException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-/**
- * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
- * Any exception during write operations are ignored, and any version change causes a DB reset.
- */
-public abstract class SQLiteCacheHelper {
- private static final String TAG = "SQLiteCacheHelper";
-
- private static final boolean IN_MEMORY_CACHE = false;
-
- private final String mTableName;
- private final MySQLiteOpenHelper mOpenHelper;
-
- private boolean mIgnoreWrites;
-
- public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
- if (IN_MEMORY_CACHE) {
- name = null;
- }
- mTableName = tableName;
- mOpenHelper = new MySQLiteOpenHelper(context, name, version);
-
- mIgnoreWrites = false;
- }
-
- /**
- * @see SQLiteDatabase#delete(String, String, String[])
- */
- public void delete(String whereClause, String[] whereArgs) {
- if (mIgnoreWrites) {
- return;
- }
- try {
- mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs);
- } catch (SQLiteFullException e) {
- onDiskFull(e);
- } catch (SQLiteException e) {
- Log.d(TAG, "Ignoring sqlite exception", e);
- }
- }
-
- /**
- * @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int)
- */
- public void insertOrReplace(ContentValues values) {
- if (mIgnoreWrites) {
- return;
- }
- try {
- mOpenHelper.getWritableDatabase().insertWithOnConflict(
- mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
- } catch (SQLiteFullException e) {
- onDiskFull(e);
- } catch (SQLiteException e) {
- Log.d(TAG, "Ignoring sqlite exception", e);
- }
- }
-
- private void onDiskFull(SQLiteFullException e) {
- Log.e(TAG, "Disk full, all write operations will be ignored", e);
- mIgnoreWrites = true;
- }
-
- /**
- * @see SQLiteDatabase#query(String, String[], String, String[], String, String, String)
- */
- public Cursor query(String[] columns, String selection, String[] selectionArgs) {
- return mOpenHelper.getReadableDatabase().query(
- mTableName, columns, selection, selectionArgs, null, null, null);
- }
-
- public void clear() {
- mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
- }
-
- public void close() {
- mOpenHelper.close();
- }
-
- protected abstract void onCreateTable(SQLiteDatabase db);
-
- /**
- * A private inner class to prevent direct DB access.
- */
- private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper {
-
- public MySQLiteOpenHelper(Context context, String name, int version) {
- super(context, name, version);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- onCreateTable(db);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- clearDB(db);
- }
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- clearDB(db);
- }
- }
-
- private void clearDB(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + mTableName);
- onCreate(db);
- }
- }
-}
diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java
deleted file mode 100644
index 48f11fd..0000000
--- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import android.content.Context;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-public class IconFactory extends BaseIconFactory {
-
- private static final Object sPoolSync = new Object();
- private static IconFactory sPool;
- private static int sPoolId = 0;
-
- /**
- * Return a new Message instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
- */
- public static IconFactory obtain(Context context) {
- int poolId;
- synchronized (sPoolSync) {
- if (sPool != null) {
- IconFactory m = sPool;
- sPool = m.next;
- m.next = null;
- return m;
- }
- poolId = sPoolId;
- }
-
- return new IconFactory(context,
- context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
- poolId);
- }
-
- public static void clearPool() {
- synchronized (sPoolSync) {
- sPool = null;
- sPoolId++;
- }
- }
-
- private final int mPoolId;
-
- private IconFactory next;
-
- private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
- super(context, fillResIconDpi, iconBitmapSize);
- mPoolId = poolId;
- }
-
- /**
- * Recycles a LauncherIcons that may be in-use.
- */
- public void recycle() {
- synchronized (sPoolSync) {
- if (sPoolId != mPoolId) {
- return;
- }
- // Clear any temporary state variables
- clear();
-
- next = sPool;
- sPool = this;
- }
- }
-
- @Override
- public void close() {
- recycle();
- }
-}
diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
deleted file mode 100644
index 1337975..0000000
--- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED;
-import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED;
-
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.SparseLongArray;
-
-import com.android.launcher3.icons.cache.BaseIconCache;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class SimpleIconCache extends BaseIconCache {
-
- private static SimpleIconCache sIconCache = null;
- private static final Object CACHE_LOCK = new Object();
-
- private final SparseLongArray mUserSerialMap = new SparseLongArray(2);
- private final UserManager mUserManager;
-
- public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi,
- int iconPixelSize, boolean inMemoryCache) {
- super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache);
- mUserManager = context.getSystemService(UserManager.class);
-
- // Listen for user cache changes.
- IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED);
- filter.addAction(ACTION_MANAGED_PROFILE_REMOVED);
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- resetUserCache();
- }
- }, filter, null, new Handler(bgLooper), 0);
- }
-
- @Override
- protected long getSerialNumberForUser(UserHandle user) {
- synchronized (mUserSerialMap) {
- int index = mUserSerialMap.indexOfKey(user.getIdentifier());
- if (index >= 0) {
- return mUserSerialMap.valueAt(index);
- }
- long serial = mUserManager.getSerialNumberForUser(user);
- mUserSerialMap.put(user.getIdentifier(), serial);
- return serial;
- }
- }
-
- private void resetUserCache() {
- synchronized (mUserSerialMap) {
- mUserSerialMap.clear();
- }
- }
-
- @Override
- protected boolean isInstantApp(ApplicationInfo info) {
- return info.isInstantApp();
- }
-
- @Override
- protected BaseIconFactory getIconFactory() {
- return IconFactory.obtain(mContext);
- }
-
- public static SimpleIconCache getIconCache(Context context) {
- synchronized (CACHE_LOCK) {
- if (sIconCache != null) {
- return sIconCache;
- }
- boolean inMemoryCache =
- context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory);
- String dbFileName = context.getString(R.string.cache_db_name);
-
- HandlerThread bgThread = new HandlerThread("simple-icon-cache");
- bgThread.start();
-
- sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName,
- bgThread.getLooper(), context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
- inMemoryCache);
- return sIconCache;
- }
- }
-}
diff --git a/proguard.flags b/proguard.flags
index 272ab7a..37b8093 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -2,12 +2,6 @@
*;
}
-# Proguard will strip new callbacks in LauncherApps.Callback from
-# WrappedCallback if compiled against an older SDK. Don't let this happen.
--keep class com.android.launcher3.compat.** {
- *;
-}
-
-keep class com.android.launcher3.graphics.ShadowDrawable {
public <init>(...);
}
@@ -23,7 +17,10 @@
# support jar.
-keep class androidx.recyclerview.widget.RecyclerView { *; }
-# Preference fragments
+# Fragments
+-keep class ** extends androidx.fragment.app.Fragment {
+ public <init>(...);
+}
-keep class ** extends android.app.Fragment {
public <init>(...);
}
@@ -50,4 +47,16 @@
-dontwarn android.app.**
-dontwarn android.view.**
-dontwarn android.os.**
--dontwarn android.graphics.**
\ No newline at end of file
+-dontwarn android.graphics.**
+
+# Ignore warnings for hidden utility classes referenced from the shared lib
+-dontwarn com.android.internal.util.**
+
+################ Do not optimize recents lib #############
+-keep class com.android.systemui.** {
+ *;
+}
+
+-keep class com.android.quickstep.** {
+ *;
+}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 055ade5..0fe5310 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -57,6 +57,36 @@
optional TargetExtension extension = 16;
optional TipType tip_type = 17;
optional int32 search_query_length = 18;
+ optional bool is_work_app = 19;
+ optional FromFolderLabelState from_folder_label_state = 20;
+ optional ToFolderLabelState to_folder_label_state = 21;
+
+ // Note: proto does not support duplicate enum values, even if they belong to different enum type.
+ // Hence "FROM" and "TO" prefix added.
+ enum FromFolderLabelState{
+ FROM_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+ FROM_EMPTY = 1;
+ FROM_CUSTOM = 2;
+ FROM_SUGGESTED = 3;
+ }
+
+ enum ToFolderLabelState{
+ TO_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+ TO_SUGGESTION0_WITH_VALID_PRIMARY = 1;
+ TO_SUGGESTION1_WITH_VALID_PRIMARY = 2;
+ TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 3;
+ TO_SUGGESTION2_WITH_VALID_PRIMARY = 4;
+ TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 5;
+ TO_SUGGESTION3_WITH_VALID_PRIMARY = 6;
+ TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 7;
+ TO_EMPTY_WITH_VALID_SUGGESTIONS = 8;
+ TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 9;
+ TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 10;
+ TO_CUSTOM_WITH_VALID_SUGGESTIONS = 11;
+ TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 12;
+ TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 13;
+ UNCHANGED = 14;
+ }
}
// Used to define what type of item a Target would represent.
@@ -92,7 +122,7 @@
TASKSWITCHER = 12; // Recents UI Container (QuickStep)
APP = 13; // Foreground activity is another app (QuickStep)
TIP = 14; // Onboarding texts (QuickStep)
- SIDELOADED_LAUNCHER = 15;
+ OTHER_LAUNCHER_APP = 15;
}
// Used to define what type of control a Target would represent.
@@ -119,6 +149,13 @@
BACK_GESTURE = 19;
UNDO = 20;
DISMISS_PREDICTION = 21;
+ HYBRID_HOTSEAT_ACCEPTED = 22;
+ HYBRID_HOTSEAT_CANCELED = 23;
+ OVERVIEW_ACTIONS_SHARE_BUTTON = 24;
+ OVERVIEW_ACTIONS_SCREENSHOT_BUTTON = 25;
+ OVERVIEW_ACTIONS_SELECT_BUTTON = 26;
+ SELECT_MODE_CLOSE_BUTTON = 27;
+ SELECT_MODE_ITEM = 28;
}
enum TipType {
@@ -128,6 +165,7 @@
QUICK_SCRUB_TEXT = 3;
PREDICTION_TEXT = 4;
DWB_TOAST = 5;
+ HYBRID_HOTSEAT = 6;
}
// Used to define the action component of the LauncherEvent.
@@ -137,7 +175,8 @@
AUTOMATED = 1;
COMMAND = 2;
TIP = 3;
- // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
+ SOFT_KEYBOARD = 4;
+ // HARD_KEYBOARD, ASSIST
}
enum Touch {
diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/protos/launcher_trace.proto
similarity index 64%
copy from go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
copy to protos/launcher_trace.proto
index fb89013..c6f3543 100644
--- a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/protos/launcher_trace.proto
@@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep.util;
-import com.android.launcher3.Launcher;
+syntax = "proto2";
-/** Empty class, only exists so that l3goWithQuickstepIconRecentsDebug compiles. */
-public class ShelfPeekAnim {
- public ShelfPeekAnim(Launcher launcher) {
- }
+package com.android.launcher3.tracing;
- public enum ShelfAnimState {
- }
+option java_multiple_files = true;
- public boolean isPeeking() {
- return false;
- }
+message LauncherTraceProto {
+
+ optional TouchInteractionServiceProto touch_interaction_service = 1;
+}
+
+message TouchInteractionServiceProto {
+
+ optional bool service_connected = 1;
}
diff --git a/protos/launcher_trace_file.proto b/protos/launcher_trace_file.proto
new file mode 100644
index 0000000..6ce182a
--- /dev/null
+++ b/protos/launcher_trace_file.proto
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "launcher_trace.proto";
+
+package com.android.launcher3.tracing;
+
+option java_multiple_files = true;
+
+/* represents a file full of launcher trace entries.
+ Encoded, it should start with 0x9 0x4C 0x4E 0x43 0x48 0x52 0x54 0x52 0x43 (.LNCHRTRC), such
+ that they can be easily identified. */
+message LauncherTraceFileProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x48434E4C; /* LNCH (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x43525452; /* RTRC (little-endian ASCII) */
+ }
+
+ optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
+ repeated LauncherTraceEntryProto entry = 2;
+}
+
+/* one launcher trace entry. */
+message LauncherTraceEntryProto {
+ /* required: elapsed realtime in nanos since boot of when this entry was logged */
+ optional fixed64 elapsed_realtime_nanos = 1;
+
+ optional LauncherTraceProto launcher = 3;
+}
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
new file mode 100644
index 0000000..527bfc3
--- /dev/null
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3">
+ <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
+ <!--
+ Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
+ Refer comments around specific entries on how to extend individual components.
+ -->
+
+ <application
+ android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+ android:fullBackupOnly="true"
+ android:fullBackupContent="@xml/backupscheme"
+ android:hardwareAccelerated="true"
+ android:icon="@drawable/ic_launcher_home"
+ android:label="@string/derived_app_name"
+ android:theme="@style/AppTheme"
+ android:largeHeap="@bool/config_largeHeap"
+ android:restoreAnyVersion="true"
+ android:supportsRtl="true" >
+
+ <!--
+ Main launcher activity. When extending only change the name, and keep all the
+ attributes and intent filters the same
+ -->
+ <activity
+ android:name="com.android.launcher3.uioverrides.QuickstepLauncher"
+ android:launchMode="singleTask"
+ android:clearTaskOnLaunch="true"
+ android:stateNotNeeded="true"
+ android:windowSoftInputMode="adjustPan"
+ android:screenOrientation="unspecified"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:resizeableActivity="true"
+ android:resumeWhilePausing="true"
+ android:taskAffinity="${packageName}.launcher"
+ android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.HOME" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.MONKEY"/>
+ <category android:name="android.intent.category.LAUNCHER_APP" />
+ </intent-filter>
+ <meta-data
+ android:name="com.android.launcher3.grid.control"
+ android:value="${packageName}.grid_control" />
+ </activity>
+
+ </application>
+</manifest>
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 826a275..1d0b045 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -24,7 +24,9 @@
<uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:fullBackupOnly="true"
@@ -46,10 +48,7 @@
</intent-filter>
</service>
- <!-- STOPSHIP: Change exported to false once all the integration is complete.
- It is set to true so that the activity can be started from command line -->
<activity android:name="com.android.quickstep.RecentsActivity"
- android:exported="true"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
@@ -91,6 +90,18 @@
android:taskAffinity="${packageName}.locktask"
android:directBootAware="true" />
+ <activity
+ android:name="com.android.quickstep.interaction.GestureSandboxActivity"
+ android:autoRemoveFromRecents="true"
+ android:excludeFromRecents="true"
+ android:taskAffinity="${packageName}.launcher"
+ android:screenOrientation="portrait">
+ <intent-filter>
+ <action android:name="com.android.quickstep.action.GESTURE_SANDBOX" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
new file mode 100644
index 0000000..4fda2a9
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector android:height="24dp" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="?android:attr/colorAccent" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
+</vector>
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
index ef272ed..ffe906c 100644
--- a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
+++ b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
@@ -28,4 +28,9 @@
android:clipToPadding="false"
android:outlineProvider="none"
android:theme="@style/HomeScreenElementTheme" />
+
+ <include
+ android:id="@+id/overview_actions_view"
+ layout="@layout/overview_actions_container" />
+
</com.android.quickstep.fallback.RecentsRootView>
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index 7f1425b..eac0bfa 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,10 +15,10 @@
-->
<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:accessibilityPaneTitle="@string/accessibility_recent_apps"
android:clipChildren="false"
android:clipToPadding="false"
- android:accessibilityPaneTitle="@string/accessibility_recent_apps"
- android:visibility="invisible" />
\ No newline at end of file
+ android:theme="@style/HomeScreenElementTheme"
+ android:visibility="invisible" />
diff --git a/iconloaderlib/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
similarity index 80%
rename from iconloaderlib/res/values/dimens.xml
rename to quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
index e8c0c44..70a765a 100644
--- a/iconloaderlib/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,6 +14,4 @@
limitations under the License.
-->
-<resources>
- <dimen name="profile_badge_size">24dp</dimen>
-</resources>
+<com.android.launcher3.uioverrides.PredictedAppIcon style="@style/BaseIcon.Workspace" />
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
new file mode 100644
index 0000000..c93cad6
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.hybridhotseat.HotseatEduDialog xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_gravity="bottom"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:backgroundTint="?android:attr/colorAccent"
+ android:background="@drawable/bottom_sheet_top_border" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/colorAccent"
+ android:orientation="vertical">
+
+ <TextView
+ style="@style/TextHeadline"
+ android:id="@+id/hotseat_edu_heading"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="18dp"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+ android:paddingRight="@dimen/bottom_sheet_edu_padding"
+ android:text="@string/hotseat_edu_title_migrate"
+ android:textAlignment="center"
+ android:textColor="@android:color/white"
+ android:textSize="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:id="@+id/hotseat_edu_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="18dp"
+ android:layout_marginBottom="18dp"
+ android:fontFamily="roboto-medium"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+ android:paddingRight="@dimen/bottom_sheet_edu_padding"
+ android:text="@string/hotseat_edu_message_migrate"
+ android:textAlignment="center"
+ android:textColor="@android:color/white"
+ android:textSize="16sp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/hotseat_wrapper"
+ android:orientation="vertical">
+
+ <com.android.launcher3.CellLayout
+ android:id="@+id/sample_prediction"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ launcher:containerType="hotseat" />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+ android:paddingTop="8dp"
+ android:paddingRight="@dimen/bottom_sheet_edu_padding">
+
+ <Button
+ android:id="@+id/turn_predictions_on"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/hotseat_edu_accept"
+ android:textAlignment="textEnd"
+ android:textColor="@android:color/white" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/no_thanks"
+ android:text="@string/hotseat_edu_dismiss"
+ android:layout_gravity="start"
+ android:background="?android:attr/selectableItemBackground"
+ android:textColor="@android:color/white" />
+
+ </FrameLayout>
+ </LinearLayout>
+ </LinearLayout>
+
+</com.android.launcher3.hybridhotseat.HotseatEduDialog>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 7426e30..361f5f7 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<resources>
<color name="chip_hint_foreground_color">#fff</color>
diff --git a/go/quickstep/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/config.xml
similarity index 60%
rename from go/quickstep/res/values/colors.xml
rename to quickstep/recents_ui_overrides/res/values/config.xml
index ff9dc9c..527eec6 100644
--- a/go/quickstep/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/config.xml
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
+<!-- Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ 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,
@@ -15,6 +14,8 @@
limitations under the License.
-->
<resources>
- <color name="clear_all_button_bg">#FFDADCE0</color>
- <color name="clear_all_button_text">#FF5F6368</color>
-</resources>
+ <integer name="app_background_blur_radius">150</integer>
+ <integer name="allapps_background_blur_radius">90</integer>
+ <integer name="overview_background_blur_radius">50</integer>
+ <integer name="folder_background_blur_radius_adjustment">20</integer>
+</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 863a8ba..20b1485 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<resources>
<dimen name="chip_hint_border_width">1dp</dimen>
<dimen name="chip_hint_corner_radius">20dp</dimen>
@@ -23,9 +37,4 @@
<!-- Minimum distance to swipe to trigger accessibility gesture -->
<dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
-
- <!-- Swipe up to home related -->
- <dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
- <dimen name="swipe_up_y_overshoot">10dp</dimen>
- <dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 114fd8e..95198b8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,12 +16,11 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
@@ -39,6 +38,7 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.util.FloatProperty;
import android.view.View;
import androidx.annotation.NonNull;
@@ -50,7 +50,8 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -81,15 +82,18 @@
@Override
protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
- @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+ @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
RecentsView recentsView = mLauncher.getOverviewPanel();
boolean skipLauncherChanges = !launcherClosing;
- TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
+ TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
- ClipAnimationHelper helper = new ClipAnimationHelper(mLauncher);
- anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
- .setDuration(RECENTS_LAUNCH_DURATION));
+ AppWindowAnimationHelper helper =
+ new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher);
+ Animator recentsAnimator = getRecentsWindowAnimator(taskView, skipLauncherChanges,
+ appTargets, wallpaperTargets, mLauncher.getBackgroundBlurController(), helper);
+ anim.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
@@ -196,7 +200,11 @@
return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
case INDEX_RECENTS_TRANSLATE_X_ANIM:
- return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X)
+ PagedOrientationHandler orientationHandler =
+ ((RecentsView)mLauncher.getOverviewPanel()).getPagedViewOrientedState()
+ .getOrientationHandler();
+ FloatProperty<View> translate = orientationHandler.getPrimaryViewTranslate();
+ return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), translate)
.setDampingRatio(0.8f)
.setStiffness(250)
.setValues(values)
@@ -212,7 +220,7 @@
LauncherStateManager stateManager = mLauncher.getStateManager();
return stateManager.createAtomicAnimation(
stateManager.getCurrentStableState(), OVERVIEW, builder,
- ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
+ ANIM_ALL_COMPONENTS, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
}
default:
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
deleted file mode 100644
index c5c4add..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-
-import java.util.function.BiPredicate;
-
-public class LauncherInitListenerEx extends LauncherInitListener {
-
- public LauncherInitListenerEx(BiPredicate<Launcher, Boolean> onInitListener) {
- super(onInitListener);
- }
-
- @Override
- protected boolean init(Launcher launcher, boolean alreadyOnHome) {
- PredictionUiStateManager.INSTANCE.get(launcher).switchClient(Client.OVERVIEW);
- return super.init(launcher, alreadyOnHome);
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index d3042cf..0ae7435 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -24,6 +24,7 @@
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.os.Handler;
+import android.os.UserManager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
@@ -33,6 +34,8 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.core.content.ContextCompat;
+
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -41,13 +44,10 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.TriangleShape;
import com.android.systemui.shared.system.LauncherEventUtil;
-import androidx.core.content.ContextCompat;
-
/**
* All apps tip view aligned just above prediction apps, shown to users that enter all apps for the
* first time.
@@ -151,7 +151,7 @@
TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE) != null
|| !launcher.isInState(ALL_APPS)
|| hasSeenAllAppsTip(launcher)
- || UserManagerCompat.getInstance(launcher).isDemoUser()
+ || launcher.getSystemService(UserManager.class).isDemoUser()
|| Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return false;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
index 425fb13..ec46418 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -16,6 +16,7 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.ALL_APPS;
import android.annotation.TargetApi;
@@ -291,7 +292,7 @@
public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
// Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(this, ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
+ setter.setFloat(this, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
index b9f4147..0712285 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
@@ -18,8 +18,6 @@
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
-import android.content.Context;
-
import com.android.launcher3.AppInfo;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.allapps.AllAppsStore;
@@ -29,11 +27,9 @@
public class ComponentKeyMapper {
protected final ComponentKey componentKey;
- private final Context mContext;
private final DynamicItemCache mCache;
- public ComponentKeyMapper(Context context, ComponentKey key, DynamicItemCache cache) {
- mContext = context;
+ public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) {
componentKey = key;
mCache = cache;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 65e69b6..54f58e2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.MATCH_INSTANT;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.content.Context;
import android.content.Intent;
@@ -37,12 +38,13 @@
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.InstantAppResolver;
import java.util.ArrayList;
@@ -72,6 +74,7 @@
private final Handler mUiHandler;
private final InstantAppResolver mInstantAppResolver;
private final Runnable mOnUpdateCallback;
+ private final IconCache mIconCache;
private final Map<ShortcutKey, WorkspaceItemInfo> mShortcuts;
private final Map<String, InstantAppItemInfo> mInstantApps;
@@ -82,6 +85,7 @@
mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
mInstantAppResolver = InstantAppResolver.newInstance(context);
mOnUpdateCallback = onUpdateCallback;
+ mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
mShortcuts = new HashMap<>();
mInstantApps = new HashMap<>();
@@ -162,21 +166,10 @@
@WorkerThread
private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
- DeepShortcutManager mgr = DeepShortcutManager.getInstance(mContext);
- List<ShortcutInfo> details = mgr.queryForFullDetails(
- shortcutKey.componentName.getPackageName(),
- Collections.<String>singletonList(shortcutKey.getId()),
- shortcutKey.user);
+ List<ShortcutInfo> details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
if (!details.isEmpty()) {
WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
- try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
- si.applyFrom(li.createShortcutIcon(details.get(0), true /* badged */, null));
- } catch (Exception e) {
- if (DEBUG) {
- Log.e(TAG, "Error loading shortcut icon for " + shortcutKey.toString());
- }
- return null;
- }
+ mIconCache.getShortcutIcon(si, details.get(0));
return si;
}
if (DEBUG) {
@@ -209,7 +202,7 @@
InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
iconCache.getTitleAndIcon(info, false);
- if (info.iconBitmap == null || iconCache.isDefaultIcon(info.iconBitmap, info.user)) {
+ if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) {
return null;
}
return info;
@@ -240,4 +233,35 @@
public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) {
return mShortcuts.get(key);
}
+
+ /**
+ * requests and caches icons for app targets
+ */
+ public void updateDependencies(List<ComponentKeyMapper> componentKeyMappers,
+ AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
+ List<String> instantAppsToLoad = new ArrayList<>();
+ List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
+ int total = componentKeyMappers.size();
+ for (int i = 0, count = 0; i < total && count < itemCount; i++) {
+ ComponentKeyMapper mapper = componentKeyMappers.get(i);
+ // Update instant apps
+ if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
+ instantAppsToLoad.add(mapper.getPackage());
+ count++;
+ } else if (mapper.getComponentKey() instanceof ShortcutKey) {
+ shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
+ count++;
+ } else {
+ // Reload high res icon
+ AppInfo info = (AppInfo) mapper.getApp(appsStore);
+ if (info != null) {
+ if (info.usingLowResIcon()) {
+ mIconCache.updateIconInBackground(callback, info);
+ }
+ count++;
+ }
+ }
+ }
+ cacheItems(shortcutsToLoad, instantAppsToLoad);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index f82af62..8faec46 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
+/*
+ * Copyright (C) 2012 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.
@@ -16,8 +16,11 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
import android.annotation.TargetApi;
import android.content.Context;
@@ -44,6 +47,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
@@ -187,7 +191,7 @@
public int getExpectedHeight() {
return getVisibility() == GONE ? 0 :
Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx
- + getPaddingTop() + getPaddingBottom();
+ + getPaddingTop() + getPaddingBottom();
}
@Override
@@ -282,7 +286,8 @@
mParent.onHeightUpdated();
}
- private List<ItemInfoWithIcon> processPredictedAppComponents(List<ComponentKeyMapper> components) {
+ private List<ItemInfoWithIcon> processPredictedAppComponents(
+ List<ComponentKeyMapper> components) {
if (getAppsStore().getApps().length == 0) {
// Apps have not been bound yet.
return Collections.emptyList();
@@ -296,7 +301,7 @@
predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
predictedApps.add(predictedApp);
} else {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
Log.e(TAG, "Predicted app not found: " + mapper);
}
}
@@ -309,16 +314,26 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
- LauncherLogProto.Target targetParent) {
+ public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
+ ArrayList<LauncherLogProto.Target> parents) {
for (int i = 0; i < mPredictedApps.size(); i++) {
ItemInfoWithIcon appInfo = mPredictedApps.get(i);
- if (appInfo == info) {
- targetParent.containerType = LauncherLogProto.ContainerType.PREDICTION;
- target.predictedRank = i;
+ if (appInfo == childInfo) {
+ child.predictedRank = i;
break;
}
}
+ parents.add(newContainerTarget(LauncherLogProto.ContainerType.PREDICTION));
+
+ // include where the prediction is coming this used to be Launcher#modifyUserEvent
+ LauncherLogProto.Target parent = newTarget(LauncherLogProto.Target.Type.CONTAINER);
+ LauncherState state = mLauncher.getStateManager().getState();
+ if (state == LauncherState.ALL_APPS) {
+ parent.containerType = LauncherLogProto.ContainerType.ALLAPPS;
+ } else if (state == OVERVIEW) {
+ parent.containerType = LauncherLogProto.ContainerType.TASKSWITCHER;
+ }
+ parents.add(parent);
}
public void setTextAlpha(int textAlpha) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 9c66107..632b9b5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,6 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
@@ -27,7 +26,6 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.ItemInfo;
@@ -40,7 +38,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -245,7 +243,7 @@
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
appTarget.getClassName()), appTarget.getUser());
}
- state.apps.add(new ComponentKeyMapper(mContext, key, mDynamicItemCache));
+ state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
}
updateDependencies(state);
@@ -256,33 +254,8 @@
if (!state.isEnabled || mAppsView == null) {
return;
}
-
- IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
- List<String> instantAppsToLoad = new ArrayList<>();
- List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
- int total = state.apps.size();
- for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) {
- ComponentKeyMapper mapper = state.apps.get(i);
- // Update instant apps
- if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
- instantAppsToLoad.add(mapper.getPackage());
- count++;
- } else if (mapper.getComponentKey() instanceof ShortcutKey) {
- shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
- count++;
- } else {
- // Reload high res icon
- AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore());
- if (info != null) {
- if (info.usingLowResIcon()) {
- // TODO: Update icon cache to support null callbacks.
- iconCache.updateIconInBackground(this, info);
- }
- count++;
- }
- }
- }
- mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad);
+ mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
+ mMaxIconsPerRow);
}
@Override
@@ -344,12 +317,17 @@
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
return;
}
+ if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) {
+ HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target);
+ return;
+ }
+
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst()
- .ifPresent((rank) -> target.predictedRank = rank);
+ .ifPresent((rank) -> target.predictedRank = 0 - rank);
}
public static class PredictionState {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
new file mode 100644
index 0000000..a07cd1d
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.hybridhotseat;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.app.NotificationCompat;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.WorkspaceLayoutManager;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.Themes;
+
+import java.util.List;
+
+/**
+ * Controller class for managing user onboaridng flow for hybrid hotseat
+ */
+public class HotseatEduController {
+ public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
+
+ private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
+ private static final int ONBOARDING_NOTIFICATION_ID = 7641;
+
+ private final Launcher mLauncher;
+ private List<WorkspaceItemInfo> mPredictedApps;
+ private HotseatEduDialog mActiveDialog;
+
+ private final NotificationManager mNotificationManager;
+ private final Notification mNotification;
+
+ HotseatEduController(Launcher launcher) {
+ mLauncher = launcher;
+ mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
+ createNotificationChannel();
+ mNotification = createNotification();
+ }
+
+ boolean migrate() {
+ Workspace workspace = mLauncher.getWorkspace();
+ CellLayout firstScreen = workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID);
+ int toPage = Workspace.FIRST_SCREEN_ID;
+ int toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
+ if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
+ toPage = workspace.getScreenIdForPageIndex(workspace.getPageCount());
+ toRow = 0;
+ } else if (!firstScreen.makeSpaceForHotseatMigration(true)) {
+ return false;
+ }
+ ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets();
+ for (int i = 0; i < hotseatVG.getChildCount(); i++) {
+ View child = hotseatVG.getChildAt(i);
+ ItemInfo tag = (ItemInfo) child.getTag();
+ mLauncher.getModelWriter().moveItemInDatabase(tag,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, toPage, tag.screenId, toRow);
+ }
+ return true;
+ }
+
+ void removeNotification() {
+ mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
+ }
+
+ void finishOnboarding() {
+ mLauncher.getModel().rebindCallbacks();
+ mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
+ removeNotification();
+ }
+
+ void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
+ mPredictedApps = predictedApps;
+ if (!mPredictedApps.isEmpty()
+ && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+ mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
+ }
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+ CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title);
+ int importance = NotificationManager.IMPORTANCE_LOW;
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name,
+ importance);
+ mNotificationManager.createNotificationChannel(channel);
+ }
+
+ private Notification createNotification() {
+ Intent intent = new Intent(mLauncher.getApplicationContext(), mLauncher.getClass());
+ intent = new NotificationHandler().addToIntent(intent);
+
+ CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title);
+ String description = mLauncher.getString(R.string.hotseat_edu_prompt_content);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mLauncher,
+ NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(name)
+ .setOngoing(true)
+ .setColor(Themes.getColorAccent(mLauncher))
+ .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT))
+ .setSmallIcon(R.drawable.hotseat_edu_notification_icon)
+ .setContentText(description);
+ return builder.build();
+
+ }
+
+ void destroy() {
+ removeNotification();
+ if (mActiveDialog != null) {
+ mActiveDialog.setHotseatEduController(null);
+ }
+ }
+
+ void showDialog() {
+ if (mPredictedApps == null || mPredictedApps.isEmpty()) {
+ return;
+ }
+ if (mActiveDialog != null) {
+ mActiveDialog.handleClose(false);
+ }
+ mActiveDialog = HotseatEduDialog.getDialog(mLauncher);
+ mActiveDialog.setHotseatEduController(this);
+ mActiveDialog.show(mPredictedApps);
+ }
+
+ static class NotificationHandler implements
+ ActivityTracker.SchedulerCallback<QuickstepLauncher> {
+ @Override
+ public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
+ activity.getHotseatPredictionController().showEduDialog();
+ return true;
+ }
+ }
+}
+
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
new file mode 100644
index 0000000..538b7f3
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.hybridhotseat;
+
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.WorkspaceLayoutManager;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.views.AbstractSlideInView;
+
+import java.util.List;
+
+/**
+ * User education dialog for hybrid hotseat. Allows user to migrate hotseat items to a new page in
+ * the workspace and shows predictions on the whole hotseat
+ */
+public class HotseatEduDialog extends AbstractSlideInView implements Insettable {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+ protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
+
+ // We don't migrate if user has more than SAME_PAGE_MAX_ROWS rows of item in their screen
+ private static final int SAME_PAGE_MAX_ROWS = 2;
+
+ private static final int MIGRATE_SAME_PAGE = 0;
+ private static final int MIGRATE_NEW_PAGE = 1;
+ private static final int MIGRATE_NO_MIGRATE = 2;
+
+
+ private final Rect mInsets = new Rect();
+ private View mHotseatWrapper;
+ private CellLayout mSampleHotseat;
+ private TextView mEduHeading;
+ private TextView mEduContent;
+ private Button mDismissBtn;
+
+ private int mMigrationMode = MIGRATE_SAME_PAGE;
+
+ public void setHotseatEduController(HotseatEduController hotseatEduController) {
+ mHotseatEduController = hotseatEduController;
+ }
+
+ private HotseatEduController mHotseatEduController;
+
+ public HotseatEduDialog(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public HotseatEduDialog(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContent = this;
+ }
+
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
+ mSampleHotseat = findViewById(R.id.sample_prediction);
+ mEduHeading = findViewById(R.id.hotseat_edu_heading);
+ mEduContent = findViewById(R.id.hotseat_edu_content);
+
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ Rect padding = grid.getHotseatLayoutPadding();
+
+ mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
+ mSampleHotseat.setGridSize(grid.inv.numHotseatIcons, 1);
+ mSampleHotseat.setPadding(padding.left, 0, padding.right, 0);
+
+ Button turnOnBtn = findViewById(R.id.turn_predictions_on);
+ turnOnBtn.setOnClickListener(this::onAccept);
+
+ mDismissBtn = findViewById(R.id.no_thanks);
+ mDismissBtn.setOnClickListener(this::onDismiss);
+
+ }
+
+ private void onAccept(View v) {
+ if (mMigrationMode == MIGRATE_NO_MIGRATE || !mHotseatEduController.migrate()) {
+ onDismiss(v);
+ return;
+ }
+ handleClose(true);
+ mHotseatEduController.finishOnboarding();
+ logUserAction(true);
+ int toastStringRes = mMigrationMode == MIGRATE_SAME_PAGE
+ ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt;
+ Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show();
+ }
+
+ private void onDismiss(View v) {
+ Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
+ mHotseatEduController.finishOnboarding();
+ logUserAction(false);
+ handleClose(true);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // Since this is on-boarding popup, it is not a user controlled action.
+ }
+
+ @Override
+ public int getLogContainerType() {
+ return LauncherLogProto.ContainerType.TIP;
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ON_BOARD_POPUP) != 0;
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
+ mHotseatWrapper.getPaddingRight(), bottomInset);
+ mHotseatWrapper.getLayoutParams().height =
+ mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
+ }
+
+ private void logUserAction(boolean migrated) {
+ LauncherLogProto.Action action = new LauncherLogProto.Action();
+ LauncherLogProto.Target target = new LauncherLogProto.Target();
+ action.type = LauncherLogProto.Action.Type.TOUCH;
+ action.touch = LauncherLogProto.Action.Touch.TAP;
+ target.containerType = LauncherLogProto.ContainerType.TIP;
+ target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+ target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
+ : HYBRID_HOTSEAT_CANCELED;
+ // encoding migration type on pageIndex
+ target.pageIndex = mMigrationMode;
+ LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+ UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+ }
+
+ private void logOnBoardingSeen() {
+ LauncherLogProto.Action action = new LauncherLogProto.Action();
+ LauncherLogProto.Target target = new LauncherLogProto.Target();
+ action.type = LauncherLogProto.Action.Type.TIP;
+ target.containerType = LauncherLogProto.ContainerType.TIP;
+ target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+ LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+ UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(true, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ handleClose(false);
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return FINAL_SCRIM_BG_COLOR;
+ }
+
+ private void populatePreview(List<WorkspaceItemInfo> predictions) {
+ for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+ WorkspaceItemInfo info = predictions.get(i);
+ PredictedAppIcon icon = PredictedAppIcon.createIcon(mSampleHotseat, info);
+ icon.setEnabled(false);
+ icon.verifyHighRes();
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
+ mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
+ }
+ }
+
+ @Override
+ protected void attachToContainer() {
+ super.attachToContainer();
+ if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
+ mEduContent.setText(R.string.hotseat_edu_message_migrate_alt);
+ mMigrationMode = MIGRATE_NEW_PAGE;
+ return;
+ }
+ CellLayout page = mLauncher.getWorkspace().getScreenWithId(
+ WorkspaceLayoutManager.FIRST_SCREEN_ID);
+
+ int maxItemsOnPage = SAME_PAGE_MAX_ROWS * mLauncher.getDeviceProfile().inv.numColumns
+ + (FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0);
+ if (page.getShortcutsAndWidgets().getChildCount() > maxItemsOnPage
+ || !page.makeSpaceForHotseatMigration(false)) {
+ mMigrationMode = MIGRATE_NO_MIGRATE;
+ mEduContent.setText(R.string.hotseat_edu_message_no_migrate);
+ mEduHeading.setText(R.string.hotseat_edu_title_no_migrate);
+ mDismissBtn.setVisibility(GONE);
+ }
+ }
+
+ /**
+ * Opens User education dialog with a list of suggested apps
+ */
+ public void show(List<WorkspaceItemInfo> predictions) {
+ if (getParent() != null
+ || predictions.size() < mLauncher.getDeviceProfile().inv.numHotseatIcons
+ || mHotseatEduController == null) {
+ return;
+ }
+ attachToContainer();
+ logOnBoardingSeen();
+ animateOpen();
+ populatePreview(predictions);
+ }
+
+ /**
+ * Factory method for HotseatPredictionUserEdu dialog
+ */
+ public static HotseatEduDialog getDialog(Launcher launcher) {
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ return (HotseatEduDialog) layoutInflater.inflate(
+ R.layout.predicted_hotseat_edu, launcher.getDragLayer(),
+ false);
+
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
new file mode 100644
index 0000000..2cdcd20
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.hybridhotseat;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.appprediction.ComponentKeyMapper;
+import com.android.launcher3.appprediction.DynamicItemCache;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+/**
+ * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
+ * pinning of predicted apps and manages replacement of predicted apps with user drag.
+ */
+public class HotseatPredictionController implements DragController.DragListener,
+ View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
+ InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
+ IconCache.ItemInfoUpdateReceiver, DragSource {
+
+ private static final String TAG = "PredictiveHotseat";
+ private static final boolean DEBUG = false;
+
+ //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
+ private static final int APPTARGET_ACTION_UNPIN = 4;
+
+ private static final String APP_LOCATION_HOTSEAT = "hotseat";
+ private static final String APP_LOCATION_WORKSPACE = "workspace";
+
+ private static final String BUNDLE_KEY_HOTSEAT = "hotseat_apps";
+ private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps";
+
+ private static final String PREDICTION_CLIENT = "hotseat";
+
+ private DropTarget.DragObject mDragObject;
+ private int mHotSeatItemsCount;
+ private int mPredictedSpotsCount = 0;
+
+ private Launcher mLauncher;
+ private final Hotseat mHotseat;
+
+ private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
+
+ private DynamicItemCache mDynamicItemCache;
+
+ private AppPredictor mAppPredictor;
+ private AllAppsStore mAllAppsStore;
+ private AnimatorSet mIconRemoveAnimators;
+ private boolean mUIUpdatePaused = false;
+
+ private HotseatEduController mHotseatEduController;
+
+ private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
+
+ private final View.OnLongClickListener mPredictionLongClickListener = v -> {
+ if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+ if (mLauncher.getWorkspace().isSwitchingState()) return false;
+ // Start the drag
+ mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
+ return false;
+ };
+
+ public HotseatPredictionController(Launcher launcher) {
+ mLauncher = launcher;
+ mHotseat = launcher.getHotseat();
+ mAllAppsStore = mLauncher.getAppsView().getAppsStore();
+ mAllAppsStore.addUpdateListener(this);
+ mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
+ mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+ launcher.getDeviceProfile().inv.addOnChangeListener(this);
+ mHotseat.addOnAttachStateChangeListener(this);
+ if (mHotseat.isAttachedToWindow()) {
+ onViewAttachedToWindow(mHotseat);
+ }
+ }
+
+ /**
+ * Returns whether or not the prediction controller is ready to show predictions
+ */
+ public boolean isReady() {
+ return mLauncher.getSharedPrefs().getBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
+ false);
+ }
+
+ /**
+ * Transitions to NORMAL workspace mode and shows edu dialog
+ */
+ public void showEduDialog() {
+ if (mHotseatEduController == null) return;
+ mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
+ () -> mHotseatEduController.showDialog());
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mLauncher.getDragController().addDragListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ mLauncher.getDragController().removeDragListener(this);
+ }
+
+ private void fillGapsWithPrediction() {
+ fillGapsWithPrediction(false, null);
+ }
+
+ private void fillGapsWithPrediction(boolean animate, Runnable callback) {
+ if (!isReady() || mUIUpdatePaused || mDragObject != null) {
+ return;
+ }
+ List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
+ int predictionIndex = 0;
+ ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
+ // make sure predicted icon removal and filling predictions don't step on each other
+ if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
+ mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ fillGapsWithPrediction(animate, callback);
+ mIconRemoveAnimators.removeListener(this);
+ }
+ });
+ return;
+ }
+ for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
+ View child = mHotseat.getChildAt(
+ mHotseat.getCellXFromOrder(rank),
+ mHotseat.getCellYFromOrder(rank));
+
+ if (child != null && !isPredictedIcon(child)) {
+ continue;
+ }
+ if (predictedApps.size() <= predictionIndex) {
+ // Remove predicted apps from the past
+ if (isPredictedIcon(child)) {
+ mHotseat.removeView(child);
+ }
+ continue;
+ }
+ WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
+ if (isPredictedIcon(child) && child.isEnabled()) {
+ PredictedAppIcon icon = (PredictedAppIcon) child;
+ icon.applyFromWorkspaceItem(predictedItem);
+ icon.finishBinding(mPredictionLongClickListener);
+ } else {
+ newItems.add(predictedItem);
+ }
+ preparePredictionInfo(predictedItem, rank);
+ }
+ mPredictedSpotsCount = predictionIndex;
+ bindItems(newItems, animate, callback);
+ }
+
+ private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate, Runnable callback) {
+ AnimatorSet animationSet = new AnimatorSet();
+ for (WorkspaceItemInfo item : itemsToAdd) {
+ PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
+ mLauncher.getWorkspace().addInScreenFromBind(icon, item);
+ icon.finishBinding(mPredictionLongClickListener);
+ if (animate) {
+ animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
+ }
+ }
+ if (animate) {
+ if (callback != null) {
+ animationSet.addListener(AnimationSuccessListener.forRunnable(callback));
+ }
+ animationSet.start();
+ } else {
+ if (callback != null) callback.run();
+ }
+ }
+
+ /**
+ * Unregisters callbacks and frees resources
+ */
+ public void destroy() {
+ mAllAppsStore.removeUpdateListener(this);
+ mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
+ mHotseat.removeOnAttachStateChangeListener(this);
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ }
+ }
+
+ /**
+ * start and pauses predicted apps update on the hotseat
+ */
+ public void setPauseUIUpdate(boolean paused) {
+ mUIUpdatePaused = paused;
+ if (!paused) {
+ fillGapsWithPrediction();
+ }
+ }
+
+ /**
+ * Creates App Predictor with all the current apps pinned on the hotseat
+ */
+ public void createPredictor() {
+ AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
+ if (apm == null) {
+ return;
+ }
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ }
+ mAppPredictor = apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(mLauncher)
+ .setUiSurface(PREDICTION_CLIENT)
+ .setPredictedTargetCount(mHotSeatItemsCount)
+ .setExtras(getAppPredictionContextExtra())
+ .build());
+ mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
+ this::setPredictedApps);
+
+ if (!isReady()) {
+ if (mHotseatEduController != null) {
+ mHotseatEduController.destroy();
+ }
+ mHotseatEduController = new HotseatEduController(mLauncher);
+ }
+ mAppPredictor.requestPredictionUpdate();
+ }
+
+ private Bundle getAppPredictionContextExtra() {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT,
+ getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets())));
+ bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup(
+ mLauncher.getWorkspace().getScreenWithId(
+ Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets()));
+
+ return bundle;
+ }
+
+ private ArrayList<AppTarget> getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) {
+ ArrayList<AppTarget> pinnedApps = new ArrayList<>();
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ View child = viewGroup.getChildAt(i);
+ if (isPinnedIcon(child)) {
+ WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag();
+ pinnedApps.add(getAppTargetFromItemInfo(itemInfo));
+ }
+ }
+ return pinnedApps;
+ }
+
+ private void setPredictedApps(List<AppTarget> appTargets) {
+ mComponentKeyMappers.clear();
+ StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
+ for (AppTarget appTarget : appTargets) {
+ ComponentKey key;
+ if (appTarget.getShortcutInfo() != null) {
+ key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
+ } else {
+ key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
+ appTarget.getClassName()), appTarget.getUser());
+ }
+ predictionLog.append(key.toString());
+ predictionLog.append(",rank:");
+ predictionLog.append(appTarget.getRank());
+ predictionLog.append("\n");
+ mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
+ }
+ predictionLog.append("]");
+ FileLog.d(TAG, predictionLog.toString());
+ updateDependencies();
+ if (isReady()) {
+ fillGapsWithPrediction();
+ } else if (mHotseatEduController != null) {
+ mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
+ }
+ }
+
+ private void updateDependencies() {
+ mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
+ mHotSeatItemsCount);
+ }
+
+ private void pinPrediction(ItemInfo info) {
+ PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
+ mHotseat.getCellXFromOrder(info.rank),
+ mHotseat.getCellYFromOrder(info.rank));
+ if (icon == null) {
+ return;
+ }
+ WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+ mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
+ workspaceItemInfo.cellX, workspaceItemInfo.cellY);
+ ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
+ icon.pin(workspaceItemInfo);
+ AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
+ notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
+ }
+
+ private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
+ List<ComponentKeyMapper> components) {
+ AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
+ if (allAppsStore.getApps().length == 0) {
+ return Collections.emptyList();
+ }
+
+ List<WorkspaceItemInfo> predictedApps = new ArrayList<>();
+ for (ComponentKeyMapper mapper : components) {
+ ItemInfoWithIcon info = mapper.getApp(allAppsStore);
+ if (info instanceof AppInfo) {
+ WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info);
+ predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ predictedApps.add(predictedApp);
+ } else if (info instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+ predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ predictedApps.add(predictedApp);
+ } else {
+ if (DEBUG) {
+ Log.e(TAG, "Predicted app not found: " + mapper);
+ }
+ }
+ // Stop at the number of hotseat items
+ if (predictedApps.size() == mHotSeatItemsCount) {
+ break;
+ }
+ }
+ return predictedApps;
+ }
+
+ private List<PredictedAppIcon> getPredictedIcons() {
+ List<PredictedAppIcon> icons = new ArrayList<>();
+ ViewGroup vg = mHotseat.getShortcutsAndWidgets();
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ View child = vg.getChildAt(i);
+ if (isPredictedIcon(child)) {
+ icons.add((PredictedAppIcon) child);
+ }
+ }
+ return icons;
+ }
+
+ private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
+ ItemInfo draggedInfo) {
+ if (mIconRemoveAnimators != null) {
+ mIconRemoveAnimators.end();
+ }
+ mIconRemoveAnimators = new AnimatorSet();
+ removeOutlineDrawings();
+ for (PredictedAppIcon icon : getPredictedIcons()) {
+ if (!icon.isEnabled()) {
+ continue;
+ }
+ if (icon.getTag().equals(draggedInfo)) {
+ mHotseat.removeView(icon);
+ continue;
+ }
+ int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
+ outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
+ mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
+ icon.setEnabled(false);
+ ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
+ animator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (icon.getParent() != null) {
+ mHotseat.removeView(icon);
+ }
+ }
+ });
+ mIconRemoveAnimators.play(animator);
+ }
+ mIconRemoveAnimators.start();
+ }
+
+ private void notifyItemAction(AppTarget target, String location, int action) {
+ if (mAppPredictor != null) {
+ mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target,
+ action).setLaunchLocation(location).build());
+ }
+ }
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ removePredictedApps(mOutlineDrawings, dragObject.dragInfo);
+ mDragObject = dragObject;
+ if (mOutlineDrawings.isEmpty()) return;
+ for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+ mHotseat.addDelegatedCellDrawing(outlineDrawing);
+ }
+ mHotseat.invalidate();
+ }
+
+ /**
+ * Unpins pinned app when it's converted into a folder
+ */
+ public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
+ if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ return;
+ }
+ AppTarget target = getAppTargetFromItemInfo(info);
+ ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
+ ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
+ Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
+
+ if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains(
+ target)) {
+ notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
+ } else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup(
+ firstScreenVG).contains(target)) {
+ notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
+ }
+ }
+
+ /**
+ * Pins workspace item created when all folder items are removed but one
+ */
+ public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
+ if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ return;
+ }
+ AppTarget target = getAppTargetFromItemInfo(info);
+ if (isInHotseat(info)) {
+ notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
+ } else if (isInFirstPage(info)) {
+ notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
+ }
+ }
+
+
+ @Override
+ public void onDragEnd() {
+ if (mDragObject == null) {
+ return;
+ }
+
+ ItemInfo dragInfo = mDragObject.dragInfo;
+ ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
+ ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
+ Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
+
+ if (dragInfo instanceof WorkspaceItemInfo
+ && dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && dragInfo.getTargetComponent() != null) {
+ AppTarget appTarget = getAppTargetFromItemInfo(dragInfo);
+ if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) {
+ if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) {
+ notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
+ }
+ }
+ if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) {
+ if (!getPinnedAppTargetsInViewGroup(firstScreenVG).contains(appTarget)) {
+ notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
+ }
+ }
+ if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) {
+ notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
+ }
+ if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) {
+ notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
+ }
+ }
+ mDragObject = null;
+ fillGapsWithPrediction(true, this::removeOutlineDrawings);
+ }
+
+ @Nullable
+ @Override
+ public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
+ ItemInfo itemInfo) {
+ if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ return null;
+ }
+ return new PinPrediction(activity, itemInfo);
+ }
+
+ private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
+ itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ itemInfo.rank = rank;
+ itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+ itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
+ itemInfo.screenId = rank;
+ }
+
+ private void removeOutlineDrawings() {
+ if (mOutlineDrawings.isEmpty()) return;
+ for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+ mHotseat.removeDelegatedCellDrawing(outlineDrawing);
+ }
+ mHotseat.invalidate();
+ mOutlineDrawings.clear();
+ }
+
+ @Override
+ public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+ this.mHotSeatItemsCount = profile.numHotseatIcons;
+ createPredictor();
+ }
+
+ @Override
+ public void onAppsUpdated() {
+ fillGapsWithPrediction();
+ }
+
+ @Override
+ public void reapplyItemInfo(ItemInfoWithIcon info) {}
+
+ @Override
+ public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
+ //Does nothing
+ }
+
+ @Override
+ public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
+ ArrayList<LauncherLogProto.Target> parents) {
+ mHotseat.fillInLogContainerData(childInfo, child, parents);
+ }
+
+ private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
+
+ private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
+ super(R.drawable.ic_pin, R.string.pin_prediction, target,
+ itemInfo);
+ }
+
+ @Override
+ public void onClick(View view) {
+ dismissTaskMenuView(mTarget);
+ pinPrediction(mItemInfo);
+ }
+ }
+
+ /**
+ * Fill in predicted_rank field based on app prediction.
+ * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
+ */
+ public static void encodeHotseatLayoutIntoPredictionRank(
+ @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+ QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
+ if (launcher == null || launcher.getHotseatPredictionController() == null
+ || itemInfo.getTargetComponent() == null) {
+ return;
+ }
+ HotseatPredictionController controller = launcher.getHotseatPredictionController();
+
+ final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+
+ final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
+ OptionalInt rank = IntStream.range(0, predictedApps.size())
+ .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+ .findFirst();
+
+ target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
+ + (rank.isPresent() ? rank.getAsInt() + 1 : 0);
+ }
+
+ private static boolean isPredictedIcon(View view) {
+ return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) view.getTag()).container
+ == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ }
+
+ private static boolean isPinnedIcon(View view) {
+ if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) {
+ return false;
+ }
+ ItemInfo info = (ItemInfo) view.getTag();
+ return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && (
+ info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION);
+ }
+
+ private static boolean isInHotseat(ItemInfo itemInfo) {
+ return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ }
+
+ private static boolean isInFirstPage(ItemInfo itemInfo) {
+ return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
+ && itemInfo.screenId == Workspace.FIRST_SCREEN_ID;
+ }
+
+ private static AppTarget getAppTargetFromItemInfo(ItemInfo info) {
+ if (info.getTargetComponent() == null) return null;
+ ComponentName cn = info.getTargetComponent();
+ return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
+ cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
new file mode 100644
index 0000000..4bbb48c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.graphics.IconShape.getShape;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.views.DoubleShadowBubbleTextView;
+
+/**
+ * A BubbleTextView with a ring around it's drawable
+ */
+public class PredictedAppIcon extends DoubleShadowBubbleTextView {
+
+ private static final float RING_EFFECT_RATIO = 0.11f;
+
+ boolean mIsDrawingDot = false;
+ private final DeviceProfile mDeviceProfile;
+ private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private boolean mIsPinned = false;
+ private int mNormalizedIconRadius;
+
+ public PredictedAppIcon(Context context) {
+ this(context, null, 0);
+ }
+
+ public PredictedAppIcon(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+ mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
+ setOnClickListener(ItemClickHandler.INSTANCE);
+ setOnFocusChangeListener(Launcher.getLauncher(context).getFocusHandler());
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int count = canvas.save();
+ if (!mIsPinned) {
+ drawEffect(canvas);
+ canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
+ canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
+ }
+ super.onDraw(canvas);
+ canvas.restoreToCount(count);
+ }
+
+ @Override
+ protected void drawDotIfNecessary(Canvas canvas) {
+ mIsDrawingDot = true;
+ int count = canvas.save();
+ canvas.translate(-getWidth() * RING_EFFECT_RATIO, -getHeight() * RING_EFFECT_RATIO);
+ canvas.scale(1 + 2 * RING_EFFECT_RATIO, 1 + 2 * RING_EFFECT_RATIO);
+ super.drawDotIfNecessary(canvas);
+ canvas.restoreToCount(count);
+ mIsDrawingDot = false;
+ }
+
+ @Override
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
+ super.applyFromWorkspaceItem(info);
+ int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
+ mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200));
+ }
+
+ /**
+ * Removes prediction ring from app icon
+ */
+ public void pin(WorkspaceItemInfo info) {
+ if (mIsPinned) return;
+ applyFromWorkspaceItem(info);
+ setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+ mIsPinned = true;
+ ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+ invalidate();
+ }
+
+ /**
+ * prepares prediction icon for usage after bind
+ */
+ public void finishBinding(OnLongClickListener longClickListener) {
+ setOnLongClickListener(longClickListener);
+ ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false;
+ setTextVisibility(false);
+ verifyHighRes();
+ }
+
+ @Override
+ public void getIconBounds(Rect outBounds) {
+ super.getIconBounds(outBounds);
+ if (!mIsPinned && !mIsDrawingDot) {
+ int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO);
+ outBounds.inset(predictionInset, predictionInset);
+ }
+ }
+
+ private int getOutlineOffsetX() {
+ return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
+ }
+
+ private int getOutlineOffsetY() {
+ return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+ }
+
+ private void drawEffect(Canvas canvas) {
+ getShape().drawShape(canvas, getOutlineOffsetX(), getOutlineOffsetY(),
+ mNormalizedIconRadius, mIconRingPaint);
+ }
+
+ /**
+ * Creates and returns a new instance of PredictedAppIcon from WorkspaceItemInfo
+ */
+ public static PredictedAppIcon createIcon(ViewGroup parent, WorkspaceItemInfo info) {
+ PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.predicted_app_icon, parent, false);
+ icon.applyFromWorkspaceItem(info);
+ return icon;
+ }
+
+ /**
+ * Draws Predicted Icon outline on cell layout
+ */
+ public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
+
+ private int mOffsetX;
+ private int mOffsetY;
+ private int mIconRadius;
+ private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) {
+ mDelegateCellX = cellX;
+ mDelegateCellY = cellY;
+ mOffsetX = icon.getOutlineOffsetX();
+ mOffsetY = icon.getOutlineOffsetY();
+ mIconRadius = icon.mNormalizedIconRadius;
+ mOutlinePaint.setStyle(Paint.Style.STROKE);
+ mOutlinePaint.setStrokeWidth(5);
+ mOutlinePaint.setPathEffect(new DashPathEffect(new float[]{15, 15}, 0));
+ mOutlinePaint.setColor(Color.argb(100, 245, 245, 245));
+ }
+
+ /**
+ * Draws predicted app icon outline under CellLayout
+ */
+ @Override
+ public void drawUnderItem(Canvas canvas) {
+ getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint);
+ }
+
+ /**
+ * Draws PredictedAppIcon outline over CellLayout
+ */
+ @Override
+ public void drawOverItem(Canvas canvas) {
+ // Does nothing
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
new file mode 100644
index 0000000..d1a487a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
+import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.RecentsView;
+
+import java.util.ArrayList;
+import java.util.stream.Stream;
+
+public class QuickstepLauncher extends BaseQuickstepLauncher {
+
+ public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
+ /**
+ * Reusable command for applying the shelf height on the background thread.
+ */
+ public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
+ SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
+ public static final RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
+ @Override
+ public void mapRect(int left, int top, int right, int bottom, Rect out) {
+ out.left = top;
+ out.top = right;
+ out.right = bottom;
+ out.bottom = left;
+ }
+
+ @Override
+ public void mapInsets(Context context, Rect insets, Rect out) {
+ // If there is a display cutout, the top insets in portrait would also include the
+ // cutout, which we will get as the left inset in landscape. Using the max of left and
+ // top allows us to cover both cases (with or without cutout).
+ if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
+ out.top = Math.max(insets.top, insets.left);
+ out.bottom = Math.max(insets.right, insets.bottom);
+ out.left = out.right = 0;
+ } else {
+ out.top = Math.max(insets.top, insets.left);
+ out.bottom = insets.right;
+ out.left = insets.bottom;
+ out.right = 0;
+ }
+ }
+ };
+ public static final RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
+ @Override
+ public void mapRect(int left, int top, int right, int bottom, Rect out) {
+ out.left = bottom;
+ out.top = left;
+ out.right = top;
+ out.bottom = right;
+ }
+
+ @Override
+ public void mapInsets(Context context, Rect insets, Rect out) {
+ if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
+ out.top = Math.max(insets.top, insets.right);
+ out.bottom = Math.max(insets.left, insets.bottom);
+ out.left = out.right = 0;
+ } else {
+ out.top = Math.max(insets.top, insets.right);
+ out.bottom = insets.left;
+ out.right = insets.bottom;
+ out.left = 0;
+ }
+ }
+
+ @Override
+ public int toNaturalGravity(int absoluteGravity) {
+ int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ if (horizontalGravity == Gravity.RIGHT) {
+ horizontalGravity = Gravity.LEFT;
+ } else if (horizontalGravity == Gravity.LEFT) {
+ horizontalGravity = Gravity.RIGHT;
+ }
+
+ if (verticalGravity == Gravity.TOP) {
+ verticalGravity = Gravity.BOTTOM;
+ } else if (verticalGravity == Gravity.BOTTOM) {
+ verticalGravity = Gravity.TOP;
+ }
+
+ return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
+ & ~Gravity.VERTICAL_GRAVITY_MASK)
+ | horizontalGravity | verticalGravity;
+ }
+ };
+ private HotseatPredictionController mHotseatPredictionController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+ mHotseatPredictionController = new HotseatPredictionController(this);
+ }
+ }
+
+ @Override
+ protected RotationMode getFakeRotationMode(DeviceProfile dp) {
+ return !dp.isVerticalBarLayout() ? RotationMode.NORMAL
+ : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ onStateOrResumeChanged();
+ }
+
+ @Override
+ public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
+ @Nullable String sourceContainer) {
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.setPauseUIUpdate(true);
+ }
+ return super.startActivitySafely(v, intent, item, sourceContainer);
+ }
+
+ @Override
+ protected void onActivityFlagsChanged(int changeBits) {
+ super.onActivityFlagsChanged(changeBits);
+
+ if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
+ | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0
+ && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) {
+ onStateOrResumeChanged();
+ }
+
+ if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null
+ && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) {
+ mHotseatPredictionController.setPauseUIUpdate(false);
+ }
+ }
+
+ @Override
+ public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
+ super.folderCreatedFromItem(folder, itemInfo);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
+ }
+ }
+
+ @Override
+ public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
+ super.folderConvertedToItem(folder, itemInfo);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
+ }
+ }
+
+ @Override
+ public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
+ if (mHotseatPredictionController != null) {
+ return Stream.concat(super.getSupportedShortcuts(),
+ Stream.of(mHotseatPredictionController));
+ } else {
+ return super.getSupportedShortcuts();
+ }
+ }
+
+ /**
+ * Returns Prediction controller for hybrid hotseat
+ */
+ public HotseatPredictionController getHotseatPredictionController() {
+ return mHotseatPredictionController;
+ }
+
+ /**
+ * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
+ */
+ private void onStateOrResumeChanged() {
+ LauncherState state = getStateManager().getState();
+ DeviceProfile profile = getDeviceProfile();
+ boolean visible = (state == NORMAL || state == OVERVIEW) && isUserActive()
+ && !profile.isVerticalBarLayout();
+ UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
+ profile.hotseatBarSizePx);
+ if (state == NORMAL) {
+ ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
+ }
+ }
+
+ @Override
+ public void finishBindingItems(int pageBoundFirst) {
+ super.finishBindingItems(pageBoundFirst);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.createPredictor();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.destroy();
+ }
+ }
+
+ @Override
+ public TouchController[] createTouchControllers() {
+ Mode mode = SysUINavigationMode.getMode(this);
+
+ ArrayList<TouchController> list = new ArrayList<>();
+ list.add(getDragController());
+ if (mode == NO_BUTTON) {
+ list.add(new NoButtonQuickSwitchTouchController(this));
+ list.add(new NavBarToHomeTouchController(this));
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ list.add(new NoButtonNavbarToOverviewTouchController(this));
+ } else {
+ list.add(new FlingAndHoldTouchController(this));
+ }
+ } else {
+ if (getDeviceProfile().isVerticalBarLayout()) {
+ list.add(new OverviewToAllAppsTouchController(this));
+ list.add(new LandscapeEdgeSwipeController(this));
+ if (mode.hasGestures) {
+ list.add(new TransposedQuickSwitchTouchController(this));
+ }
+ } else {
+ list.add(new PortraitStatesTouchController(this,
+ mode.hasGestures /* allowDragToOverview */));
+ if (mode.hasGestures) {
+ list.add(new QuickSwitchTouchController(this));
+ }
+ }
+ }
+
+ if (!getDeviceProfile().isMultiWindowMode) {
+ list.add(new StatusBarTouchController(this));
+ }
+
+ list.add(new LauncherTaskViewController(this));
+ return list.toArray(new TouchController[list.size()]);
+ }
+
+ private static final class LauncherTaskViewController extends
+ TaskViewTouchController<Launcher> {
+
+ LauncherTaskViewController(Launcher activity) {
+ super(activity);
+ }
+
+ @Override
+ protected boolean isRecentsInteractive() {
+ return mActivity.isInState(OVERVIEW);
+ }
+
+ @Override
+ protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
deleted file mode 100644
index 4d935e1..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.Gravity;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
-import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.TouchInteractionService;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.ArrayList;
-
-/**
- * Provides recents-related {@link UiFactory} logic and classes.
- */
-public abstract class RecentsUiFactory {
-
- public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
- private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
- WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
-
- public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
- @Override
- public void mapRect(int left, int top, int right, int bottom, Rect out) {
- out.left = top;
- out.top = right;
- out.right = bottom;
- out.bottom = left;
- }
-
- @Override
- public void mapInsets(Context context, Rect insets, Rect out) {
- // If there is a display cutout, the top insets in portrait would also include the
- // cutout, which we will get as the left inset in landscape. Using the max of left and
- // top allows us to cover both cases (with or without cutout).
- if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
- out.top = Math.max(insets.top, insets.left);
- out.bottom = Math.max(insets.right, insets.bottom);
- out.left = out.right = 0;
- } else {
- out.top = Math.max(insets.top, insets.left);
- out.bottom = insets.right;
- out.left = insets.bottom;
- out.right = 0;
- }
- }
- };
-
- public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
- @Override
- public void mapRect(int left, int top, int right, int bottom, Rect out) {
- out.left = bottom;
- out.top = left;
- out.right = top;
- out.bottom = right;
- }
-
- @Override
- public void mapInsets(Context context, Rect insets, Rect out) {
- if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
- out.top = Math.max(insets.top, insets.right);
- out.bottom = Math.max(insets.left, insets.bottom);
- out.left = out.right = 0;
- } else {
- out.top = Math.max(insets.top, insets.right);
- out.bottom = insets.left;
- out.right = insets.bottom;
- out.left = 0;
- }
- }
-
- @Override
- public int toNaturalGravity(int absoluteGravity) {
- int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
-
- if (horizontalGravity == Gravity.RIGHT) {
- horizontalGravity = Gravity.LEFT;
- } else if (horizontalGravity == Gravity.LEFT) {
- horizontalGravity = Gravity.RIGHT;
- }
-
- if (verticalGravity == Gravity.TOP) {
- verticalGravity = Gravity.BOTTOM;
- } else if (verticalGravity == Gravity.BOTTOM) {
- verticalGravity = Gravity.TOP;
- }
-
- return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
- & ~Gravity.VERTICAL_GRAVITY_MASK)
- | horizontalGravity | verticalGravity;
- }
- };
-
- public static RotationMode getRotationMode(DeviceProfile dp) {
- return !dp.isVerticalBarLayout() ? RotationMode.NORMAL
- : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE);
- }
-
- public static TouchController[] createTouchControllers(Launcher launcher) {
- Mode mode = SysUINavigationMode.getMode(launcher);
-
- ArrayList<TouchController> list = new ArrayList<>();
- list.add(launcher.getDragController());
- if (mode == NO_BUTTON) {
- list.add(new NoButtonQuickSwitchTouchController(launcher));
- list.add(new NavBarToHomeTouchController(launcher));
- list.add(new FlingAndHoldTouchController(launcher));
- } else {
- if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- list.add(new OverviewToAllAppsTouchController(launcher));
- list.add(new LandscapeEdgeSwipeController(launcher));
- if (mode.hasGestures) {
- list.add(new TransposedQuickSwitchTouchController(launcher));
- }
- } else {
- list.add(new PortraitStatesTouchController(launcher,
- mode.hasGestures /* allowDragToOverview */));
- if (mode.hasGestures) {
- list.add(new QuickSwitchTouchController(launcher));
- }
- }
- }
-
- if (FeatureFlags.PULL_DOWN_STATUS_BAR
- && !launcher.getDeviceProfile().isMultiWindowMode) {
- list.add(new StatusBarTouchController(launcher));
- }
-
- list.add(new LauncherTaskViewController(launcher));
- return list.toArray(new TouchController[list.size()]);
- }
-
- /**
- * Creates and returns the controller responsible for recents view state transitions.
- *
- * @param launcher the launcher activity
- * @return state handler for recents
- */
- public static StateHandler createRecentsViewStateController(Launcher launcher) {
- return new RecentsViewStateController(launcher);
- }
-
- /**
- * Clears the swipe shared state for the current swipe gesture.
- */
- public static void clearSwipeSharedState(boolean finishAnimation) {
- TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
- }
-
- /**
- * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
- *
- * @param launcher the launcher activity
- */
- public static void onLauncherStateOrResumeChanged(Launcher launcher) {
- LauncherState state = launcher.getStateManager().getState();
- DeviceProfile profile = launcher.getDeviceProfile();
- boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
- && !profile.isVerticalBarLayout();
- UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
- visible ? 1 : 0, profile.hotseatBarSizePx);
-
- if (state == NORMAL) {
- launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
- }
- }
-
- /** Closes system windows. */
- public static void closeSystemWindows() {
- ActivityManagerWrapper.getInstance()
- .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- }
-
- private static final class LauncherTaskViewController extends
- TaskViewTouchController<Launcher> {
-
- LauncherTaskViewController(Launcher activity) {
- super(activity);
- }
-
- @Override
- protected boolean isRecentsInteractive() {
- return mActivity.isInState(OVERVIEW);
- }
-
- @Override
- protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
- mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
- }
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index b5d8424..65aaf22 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -30,6 +30,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.quickstep.views.ClearAllButton;
@@ -64,20 +65,16 @@
@NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
super.setStateWithAnimationInternal(toState, builder, config);
- if (!toState.overviewUi) {
- builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
- }
-
+ ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1).setDuration(config.duration);
if (toState.overviewUi) {
- ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
- updateAnim.addUpdateListener(valueAnimator -> {
- // While animating into recents, update the visible task data as needed
- mRecentsView.loadVisibleTaskData();
- });
- updateAnim.setDuration(config.duration);
- builder.play(updateAnim);
+ // While animating into recents, update the visible task data as needed
+ updateAnim.addUpdateListener(valueAnimator -> mRecentsView.loadVisibleTaskData());
mRecentsView.updateEmptyMessage();
+ } else {
+ updateAnim.addListener(
+ AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
+ builder.play(updateAnim);
PropertySetter propertySetter = config.getPropertySetter(builder);
setAlphas(propertySetter, toState.getVisibleElements(mLauncher));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 63ac528..5bac964 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -15,10 +15,11 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import android.content.Context;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.LayoutUtils;
@@ -39,7 +40,7 @@
}
protected BackgroundAppState(int id, int logContainer) {
- super(id, logContainer, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+ super(id, logContainer, STATE_FLAGS);
}
@Override
@@ -69,7 +70,7 @@
return super.getOverviewScaleAndTranslation(launcher);
}
TaskView dummyTask;
- if (recentsView.getCurrentPage() >= 0) {
+ if (recentsView.getCurrentPage() >= recentsView.getTaskViewStartIndex()) {
if (recentsView.getCurrentPage() <= taskCount - 1) {
dummyTask = recentsView.getCurrentPageTaskView();
} else {
@@ -78,7 +79,7 @@
} else {
dummyTask = recentsView.getTaskViewAt(0);
}
- return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
+ return recentsView.getTempAppWindowAnimationHelper().updateForFullscreenOverview(dummyTask)
.getScaleAndTranslation();
}
@@ -104,4 +105,9 @@
}
return super.getHotseatScaleAndTranslation(launcher);
}
+
+ @Override
+ public int getBackgroundBlurRadius(Context context) {
+ return context.getResources().getInteger(R.integer.app_background_blur_radius);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index ed5dba1..bfbb630 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -17,7 +17,6 @@
import static android.view.View.VISIBLE;
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
@@ -30,11 +29,17 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import android.content.Context;
import android.graphics.Rect;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
@@ -44,6 +49,7 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -65,15 +71,22 @@
| FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
public OverviewState(int id) {
- this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+ this(id, STATE_FLAGS);
}
- protected OverviewState(int id, int transitionDuration, int stateFlags) {
- this(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+ protected OverviewState(int id, int stateFlags) {
+ this(id, ContainerType.TASKSWITCHER, stateFlags);
}
- protected OverviewState(int id, int logContainer, int transitionDuration, int stateFlags) {
- super(id, logContainer, transitionDuration, stateFlags);
+ protected OverviewState(int id, int logContainer, int stateFlags) {
+ super(id, logContainer, stateFlags);
+ }
+
+ @Override
+ public int getTransitionDuration(Launcher launcher) {
+ // In no-button mode, overview comes in all the way from the left, so give it more time.
+ boolean isNoButtonMode = SysUINavigationMode.INSTANCE.get(launcher).getMode() == NO_BUTTON;
+ return isNoButtonMode && ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250;
}
@Override
@@ -115,6 +128,16 @@
}
@Override
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()
+ && removeShelfFromOverview(launcher)) {
+ // Treat the QSB as part of the hotseat so they move together.
+ return getHotseatScaleAndTranslation(launcher);
+ }
+ return super.getQsbScaleAndTranslation(launcher);
+ }
+
+ @Override
public void onStateEnabled(Launcher launcher) {
AbstractFloatingView.closeAllOpenViews(launcher);
}
@@ -123,6 +146,10 @@
public void onStateTransitionEnd(Launcher launcher) {
launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
DiscoveryBounce.showForOverviewIfNeeded(launcher);
+ RecentsView recentsView = launcher.getOverviewPanel();
+ AccessibilityManagerCompat.sendCustomAccessibilityEvent(
+ recentsView.getPageAt(recentsView.getCurrentPage()),
+ AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
}
@Override
@@ -140,6 +167,10 @@
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
} else {
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) {
+ return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
+ }
+
boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
&& launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
@@ -168,7 +199,7 @@
@Override
public String getDescription(Launcher launcher) {
- return launcher.getString(R.string.accessibility_desc_recent_apps);
+ return launcher.getString(R.string.accessibility_recent_apps);
}
public static float getDefaultSwipeHeight(Launcher launcher) {
@@ -176,6 +207,11 @@
}
@Override
+ public int getBackgroundBlurRadius(Context context) {
+ return context.getResources().getInteger(R.integer.overview_background_blur_radius);
+ }
+
+ @Override
public void onBackPressed(Launcher launcher) {
TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
if (taskView != null) {
@@ -190,9 +226,10 @@
@Override
public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
AnimatorSetBuilder builder) {
- if (fromState == NORMAL && this == OVERVIEW) {
- if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
- builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
+ if ((fromState == NORMAL || fromState == HINT_STATE) && this == OVERVIEW) {
+ if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) {
+ builder.setInterpolator(ANIM_WORKSPACE_SCALE,
+ fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
} else {
builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
@@ -205,8 +242,12 @@
}
builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
+ Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
+ && removeShelfFromOverview(launcher)
+ ? OVERSHOOT_1_2
+ : OVERSHOOT_1_7;
+ builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
+ builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 6c9f46f..7b4bb02 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -20,13 +20,14 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.GestureState;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
/**
* State to indicate we are about to launch a recent task. Note that this state is only used when
- * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper.
- * @see com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget#NEW_TASK
+ * quick switching from launcher; quick switching from an app uses LauncherSwipeHandler.
+ * @see GestureState.GestureEndTarget#NEW_TASK
*/
public class QuickSwitchState extends BackgroundAppState {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 3231f37..b347013 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -21,7 +21,8 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
@@ -43,11 +44,13 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppTransitionManagerImpl;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.views.RecentsView;
@@ -60,7 +63,7 @@
private static final long PEEK_OUT_ANIM_DURATION = 100;
private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;
- private final MotionPauseDetector mMotionPauseDetector;
+ protected final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
private final float mMotionPauseMaxDisplacement;
@@ -79,44 +82,50 @@
}
@Override
- public void onDragStart(boolean start) {
+ public void onDragStart(boolean start, float startDisplacement) {
mMotionPauseDetector.clear();
- super.onDragStart(start);
+ super.onDragStart(start, startDisplacement);
if (handlingOverviewAnim()) {
- mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
- RecentsView recentsView = mLauncher.getOverviewPanel();
- recentsView.setOverviewStateEnabled(isPaused);
- if (mPeekAnim != null) {
- mPeekAnim.cancel();
- }
- LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
- LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
- long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
- mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
- new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration);
- mPeekAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPeekAnim = null;
- }
- });
- mPeekAnim.start();
- VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-
- mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
- peekDuration, 0);
- });
+ mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
}
+
+ if (mAtomicAnim != null) {
+ mAtomicAnim.cancel();
+ }
+ }
+
+ protected void onMotionPauseChanged(boolean isPaused) {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.setOverviewStateEnabled(isPaused);
+ if (mPeekAnim != null) {
+ mPeekAnim.cancel();
+ }
+ LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
+ LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
+ long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
+ mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
+ new AnimatorSetBuilder(), PLAY_ATOMIC_OVERVIEW_PEEK, peekDuration);
+ mPeekAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPeekAnim = null;
+ }
+ });
+ mPeekAnim.start();
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+ mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
+ peekDuration, 0);
}
/**
* @return Whether we are handling the overview animation, rather than
* having it as part of the existing animation to the target state.
*/
- private boolean handlingOverviewAnim() {
- int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+ protected boolean handlingOverviewAnim() {
+ int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
}
@@ -162,28 +171,17 @@
@Override
public boolean onDrag(float displacement, MotionEvent event) {
float upDisplacement = -displacement;
- mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement
+ mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
+ || upDisplacement < mMotionPauseMinDisplacement
|| upDisplacement > mMotionPauseMaxDisplacement);
- mMotionPauseDetector.addPosition(displacement, event.getEventTime());
+ mMotionPauseDetector.addPosition(event);
return super.onDrag(displacement, event);
}
@Override
public void onDragEnd(float velocity) {
if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
- if (mPeekAnim != null) {
- mPeekAnim.cancel();
- }
-
- Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
- INDEX_PAUSE_TO_OVERVIEW_ANIM);
- overviewAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
- }
- });
- overviewAnim.start();
+ goToOverviewOnDragEnd(velocity);
} else {
super.onDragEnd(velocity);
}
@@ -195,6 +193,36 @@
mMotionPauseDetector.clear();
}
+ protected void goToOverviewOnDragEnd(float velocity) {
+ if (mPeekAnim != null) {
+ mPeekAnim.cancel();
+ }
+
+ Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+ INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ mAtomicAnim = new AnimatorSet();
+ mAtomicAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mCancelled) {
+ mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(mFromState,
+ mToState, new AnimatorSetBuilder(), PLAY_ATOMIC_OVERVIEW_PEEK,
+ PEEK_OUT_ANIM_DURATION);
+ mPeekAnim.start();
+ }
+ mAtomicAnim = null;
+ }
+ });
+ mAtomicAnim.play(overviewAnim);
+ mAtomicAnim.start();
+ }
+
@Override
protected void goToTargetState(LauncherState targetState, int logAction) {
if (mPeekAnim != null && mPeekAnim.isStarted()) {
@@ -211,11 +239,14 @@
}
@Override
- protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+ @AnimationFlags
+ protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
if (handlingOverviewAnim()) {
// We don't want the state transition to all apps to animate overview,
// as that will cause a jump after our atomic animation.
- builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+ return animComponents | SKIP_OVERVIEW;
+ } else {
+ return animComponents;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index ef50c7b..a0ca886 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -44,7 +45,7 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.BaseFlags;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -108,7 +109,7 @@
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return true;
}
- if (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+ if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
&& AssistantUtilities.isExcludedAssistantRunning()) {
return true;
}
@@ -125,7 +126,7 @@
}
@Override
- public void onDragStart(boolean start) {
+ public void onDragStart(boolean start, float startDisplacement) {
initCurrentAnimation();
}
@@ -138,8 +139,13 @@
if (!recentsView.isRtl()) {
pullbackDist = -pullbackDist;
}
- Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist);
+ ObjectAnimator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X,
+ pullbackDist);
pullback.setInterpolator(PULLBACK_INTERPOLATOR);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ pullback.addUpdateListener(
+ valueAnimator -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
+ }
anim.play(pullback);
} else if (mStartState == ALL_APPS) {
AnimatorSetBuilder builder = new AnimatorSetBuilder();
@@ -165,7 +171,8 @@
}
}
anim.setDuration(accuracy);
- mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy, this::clearState);
+ mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy)
+ .setOnCancelRunnable(this::clearState);
}
private void clearState() {
@@ -192,6 +199,11 @@
boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
|| (velocity < 0 && fling);
if (success) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.switchToScreenshot(null,
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
+ }
mLauncher.getStateManager().goToState(mEndState, true,
() -> onSwipeInteractionCompleted(mEndState));
if (mStartState != mEndState) {
@@ -203,17 +215,13 @@
logStateChange(topOpenView.getLogContainerType(), logAction);
}
ActivityManagerWrapper.getInstance()
- .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
} else {
// Quickly return to the state we came from (we didn't move far).
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(progress, 0);
- anim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- onSwipeInteractionCompleted(mStartState);
- }
- });
+ anim.addListener(AnimationSuccessListener.forRunnable(
+ () -> onSwipeInteractionCompleted(mStartState)));
anim.setDuration(80).start();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
new file mode 100644
index 0000000..7cebabe
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
+ * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
+ * first home screen instead of to Overview.
+ */
+public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchController {
+
+
+ // How much of the movement to use for translating overview after swipe and hold.
+ private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f;
+ private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
+ private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
+
+ private final RecentsView mRecentsView;
+
+ private boolean mDidTouchStartInNavBar;
+ private boolean mReachedOverview;
+ // The last recorded displacement before we reached overview.
+ private PointF mStartDisplacement = new PointF();
+
+ public NoButtonNavbarToOverviewTouchController(Launcher l) {
+ super(l);
+ mRecentsView = l.getOverviewPanel();
+ }
+
+ @Override
+ protected boolean canInterceptTouch(MotionEvent ev) {
+ mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+ return super.canInterceptTouch(ev);
+ }
+
+ @Override
+ protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+ if (fromState == NORMAL && mDidTouchStartInNavBar) {
+ return HINT_STATE;
+ } else if (fromState == OVERVIEW && isDragTowardPositive) {
+ // Don't allow swiping up to all apps.
+ return OVERVIEW;
+ }
+ return super.getTargetState(fromState, isDragTowardPositive);
+ }
+
+ @Override
+ protected float initCurrentAnimation(int animComponents) {
+ float progressMultiplier = super.initCurrentAnimation(animComponents);
+ if (mToState == HINT_STATE) {
+ // Track the drag across the entire height of the screen.
+ progressMultiplier = -1 / getShiftRange();
+ }
+ return progressMultiplier;
+ }
+
+ @Override
+ public void onDragStart(boolean start, float startDisplacement) {
+ super.onDragStart(start, startDisplacement);
+
+ mReachedOverview = false;
+ }
+
+ @Override
+ protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+ LauncherState targetState, float velocity, boolean isFling) {
+ super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
+ isFling);
+ if (targetState == HINT_STATE) {
+ // Normally we compute the duration based on the velocity and distance to the given
+ // state, but since the hint state tracks the entire screen without a clear endpoint, we
+ // need to manually set the duration to a reasonable value.
+ animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher));
+ }
+ }
+
+ @Override
+ protected void onMotionPauseChanged(boolean isPaused) {
+ if (mCurrentAnimation == null) {
+ return;
+ }
+ mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
+ mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+ mReachedOverview = true;
+ maybeSwipeInteractionToOverviewComplete();
+ });
+ });
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+ }
+
+ private void maybeSwipeInteractionToOverviewComplete() {
+ if (mReachedOverview && mDetector.isSettlingState()) {
+ onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+ }
+ }
+
+ @Override
+ protected boolean handlingOverviewAnim() {
+ return mDidTouchStartInNavBar && super.handlingOverviewAnim();
+ }
+
+ @Override
+ public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
+ if (mMotionPauseDetector.isPaused()) {
+ if (!mReachedOverview) {
+ mStartDisplacement.set(xDisplacement, yDisplacement);
+ } else {
+ mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
+ * OVERVIEW_MOVEMENT_FACTOR);
+ mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
+ * OVERVIEW_MOVEMENT_FACTOR);
+ }
+ // Stay in Overview.
+ return true;
+ }
+ return super.onDrag(yDisplacement, xDisplacement, event);
+ }
+
+ @Override
+ protected void goToOverviewOnDragEnd(float velocity) {
+ float velocityDp = dpiFromPx(velocity);
+ boolean isFling = Math.abs(velocityDp) > 1;
+ LauncherStateManager stateManager = mLauncher.getStateManager();
+ if (isFling) {
+ // When flinging, go back to home instead of overview.
+ if (velocity > 0) {
+ stateManager.goToState(NORMAL, true,
+ () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
+ } else {
+ StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
+ mLauncher, velocity, false /* animateOverviewScrim */);
+ staggeredWorkspaceAnim.start();
+
+ // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
+ stateManager.cancelAnimation();
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ long duration = OVERVIEW.getTransitionDuration(mLauncher);
+ AnimatorSet anim = stateManager.createAtomicAnimation(
+ stateManager.getState(), NORMAL, builder,
+ PLAY_ATOMIC_OVERVIEW_PEEK, duration);
+ anim.addListener(AnimationSuccessListener.forRunnable(
+ () -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE)));
+ anim.start();
+ }
+ } else {
+ if (mReachedOverview) {
+ float distanceDp = dpiFromPx(Math.max(
+ Math.abs(mRecentsView.getTranslationX()),
+ Math.abs(mRecentsView.getTranslationY())));
+ long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+ distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+ mRecentsView.animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(ACCEL_DEACCEL)
+ .setDuration(duration)
+ .withEndAction(this::maybeSwipeInteractionToOverviewComplete);
+ }
+ }
+ }
+
+ private float dpiFromPx(float pixels) {
+ return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 76374af..81a6d9b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -21,13 +21,13 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
@@ -53,16 +53,16 @@
import android.view.View;
import android.view.animation.Interpolator;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
@@ -71,7 +71,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ShelfPeekAnim;
@@ -92,17 +92,18 @@
private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
- private final Launcher mLauncher;
+ private final BaseQuickstepLauncher mLauncher;
private final BothAxesSwipeDetector mSwipeDetector;
+ private final ShelfPeekAnim mShelfPeekAnim;
private final float mXRange;
private final float mYRange;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
+ private final LauncherRecentsView mRecentsView;
private boolean mNoIntercept;
private LauncherState mStartState;
- private ShelfPeekAnim mShelfPeekAnim;
private boolean mIsHomeScreenVisible = true;
// As we drag, we control 3 animations: one to get non-overview components out of the way,
@@ -111,11 +112,14 @@
private AnimatorPlaybackController mXOverviewAnim;
private AnimatorPlaybackController mYOverviewAnim;
- public NoButtonQuickSwitchTouchController(Launcher launcher) {
+ public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
+ mShelfPeekAnim = mLauncher.getShelfPeekAnim();
+ mRecentsView = mLauncher.getOverviewPanel();
mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
- mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile());
+ mYRange = LayoutUtils.getShelfTrackingDistance(
+ mLauncher, mLauncher.getDeviceProfile());
mMotionPauseDetector = new MotionPauseDetector(mLauncher);
mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
@@ -154,7 +158,7 @@
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
- int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+ int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return false;
}
@@ -165,9 +169,6 @@
public void onDragStart(boolean start) {
mMotionPauseDetector.clear();
if (start) {
- mShelfPeekAnim = ((QuickstepAppTransitionManagerImpl) mLauncher
- .getAppTransitionManager()).getShelfPeekAnim();
-
mStartState = mLauncher.getStateManager().getState();
mMotionPauseDetector.setOnMotionPauseListener(this);
@@ -182,6 +183,12 @@
@Override
public void onMotionPauseChanged(boolean isPaused) {
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ return;
+ }
+
ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
if (shelfState == PEEK) {
// Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
@@ -198,7 +205,6 @@
}
mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
ShelfPeekAnim.DURATION);
- VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
}
private void setupAnimators() {
@@ -208,19 +214,27 @@
nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
- updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL);
+ updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL_COMPONENTS);
mNonOverviewAnim.dispatchOnStart();
+ if (mRecentsView.getTaskViewCount() == 0) {
+ mRecentsView.setOnEmptyMessageUpdatedListener(isEmpty -> {
+ if (!isEmpty && mSwipeDetector.isDraggingState()) {
+ // We have loaded tasks, update the animators to start at the correct scale etc.
+ setupOverviewAnimators();
+ }
+ });
+ }
+
setupOverviewAnimators();
}
/** Create state animation to control non-overview components. */
private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
- @LauncherStateManager.AnimationComponents int animComponents) {
- builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
+ @AnimationFlags int animComponents) {
long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
- builder, accuracy, this::clearState, animComponents);
+ builder, accuracy, this::clearState, animComponents | SKIP_OVERVIEW);
}
private void setupOverviewAnimators() {
@@ -231,25 +245,25 @@
LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
.getOverviewScaleAndTranslation(mLauncher);
// Update RecentView's translationX to have it start offscreen.
- LauncherRecentsView recentsView = mLauncher.getOverviewPanel();
float startScale = Utilities.mapRange(
SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
fromScaleAndTranslation.scale,
toScaleAndTranslation.scale);
- fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale);
+ fromScaleAndTranslation.translationX = mRecentsView.getOffscreenTranslationX(startScale);
// Set RecentView's initial properties.
- recentsView.setScaleX(fromScaleAndTranslation.scale);
- recentsView.setScaleY(fromScaleAndTranslation.scale);
- recentsView.setTranslationX(fromScaleAndTranslation.translationX);
- recentsView.setTranslationY(fromScaleAndTranslation.translationY);
- recentsView.setContentAlpha(1);
+ mRecentsView.setScaleX(fromScaleAndTranslation.scale);
+ mRecentsView.setScaleY(fromScaleAndTranslation.scale);
+ mRecentsView.setTranslationX(fromScaleAndTranslation.translationX);
+ mRecentsView.setTranslationY(fromScaleAndTranslation.translationY);
+ mRecentsView.setContentAlpha(1);
+ mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
// As we drag right, animate the following properties:
// - RecentsView translationX
// - OverviewScrim
AnimatorSet xOverviewAnim = new AnimatorSet();
- xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X,
+ xOverviewAnim.play(ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_X,
toScaleAndTranslation.translationX));
xOverviewAnim.play(ObjectAnimator.ofFloat(
mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
@@ -264,11 +278,11 @@
// - RecentsView scale
// - RecentsView fullscreenProgress
AnimatorSet yAnimation = new AnimatorSet();
- Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y,
+ Animator translateYAnim = ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_Y,
toScaleAndTranslation.translationY);
- Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
+ Animator scaleAnim = ObjectAnimator.ofFloat(mRecentsView, SCALE_PROPERTY,
toScaleAndTranslation.scale);
- Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS,
+ Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(mRecentsView, FULLSCREEN_PROGRESS,
fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
@@ -303,7 +317,7 @@
// home screen elements will appear in the shelf on motion pause.
mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
|| -displacement.y < mMotionPauseMinDisplacement);
- mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime());
+ mMotionPauseDetector.addPosition(ev);
if (mIsHomeScreenVisible) {
// Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
@@ -378,7 +392,7 @@
xOverviewAnim.setFloatValues(startXProgress, endXProgress);
xOverviewAnim.setDuration(xDuration)
.setInterpolator(scrollInterpolatorForVelocity(velocity.x));
- mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x);
+ mXOverviewAnim.dispatchOnStart();
boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
@@ -399,7 +413,7 @@
ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
yOverviewAnim.setFloatValues(startYProgress, endYProgress);
yOverviewAnim.setDuration(yDuration);
- mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y);
+ mYOverviewAnim.dispatchOnStart();
ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
if (flingUpToNormal && !mIsHomeScreenVisible) {
@@ -421,8 +435,7 @@
float startProgress = mNonOverviewAnim.getProgressFraction();
float endProgress = canceled ? 0 : 1;
nonOverviewAnim.setFloatValues(startProgress, endProgress);
- mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress,
- horizontalFling ? velocity.x : velocity.y);
+ mNonOverviewAnim.dispatchOnStart();
}
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
@@ -469,5 +482,6 @@
mYOverviewAnim = null;
mIsHomeScreenVisible = true;
mSwipeDetector.finishedScrolling();
+ mRecentsView.setOnEmptyMessageUpdatedListener(null);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 03862db..845699a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -19,9 +19,10 @@
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.isTouchOverHotseat;
import android.view.MotionEvent;
+import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
-import com.android.launcher3.util.PendingAnimation;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -74,12 +75,12 @@
* @param duration how long the animation should be
* @return the animation
*/
- PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+ PendingAnimation createSwipeDownToTaskAppAnimation(long duration, Interpolator interpolator) {
mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
TaskView taskView = mRecentsView.getCurrentPageTaskView();
if (taskView == null) {
throw new IllegalStateException("There is no task view to animate to.");
}
- return mRecentsView.createTaskLauncherAnimation(taskView, duration);
+ return mRecentsView.createTaskLaunchAnimation(taskView, duration, interpolator);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 14216ff..8de1b3a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
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_OVERVIEW_SCALE;
@@ -39,16 +40,15 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -84,7 +84,7 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+ int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return NORMAL;
}
@@ -92,12 +92,12 @@
}
@Override
- public void onDragStart(boolean start) {
- super.onDragStart(start);
+ public void onDragStart(boolean start, float startDisplacement) {
+ super.onDragStart(start, startDisplacement);
mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
mTaskToLaunch = mLauncher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
ActivityManagerWrapper.getInstance()
- .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
}
@Override
@@ -112,7 +112,7 @@
setupInterpolators(animatorSetBuilder);
long accuracy = (long) (getShiftRange() * 2);
mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
- animatorSetBuilder, accuracy, this::clearState, LauncherStateManager.ANIM_ALL);
+ animatorSetBuilder, accuracy, this::clearState, ANIM_ALL_COMPONENTS);
mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
updateFullscreenProgress((Float) valueAnimator.getAnimatedValue());
});
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index ad02de1..cc58fcf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,17 +16,13 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
@@ -35,11 +31,12 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.FlingBlockCheck;
-import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SysUINavigationMode;
@@ -77,7 +74,9 @@
public TaskViewTouchController(T activity) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
- mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL);
+ SingleAxisSwipeDetector.Direction dir =
+ mRecentsView.getPagedOrientationHandler().getOppositeSwipeDirection();
+ mDetector = new SingleAxisSwipeDetector(activity, this, dir);
}
private boolean canInterceptTouch() {
@@ -105,6 +104,10 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if ((ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL)
+ && mCurrentAnimation == null) {
+ clearState();
+ }
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch();
if (mNoIntercept) {
@@ -125,6 +128,11 @@
TaskView view = mRecentsView.getTaskViewAt(i);
if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
.isEventOverView(view, ev)) {
+ // Disable swiping up and down if the task overlay is modal.
+ if (view.isTaskOverlayModal()) {
+ mTaskBeingDragged = null;
+ break;
+ }
mTaskBeingDragged = view;
if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
// Don't allow swipe down to open if we don't support swipe up
@@ -181,30 +189,33 @@
mPendingAnimation = null;
}
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
mCurrentAnimationIsGoingUp = goingUp;
BaseDragLayer dl = mActivity.getDragLayer();
- long maxDuration = (long) (2 * dl.getHeight());
-
+ final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl);
+ long maxDuration = (long) (2 * secondaryLayerDimension);
+ int verticalFactor = -orientationHandler.getTaskDismissDirectionFactor();
+ int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
if (goingUp) {
mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration);
- mEndDisplacement = -mTaskBeingDragged.getHeight();
+ mEndDisplacement = -secondaryTaskDimension;
} else {
- mPendingAnimation = mRecentsView.createTaskLauncherAnimation(
- mTaskBeingDragged, maxDuration);
- mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+ mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
+ mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
mTempCords[1] = mTaskBeingDragged.getHeight();
dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
mEndDisplacement = dl.getHeight() - mTempCords[1];
}
+ mEndDisplacement *= verticalFactor;
if (mCurrentAnimation != null) {
mCurrentAnimation.setOnCancelRunnable(null);
}
- mCurrentAnimation = AnimatorPlaybackController
- .wrap(mPendingAnimation.anim, maxDuration, this::clearState);
+ mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation, maxDuration)
+ .setOnCancelRunnable(this::clearState);
onUserControlledAnimationCreated(mCurrentAnimation);
mCurrentAnimation.getTarget().addListener(this);
mCurrentAnimation.dispatchOnStart();
@@ -212,9 +223,10 @@
}
@Override
- public void onDragStart(boolean start) {
+ public void onDragStart(boolean start, float startDisplacement) {
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
if (mCurrentAnimation == null) {
- reInitAnimationController(mDetector.wasInitialTouchPositive());
+ reInitAnimationController(orientationHandler.isGoingUp(startDisplacement));
mDisplacementShift = 0;
} else {
mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
@@ -225,9 +237,10 @@
@Override
public boolean onDrag(float displacement) {
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
float totalDisplacement = displacement + mDisplacementShift;
- boolean isGoingUp =
- totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
+ boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp :
+ orientationHandler.isGoingUp(totalDisplacement);
if (isGoingUp != mCurrentAnimationIsGoingUp) {
reInitAnimationController(isGoingUp);
mFlingBlockCheck.blockFling();
@@ -254,11 +267,12 @@
if (blockedFling) {
fling = false;
}
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
logAction = Touch.FLING;
- boolean goingUp = velocity < 0;
+ boolean goingUp = orientationHandler.isGoingUp(velocity);
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
logAction = Touch.SWIPE;
@@ -270,26 +284,16 @@
animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
}
- float nextFrameProgress = Utilities.boundToRange(progress
- + velocity * getSingleFrameMs(mActivity) / Math.abs(mEndDisplacement), 0f, 1f);
-
mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
-
- ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
- anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
- anim.setDuration(animationDuration);
- anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- anim.addUpdateListener(valueAnimator -> {
+ mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) {
mRecentsView.redrawLiveTile(true);
}
});
}
- if (QUICKSTEP_SPRINGS.get()) {
- mCurrentAnimation.dispatchOnStartWithVelocity(goingToEnd ? 1f : 0f, velocity);
- }
- anim.start();
+ mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
+ velocity, mEndDisplacement, animationDuration);
}
private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index ad90e16..e1ff4f4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -15,13 +15,17 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.util.Log;
@@ -30,16 +34,17 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
import com.android.quickstep.util.RemoteAnimationProvider;
-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.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.TransactionCompat;
/**
- * Provider for the atomic remote window animation from the app to the overview.
+ * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
*
* @param <T> activity that contains the overview
*/
@@ -49,15 +54,15 @@
private static final long RECENTS_LAUNCH_DURATION = 250;
private static final String TAG = "AppToOverviewAnimationProvider";
- private final ActivityControlHelper<T> mHelper;
+ private final BaseActivityInterface<T> mActivityInterface;
// The id of the currently running task that is transitioning to overview.
private final int mTargetTaskId;
private T mActivity;
private RecentsView mRecentsView;
- AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
- mHelper = helper;
+ AppToOverviewAnimationProvider(BaseActivityInterface<T> activityInterface, int targetTaskId) {
+ mActivityInterface = activityInterface;
mTargetTaskId = targetTaskId;
}
@@ -70,8 +75,8 @@
boolean onActivityReady(T activity, Boolean wasVisible) {
activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
- ActivityControlHelper.AnimationFactory factory =
- mHelper.prepareRecentsUI(activity, wasVisible,
+ BaseActivityInterface.AnimationFactory factory =
+ mActivityInterface.prepareRecentsUI(wasVisible,
false /* animate activity */, (controller) -> {
controller.dispatchOnStart();
ValueAnimator anim = controller.getAnimationPlayer()
@@ -80,7 +85,7 @@
anim.start();
});
factory.onRemoteAnimationReceived(null);
- factory.createActivityController(RECENTS_LAUNCH_DURATION);
+ factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
factory.setRecentsAttachedToAppWindow(true, false);
mActivity = activity;
mRecentsView = mActivity.getOverviewPanel();
@@ -90,19 +95,27 @@
/**
* Create remote window animation from the currently running app to the overview panel.
*
- * @param targetCompats the target apps
+ * @param appTargets the target apps
* @return animation from app to overview
*/
@Override
- public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+ public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets) {
if (mRecentsView != null) {
mRecentsView.setRunningTaskIconScaledDown(true);
}
+
+ BackgroundBlurController blurController = mActivityInterface.getBackgroundBlurController();
+ if (blurController != null) {
+ // Update the surface to be the lowest closing app surface
+ blurController.setSurfaceToLauncher(mRecentsView);
+ }
+
AnimatorSet anim = new AnimatorSet();
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- mHelper.onSwipeUpToRecentsComplete(mActivity);
+ mActivityInterface.onSwipeUpToRecentsComplete();
if (mRecentsView != null) {
mRecentsView.animateUpRunningTaskIconScale();
}
@@ -110,22 +123,25 @@
});
if (mActivity == null) {
Log.e(TAG, "Animation created, before activity");
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
+ .with(createBackgroundBlurAnimator(blurController));
return anim;
}
- RemoteAnimationTargetSet targetSet =
- new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
+ RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
+ wallpaperTargets, MODE_CLOSING);
// Use the top closing app to determine the insets for the animation
- RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mTargetTaskId);
+ RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
if (runningTaskTarget == null) {
Log.e(TAG, "No closing app");
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
+ .with(createBackgroundBlurAnimator(blurController));
return anim;
}
- final ClipAnimationHelper clipHelper = new ClipAnimationHelper(mActivity);
+ final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper(
+ mRecentsView.getPagedViewOrientedState(), mActivity);
// At this point, the activity is already started and laid-out. Get the home-bounds
// relative to the screen using the rootView of the activity.
@@ -137,24 +153,27 @@
clipHelper.updateSource(homeBounds, runningTaskTarget);
Rect targetRect = new Rect();
- mHelper.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity, targetRect);
+ mActivityInterface.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity,
+ targetRect);
clipHelper.updateTargetRect(targetRect);
clipHelper.prepareAnimation(mActivity.getDeviceProfile(), false /* isOpening */);
- ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
+ TransformParams params = new TransformParams()
.setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(rootView));
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(RECENTS_LAUNCH_DURATION);
valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
valueAnimator.addUpdateListener((v) -> {
- params.setProgress((float) v.getAnimatedValue());
- clipHelper.applyTransform(targetSet, params);
+ params.setProgress((float) v.getAnimatedValue())
+ .setTargetSet(targets)
+ .setLauncherOnTop(true);
+ clipHelper.applyTransform(params);
});
- if (targetSet.isAnimatingHome()) {
+ if (targets.isAnimatingHome()) {
// If we are animating home, fade in the opening targets
- RemoteAnimationTargetSet openingSet =
- new RemoteAnimationTargetSet(targetCompats, MODE_OPENING);
+ RemoteAnimationTargets openingSet = new RemoteAnimationTargets(appTargets,
+ wallpaperTargets, MODE_OPENING);
TransactionCompat transaction = new TransactionCompat();
valueAnimator.addUpdateListener((v) -> {
@@ -164,7 +183,8 @@
transaction.apply();
});
}
- anim.play(valueAnimator);
+ anim.play(valueAnimator)
+ .with(createBackgroundBlurAnimator(blurController));
return anim;
}
@@ -176,4 +196,15 @@
long getRecentsLaunchDuration() {
return RECENTS_LAUNCH_DURATION;
}
+
+ private Animator createBackgroundBlurAnimator(BackgroundBlurController blurController) {
+ if (blurController == null) {
+ // Dummy animation
+ return ValueAnimator.ofInt(0);
+ }
+ return ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
+ BACKGROUND_APP.getBackgroundBlurRadius(mActivity),
+ OVERVIEW.getBackgroundBlurRadius(mActivity))
+ .setDuration(RECENTS_LAUNCH_DURATION);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index e5d2b41..7786a8f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -15,18 +15,15 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import android.animation.Animator;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -34,8 +31,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -50,24 +46,27 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.model.PagedViewOrientedState;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.FloatingIconView;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import java.util.ArrayList;
import java.util.function.Consumer;
/**
@@ -75,33 +74,32 @@
*/
@TargetApi(Build.VERSION_CODES.Q)
public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView>
- implements SwipeAnimationListener {
+ implements RecentsAnimationListener {
private static final String TAG = "BaseSwipeUpHandler";
protected static final Rect TEMP_RECT = new Rect();
- // Start resisting when swiping past this factor of mTransitionDragLength.
- private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
- // This is how far down we can scale down, where 0f is full screen and 1f is recents.
- private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
+ public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
// The distance needed to drag to reach the task size in recents.
protected int mTransitionDragLength;
// How much further we can drag past recents, as a factor of mTransitionDragLength.
protected float mDragLengthFactor = 1;
+ // Start resisting when swiping past this factor of mTransitionDragLength.
+ private float mDragLengthFactorStartPullback = 1f;
+ // This is how far down we can scale down, where 0f is full screen and 1f is recents.
+ private float mDragLengthFactorMaxPullback = 1f;
protected final Context mContext;
- protected final OverviewComponentObserver mOverviewComponentObserver;
- protected final ActivityControlHelper<T> mActivityControlHelper;
- protected final RecentsModel mRecentsModel;
- protected final int mRunningTaskId;
+ protected final RecentsAnimationDeviceState mDeviceState;
+ protected final GestureState mGestureState;
+ protected final BaseActivityInterface<T> mActivityInterface;
+ protected final InputConsumerController mInputConsumer;
- protected final ClipAnimationHelper mClipAnimationHelper;
+ protected AppWindowAnimationHelper mAppWindowAnimationHelper;
protected final TransformParams mTransformParams = new TransformParams();
- protected final Mode mMode;
-
// Shift in the range of [0, 1].
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
@@ -109,7 +107,12 @@
protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
protected final ActivityInitListener mActivityInitListener;
- protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+
+ protected RecentsAnimationController mRecentsAnimationController;
+ protected RecentsAnimationTargets mRecentsAnimationTargets;
+
+ // Callbacks to be made once the recents animation starts
+ private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
protected T mActivity;
protected Q mRecentsView;
@@ -118,46 +121,34 @@
protected Runnable mGestureEndCallback;
- protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
protected int mFinishingRecentsAnimationForNewTaskId = -1;
- protected BaseSwipeUpHandler(Context context,
- OverviewComponentObserver overviewComponentObserver,
- RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
- mContext = context;
- mOverviewComponentObserver = overviewComponentObserver;
- mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
- mRecentsModel = recentsModel;
- mActivityInitListener =
- mActivityControlHelper.createActivityInitListener(this::onActivityInit);
- mRunningTaskId = runningTaskId;
- mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
- this::createNewInputProxyHandler);
- mMode = SysUINavigationMode.getMode(context);
+ private PagedViewOrientedState mOrientedState;
- mClipAnimationHelper = new ClipAnimationHelper(context);
+ protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
+ GestureState gestureState, InputConsumerController inputConsumer) {
+ mContext = context;
+ mDeviceState = deviceState;
+ mGestureState = gestureState;
+ mActivityInterface = gestureState.getActivityInterface();
+ mActivityInitListener =
+ mActivityInterface.createActivityInitListener(this::onActivityInit);
+ mInputConsumer = inputConsumer;
+ mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
- .getDeviceProfile(mContext));
- }
-
- protected void setStateOnUiThread(int stateFlag) {
- if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
- mStateCallback.setState(stateFlag);
- } else {
- postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
- }
+ .getDeviceProfile(mContext));
}
protected void performHapticFeedback() {
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
}
- public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
- return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
+ public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode navBarRotationMode) {
+ return mRecentsView != null ? mRecentsView.getEventDispatcher(navBarRotationMode) : null;
}
@UiThread
@@ -170,12 +161,12 @@
} else {
float translation = Math.max(displacement, 0);
shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
- if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
+ if (shift > mDragLengthFactorStartPullback) {
float pullbackProgress = Utilities.getProgress(shift,
- DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
+ mDragLengthFactorStartPullback, mDragLengthFactor);
pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
- shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
- * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
+ shift = mDragLengthFactorStartPullback + pullbackProgress
+ * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
}
}
@@ -191,8 +182,8 @@
protected void linkRecentsViewScroll() {
SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
mTransformParams.setSyncTransactionApplier(applier);
- mRecentsAnimationWrapper.runOnInit(() ->
- mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+ runOnRecentsAnimationStart(() ->
+ mRecentsAnimationTargets.addDependentTransactionApplier(applier));
});
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
@@ -200,8 +191,10 @@
updateFinalShift();
}
});
- mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
- mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+ mRecentsView.setAppWindowAnimationHelper(mAppWindowAnimationHelper);
+ runOnRecentsAnimationStart(() ->
+ mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
+ mRecentsAnimationTargets));
}
protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
@@ -213,7 +206,7 @@
} else {
int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
mFinishingRecentsAnimationForNewTaskId = taskId;
- mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
+ mRecentsAnimationController.finish(true /* toRecents */, () -> {
if (!mCanceled) {
TaskView nextTask = mRecentsView.getTaskView(taskId);
if (nextTask != null) {
@@ -221,31 +214,54 @@
success -> {
resultCallback.accept(success);
if (!success) {
- mActivityControlHelper.onLaunchTaskFailed(mActivity);
+ mActivityInterface.onLaunchTaskFailed();
nextTask.notifyTaskLaunchFailed(TAG);
} else {
- mActivityControlHelper.onLaunchTaskSuccess(mActivity);
+ mActivityInterface.onLaunchTaskSuccess();
}
- }, mMainThreadHandler);
+ }, MAIN_EXECUTOR.getHandler());
}
- setStateOnUiThread(successStateFlag);
+ mStateCallback.setStateOnUiThread(successStateFlag);
}
mCanceled = false;
mFinishingRecentsAnimationForNewTaskId = -1;
});
}
- TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+ }
+
+ /**
+ * Runs the given {@param action} if the recents animation has already started, or queues it to
+ * be run when it is next started.
+ */
+ protected void runOnRecentsAnimationStart(Runnable action) {
+ if (mRecentsAnimationTargets == null) {
+ mRecentsAnimationStartCallbacks.add(action);
+ } else {
+ action.run();
+ }
+ }
+
+ /**
+ * @return whether the recents animation has started and there are valid app targets.
+ */
+ protected boolean hasTargets() {
+ return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
}
@Override
- public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+ public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
+ RecentsAnimationTargets targets) {
+ mRecentsAnimationController = recentsAnimationController;
+ mRecentsAnimationTargets = targets;
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
final Rect overviewStackBounds;
- RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+ RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
+ mGestureState.getRunningTaskId());
- if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
- overviewStackBounds = mActivityControlHelper
- .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
+ if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+ overviewStackBounds = mActivityInterface
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
dp = dp.getMultiWindowProfile(mContext, new Point(
overviewStackBounds.width(), overviewStackBounds.height()));
} else {
@@ -253,16 +269,40 @@
dp = dp.copy(mContext);
overviewStackBounds = getStackBounds(dp);
}
- dp.updateInsets(targetSet.homeContentInsets);
+ dp.updateInsets(targets.homeContentInsets);
dp.updateIsSeascape(mContext);
if (runningTaskTarget != null) {
- mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+ mAppWindowAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
}
- mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
+ mAppWindowAnimationHelper.prepareAnimation(dp, false /* isOpening */);
initTransitionEndpoints(dp);
- mRecentsAnimationWrapper.setController(targetSet);
+ // Notify when the animation starts
+ if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+ for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+ action.run();
+ }
+ mRecentsAnimationStartCallbacks.clear();
+ }
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
+ }
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
+ }
}
private Rect getStackBounds(DeviceProfile dp) {
@@ -280,19 +320,34 @@
protected void initTransitionEndpoints(DeviceProfile dp) {
mDp = dp;
- mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+ mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
dp, mContext, TEMP_RECT);
if (!dp.isMultiWindowMode) {
// When updating the target rect, also update the home bounds since the location on
// screen of the launcher window may be stale (position is not updated until first
// traversal after the window is resized). We only do this for non-multiwindow because
// we otherwise use the minimized home bounds provided by the system.
- mClipAnimationHelper.updateHomeBounds(getStackBounds(dp));
+ mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
}
- mClipAnimationHelper.updateTargetRect(TEMP_RECT);
- if (mMode == Mode.NO_BUTTON) {
+ int displayRotation = 0;
+ if (mOrientedState != null) {
+ // TODO(b/150300347): The first recents animation after launcher is started with the
+ // foreground app not in landscape will look funky until that bug is fixed
+ displayRotation = mOrientedState.getDisplayRotation();
+ }
+ RotationHelper.getTargetRectForRotation(TEMP_RECT, dp.widthPx, dp.heightPx,
+ displayRotation);
+ mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
+ if (mDeviceState.isFullyGesturalNavMode()) {
// We can drag all the way to the top of the screen.
- mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
+ // TODO(b/149609070): Landscape apps are currently limited in
+ // their ability to scale past the target rect.
+ float dragFactor = (float) dp.heightPx / mTransitionDragLength;
+ mDragLengthFactor = displayRotation == 0 ? dragFactor : Math.min(1.0f, dragFactor);
+ Pair<Float, Float> dragFactorStartAndMaxProgress =
+ mActivityInterface.getSwipeUpPullbackStartAndMaxProgress();
+ mDragLengthFactorStartPullback = dragFactorStartAndMaxProgress.first;
+ mDragLengthFactorMaxPullback = dragFactorStartAndMaxProgress.second;
}
}
@@ -301,7 +356,17 @@
*/
protected abstract boolean moveWindowWithRecentsScroll();
- protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
+ protected boolean onActivityInit(Boolean alreadyOnHome) {
+ T createdActivity = mActivityInterface.getCreatedActivity();
+ if (createdActivity != null) {
+ mOrientedState = ((RecentsView) createdActivity.getOverviewPanel())
+ .getPagedViewOrientedState();
+ mAppWindowAnimationHelper = new AppWindowAnimationHelper(mOrientedState, mContext);
+ initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
+ .getDeviceProfile(mContext));
+ }
+ return true;
+ }
/**
* Called to create a input proxy for the running task
@@ -329,13 +394,13 @@
@UiThread
public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
- public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
+ public abstract void onConsumerAboutToBeSwitched();
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
public void initWhenReady() {
// Preload the plan
- mRecentsModel.getTasks(null);
+ RecentsModel.INSTANCE.get(mContext).getTasks(null);
mActivityInitListener.register();
}
@@ -345,20 +410,32 @@
*/
protected void applyTransformUnchecked() {
float shift = mCurrentShift.value;
- float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
- float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
- mClipAnimationHelper.getTargetRect().width());
- mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
- mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
- mTransformParams);
+ float offset = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
+ float taskSize = getOrientationHandler()
+ .getPrimarySize(mAppWindowAnimationHelper.getTargetRect());
+ float offsetScale = getTaskCurveScaleForOffset(offset, taskSize);
+ mTransformParams.setProgress(shift)
+ .setOffset(offset)
+ .setOffsetScale(offsetScale)
+ .setTargetSet(mRecentsAnimationTargets)
+ .setLauncherOnTop(true);
+ mAppWindowAnimationHelper.applyTransform(mTransformParams);
}
- private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
- float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing;
- float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+ private float getTaskCurveScaleForOffset(float offset, float taskSize) {
+ int dpPixel = getOrientationHandler().getShortEdgeLength(mDp);
+ float distanceToReachEdge = dpPixel / 2 + taskSize / 2 + mPageSpacing;
+ float interpolation = Math.min(1, offset / distanceToReachEdge);
return TaskView.getCurveScaleForInterpolation(interpolation);
}
+ protected PagedOrientationHandler getOrientationHandler() {
+ if (mOrientedState == null) {
+ return new PortraitPagedViewHandler();
+ }
+ return mOrientedState.getOrientationHandler();
+ }
+
/**
* Creates an animation that transforms the current app window into the home app.
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
@@ -366,14 +443,19 @@
*/
protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
- final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
- final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
- mTransformParams.setProgress(startProgress), false /* launcherOnTop */));
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
-
final View floatingView = homeAnimationFactory.getFloatingView();
final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
- RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext.getResources());
+ final RectF startRect = new RectF(
+ mAppWindowAnimationHelper.applyTransform(
+ mTransformParams.setProgress(startProgress)
+ .setTargetSet(mRecentsAnimationTargets)
+ .setLauncherOnTop(false)));
+ if (isFloatingIconView) {
+ RotationHelper.mapInverseRectFromNormalOrientation(startRect,
+ mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
+ }
+ RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
if (isFloatingIconView) {
FloatingIconView fiv = (FloatingIconView) floatingView;
anim.addAnimatorListener(fiv);
@@ -384,7 +466,7 @@
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
// rounding at the end of the animation.
- float startRadius = mClipAnimationHelper.getCurrentCornerRadius();
+ float startRadius = mAppWindowAnimationHelper.getCurrentCornerRadius();
float endRadius = startRect.width() / 6f;
float startTransformProgress = mTransformParams.getProgress();
@@ -393,39 +475,29 @@
// We want the window alpha to be 0 once this threshold is met, so that the
// FolderIconView can be seen morphing into the icon shape.
final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
+ final RectF rotatedRect = new RectF();
anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
- // Alpha interpolates between [1, 0] between progress values [start, end]
- final float start = 0f;
- final float end = 0.85f;
-
- private float getWindowAlpha(float progress) {
- if (progress <= start) {
- return 1f;
- }
- if (progress >= end) {
- return 0f;
- }
- return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
- }
-
@Override
public void onUpdate(RectF currentRect, float progress) {
homeAnim.setPlayFraction(progress);
- mTransformParams.setProgress(
- Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
- .setCurrentRectAndTargetAlpha(currentRect, getWindowAlpha(progress));
+ rotatedRect.set(currentRect);
if (isFloatingIconView) {
+ RotationHelper.mapRectFromNormalOrientation(rotatedRect,
+ mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
mTransformParams.setCornerRadius(endRadius * progress + startRadius
- * (1f - progress));
+ * (1f - progress));
}
- mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
- false /* launcherOnTop */);
+ mTransformParams.setProgress(
+ Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
+ .setCurrentRect(rotatedRect)
+ .setTargetAlpha(getWindowAlpha(progress));
+ mAppWindowAnimationHelper.applyTransform(mTransformParams);
if (isFloatingIconView) {
((FloatingIconView) floatingView).update(currentRect, 1f, progress,
- windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(),
+ windowAlphaThreshold, mAppWindowAnimationHelper.getCurrentCornerRadius(),
false);
}
}
@@ -451,10 +523,28 @@
return anim;
}
+ /**
+ * @param progress The progress of the animation to the home screen.
+ * @return The current alpha to set on the animating app window.
+ */
+ protected float getWindowAlpha(float progress) {
+ // Alpha interpolates between [1, 0] between progress values [start, end]
+ final float start = 0f;
+ final float end = 0.85f;
+
+ if (progress <= start) {
+ return 1f;
+ }
+ if (progress >= end) {
+ return 0f;
+ }
+ return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+ }
+
public interface Factory {
- BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
- long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
+ BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
+ boolean continuingLastGesture, boolean isLikelyToStartNewTask);
}
protected interface RunningWindowAnim {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
similarity index 81%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 8c5a788..71580ca 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -33,28 +33,30 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import java.util.function.BiPredicate;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
- * {@link ActivityControlHelper} for recents when the default launcher is different than the
+ * {@link BaseActivityInterface} for recents when the default launcher is different than the
* currently running one and apps should interact with the {@link RecentsActivity} as opposed
* to the in-launcher one.
*/
-public final class FallbackActivityControllerHelper implements
- ActivityControlHelper<RecentsActivity> {
+public final class FallbackActivityInterface implements
+ BaseActivityInterface<RecentsActivity> {
- public FallbackActivityControllerHelper() { }
+ public FallbackActivityInterface() { }
@Override
- public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
+ public void onTransitionCancelled(boolean activityVisible) {
// TODO:
}
@@ -72,7 +74,11 @@
}
@Override
- public void onSwipeUpToRecentsComplete(RecentsActivity activity) {
+ public void onSwipeUpToRecentsComplete() {
+ RecentsActivity activity = getCreatedActivity();
+ if (activity == null) {
+ return;
+ }
RecentsView recentsView = activity.getOverviewPanel();
recentsView.getClearAllButton().setVisibilityAlpha(1);
recentsView.setDisallowScrollToClearAll(false);
@@ -87,7 +93,8 @@
@NonNull
@Override
- public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) {
+ public HomeAnimationFactory prepareHomeUI() {
+ RecentsActivity activity = getCreatedActivity();
RecentsView recentsView = activity.getOverviewPanel();
return new HomeAnimationFactory() {
@@ -118,8 +125,9 @@
}
@Override
- public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
+ public AnimationFactory prepareRecentsUI(boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
+ RecentsActivity activity = getCreatedActivity();
if (activityVisible) {
return (transitionLength) -> { };
}
@@ -137,17 +145,17 @@
boolean isAnimatingToRecents = false;
@Override
- public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
+ public void onRemoteAnimationReceived(RemoteAnimationTargets targets) {
isAnimatingToRecents = targets != null && targets.isAnimatingHome();
if (!isAnimatingToRecents) {
rv.setContentAlpha(1);
}
- createActivityController(getSwipeUpDestinationAndLength(
+ createActivityInterface(getSwipeUpDestinationAndLength(
activity.getDeviceProfile(), activity, new Rect()));
}
@Override
- public void createActivityController(long transitionLength) {
+ public void createActivityInterface(long transitionLength) {
AnimatorSet animatorSet = new AnimatorSet();
if (isAnimatingToRecents) {
ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
@@ -176,14 +184,15 @@
@Override
public ActivityInitListener createActivityInitListener(
- BiPredicate<RecentsActivity, Boolean> onInitListener) {
- return new RecentsActivityTracker(onInitListener);
+ Predicate<Boolean> onInitListener) {
+ return new ActivityInitListener<>((activity, alreadyOnHome) ->
+ onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
}
@Nullable
@Override
public RecentsActivity getCreatedActivity() {
- return RecentsActivityTracker.getCurrentActivity();
+ return BaseRecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
}
@Nullable
@@ -218,7 +227,7 @@
RecentsActivity activity = getCreatedActivity();
boolean visible = activity != null && activity.isStarted() && activity.hasWindowFocus();
return visible
- ? LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER
+ ? LauncherLogProto.ContainerType.OTHER_LAUNCHER_APP
: LauncherLogProto.ContainerType.APP;
}
@@ -228,13 +237,21 @@
}
@Override
- public void onLaunchTaskFailed(RecentsActivity activity) {
+ public void onLaunchTaskFailed() {
// TODO: probably go back to overview instead.
+ RecentsActivity activity = getCreatedActivity();
+ if (activity == null) {
+ return;
+ }
activity.<RecentsView>getOverviewPanel().startHome();
}
@Override
- public void onLaunchTaskSuccess(RecentsActivity activity) {
+ public void onLaunchTaskSuccess() {
+ RecentsActivity activity = getCreatedActivity();
+ if (activity == null) {
+ return;
+ }
activity.onTaskLaunched();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
new file mode 100644
index 0000000..ea5561b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
+import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
+import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.util.ArrayMap;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.touch.LandscapePagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.util.ObjectWrapper;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
+import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.InputConsumerController;
+
+/**
+ * Handles the navigation gestures when a 3rd party launcher is the default home activity.
+ */
+public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+
+ private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
+
+ private static int getFlagForIndex(int index, String name) {
+ if (DEBUG_STATES) {
+ STATE_NAMES[index] = name;
+ }
+ return 1 << index;
+ }
+
+ private static final int STATE_RECENTS_PRESENT =
+ getFlagForIndex(0, "STATE_RECENTS_PRESENT");
+ private static final int STATE_HANDLER_INVALIDATED =
+ getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
+
+ private static final int STATE_GESTURE_CANCELLED =
+ getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
+ private static final int STATE_GESTURE_COMPLETED =
+ getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
+ private static final int STATE_APP_CONTROLLER_RECEIVED =
+ getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
+
+ public static class EndTargetAnimationParams {
+ private final float mEndProgress;
+ private final long mDurationMultiplier;
+ private final float mLauncherAlpha;
+
+ EndTargetAnimationParams(float endProgress, long durationMultiplier, float launcherAlpha) {
+ mEndProgress = endProgress;
+ mDurationMultiplier = durationMultiplier;
+ mLauncherAlpha = launcherAlpha;
+ }
+ }
+ private final ArrayMap<GestureEndTarget, EndTargetAnimationParams>
+ mEndTargetAnimationParams = new ArrayMap();
+
+ private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
+
+ private boolean mOverviewThresholdPassed = false;
+
+ private final boolean mInQuickSwitchMode;
+ private final boolean mContinuingLastGesture;
+ private final boolean mRunningOverHome;
+ private final boolean mSwipeUpOverHome;
+ private boolean mTouchedHomeDuringTransition;
+
+ private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
+ private RunningWindowAnim mFinishAnimation;
+
+ public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+ GestureState gestureState, InputConsumerController inputConsumer,
+ boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
+ super(context, deviceState, gestureState, inputConsumer);
+
+ mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
+ mContinuingLastGesture = continuingLastGesture;
+ mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
+ mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
+
+ // Keep the home launcher invisible until we decide to land there.
+ mLauncherAlpha.value = mRunningOverHome ? 1 : 0;
+ if (mSwipeUpOverHome) {
+ mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
+ } else {
+ mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
+ }
+
+ // Going home has an extra long progress to ensure that it animates into the screen
+ mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1));
+ mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0));
+ mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1));
+ mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1));
+
+ initStateCallbacks();
+ }
+
+ private void initStateCallbacks() {
+ mStateCallback = new MultiStateCallback(STATE_NAMES);
+
+ mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
+ this::onHandlerInvalidated);
+ mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
+ this::onHandlerInvalidatedWithRecents);
+
+ mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
+ this::finishAnimationTargetSetAnimationComplete);
+
+ if (mInQuickSwitchMode) {
+ mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
+ | STATE_RECENTS_PRESENT,
+ this::finishAnimationTargetSet);
+ } else {
+ mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
+ this::finishAnimationTargetSet);
+ }
+ }
+
+ private void onLauncherAlphaChanged() {
+ if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) {
+ applyTransformUnchecked();
+ }
+ }
+
+ @Override
+ protected boolean onActivityInit(Boolean alreadyOnHome) {
+ super.onActivityInit(alreadyOnHome);
+ mActivity = mActivityInterface.getCreatedActivity();
+ mRecentsView = mActivity.getOverviewPanel();
+ linkRecentsViewScroll();
+ mRecentsView.setDisallowScrollToClearAll(true);
+ mRecentsView.getClearAllButton().setVisibilityAlpha(0);
+ mRecentsView.setZoomProgress(1);
+
+ if (!mContinuingLastGesture) {
+ if (mRunningOverHome) {
+ mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
+ } else {
+ mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
+ }
+ }
+ mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
+ mDeviceState.enableMultipleRegions(false);
+ return true;
+ }
+
+ @Override
+ protected boolean moveWindowWithRecentsScroll() {
+ return mInQuickSwitchMode;
+ }
+
+ @Override
+ public void initWhenReady() {
+ if (mInQuickSwitchMode) {
+ // Only init if we are in quickswitch mode
+ super.initWhenReady();
+ }
+ }
+
+ @Override
+ public void updateDisplacement(float displacement) {
+ if (!mInQuickSwitchMode) {
+ super.updateDisplacement(displacement);
+ }
+ }
+
+ @Override
+ protected InputConsumer createNewInputProxyHandler() {
+ // Just consume all input on the active task
+ return new InputConsumer() {
+ @Override
+ public int getType() {
+ return InputConsumer.TYPE_NO_OP;
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ mTouchedHomeDuringTransition = true;
+ }
+ };
+ }
+
+ @Override
+ public void onMotionPauseChanged(boolean isPaused) {
+ if (!mInQuickSwitchMode && mDeviceState.isFullyGesturalNavMode()) {
+ updateOverviewThresholdPassed(isPaused);
+ }
+ }
+
+ private void updateOverviewThresholdPassed(boolean passed) {
+ if (passed != mOverviewThresholdPassed) {
+ mOverviewThresholdPassed = passed;
+ if (mSwipeUpOverHome) {
+ mLauncherAlpha.animateToValue(mLauncherAlpha.value, passed ? 0 : 1)
+ .setDuration(150).start();
+ }
+ performHapticFeedback();
+ }
+ }
+
+ @Override
+ public Intent getLaunchIntent() {
+ if (mInQuickSwitchMode || mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
+ return mGestureState.getOverviewIntent();
+ } else {
+ return mGestureState.getHomeIntent();
+ }
+ }
+
+ @Override
+ public void updateFinalShift() {
+ mTransformParams.setProgress(mCurrentShift.value);
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.setWindowThresholdCrossed(!mInQuickSwitchMode
+ && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
+ }
+
+ if (!mInQuickSwitchMode && !mDeviceState.isFullyGesturalNavMode()) {
+ updateOverviewThresholdPassed(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW);
+ }
+
+ if (mRecentsAnimationTargets != null) {
+ applyTransformUnchecked();
+ }
+ }
+
+ @Override
+ public void onGestureCancelled() {
+ updateDisplacement(0);
+ mGestureState.setEndTarget(LAST_TASK);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED);
+ }
+
+ @Override
+ public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
+ mEndVelocityPxPerMs.set(0, velocity.y / 1000);
+ if (mInQuickSwitchMode) {
+ // For now set it to non-null, it will be reset before starting the animation
+ mGestureState.setEndTarget(LAST_TASK);
+ } else {
+ float flingThreshold = mContext.getResources()
+ .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+ boolean isFling = Math.abs(endVelocity) > flingThreshold;
+
+ if (mDeviceState.isFullyGesturalNavMode()) {
+ if (isFling) {
+ mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
+ } else if (mOverviewThresholdPassed) {
+ mGestureState.setEndTarget(RECENTS);
+ } else {
+ mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
+ ? HOME
+ : LAST_TASK);
+ }
+ } else {
+ GestureEndTarget startState = mSwipeUpOverHome ? HOME : LAST_TASK;
+ if (isFling) {
+ mGestureState.setEndTarget(endVelocity < 0 ? RECENTS : startState);
+ } else {
+ mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
+ ? RECENTS
+ : startState);
+ }
+ }
+ }
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
+ }
+
+ @Override
+ public void onConsumerAboutToBeSwitched() {
+ if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) {
+ mGestureState.setEndTarget(NEW_TASK);
+
+ mCanceled = true;
+ mCurrentShift.cancelAnimation();
+ if (mFinishAnimation != null) {
+ mFinishAnimation.cancel();
+ }
+
+ if (mRecentsView != null) {
+ if (mFinishingRecentsAnimationForNewTaskId != -1) {
+ TaskView newRunningTaskView = mRecentsView.getTaskView(
+ mFinishingRecentsAnimationForNewTaskId);
+ int newRunningTaskId = newRunningTaskView != null
+ ? newRunningTaskView.getTask().key.id
+ : -1;
+ mRecentsView.setCurrentTask(newRunningTaskId);
+ mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
+ }
+ mRecentsView.setOnScrollChangeListener(null);
+ }
+ } else {
+ mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ }
+ }
+
+ private void onHandlerInvalidated() {
+ mActivityInitListener.unregister();
+ if (mGestureEndCallback != null) {
+ mGestureEndCallback.run();
+ }
+ if (mFinishAnimation != null) {
+ mFinishAnimation.end();
+ }
+ }
+
+ private void onHandlerInvalidatedWithRecents() {
+ mRecentsView.onGestureAnimationEnd();
+ mRecentsView.setDisallowScrollToClearAll(false);
+ mRecentsView.getClearAllButton().setVisibilityAlpha(1);
+ }
+
+ private void finishAnimationTargetSetAnimationComplete() {
+ switch (mGestureState.getEndTarget()) {
+ case HOME: {
+ if (mSwipeUpOverHome) {
+ mRecentsAnimationController.finish(false, null, false);
+ // Send a home intent to clear the task stack
+ mContext.startActivity(mGestureState.getHomeIntent());
+ } else {
+ // Notify swipe-to-home (recents animation) is finished
+ SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+ mRecentsAnimationController.finish(true, () -> {
+ if (!mTouchedHomeDuringTransition) {
+ // If the user hasn't interacted with the screen during the transition,
+ // send a home intent so launcher can go to the default home screen.
+ // (If they are trying to touch something, we don't want to interfere.)
+ mContext.startActivity(mGestureState.getHomeIntent());
+ }
+ }, true);
+ }
+ break;
+ }
+ case LAST_TASK:
+ mRecentsAnimationController.finish(false, null, false);
+ break;
+ case RECENTS: {
+ if (mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
+ mRecentsAnimationController.finish(true, null, true);
+ break;
+ }
+
+ final int runningTaskId = mGestureState.getRunningTaskId();
+ ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId);
+ mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
+ false /* screenshot */);
+
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+ ActivityOptionsCompat.setFreezeRecentTasksList(options);
+
+ Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
+ extras.putInt(EXTRA_TASK_ID, runningTaskId);
+
+ Intent intent = new Intent(mGestureState.getOverviewIntent())
+ .putExtras(extras);
+ mContext.startActivity(intent, options.toBundle());
+ mRecentsAnimationController.cleanupScreenshot();
+ break;
+ }
+ case NEW_TASK: {
+ startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
+ break;
+ }
+ }
+
+ mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ }
+
+ private void finishAnimationTargetSet() {
+ if (mInQuickSwitchMode) {
+ // Recalculate the end target, some views might have been initialized after
+ // gesture has ended.
+ if (mRecentsView == null || !hasTargets()) {
+ mGestureState.setEndTarget(LAST_TASK);
+ } else {
+ final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+ final int taskToLaunch = mRecentsView.getNextPage();
+ mGestureState.setEndTarget(
+ (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
+ ? NEW_TASK
+ : LAST_TASK);
+ }
+ }
+
+ EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget());
+ float endProgress = params.mEndProgress;
+ long duration = (long) (params.mDurationMultiplier *
+ Math.abs(endProgress - mCurrentShift.value));
+ if (mRecentsView != null) {
+ duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+ }
+ if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
+ AnimationSuccessListener endListener = new AnimationSuccessListener() {
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ finishAnimationTargetSetAnimationComplete();
+ mFinishAnimation = null;
+ }
+ };
+
+ if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
+ mRecentsAnimationController.enableInputProxy(mInputConsumer,
+ this::createNewInputProxyHandler);
+ RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
+ anim.addAnimatorListener(endListener);
+ anim.start(mContext, mEndVelocityPxPerMs);
+ mFinishAnimation = RunningWindowAnim.wrap(anim);
+ } else {
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(mLauncherAlpha.animateToValue(
+ mLauncherAlpha.value, params.mLauncherAlpha));
+ anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
+
+ anim.setDuration(duration);
+ anim.addListener(endListener);
+ anim.start();
+ mFinishAnimation = RunningWindowAnim.wrap(anim);
+ }
+
+ } else {
+ finishAnimationTargetSetAnimationComplete();
+ }
+ }
+
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ super.onRecentsAnimationStart(controller, targets);
+ mRecentsAnimationController.enableInputConsumer();
+
+ if (mRunningOverHome) {
+ mAppWindowAnimationHelper.prepareAnimation(mDp, true);
+ }
+ applyTransformUnchecked();
+
+ mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+
+ // Defer clearing the controller and the targets until after we've updated the state
+ super.onRecentsAnimationCanceled(thumbnailData);
+ }
+
+ /**
+ * Creates an animation that transforms the current app window into the home app.
+ * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+ */
+ private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
+ HomeAnimationFactory factory = new HomeAnimationFactory() {
+ @Override
+ public RectF getWindowTargetRect() {
+ PagedOrientationHandler orientationHandler = mRecentsView != null
+ ? mRecentsView.getPagedOrientationHandler()
+ : (mDp.isLandscape
+ ? new LandscapePagedViewHandler()
+ : new PortraitPagedViewHandler());
+ return HomeAnimationFactory
+ .getDefaultWindowTargetRect(orientationHandler, mDp);
+ }
+
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ AnimatorSet anim = new AnimatorSet();
+ Animator fadeInLauncher = mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1);
+ fadeInLauncher.setInterpolator(ACCEL_2);
+ anim.play(fadeInLauncher);
+ anim.setDuration(duration);
+ return AnimatorPlaybackController.wrap(anim, duration);
+ }
+ };
+ return createWindowAnimationToHome(startProgress, factory);
+ }
+
+ @Override
+ protected float getWindowAlpha(float progress) {
+ return 1 - ACCEL_1_5.getInterpolation(progress);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
similarity index 64%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index c0be9ec..d402a75 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -28,7 +28,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -37,8 +37,8 @@
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.Region;
import android.os.UserHandle;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -47,17 +47,21 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherInitListenerEx;
+import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.BackgroundBlurController.ClampedBlurProperty;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
@@ -65,17 +69,21 @@
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import java.util.function.BiPredicate;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
- * {@link ActivityControlHelper} for the in-launcher recents.
+ * {@link BaseActivityInterface} for the in-launcher recents.
*/
-public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
+public final class LauncherActivityInterface implements BaseActivityInterface<Launcher> {
private Runnable mAdjustInterpolatorsRunnable;
+ private Pair<Float, Float> mSwipeUpPullbackStartAndMaxProgress =
+ BaseActivityInterface.super.getSwipeUpPullbackStartAndMaxProgress();
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
@@ -90,42 +98,70 @@
}
@Override
- public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
- LauncherState startState = activity.getStateManager().getRestState();
- activity.getStateManager().goToState(startState, activityVisible);
+ public Pair<Float, Float> getSwipeUpPullbackStartAndMaxProgress() {
+ return mSwipeUpPullbackStartAndMaxProgress;
}
@Override
- public void onSwipeUpToRecentsComplete(Launcher activity) {
+ public void onTransitionCancelled(boolean activityVisible) {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ LauncherState startState = launcher.getStateManager().getRestState();
+ launcher.getStateManager().goToState(startState, activityVisible);
+ }
+
+ @Override
+ public void onSwipeUpToRecentsComplete() {
// Re apply state in case we did something funky during the transition.
- activity.getStateManager().reapplyState();
- DiscoveryBounce.showForOverviewIfNeeded(activity);
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ launcher.getStateManager().reapplyState();
+ DiscoveryBounce.showForOverviewIfNeeded(launcher);
}
@Override
- public void onSwipeUpToHomeComplete(Launcher activity) {
+ public void onSwipeUpToHomeComplete() {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
// Ensure recents is at the correct position for NORMAL state. For example, when we detach
// recents, we assume the first task is invisible, making translation off by one task.
- activity.getStateManager().reapplyState();
+ launcher.getStateManager().reapplyState();
+ setLauncherHideBackArrow(false);
+ }
+
+ private void setLauncherHideBackArrow(boolean hideBackArrow) {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ launcher.getRootView().setForceHideBackArrow(hideBackArrow);
}
@Override
public void onAssistantVisibilityChanged(float visibility) {
Launcher launcher = getCreatedActivity();
- if (launcher != null) {
- launcher.onAssistantVisibilityChanged(visibility);
+ if (launcher == null) {
+ return;
}
+ launcher.onAssistantVisibilityChanged(visibility);
}
@NonNull
@Override
- public HomeAnimationFactory prepareHomeUI(Launcher activity) {
- final DeviceProfile dp = activity.getDeviceProfile();
- final RecentsView recentsView = activity.getOverviewPanel();
+ public HomeAnimationFactory prepareHomeUI() {
+ Launcher launcher = getCreatedActivity();
+ final DeviceProfile dp = launcher.getDeviceProfile();
+ final RecentsView recentsView = launcher.getOverviewPanel();
final TaskView runningTaskView = recentsView.getRunningTaskView();
final View workspaceView;
if (runningTaskView != null && runningTaskView.getTask().key.getComponent() != null) {
- workspaceView = activity.getWorkspace().getFirstMatchForAppClose(
+ workspaceView = launcher.getWorkspace().getFirstMatchForAppClose(
runningTaskView.getTask().key.getComponent().getPackageName(),
UserHandle.of(runningTaskView.getTask().key.userId));
} else {
@@ -134,10 +170,10 @@
final RectF iconLocation = new RectF();
boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
FloatingIconView floatingIconView = canUseWorkspaceView
- ? FloatingIconView.getFloatingIconView(activity, workspaceView,
+ ? FloatingIconView.getFloatingIconView(launcher, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */)
: null;
-
+ setLauncherHideBackArrow(true);
return new HomeAnimationFactory() {
@Nullable
@Override
@@ -151,7 +187,8 @@
if (canUseWorkspaceView) {
return iconLocation;
} else {
- return HomeAnimationFactory.getDefaultWindowTargetRect(dp);
+ return HomeAnimationFactory
+ .getDefaultWindowTargetRect(recentsView.getPagedOrientationHandler(), dp);
}
}
@@ -160,48 +197,47 @@
public AnimatorPlaybackController createActivityAnimationToHome() {
// Return an empty APC here since we have an non-user controlled animation to home.
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
- return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
+ return launcher.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
0 /* animComponents */);
}
@Override
public void playAtomicAnimation(float velocity) {
- new StaggeredWorkspaceAnim(activity, velocity, true /* animateOverviewScrim */)
+ new StaggeredWorkspaceAnim(launcher, velocity, true /* animateOverviewScrim */)
.start();
}
};
}
@Override
- public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
+ public AnimationFactory prepareRecentsUI(boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
- final LauncherState startState = activity.getStateManager().getState();
+ BaseQuickstepLauncher launcher = getCreatedActivity();
+ final LauncherState startState = launcher.getStateManager().getState();
LauncherState resetState = startState;
if (startState.disableRestore) {
- resetState = activity.getStateManager().getRestState();
+ resetState = launcher.getStateManager().getRestState();
}
- activity.getStateManager().setRestState(resetState);
+ launcher.getStateManager().setRestState(resetState);
final LauncherState fromState = animateActivity ? BACKGROUND_APP : OVERVIEW;
- activity.getStateManager().goToState(fromState, false);
+ launcher.getStateManager().goToState(fromState, false);
// Since all apps is not visible, we can safely reset the scroll position.
// This ensures then the next swipe up to all-apps starts from scroll 0.
- activity.getAppsView().reset(false /* animate */);
+ launcher.getAppsView().reset(false /* animate */);
return new AnimationFactory() {
- private final ShelfPeekAnim mShelfAnim =
- ((QuickstepAppTransitionManagerImpl) activity.getAppTransitionManager())
- .getShelfPeekAnim();
+ private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim();
private boolean mIsAttachedToWindow;
@Override
- public void createActivityController(long transitionLength) {
- createActivityControllerInternal(activity, fromState, transitionLength, callback);
+ public void createActivityInterface(long transitionLength) {
+ createActivityInterfaceInternal(launcher, fromState, transitionLength, callback);
// Creating the activity controller animation sometimes reapplies the launcher state
// (because we set the animation as the current state animation), so we reapply the
// attached state here as well to ensure recents is shown/hidden appropriately.
- if (SysUINavigationMode.getMode(activity) == Mode.NO_BUTTON) {
+ if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) {
setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
}
}
@@ -210,12 +246,13 @@
public void adjustActivityControllerInterpolators() {
if (mAdjustInterpolatorsRunnable != null) {
mAdjustInterpolatorsRunnable.run();
+ mAdjustInterpolatorsRunnable = null;
}
}
@Override
public void onTransitionCancelled() {
- activity.getStateManager().goToState(startState, false /* animate */);
+ launcher.getStateManager().goToState(startState, false /* animate */);
}
@Override
@@ -230,8 +267,8 @@
return;
}
mIsAttachedToWindow = attached;
- LauncherRecentsView recentsView = activity.getOverviewPanel();
- Animator fadeAnim = activity.getStateManager()
+ LauncherRecentsView recentsView = launcher.getOverviewPanel();
+ Animator fadeAnim = launcher.getStateManager()
.createStateElementAnimation(
INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
@@ -243,23 +280,28 @@
float scrollOffsetX = recentsView.getScrollOffset();
float offscreenX = recentsView.getOffscreenTranslationX(currScale);
- float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
- float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
- activity.getStateManager()
+ float fromTranslation = attached ? offscreenX - scrollOffsetX : 0;
+ float toTranslation = attached ? 0 : offscreenX - scrollOffsetX;
+ launcher.getStateManager()
.cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+ PagedOrientationHandler pagedOrientationHandler =
+ recentsView.getPagedViewOrientedState().getOrientationHandler();
if (!recentsView.isShown() && animate) {
- recentsView.setTranslationX(fromTranslationX);
+ pagedOrientationHandler
+ .getPrimaryViewTranslate().set(recentsView, fromTranslation);
} else {
- fromTranslationX = recentsView.getTranslationX();
+ fromTranslation =
+ pagedOrientationHandler.getPrimaryViewTranslate().get(recentsView);
}
if (!animate) {
- recentsView.setTranslationX(toTranslationX);
+ pagedOrientationHandler
+ .getPrimaryViewTranslate().set(recentsView, toTranslation);
} else {
- activity.getStateManager().createStateElementAnimation(
+ launcher.getStateManager().createStateElementAnimation(
INDEX_RECENTS_TRANSLATE_X_ANIM,
- fromTranslationX, toTranslationX).start();
+ fromTranslation, toTranslation).start();
}
fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
@@ -271,7 +313,7 @@
};
}
- private void createActivityControllerInternal(Launcher activity, LauncherState fromState,
+ private void createActivityInterfaceInternal(Launcher activity, LauncherState fromState,
long transitionLength, Consumer<AnimatorPlaybackController> callback) {
LauncherState endState = OVERVIEW;
if (fromState == endState) {
@@ -287,8 +329,19 @@
fromState.getVerticalProgress(activity),
endState.getVerticalProgress(activity)));
}
+
+ // Animate the blur
+ BackgroundBlurController blurController = getBackgroundBlurController();
+ int fromBlurRadius = fromState.getBackgroundBlurRadius(activity);
+ int toBlurRadius = endState.getBackgroundBlurRadius(activity);
+ Animator backgroundBlur = ObjectAnimator.ofInt(blurController,
+ new ClampedBlurProperty(toBlurRadius, fromBlurRadius),
+ fromBlurRadius, toBlurRadius);
+ anim.play(backgroundBlur);
+
playScaleDownAnim(anim, activity, fromState, endState);
+
anim.setDuration(transitionLength * 2);
anim.setInterpolator(LINEAR);
AnimatorPlaybackController controller =
@@ -352,22 +405,26 @@
return newT <= 1f ? newT : newT + normalizedTranslationY * (newT - 1);
});
};
+
+ // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
+ float pullbackStartProgress = (0.75f - fromScaleAndTranslation.scale)
+ / (endScaleAndTranslation.scale - fromScaleAndTranslation.scale);
+ float pullbackMaxProgress = (0.5f - fromScaleAndTranslation.scale)
+ / (endScaleAndTranslation.scale - fromScaleAndTranslation.scale);
+ mSwipeUpPullbackStartAndMaxProgress = new Pair<>(
+ pullbackStartProgress, pullbackMaxProgress);
}
@Override
- public ActivityInitListener createActivityInitListener(
- BiPredicate<Launcher, Boolean> onInitListener) {
- return new LauncherInitListenerEx(onInitListener);
+ public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
+ return new LauncherInitListener((activity, alreadyOnHome) ->
+ onInitListener.test(alreadyOnHome));
}
@Nullable
@Override
- public Launcher getCreatedActivity() {
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app == null) {
- return null;
- }
- return (Launcher) app.getModel().getCallback();
+ public BaseQuickstepLauncher getCreatedActivity() {
+ return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
}
@Nullable
@@ -403,8 +460,8 @@
}
@Override
- public boolean deferStartingActivity(Region activeNavBarRegion, MotionEvent ev) {
- return activeNavBarRegion.contains((int) ev.getX(), (int) ev.getY());
+ public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ return deviceState.isInDeferredGestureRegion(ev);
}
@Override
@@ -432,12 +489,80 @@
}
@Override
- public void onLaunchTaskFailed(Launcher launcher) {
+ public void onLaunchTaskFailed() {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
launcher.getStateManager().goToState(OVERVIEW);
}
@Override
- public void onLaunchTaskSuccess(Launcher launcher) {
+ public void onLaunchTaskSuccess() {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
launcher.getStateManager().moveToRestState();
}
+
+ @Override
+ public void closeOverlay() {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ LauncherOverlayManager om = launcher.getOverlayManager();
+ if (!launcher.isStarted() || launcher.isForceInvisible()) {
+ om.hideOverlay(false /* animate */);
+ } else {
+ om.hideOverlay(150);
+ }
+ }
+
+ @Override
+ public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
+ Runnable onFinishRunnable) {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ RecentsView recentsView = launcher.getOverviewPanel();
+ if (recentsView == null) {
+ if (onFinishRunnable != null) {
+ onFinishRunnable.run();
+ }
+ return;
+ }
+ recentsView.switchToScreenshot(thumbnailData, onFinishRunnable);
+ }
+
+ @Override
+ public void setOnDeferredActivityLaunchCallback(Runnable r) {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ launcher.setOnDeferredActivityLaunchCallback(r);
+ }
+
+ @Override
+ public void updateOverviewPredictionState() {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
+ PredictionUiStateManager.Client.OVERVIEW);
+ }
+
+ @Nullable
+ @Override
+ public BackgroundBlurController getBackgroundBlurController() {
+ BaseQuickstepLauncher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return null;
+ }
+ return launcher.getBackgroundBlurController();
+ }
}
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
similarity index 68%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 2fa4feb..3328abc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -21,31 +21,27 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
-import static com.android.launcher3.util.RaceConditionTracker.ENTER;
-import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
+import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build;
@@ -71,18 +67,16 @@
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;
-import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.BaseActivityInterface.AnimationFactory;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
+import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -90,13 +84,14 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.WindowCallbacksCompat;
+/**
+ * Handles the navigation gestures when Launcher is the default home activity.
+ */
@TargetApi(Build.VERSION_CODES.O)
-public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
- extends BaseSwipeUpHandler<T, RecentsView>
- implements OnApplyWindowInsetsListener {
- private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
+public class LauncherSwipeHandler<T extends BaseDraggingActivity>
+ extends BaseSwipeUpHandler<T, RecentsView> implements OnApplyWindowInsetsListener {
+ private static final String TAG = LauncherSwipeHandler.class.getSimpleName();
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@@ -148,42 +143,6 @@
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
- public enum GestureEndTarget {
- HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false,
- ContainerType.WORKSPACE, false),
-
- RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
- | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true),
-
- NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true,
- ContainerType.APP, true),
-
- LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false);
-
- GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
- int containerType, boolean recentsAttachedToAppWindow) {
- this.endShift = endShift;
- this.endState = endState;
- this.isLauncher = isLauncher;
- this.canBeContinued = canBeContinued;
- this.containerType = containerType;
- this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
- }
-
- /** 0 is app, 1 is overview */
- public final float endShift;
- /** The state to apply when we reach this final target */
- public final int endState;
- /** Whether the target is in the launcher activity */
- public final boolean isLauncher;
- /** Whether the user can start a new gesture while this one is finishing */
- public final boolean canBeContinued;
- /** Used to log where the user ended up after the gesture ends */
- public final int containerType;
- /** Whether RecentsView should be attached to the window as we animate to this target */
- public final boolean recentsAttachedToAppWindow;
- }
-
public static final long MAX_SWIPE_DURATION = 350;
public static final long MIN_SWIPE_DURATION = 80;
public static final long MIN_OVERSHOOT_DURATION = 120;
@@ -200,7 +159,8 @@
*/
private static final int LOG_NO_OP_PAGE_INDEX = -1;
- private GestureEndTarget mGestureEndTarget;
+ private final TaskAnimationManager mTaskAnimationManager;
+
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
private boolean mIsShelfPeeking;
@@ -214,8 +174,6 @@
private boolean mHasLauncherTransitionControllerStarted;
private AnimationFactory mAnimationFactory = (t) -> { };
- private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
- private boolean mLiveTileOverlayAttached = false;
private boolean mWasLauncherAlreadyVisible;
@@ -229,11 +187,14 @@
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
- public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
- long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
- boolean continuingLastGesture,
- InputConsumerController inputConsumer, RecentsModel recentsModel) {
- super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+ private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
+
+ public LauncherSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager, GestureState gestureState,
+ long touchTimeMs, boolean continuingLastGesture,
+ InputConsumerController inputConsumer) {
+ super(context, deviceState, gestureState, inputConsumer);
+ mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
initStateCallbacks();
@@ -242,65 +203,66 @@
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
this::onLauncherPresentAndGestureStarted);
- mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
this::initializeLauncherAnimationController);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
- mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
this::sendRemoteAnimationsToAnimationFactory);
- mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
+ mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
- mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
+ mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
this::startNewTask);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
this::switchToScreenshot);
- mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+ mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_RECENTS,
this::finishCurrentTransitionToRecents);
- mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+ mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_HOME,
this::finishCurrentTransitionToHome);
- mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
+ mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
this::reset);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
| STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
| STATE_GESTURE_STARTED,
this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
- mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+ mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet);
+
+ mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
- mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
+ mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
- mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
- mRecentsAnimationWrapper::enableInputConsumer);
-
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
+ mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
| STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
(b) -> mRecentsView.setRunningTaskHidden(!b));
}
}
@Override
- protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+ protected boolean onActivityInit(Boolean alreadyOnHome) {
+ super.onActivityInit(alreadyOnHome);
+ final T activity = mActivityInterface.getCreatedActivity();
if (mActivity == activity) {
return true;
}
@@ -326,21 +288,29 @@
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
if (alreadyOnHome) {
- onLauncherStart(activity);
+ onLauncherStart();
} else {
- activity.setOnStartCallback(this::onLauncherStart);
+ activity.runOnceOnStart(this::onLauncherStart);
}
setupRecentsViewUi();
+ mActivityInterface.getBackgroundBlurController().setSurfaceToLauncher(mRecentsView);
+
+ if (mDeviceState.getNavMode() == TWO_BUTTONS) {
+ // If the device is in two button mode, swiping up will show overview with predictions
+ // so we need to kick off switching to the overview predictions as soon as possible
+ mActivityInterface.updateOverviewPredictionState();
+ }
return true;
}
@Override
protected boolean moveWindowWithRecentsScroll() {
- return mGestureEndTarget != HOME;
+ return mGestureState.getEndTarget() != HOME;
}
- private void onLauncherStart(final T activity) {
+ private void onLauncherStart() {
+ final T activity = mActivityInterface.getCreatedActivity();
if (mActivity != activity) {
return;
}
@@ -350,9 +320,9 @@
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
- if (mGestureEndTarget != HOME) {
+ if (mGestureState.getEndTarget() != HOME) {
Runnable initAnimFactory = () -> {
- mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+ mAnimationFactory = mActivityInterface.prepareRecentsUI(
mWasLauncherAlreadyVisible, true,
this::onAnimatorPlaybackControllerCreated);
maybeUpdateRecentsAttachedState(false /* animate */);
@@ -361,7 +331,7 @@
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
// now, it might get overridden by moveToRestState() in onStop(). To avoid this,
// wait until the next gesture (and possibly launcher) starts.
- mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
} else {
initAnimFactory.run();
}
@@ -372,13 +342,19 @@
if (mWasLauncherAlreadyVisible) {
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
} else {
- TraceHelper.beginSection("WTS-init");
+ Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
View dragLayer = activity.getDragLayer();
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
+ boolean mHandled = false;
@Override
public void onDraw() {
- TraceHelper.endSection("WTS-init", "Launcher frame is drawn");
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ TraceHelper.INSTANCE.endSection(traceToken);
dragLayer.post(() ->
dragLayer.getViewTreeObserver().removeOnDrawListener(this));
if (activity != mActivity) {
@@ -399,15 +375,31 @@
// that time by a previous window transition.
setupRecentsViewUi();
+ // For the duration of the gesture, in cases where an activity is launched while the
+ // activity is not yet resumed, finish the animation to ensure we get resumed
+ mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
+ mOnDeferredActivityLaunch);
+
notifyGestureStartedAsync();
}
+ private void onDeferredActivityLaunch() {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mActivityInterface.switchRunningTaskViewToScreenshot(
+ null, () -> {
+ mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+ });
+ } else {
+ mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+ }
+ }
+
private void setupRecentsViewUi() {
if (mContinuingLastGesture) {
updateSysUiFlags(mCurrentShift.value);
return;
}
- mRecentsView.onGestureAnimationStart(mRunningTaskId);
+ mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
}
private void launcherFrameDrawn() {
@@ -415,15 +407,20 @@
}
private void sendRemoteAnimationsToAnimationFactory() {
- mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet);
+ mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationTargets);
}
private void initializeLauncherAnimationController() {
buildAnimationController();
+ Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
+ TraceHelper.FLAG_IGNORE_BINDERS);
+ // Only used in debug builds
if (LatencyTrackerCompat.isEnabled(mContext)) {
- LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+ LatencyTrackerCompat.logToggleRecents(
+ (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
}
+ TraceHelper.INSTANCE.endSection(traceToken);
// This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
// high-res thumbnail loader here once we are sure that we will end up in an overview state
@@ -431,16 +428,15 @@
.getHighResLoadingState().setVisible(true);
}
- private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
- float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 +
- mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
- float interpolation = Math.min(1, offsetX / distanceToReachEdge);
- return TaskView.getCurveScaleForInterpolation(interpolation);
- }
-
@Override
public void onMotionPauseChanged(boolean isPaused) {
setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
+
+ if (mDeviceState.isFullyGesturalNavMode() && isPaused) {
+ // In fully gestural nav mode, switch to overview predictions once the user has paused
+ // (this is a no-op if the predictions are already in that state)
+ mActivityInterface.updateOverviewPredictionState();
+ }
}
public void maybeUpdateRecentsAttachedState() {
@@ -455,16 +451,15 @@
* Note this method has no effect unless the navigation mode is NO_BUTTON.
*/
private void maybeUpdateRecentsAttachedState(boolean animate) {
- if (mMode != Mode.NO_BUTTON || mRecentsView == null) {
+ if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
return;
}
- RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null
- ? null
- : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
+ RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
+ ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ : null;
final boolean recentsAttachedToAppWindow;
- int runningTaskIndex = mRecentsView.getRunningTaskIndex();
- if (mGestureEndTarget != null) {
- recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
+ if (mGestureState.getEndTarget() != null) {
+ recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
} else if (mContinuingLastGesture
&& mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
recentsAttachedToAppWindow = true;
@@ -511,13 +506,14 @@
}
private void buildAnimationController() {
- if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) {
- // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it
- // has its own animation) or if we're already animating the current controller.
+ if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) {
+ // We don't want a new mLauncherTransitionController if
+ // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already
+ // animating the current controller.
return;
}
initTransitionEndpoints(mActivity.getDeviceProfile());
- mAnimationFactory.createActivityController(mTransitionDragLength);
+ mAnimationFactory.createActivityInterface(mTransitionDragLength);
}
@Override
@@ -537,29 +533,28 @@
@Override
public Intent getLaunchIntent() {
- return mOverviewComponentObserver.getOverviewIntent();
+ return mGestureState.getOverviewIntent();
}
@Override
public void updateFinalShift() {
-
- SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
- if (controller != null) {
+ if (mRecentsAnimationTargets != null) {
applyTransformUnchecked();
updateSysUiFlags(mCurrentShift.value);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- if (mRecentsAnimationWrapper.getController() != null) {
- mLiveTileOverlay.update(mClipAnimationHelper.getCurrentRectWithInsets(),
- mClipAnimationHelper.getCurrentCornerRadius());
+ if (mRecentsAnimationTargets != null) {
+ LiveTileOverlay.INSTANCE.update(
+ mAppWindowAnimationHelper.getCurrentRectWithInsets(),
+ mAppWindowAnimationHelper.getCurrentCornerRadius());
}
}
final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
if (passed != mPassedOverviewThreshold) {
mPassedOverviewThreshold = passed;
- if (mMode != Mode.NO_BUTTON) {
+ if (!mDeviceState.isFullyGesturalNavMode()) {
performHapticFeedback();
}
}
@@ -572,7 +567,7 @@
}
private void updateLauncherTransitionProgress() {
- if (mGestureEndTarget == HOME) {
+ if (mGestureState.getEndTarget() == HOME) {
return;
}
// Normalize the progress to 0 to 1, as the animation controller will clamp it to that
@@ -591,34 +586,43 @@
: centermostTask.getThumbnail().getSysUiStatusNavFlags();
boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
// We will handle the sysui flags based on the centermost task view.
- mRecentsAnimationWrapper.setWindowThresholdCrossed(centermostTaskFlags != 0
- || useHomeScreenFlags);
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.setWindowThresholdCrossed(centermostTaskFlags != 0
+ || useHomeScreenFlags);
+ }
int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
}
}
@Override
- public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- super.onRecentsAnimationStart(targetSet);
- TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
- setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+ super.onRecentsAnimationStart(controller, targets);
+
+ // Only add the callback to enable the input consumer after we actually have the controller
+ mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
+ mRecentsAnimationController::enableInputConsumer);
+ mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mPassedOverviewThreshold = false;
}
@Override
- public void onRecentsAnimationCanceled() {
- mRecentsAnimationWrapper.setController(null);
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
mActivityInitListener.unregister();
- setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
- TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+
+ // Defer clearing the controller and the targets until after we've updated the state
+ super.onRecentsAnimationCanceled(thumbnailData);
}
@Override
public void onGestureStarted() {
notifyGestureStartedAsync();
- setStateOnUiThread(STATE_GESTURE_STARTED);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@@ -641,7 +645,7 @@
@Override
public void onGestureCancelled() {
updateDisplacement(0);
- setStateOnUiThread(STATE_GESTURE_COMPLETED);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = Touch.SWIPE_NOOP;
handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
}
@@ -656,7 +660,7 @@
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
- setStateOnUiThread(STATE_GESTURE_COMPLETED);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
@@ -671,16 +675,16 @@
@Override
protected InputConsumer createNewInputProxyHandler() {
- endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */);
+ endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// Hide the task view, if not already hidden
- setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+ setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha);
}
- BaseDraggingActivity activity = mActivityControlHelper.getCreatedActivity();
- return activity == null
- ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
+ BaseDraggingActivity activity = mActivityInterface.getCreatedActivity();
+ return activity == null ? InputConsumer.NO_OP
+ : new OverviewInputConsumer(mGestureState, activity, null, true);
}
private void endRunningWindowAnim(boolean cancel) {
@@ -693,12 +697,32 @@
}
}
+ private void onEndTargetSet() {
+ switch (mGestureState.getEndTarget()) {
+ case HOME:
+ mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
+ // Notify swipe-to-home (recents animation) is finished
+ SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+ break;
+ case RECENTS:
+ mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
+ | STATE_SCREENSHOT_VIEW_SHOWN);
+ break;
+ case NEW_TASK:
+ mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
+ break;
+ case LAST_TASK:
+ mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ break;
+ }
+ }
+
private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
boolean isCancel) {
final GestureEndTarget endTarget;
final boolean goingToNewTask;
if (mRecentsView != null) {
- if (!mRecentsAnimationWrapper.hasTargets()) {
+ if (!hasTargets()) {
// If there are no running tasks, then we can assume that this is a continuation of
// the last gesture, but after the recents animation has finished
goingToNewTask = true;
@@ -714,7 +738,7 @@
if (!isFling) {
if (isCancel) {
endTarget = LAST_TASK;
- } else if (mMode == Mode.NO_BUTTON) {
+ } else if (mDeviceState.isFullyGesturalNavMode()) {
if (mIsShelfPeeking) {
endTarget = RECENTS;
} else if (goingToNewTask) {
@@ -735,9 +759,9 @@
boolean willGoToNewTaskOnSwipeUp =
goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
- if (mMode == Mode.NO_BUTTON && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
+ if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
endTarget = HOME;
- } else if (mMode == Mode.NO_BUTTON && isSwipeUp && !mIsShelfPeeking) {
+ } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
// If swiping at a diagonal, base end target on the faster velocity.
endTarget = NEW_TASK;
} else if (isSwipeUp) {
@@ -748,9 +772,17 @@
}
}
- int stateFlags = OverviewInteractionState.INSTANCE.get(mActivity).getSystemUiStateFlags();
- if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0
- && (endTarget == RECENTS || endTarget == LAST_TASK)) {
+ if (endTarget == NEW_TASK) {
+ SystemUiProxy.INSTANCE.get(mContext).onQuickSwitchToNewTask();
+ }
+
+ if (endTarget == RECENTS || endTarget == HOME) {
+ // Since we're now done quickStepping, we want to only listen for touch events
+ // for the main orientation's nav bar, instead of multiple
+ mDeviceState.enableMultipleRegions(false);
+ }
+
+ if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
return LAST_TASK;
}
return endTarget;
@@ -764,7 +796,7 @@
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
isFling, isCancel);
- float endShift = endTarget.endShift;
+ float endShift = endTarget.isLauncher ? 1 : 0;
final float startShift;
Interpolator interpolator = DEACCEL;
if (!isFling) {
@@ -779,7 +811,7 @@
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
- if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) {
+ if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
startShift, endShift, endShift, endVelocity / 1000,
mTransitionDragLength, mContext);
@@ -803,15 +835,16 @@
}
}
- if (endTarget.isLauncher) {
- mRecentsAnimationWrapper.enableInputProxy();
+ if (endTarget.isLauncher && mRecentsAnimationController != null) {
+ mRecentsAnimationController.enableInputProxy(mInputConsumer,
+ this::createNewInputProxyHandler);
}
if (endTarget == HOME) {
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
} else if (endTarget == RECENTS) {
- mLiveTileOverlay.startIconAnimation();
+ LiveTileOverlay.INSTANCE.startIconAnimation();
if (mRecentsView != null) {
int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
if (mRecentsView.getNextPage() != nearestPage) {
@@ -824,7 +857,7 @@
}
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
- if (mMode == Mode.NO_BUTTON) {
+ if (mDeviceState.isFullyGesturalNavMode()) {
setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
}
} else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
@@ -859,27 +892,28 @@
@UiThread
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
GestureEndTarget target, PointF velocityPxPerMs) {
- mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
+ runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
interpolator, target, velocityPxPerMs));
}
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
- mGestureEndTarget = target;
+ // Set the state, but don't notify until the animation completes
+ mGestureState.setEndTarget(target, false /* isAtomic */);
maybeUpdateRecentsAttachedState();
- if (mGestureEndTarget == HOME) {
+ if (mGestureState.getEndTarget() == HOME) {
HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
- homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
+ homeAnimFactory = mActivityInterface.prepareHomeUI();
} else {
homeAnimFactory = new HomeAnimationFactory() {
@NonNull
@Override
public RectF getWindowTargetRect() {
- RectF fallbackTarget = new RectF(mClipAnimationHelper.getTargetRect());
+ RectF fallbackTarget = new RectF(mAppWindowAnimationHelper.getTargetRect());
Utilities.scaleRectFAboutCenter(fallbackTarget, 0.25f);
return fallbackTarget;
}
@@ -890,17 +924,25 @@
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
- mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+ mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- setStateOnUiThread(target.endState);
+ if (mRecentsAnimationController == null) {
+ // If the recents animation is interrupted, we still end the running
+ // animation (not canceled) so this is still called. In that case, we can
+ // skip doing any future work here for the current gesture.
+ return;
+ }
+ // Finalize the state and notify of the change
+ mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
- windowAnim.start(velocityPxPerMs);
+ getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
+ windowAnim.start(mContext, velocityPxPerMs);
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
mLauncherTransitionController = null;
@@ -917,15 +959,20 @@
windowAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
+ if (mRecentsAnimationController == null) {
+ // If the recents animation is interrupted, we still end the running
+ // animation (not canceled) so this is still called. In that case, we can
+ // skip doing any future work here for the current gesture.
+ return;
+ }
if (target == NEW_TASK && mRecentsView != null
&& mRecentsView.getNextPage() == mRecentsView.getRunningTaskIndex()) {
// We are about to launch the current running task, so use LAST_TASK state
// instead of NEW_TASK. This could happen, for example, if our scroll is
// aborted after we determined the target to be NEW_TASK.
- setStateOnUiThread(LAST_TASK.endState);
- } else {
- setStateOnUiThread(target.endState);
+ mGestureState.setEndTarget(LAST_TASK);
}
+ mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
windowAnim.start();
@@ -933,7 +980,7 @@
}
// Always play the entire launcher animation when going home, since it is separate from
// the animation that has been controlled thus far.
- if (mGestureEndTarget == HOME) {
+ if (mGestureState.getEndTarget() == HOME) {
start = 0;
}
@@ -952,8 +999,8 @@
}
mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
- if (QUICKSTEP_SPRINGS.get()) {
- mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
+ if (UNSTABLE_SPRINGS.get()) {
+ mLauncherTransitionController.dispatchOnStart();
}
mLauncherTransitionController.getAnimationPlayer().start();
mHasLauncherTransitionControllerStarted = true;
@@ -969,7 +1016,9 @@
HomeAnimationFactory homeAnimationFactory) {
RectFSpringAnim anim =
super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
- anim.addOnUpdateListener((r, p) -> updateSysUiFlags(Math.max(p, mCurrentShift.value)));
+ anim.addOnUpdateListener((r, p) -> {
+ updateSysUiFlags(Math.max(p, mCurrentShift.value));
+ });
anim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
@@ -985,36 +1034,50 @@
}
// Make sure recents is in its final state
maybeUpdateRecentsAttachedState(false);
- mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
+ mActivityInterface.onSwipeUpToHomeComplete();
}
});
return anim;
}
@Override
- public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
- if (mGestureEndTarget != null) {
- sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued;
- sharedState.goingToLauncher = mGestureEndTarget.isLauncher;
+ public void onConsumerAboutToBeSwitched() {
+ if (mActivity != null) {
+ // In the off chance that the gesture ends before Launcher is started, we should clear
+ // the callback here so that it doesn't update with the wrong state
+ mActivity.clearRunOnceOnStartCallback();
+ resetLauncherListenersAndOverlays();
}
-
- if (sharedState.canGestureBeContinued) {
- cancelCurrentAnimation(sharedState);
+ if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
+ cancelCurrentAnimation();
} else {
reset();
}
}
+ public boolean isCanceled() {
+ return mCanceled;
+ }
+
@UiThread
private void resumeLastTask() {
- mRecentsAnimationWrapper.finish(false /* toRecents */, null);
- TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
+ mRecentsAnimationController.finish(false /* toRecents */, null);
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
doLogGesture(LAST_TASK);
reset();
}
@UiThread
private void startNewTask() {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
+ } else {
+ startNewTaskInternal();
+ }
+ }
+
+ @UiThread
+ private void startNewTaskInternal() {
startNewTask(STATE_HANDLER_INVALIDATED, success -> {
if (!success) {
// We couldn't launch the task, so take user to overview so they can
@@ -1027,14 +1090,14 @@
}
private void reset() {
- setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
/**
* Cancels any running animation so that the active target can be overriden by a new swipe
* handle (in case of quick switch).
*/
- private void cancelCurrentAnimation(SwipeSharedState sharedState) {
+ private void cancelCurrentAnimation() {
mCanceled = true;
mCurrentShift.cancelAnimation();
if (mLauncherTransitionController != null && mLauncherTransitionController
@@ -1052,7 +1115,7 @@
? newRunningTaskView.getTask().key.id
: -1;
mRecentsView.setCurrentTask(newRunningTaskId);
- sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+ mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
}
}
@@ -1071,9 +1134,7 @@
endLauncherTransitionController();
mRecentsView.onGestureAnimationEnd();
-
- mActivity.getRootView().setOnApplyWindowInsetsListener(null);
- removeLiveTileOverlay();
+ resetLauncherListenersAndOverlays();
}
private void endLauncherTransitionController() {
@@ -1084,139 +1145,129 @@
}
}
+ private void resetLauncherListenersAndOverlays() {
+ // Reset the callback for deferred activity launches
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mActivityInterface.setOnDeferredActivityLaunchCallback(null);
+ }
+ mActivity.getRootView().setOnApplyWindowInsetsListener(null);
+ removeLiveTileOverlay();
+ }
+
private void notifyTransitionCancelled() {
mAnimationFactory.onTransitionCancelled();
}
private void resetStateForAnimationCancel() {
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
- mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
+ mActivityInterface.onTransitionCancelled(wasVisible);
// Leave the pending invisible flag, as it may be used by wallpaper open animation.
mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
}
private void switchToScreenshot() {
+ final int runningTaskId = mGestureState.getRunningTaskId();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- } else if (!mRecentsAnimationWrapper.hasTargets()) {
- // If there are no targets, then we don't need to capture anything
- setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- } else {
- boolean finishTransitionPosted = false;
- SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
- if (controller != null) {
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.getController().setWillFinishToHome(true);
// Update the screenshot of the task
if (mTaskSnapshot == null) {
- mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
+ mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
+ }
+ mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */);
+ }
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else if (!hasTargets()) {
+ // If there are no targets, then we don't need to capture anything
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else {
+ boolean finishTransitionPosted = false;
+ if (mRecentsAnimationController != null) {
+ // Update the screenshot of the task
+ if (mTaskSnapshot == null) {
+ mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
}
final TaskView taskView;
- if (mGestureEndTarget == HOME) {
+ if (mGestureState.getEndTarget() == HOME) {
// Capture the screenshot before finishing the transition to home to ensure it's
// taken in the correct orientation, but no need to update the thumbnail.
taskView = null;
} else {
- taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
+ taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
}
if (taskView != null && !mCanceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
- finishTransitionPosted = new WindowCallbacksCompat(taskView) {
-
- // The number of frames to defer until we actually finish the animation
- private int mDeferFrameCount = 2;
-
- @Override
- public void onPostDraw(Canvas canvas) {
- // If we were cancelled after this was attached, do not update
- // the state.
- if (mCanceled) {
- detach();
- return;
- }
-
- if (mDeferFrameCount > 0) {
- mDeferFrameCount--;
- // Workaround, detach and reattach to invalidate the root node for
- // another draw
- detach();
- attach();
- taskView.invalidate();
- return;
- }
-
- setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- detach();
- }
- }.attach();
+ finishTransitionPosted = ViewUtils.postDraw(taskView,
+ () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
+ this::isCanceled);
}
}
if (!finishTransitionPosted) {
// If we haven't posted a draw callback, set the state immediately.
- RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, ENTER);
- setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, EXIT);
+ Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
+ TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ TraceHelper.INSTANCE.endSection(traceToken);
}
}
}
private void finishCurrentTransitionToRecents() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
- } else if (!mRecentsAnimationWrapper.hasTargets()) {
- // If there are no targets, then there is nothing to finish
- setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else if (!hasTargets() || mRecentsAnimationController == null) {
+ // If there are no targets or the animation not started, then there is nothing to finish
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
- synchronized (mRecentsAnimationWrapper) {
- mRecentsAnimationWrapper.finish(true /* toRecents */,
- () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
- }
+ mRecentsAnimationController.finish(true /* toRecents */,
+ () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
- TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
}
private void finishCurrentTransitionToHome() {
- synchronized (mRecentsAnimationWrapper) {
- mRecentsAnimationWrapper.finish(true /* toRecents */,
- () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
+ if (!hasTargets() || mRecentsAnimationController == null) {
+ // If there are no targets or the animation not started, then there is nothing to finish
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else {
+ mRecentsAnimationController.finish(true /* toRecents */,
+ () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
true /* sendUserLeaveHint */);
}
- TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
doLogGesture(HOME);
}
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
- mActivityControlHelper.onSwipeUpToRecentsComplete(mActivity);
- mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
- true /* screenshot */);
+ mActivityInterface.onSwipeUpToRecentsComplete();
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
+ true /* screenshot */);
+ }
mRecentsView.onSwipeUpAnimationSuccess();
- RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
-
+ SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
doLogGesture(RECENTS);
reset();
}
private void setTargetAlphaProvider(TargetAlphaProvider provider) {
- mClipAnimationHelper.setTaskAlphaCallback(provider);
+ mAppWindowAnimationHelper.setTaskAlphaCallback(provider);
updateFinalShift();
}
- private synchronized void addLiveTileOverlay() {
- if (!mLiveTileOverlayAttached) {
- mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
- mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
- mLiveTileOverlayAttached = true;
+ private void addLiveTileOverlay() {
+ if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
+ mRecentsView.setLiveTileOverlayAttached(true);
}
}
- private synchronized void removeLiveTileOverlay() {
- if (mLiveTileOverlayAttached) {
- mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
- mRecentsView.setLiveTileOverlay(null);
- mLiveTileOverlayAttached = false;
- }
+ private void removeLiveTileOverlay() {
+ LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
+ mRecentsView.setLiveTileOverlayAttached(false);
}
public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
deleted file mode 100644
index 357c9fc..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.launcher3.config.FeatureFlags;
-
-import java.util.StringJoiner;
-import java.util.function.Consumer;
-
-/**
- * Utility class to help manage multiple callbacks based on different states.
- */
-public class MultiStateCallback {
-
- private static final String TAG = "MultiStateCallback";
- public static final boolean DEBUG_STATES = false;
-
- private final SparseArray<Runnable> mCallbacks = new SparseArray<>();
- private final SparseArray<Consumer<Boolean>> mStateChangeHandlers = new SparseArray<>();
-
- private final String[] mStateNames;
-
- public MultiStateCallback(String[] stateNames) {
- mStateNames = DEBUG_STATES ? stateNames : null;
- }
-
- private int mState = 0;
-
- /**
- * Adds the provided state flags to the global state and executes any callbacks as a result.
- */
- public void setState(int stateFlag) {
- if (DEBUG_STATES) {
- Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
- + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
- }
-
- int oldState = mState;
- mState = mState | stateFlag;
-
- int count = mCallbacks.size();
- for (int i = 0; i < count; i++) {
- int state = mCallbacks.keyAt(i);
-
- if ((mState & state) == state) {
- Runnable callback = mCallbacks.valueAt(i);
- if (callback != null) {
- // Set the callback to null, so that it does not run again.
- mCallbacks.setValueAt(i, null);
- callback.run();
- }
- }
- }
- notifyStateChangeHandlers(oldState);
- }
-
- /**
- * Adds the provided state flags to the global state and executes any change handlers
- * as a result.
- */
- public void clearState(int stateFlag) {
- if (DEBUG_STATES) {
- Log.d(TAG, "[" + System.identityHashCode(this) + "] Removing "
- + convertToFlagNames(stateFlag) + " from " + convertToFlagNames(mState));
- }
-
- int oldState = mState;
- mState = mState & ~stateFlag;
- notifyStateChangeHandlers(oldState);
- }
-
- private void notifyStateChangeHandlers(int oldState) {
- int count = mStateChangeHandlers.size();
- for (int i = 0; i < count; i++) {
- int state = mStateChangeHandlers.keyAt(i);
- boolean wasOn = (state & oldState) == state;
- boolean isOn = (state & mState) == state;
-
- if (wasOn != isOn) {
- mStateChangeHandlers.valueAt(i).accept(isOn);
- }
- }
- }
-
- /**
- * Sets the callbacks to be run when the provided states are enabled.
- * The callback is only run once.
- */
- public void addCallback(int stateMask, Runnable callback) {
- if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) {
- throw new IllegalStateException("Multiple callbacks on same state");
- }
- mCallbacks.put(stateMask, callback);
- }
-
- /**
- * Sets the handler to be called when the provided states are enabled or disabled.
- */
- public void addChangeHandler(int stateMask, Consumer<Boolean> handler) {
- mStateChangeHandlers.put(stateMask, handler);
- }
-
- public int getState() {
- return mState;
- }
-
- public boolean hasStates(int stateMask) {
- return (mState & stateMask) == stateMask;
- }
-
- private String convertToFlagNames(int flags) {
- StringJoiner joiner = new StringJoiner(", ", "[", " (" + flags + ")]");
- for (int i = 0; i < mStateNames.length; i++) {
- if ((flags & (1 << i)) != 0) {
- joiner.add(mStateNames[i]);
- }
- }
- return joiner.toString();
- }
-
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index a8d402e..fc50660 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -25,14 +25,14 @@
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
-import android.util.Log;
import android.view.ViewConfiguration;
+import androidx.annotation.BinderThread;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -46,37 +46,47 @@
public class OverviewCommandHelper {
private final Context mContext;
- private final ActivityManagerWrapper mAM;
+ private final RecentsAnimationDeviceState mDeviceState;
private final RecentsModel mRecentsModel;
private final OverviewComponentObserver mOverviewComponentObserver;
private long mLastToggleTime;
- public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
+ public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
+ OverviewComponentObserver observer) {
mContext = context;
- mAM = ActivityManagerWrapper.getInstance();
+ mDeviceState = deviceState;
mRecentsModel = RecentsModel.INSTANCE.get(mContext);
mOverviewComponentObserver = observer;
}
+ @BinderThread
public void onOverviewToggle() {
// If currently screen pinning, do not enter overview
- if (mAM.isScreenPinningActive()) {
+ if (mDeviceState.isScreenPinningActive()) {
return;
}
- mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
}
+ @BinderThread
public void onOverviewShown(boolean triggeredFromAltTab) {
+ if (triggeredFromAltTab) {
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ }
MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
}
+ @BinderThread
public void onOverviewHidden() {
MAIN_EXECUTOR.execute(new HideRecentsCommand());
}
+ @BinderThread
public void onTip(int actionType, int viewType) {
MAIN_EXECUTOR.execute(() ->
UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
@@ -93,14 +103,14 @@
@Override
protected boolean handleCommand(long elapsedTime) {
// TODO: Go to the next page if started from alt-tab.
- return mHelper.getVisibleRecentsView() != null;
+ return mActivityInterface.getVisibleRecentsView() != null;
}
@Override
protected void onTransitionComplete() {
// TODO(b/138729100) This doesn't execute first time launcher is run
if (mTriggeredFromAltTab) {
- RecentsView rv = (RecentsView) mHelper.getVisibleRecentsView();
+ RecentsView rv = (RecentsView) mActivityInterface.getVisibleRecentsView();
if (rv == null) {
return;
}
@@ -125,7 +135,7 @@
@Override
protected boolean handleCommand(long elapsedTime) {
- RecentsView recents = (RecentsView) mHelper.getVisibleRecentsView();
+ RecentsView recents = (RecentsView) mActivityInterface.getVisibleRecentsView();
if (recents == null) {
return false;
}
@@ -141,7 +151,7 @@
private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
- protected final ActivityControlHelper<T> mHelper;
+ protected final BaseActivityInterface<T> mActivityInterface;
private final long mCreateTime;
private final AppToOverviewAnimationProvider<T> mAnimationProvider;
@@ -150,10 +160,10 @@
private ActivityInitListener mListener;
public RecentsActivityCommand() {
- mHelper = mOverviewComponentObserver.getActivityControlHelper();
+ mActivityInterface = mOverviewComponentObserver.getActivityInterface();
mCreateTime = SystemClock.elapsedRealtime();
- mAnimationProvider =
- new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId());
+ mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
+ RecentsModel.getRunningTaskId());
// Preload the plan
mRecentsModel.getTasks(null);
@@ -169,13 +179,13 @@
return;
}
- if (mHelper.switchToRecentsIfVisible(this::onTransitionComplete)) {
+ if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
// If successfully switched, then return
return;
}
// Otherwise, start overview.
- mListener = mHelper.createActivityInitListener(this::onActivityReady);
+ mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(),
mAnimationProvider.getRecentsLaunchDuration());
@@ -185,7 +195,7 @@
// TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
// the menu activity which takes window focus, preventing the right condition from
// being run below
- RecentsView recents = mHelper.getVisibleRecentsView();
+ RecentsView recents = mActivityInterface.getVisibleRecentsView();
if (recents != null) {
// Launch the next task
recents.showNextTask();
@@ -198,18 +208,24 @@
return false;
}
- private boolean onActivityReady(T activity, Boolean wasVisible) {
+ private boolean onActivityReady(Boolean wasVisible) {
+ final T activity = mActivityInterface.getCreatedActivity();
if (!mUserEventLogged) {
activity.getUserEventDispatcher().logActionCommand(
LauncherLogProto.Action.Command.RECENTS_BUTTON,
- mHelper.getContainerType(),
+ mActivityInterface.getContainerType(),
LauncherLogProto.ContainerType.TASKSWITCHER);
mUserEventLogged = true;
}
+
+ // Switch prediction client to overview
+ PredictionUiStateManager.INSTANCE.get(activity).switchClient(
+ PredictionUiStateManager.Client.OVERVIEW);
return mAnimationProvider.onActivityReady(activity, wasVisible);
}
- private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+ private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets) {
if (LatencyTrackerCompat.isEnabled(mContext)) {
LatencyTrackerCompat.logToggleRecents(
(int) (SystemClock.uptimeMillis() - mToggleClickedTime));
@@ -217,7 +233,8 @@
mListener.unregister();
- AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(targetCompats);
+ AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(appTargets,
+ wallpaperTargets);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 3c78dd8..d4c746f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,21 +1,25 @@
package com.android.quickstep;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
+import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.Task;
-import java.util.concurrent.ExecutionException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class QuickstepTestInformationHandler extends TestInformationHandler {
+ private final Context mContext;
public QuickstepTestInformationHandler(Context context) {
+ mContext = context;
}
@Override
@@ -37,37 +41,33 @@
}
case TestProtocol.REQUEST_HOTSEAT_TOP: {
- if (mLauncher == null) return null;
+ return getLauncherUIProperty(
+ Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
+ }
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- PortraitStatesTouchController.getHotseatTop(mLauncher));
+ case TestProtocol.REQUEST_RECENT_TASKS_LIST: {
+ ArrayList<String> taskBaseIntentComponents = new ArrayList<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ RecentsModel.INSTANCE.get(mContext).getTasks((tasks) -> {
+ for (Task t : tasks) {
+ taskBaseIntentComponents.add(
+ t.key.baseIntent.getComponent().flattenToString());
+ }
+ latch.countDown();
+ });
+ try {
+ latch.await(2, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ response.putStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ taskBaseIntentComponents);
return response;
}
- case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: {
- try {
- final int leftMargin = MAIN_EXECUTOR.submit(() ->
- mLauncher.<RecentsView>getOverviewPanel().getLeftGestureMargin()).get();
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin);
- } catch (ExecutionException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return response;
- }
-
- case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: {
- try {
- final int rightMargin = MAIN_EXECUTOR.submit(() ->
- mLauncher.<RecentsView>getOverviewPanel().getRightGestureMargin()).
- get();
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin);
- } catch (ExecutionException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
return response;
}
}
@@ -76,6 +76,17 @@
}
@Override
+ protected Activity getCurrentActivity() {
+ OverviewComponentObserver observer = new OverviewComponentObserver(mContext,
+ new RecentsAnimationDeviceState(mContext));
+ try {
+ return observer.getActivityInterface().getCreatedActivity();
+ } finally {
+ observer.onDestroy();
+ }
+ }
+
+ @Override
protected boolean isLauncherInitialized() {
return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 9bdc98b..3ab0f19 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -16,12 +16,11 @@
package com.android.quickstep;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl
- .STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl
- .STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
@@ -40,11 +39,12 @@
import com.android.launcher3.LauncherAnimationRunner;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.util.ObjectWrapper;
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.util.ObjectWrapper;
+import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -70,7 +70,7 @@
setContentView(R.layout.fallback_recents_activity);
mRecentsRootView = findViewById(R.id.drag_layer);
mFallbackRecentsView = findViewById(R.id.overview_panel);
- mRecentsRootView.setup();
+ mRecentsRootView.recreateControllers();
}
@Override
@@ -91,9 +91,13 @@
int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
- ThumbnailData thumbnailData = ((ObjectWrapper<ThumbnailData>) thumbnail).get();
+ ObjectWrapper<ThumbnailData> obj = (ObjectWrapper<ThumbnailData>) thumbnail;
+ ThumbnailData thumbnailData = obj.get();
mFallbackRecentsView.showCurrentTask(taskID);
mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
+ // Clear the ref since any reference to the extras on the system side will still
+ // hold a reference to the wrapper
+ obj.clear();
}
}
intent.removeExtra(EXTRA_TASK_ID);
@@ -104,7 +108,7 @@
@Override
protected void onHandleConfigChanged() {
super.onHandleConfigChanged();
- mRecentsRootView.setup();
+ mRecentsRootView.recreateControllers();
}
@Override
@@ -152,9 +156,10 @@
true /* startAtFrontOfQueue */) {
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
- AnimationResult result) {
- AnimatorSet anim = composeRecentsLaunchAnimator(taskView, targetCompats);
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+ AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
+ wallpaperTargets);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -174,12 +179,16 @@
* Composes the animations for a launch from the recents list if possible.
*/
private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
- RemoteAnimationTargetCompat[] targets) {
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets) {
AnimatorSet target = new AnimatorSet();
- boolean activityClosing = taskIsATargetWithMode(targets, getTaskId(), MODE_CLOSING);
- ClipAnimationHelper helper = new ClipAnimationHelper(this);
- target.play(getRecentsWindowAnimator(taskView, !activityClosing, targets, helper)
- .setDuration(RECENTS_LAUNCH_DURATION));
+ boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
+ AppWindowAnimationHelper helper = new AppWindowAnimationHelper(
+ mFallbackRecentsView.getPagedViewOrientedState(), this);
+ Animator recentsAnimator = getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
+ wallpaperTargets, null /* backgroundBlurController */,
+ helper);
+ target.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
// Found a visible recents task that matches the opening app, lets launch the app from there
if (activityClosing) {
@@ -213,6 +222,12 @@
mFallbackRecentsView.reset();
}
+ @Override
+ protected void onResume() {
+ super.onResume();
+ AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL);
+ }
+
public void onTaskLaunched() {
mFallbackRecentsView.resetTaskVisuals();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
deleted file mode 100644
index c4d3fa0..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ /dev/null
@@ -1,227 +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;
-
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.inputconsumers.InputConsumer;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.systemui.shared.system.InputConsumerController;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Wrapper around RecentsAnimationController to help with some synchronization
- */
-public class RecentsAnimationWrapper {
-
- private static final String TAG = "RecentsAnimationWrapper";
-
- // A list of callbacks to run when we receive the recents animation target. There are different
- // than the state callbacks as these run on the current worker thread.
- private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
-
- public SwipeAnimationTargetSet targetSet;
-
- private boolean mWindowThresholdCrossed = false;
-
- private final InputConsumerController mInputConsumerController;
- private final Supplier<InputConsumer> mInputProxySupplier;
-
- private InputConsumer mInputConsumer;
- private boolean mTouchInProgress;
-
- private boolean mFinishPending;
-
- public RecentsAnimationWrapper(InputConsumerController inputConsumerController,
- Supplier<InputConsumer> inputProxySupplier) {
- mInputConsumerController = inputConsumerController;
- mInputProxySupplier = inputProxySupplier;
- }
-
- public boolean hasTargets() {
- return targetSet != null && targetSet.hasTargets();
- }
-
- @UiThread
- public synchronized void setController(SwipeAnimationTargetSet targetSet) {
- Preconditions.assertUIThread();
- this.targetSet = targetSet;
-
- if (targetSet == null) {
- return;
- }
- targetSet.setWindowThresholdCrossed(mWindowThresholdCrossed);
-
- if (!mCallbacks.isEmpty()) {
- for (Runnable action : new ArrayList<>(mCallbacks)) {
- action.run();
- }
- mCallbacks.clear();
- }
- }
-
- public synchronized void runOnInit(Runnable action) {
- if (targetSet == null) {
- mCallbacks.add(action);
- } else {
- action.run();
- }
- }
-
- /** See {@link #finish(boolean, Runnable, boolean)} */
- @UiThread
- public void finish(boolean toRecents, Runnable onFinishComplete) {
- finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
- }
-
- /**
- * @param onFinishComplete A callback that runs on the main thread after the animation
- * controller has finished on the background thread.
- * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
- * activity. If userLeaveHint is true, the activity will enter into
- * picture-in-picture mode upon being paused.
- */
- @UiThread
- public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
- Preconditions.assertUIThread();
- if (!toRecents) {
- finishAndClear(false, onFinishComplete, sendUserLeaveHint);
- } else {
- if (mTouchInProgress) {
- mFinishPending = true;
- // Execute the callback
- if (onFinishComplete != null) {
- onFinishComplete.run();
- }
- } else {
- finishAndClear(true, onFinishComplete, sendUserLeaveHint);
- }
- }
- }
-
- private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
- boolean sendUserLeaveHint) {
- SwipeAnimationTargetSet controller = targetSet;
- targetSet = null;
- disableInputProxy();
- if (controller != null) {
- controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
- }
- }
-
- public void enableInputConsumer() {
- if (targetSet != null) {
- targetSet.enableInputConsumer();
- }
- }
-
- /**
- * Indicates that the gesture has crossed the window boundary threshold and system UI can be
- * update the represent the window behind
- */
- public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
- if (mWindowThresholdCrossed != windowThresholdCrossed) {
- mWindowThresholdCrossed = windowThresholdCrossed;
- if (targetSet != null) {
- targetSet.setWindowThresholdCrossed(windowThresholdCrossed);
- }
- }
- }
-
- public void enableInputProxy() {
- mInputConsumerController.setInputListener(this::onInputConsumerEvent);
- }
-
- private void disableInputProxy() {
- if (mInputConsumer != null && mTouchInProgress) {
- long now = SystemClock.uptimeMillis();
- MotionEvent dummyCancel = MotionEvent.obtain(now, now, ACTION_CANCEL, 0, 0, 0);
- mInputConsumer.onMotionEvent(dummyCancel);
- dummyCancel.recycle();
- }
- mInputConsumerController.setInputListener(null);
- }
-
- private boolean onInputConsumerEvent(InputEvent ev) {
- if (ev instanceof MotionEvent) {
- onInputConsumerMotionEvent((MotionEvent) ev);
- } else if (ev instanceof KeyEvent) {
- if (mInputConsumer == null) {
- mInputConsumer = mInputProxySupplier.get();
- }
- mInputConsumer.onKeyEvent((KeyEvent) ev);
- return true;
- }
- return false;
- }
-
- private boolean onInputConsumerMotionEvent(MotionEvent ev) {
- int action = ev.getAction();
-
- // Just to be safe, verify that ACTION_DOWN comes before any other action,
- // and ignore any ACTION_DOWN after the first one (though that should not happen).
- if (!mTouchInProgress && action != ACTION_DOWN) {
- Log.w(TAG, "Received non-down motion before down motion: " + action);
- return false;
- }
- if (mTouchInProgress && action == ACTION_DOWN) {
- Log.w(TAG, "Received down motion while touch was already in progress");
- return false;
- }
-
- if (action == ACTION_DOWN) {
- mTouchInProgress = true;
- if (mInputConsumer == null) {
- mInputConsumer = mInputProxySupplier.get();
- }
- } else if (action == ACTION_CANCEL || action == ACTION_UP) {
- // Finish any pending actions
- mTouchInProgress = false;
- if (mFinishPending) {
- mFinishPending = false;
- finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
- }
- }
- if (mInputConsumer != null) {
- mInputConsumer.onMotionEvent(ev);
- }
-
- return true;
- }
-
- public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
- if (targetSet != null) {
- targetSet.controller.setDeferCancelUntilNextTransition(defer, screenshot);
- }
- }
-
- public SwipeAnimationTargetSet getController() {
- return targetSet;
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
deleted file mode 100644
index 8783ee3..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.util.Log;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
-
-import java.io.PrintWriter;
-
-/**
- * Utility class used to store state information shared across multiple transitions.
- */
-public class SwipeSharedState implements SwipeAnimationListener {
-
- private OverviewComponentObserver mOverviewComponentObserver;
-
- private RecentsAnimationListenerSet mRecentsAnimationListener;
- private SwipeAnimationTargetSet mLastAnimationTarget;
-
- private boolean mLastAnimationCancelled = false;
- private boolean mLastAnimationRunning = false;
-
- public boolean canGestureBeContinued;
- public boolean goingToLauncher;
- public boolean recentsAnimationFinishInterrupted;
- public int nextRunningTaskId = -1;
- private int mLogId;
-
- public void setOverviewComponentObserver(OverviewComponentObserver observer) {
- mOverviewComponentObserver = observer;
- }
-
- @Override
- public final void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- mLastAnimationTarget = targetSet;
-
- mLastAnimationCancelled = false;
- mLastAnimationRunning = true;
- }
-
- private void clearAnimationTarget() {
- if (mLastAnimationTarget != null) {
- mLastAnimationTarget.release();
- mLastAnimationTarget = null;
- }
- }
-
- @Override
- public final void onRecentsAnimationCanceled() {
- clearAnimationTarget();
-
- mLastAnimationCancelled = true;
- mLastAnimationRunning = false;
- }
-
- private void clearListenerState(boolean finishAnimation) {
- if (mRecentsAnimationListener != null) {
- mRecentsAnimationListener.removeListener(this);
- mRecentsAnimationListener.cancelListener();
- if (mLastAnimationRunning && mLastAnimationTarget != null) {
- Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
- finishAnimation
- ? mLastAnimationTarget::finishAnimation
- : mLastAnimationTarget::cancelAnimation);
- mLastAnimationTarget = null;
- }
- }
- mRecentsAnimationListener = null;
- clearAnimationTarget();
- mLastAnimationCancelled = false;
- mLastAnimationRunning = false;
- }
-
- private void onSwipeAnimationFinished(SwipeAnimationTargetSet targetSet) {
- if (mLastAnimationTarget == targetSet) {
- mLastAnimationRunning = false;
- }
- }
-
- public RecentsAnimationListenerSet newRecentsAnimationListenerSet() {
- Preconditions.assertUIThread();
-
- if (mLastAnimationRunning) {
- String msg = "New animation started before completing old animation";
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
- throw new IllegalArgumentException(msg);
- } else {
- Log.e("SwipeSharedState", msg, new Exception());
- }
- }
-
- clearListenerState(false /* finishAnimation */);
- boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false
- : mOverviewComponentObserver.getActivityControlHelper().shouldMinimizeSplitScreen();
- mRecentsAnimationListener = new RecentsAnimationListenerSet(
- shouldMinimiseSplitScreen, this::onSwipeAnimationFinished);
- mRecentsAnimationListener.addListener(this);
- return mRecentsAnimationListener;
- }
-
- public RecentsAnimationListenerSet getActiveListener() {
- return mRecentsAnimationListener;
- }
-
- public void applyActiveRecentsAnimationState(SwipeAnimationListener listener) {
- if (mLastAnimationTarget != null) {
- listener.onRecentsAnimationStart(mLastAnimationTarget);
- } else if (mLastAnimationCancelled) {
- listener.onRecentsAnimationCanceled();
- }
- }
-
- /**
- * Called when a recents animation has finished, but was interrupted before the next task was
- * launched. The given {@param runningTaskId} should be used as the running task for the
- * continuing input consumer.
- */
- public void setRecentsAnimationFinishInterrupted(int runningTaskId) {
- recentsAnimationFinishInterrupted = true;
- nextRunningTaskId = runningTaskId;
- mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets();
- }
-
- public void clearAllState(boolean finishAnimation) {
- clearListenerState(finishAnimation);
- canGestureBeContinued = false;
- recentsAnimationFinishInterrupted = false;
- nextRunningTaskId = -1;
- goingToLauncher = false;
- }
-
- public void dump(String prefix, PrintWriter pw) {
- pw.println(prefix + "goingToLauncher=" + goingToLauncher);
- pw.println(prefix + "canGestureBeContinued=" + canGestureBeContinued);
- pw.println(prefix + "recentsAnimationFinishInterrupted=" + recentsAnimationFinishInterrupted);
- pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId);
- pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled);
- pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning);
- pw.println(prefix + "logTraceId=" + mLogId);
- }
-
- public void setLogTraceId(int logId) {
- this.mLogId = logId;
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 17457aa..33d9d9a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -21,13 +21,17 @@
import android.graphics.Matrix;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
+import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.plugins.OverscrollPlugin;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -40,28 +44,35 @@
public class TaskOverlayFactory implements ResourceBasedOverride {
/** Note that these will be shown in order from top to bottom, if available for the task. */
- private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
- new TaskSystemShortcut.AppInfo(),
- new TaskSystemShortcut.SplitScreen(),
- new TaskSystemShortcut.Pin(),
- new TaskSystemShortcut.Install(),
- new TaskSystemShortcut.Freeform()
+ private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
+ TaskShortcutFactory.APP_INFO,
+ TaskShortcutFactory.SPLIT_SCREEN,
+ TaskShortcutFactory.PIN,
+ TaskShortcutFactory.INSTALL,
+ TaskShortcutFactory.FREE_FORM,
+ TaskShortcutFactory.WELLBEING
};
+ public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
+ final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
+ final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
+ for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
+ SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
+ if (shortcut != null) {
+ shortcuts.add(shortcut);
+ }
+ }
+ return shortcuts;
+ }
+
public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
- public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
- final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
- final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
- for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
- View.OnClickListener onClickListener =
- menuOption.getOnClickListener(activity, taskView);
- if (onClickListener != null) {
- shortcuts.add(menuOption);
- }
- }
- return shortcuts;
+ /**
+ * @return a launcher-provided OverscrollPlugin if available, otherwise null
+ */
+ public OverscrollPlugin getLocalOverscrollPlugin() {
+ return null;
}
public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
@@ -75,9 +86,21 @@
*/
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { }
+ @Nullable
+ public View getActionsView() {
+ return null;
+ }
+
/**
* Called when the overlay is no longer used.
*/
public void reset() { }
+
+ /**
+ * Whether the overlay is modal, which means only tapping is enabled, but no swiping.
+ */
+ public boolean isOverlayModal() {
+ return false;
+ }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
new file mode 100644
index 0000000..9ba2e5a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
@@ -0,0 +1,315 @@
+/*
+ * 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;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.view.View;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.WellbeingModel;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.popup.SystemShortcut.AppInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.InstantAppResolver;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityCompat;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Represents a system shortcut that can be shown for a recent task.
+ */
+public interface TaskShortcutFactory {
+
+ SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
+
+ static WorkspaceItemInfo dummyInfo(TaskView view) {
+ Task task = view.getTask();
+
+ WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
+ dummyInfo.intent = new Intent();
+ ComponentName component = task.getTopComponent();
+ dummyInfo.intent.setComponent(component);
+ dummyInfo.user = UserHandle.of(task.key.userId);
+ dummyInfo.title = TaskUtils.getTitle(view.getContext(), task);
+ return dummyInfo;
+ }
+
+ TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view));
+
+ abstract class MultiWindowFactory implements TaskShortcutFactory {
+
+ private final int mIconRes;
+ private final int mTextRes;
+
+ MultiWindowFactory(int iconRes, int textRes) {
+ mIconRes = iconRes;
+ mTextRes = textRes;
+ }
+
+ protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
+ protected abstract ActivityOptions makeLaunchOptions(Activity activity);
+ protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
+
+ @Override
+ public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
+ final Task task = taskView.getTask();
+ if (!task.isDockable) {
+ return null;
+ }
+ if (!isAvailable(activity, task.key.displayId)) {
+ return null;
+ }
+ return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this);
+ }
+ }
+
+ class MultiWindowSystemShortcut extends SystemShortcut {
+
+ private Handler mHandler;
+
+ private final RecentsView mRecentsView;
+ private final TaskThumbnailView mThumbnailView;
+ private final TaskView mTaskView;
+ private final MultiWindowFactory mFactory;
+
+ public MultiWindowSystemShortcut(int iconRes, int textRes,
+ BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) {
+ super(iconRes, textRes, activity, dummyInfo(taskView));
+
+ mHandler = new Handler(Looper.getMainLooper());
+ mTaskView = taskView;
+ mRecentsView = activity.getOverviewPanel();
+ mThumbnailView = taskView.getThumbnail();
+ mFactory = factory;
+ }
+
+ @Override
+ public void onClick(View view) {
+ Task.TaskKey taskKey = mTaskView.getTask().key;
+ final int taskId = taskKey.id;
+
+ final View.OnLayoutChangeListener onLayoutChangeListener =
+ new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int l, int t, int r, int b,
+ int oldL, int oldT, int oldR, int oldB) {
+ mTaskView.getRootView().removeOnLayoutChangeListener(this);
+ mRecentsView.clearIgnoreResetTask(taskId);
+
+ // Start animating in the side pages once launcher has been resized
+ mRecentsView.dismissTask(mTaskView, false, false);
+ }
+ };
+
+ final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
+ new DeviceProfile.OnDeviceProfileChangeListener() {
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ mTarget.removeOnDeviceProfileChangeListener(this);
+ if (dp.isMultiWindowMode) {
+ mTaskView.getRootView().addOnLayoutChangeListener(
+ onLayoutChangeListener);
+ }
+ }
+ };
+
+ dismissTaskMenuView(mTarget);
+
+ ActivityOptions options = mFactory.makeLaunchOptions(mTarget);
+ if (options != null
+ && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
+ options)) {
+ if (!mFactory.onActivityStarted(mTarget)) {
+ return;
+ }
+ // Add a device profile change listener to kick off animating the side tasks
+ // once we enter multiwindow mode and relayout
+ mTarget.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
+
+ final Runnable animStartedListener = () -> {
+ // Hide the task view and wait for the window to be resized
+ // TODO: Consider animating in launcher and do an in-place start activity
+ // afterwards
+ mRecentsView.setIgnoreResetTask(taskId);
+ mTaskView.setAlpha(0f);
+ };
+
+ final int[] position = new int[2];
+ mThumbnailView.getLocationOnScreen(position);
+ final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX());
+ final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY());
+ final Rect taskBounds = new Rect(position[0], position[1],
+ position[0] + width, position[1] + height);
+
+ // Take the thumbnail of the task without a scrim and apply it back after
+ float alpha = mThumbnailView.getDimAlpha();
+ mThumbnailView.setDimAlpha(0);
+ Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
+ taskBounds.width(), taskBounds.height(), mThumbnailView, 1f,
+ Color.BLACK);
+ mThumbnailView.setDimAlpha(alpha);
+
+ AppTransitionAnimationSpecsFuture future =
+ new AppTransitionAnimationSpecsFuture(mHandler) {
+ @Override
+ public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+ return Collections.singletonList(new AppTransitionAnimationSpecCompat(
+ taskId, thumbnail, taskBounds));
+ }
+ };
+ WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+ future, animStartedListener, mHandler, true /* scaleUp */,
+ taskKey.displayId);
+ }
+ }
+ }
+
+ TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(
+ R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) {
+
+ @Override
+ protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
+ // Don't show menu-item if already in multi-window and the task is from
+ // the secondary display.
+ // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
+ // implementation is enabled
+ return !activity.getDeviceProfile().isMultiWindowMode
+ && (displayId == -1 || displayId == DEFAULT_DISPLAY);
+ }
+
+ @Override
+ protected ActivityOptions makeLaunchOptions(Activity activity) {
+ final ActivityCompat act = new ActivityCompat(activity);
+ final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
+ act.getDisplayId());
+ if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
+ return null;
+ }
+ boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
+ return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
+ }
+
+ @Override
+ protected boolean onActivityStarted(BaseDraggingActivity activity) {
+ SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked();
+ activity.getUserEventDispatcher().logActionOnControl(TAP,
+ LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
+ return true;
+ }
+ };
+
+ TaskShortcutFactory FREE_FORM = new MultiWindowFactory(
+ R.drawable.ic_split_screen, R.string.recent_task_option_freeform) {
+
+ @Override
+ protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
+ return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
+ }
+
+ @Override
+ protected ActivityOptions makeLaunchOptions(Activity activity) {
+ ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
+ // Arbitrary bounds only because freeform is in dev mode right now
+ Rect r = new Rect(50, 50, 200, 200);
+ activityOptions.setLaunchBounds(r);
+ return activityOptions;
+ }
+
+ @Override
+ protected boolean onActivityStarted(BaseDraggingActivity activity) {
+ activity.returnToHomescreen();
+ return true;
+ }
+ };
+
+ TaskShortcutFactory PIN = (activity, tv) -> {
+ if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
+ return null;
+ }
+ if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
+ return null;
+ }
+ if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
+ // We shouldn't be able to pin while an app is locked.
+ return null;
+ }
+ return new PinSystemShortcut(activity, tv);
+ };
+
+ class PinSystemShortcut extends SystemShortcut {
+
+ private static final String TAG = "PinSystemShortcut";
+
+ private final TaskView mTaskView;
+
+ public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
+ super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv));
+ mTaskView = tv;
+ }
+
+ @Override
+ public void onClick(View view) {
+ Consumer<Boolean> resultCallback = success -> {
+ if (success) {
+ SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
+ mTaskView.getTask().key.id);
+ } else {
+ mTaskView.notifyTaskLaunchFailed(TAG);
+ }
+ };
+ mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
+ dismissTaskMenuView(mTarget);
+ }
+ }
+
+ TaskShortcutFactory INSTALL = (activity, view) ->
+ InstantAppResolver.newInstance(activity).isInstantApp(activity,
+ view.getTask().getTopComponent().getPackageName())
+ ? new SystemShortcut.Install(activity, dummyInfo(view)) : null;
+
+ TaskShortcutFactory WELLBEING = (activity, view) ->
+ WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view));
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
deleted file mode 100644
index 1af0db0..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ /dev/null
@@ -1,340 +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;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.InstantAppResolver;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.ActivityCompat;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Represents a system shortcut that can be shown for a recent task.
- */
-public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut {
-
- private static final String TAG = "TaskSystemShortcut";
-
- protected T mSystemShortcut;
-
- public TaskSystemShortcut(T systemShortcut) {
- super(systemShortcut);
- mSystemShortcut = systemShortcut;
- }
-
- protected TaskSystemShortcut(int iconResId, int labelResId) {
- super(iconResId, labelResId);
- }
-
- @Override
- public View.OnClickListener getOnClickListener(
- BaseDraggingActivity activity, ItemInfo itemInfo) {
- return null;
- }
-
- public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) {
- Task task = view.getTask();
-
- WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
- dummyInfo.intent = new Intent();
- ComponentName component = task.getTopComponent();
- dummyInfo.intent.setComponent(component);
- dummyInfo.user = UserHandle.of(task.key.userId);
- dummyInfo.title = TaskUtils.getTitle(activity, task);
-
- return getOnClickListenerForTask(activity, task, dummyInfo);
- }
-
- protected View.OnClickListener getOnClickListenerForTask(
- BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) {
- return mSystemShortcut.getOnClickListener(activity, dummyInfo);
- }
-
- public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> {
- public AppInfo() {
- super(new SystemShortcut.AppInfo());
- }
- }
-
- public static abstract class MultiWindow extends TaskSystemShortcut {
-
- private Handler mHandler;
-
- public MultiWindow(int iconRes, int textRes) {
- super(iconRes, textRes);
- mHandler = new Handler(Looper.getMainLooper());
- }
-
- protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId);
- protected abstract ActivityOptions makeLaunchOptions(Activity activity);
- protected abstract boolean onActivityStarted(BaseDraggingActivity activity);
-
- @Override
- public View.OnClickListener getOnClickListener(
- BaseDraggingActivity activity, TaskView taskView) {
- final Task task = taskView.getTask();
- final int taskId = task.key.id;
- final int displayId = task.key.displayId;
- if (!task.isDockable) {
- return null;
- }
- if (!isAvailable(activity, displayId)) {
- return null;
- }
- final RecentsView recentsView = activity.getOverviewPanel();
-
- final TaskThumbnailView thumbnailView = taskView.getThumbnail();
- return (v -> {
- final View.OnLayoutChangeListener onLayoutChangeListener =
- new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int l, int t, int r, int b,
- int oldL, int oldT, int oldR, int oldB) {
- taskView.getRootView().removeOnLayoutChangeListener(this);
- recentsView.clearIgnoreResetTask(taskId);
-
- // Start animating in the side pages once launcher has been resized
- recentsView.dismissTask(taskView, false, false);
- }
- };
-
- final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
- new DeviceProfile.OnDeviceProfileChangeListener() {
- @Override
- public void onDeviceProfileChanged(DeviceProfile dp) {
- activity.removeOnDeviceProfileChangeListener(this);
- if (dp.isMultiWindowMode) {
- taskView.getRootView().addOnLayoutChangeListener(
- onLayoutChangeListener);
- }
- }
- };
-
- dismissTaskMenuView(activity);
-
- ActivityOptions options = makeLaunchOptions(activity);
- if (options != null
- && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
- options)) {
- if (!onActivityStarted(activity)) {
- return;
- }
- // Add a device profile change listener to kick off animating the side tasks
- // once we enter multiwindow mode and relayout
- activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
-
- final Runnable animStartedListener = () -> {
- // Hide the task view and wait for the window to be resized
- // TODO: Consider animating in launcher and do an in-place start activity
- // afterwards
- recentsView.setIgnoreResetTask(taskId);
- taskView.setAlpha(0f);
- };
-
- final int[] position = new int[2];
- thumbnailView.getLocationOnScreen(position);
- final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX());
- final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY());
- final Rect taskBounds = new Rect(position[0], position[1],
- position[0] + width, position[1] + height);
-
- // Take the thumbnail of the task without a scrim and apply it back after
- float alpha = thumbnailView.getDimAlpha();
- thumbnailView.setDimAlpha(0);
- Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
- taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
- Color.BLACK);
- thumbnailView.setDimAlpha(alpha);
-
- AppTransitionAnimationSpecsFuture future =
- new AppTransitionAnimationSpecsFuture(mHandler) {
- @Override
- public List<AppTransitionAnimationSpecCompat> composeSpecs() {
- return Collections.singletonList(new AppTransitionAnimationSpecCompat(
- taskId, thumbnail, taskBounds));
- }
- };
- WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
- future, animStartedListener, mHandler, true /* scaleUp */, displayId);
- }
- });
- }
- }
-
- public static class SplitScreen extends MultiWindow {
- public SplitScreen() {
- super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
- }
-
- @Override
- protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
- // Don't show menu-item if already in multi-window and the task is from
- // the secondary display.
- // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new
- // implementation is enabled
- return !activity.getDeviceProfile().isMultiWindowMode
- && (displayId == -1 || displayId == DEFAULT_DISPLAY);
- }
-
- @Override
- protected ActivityOptions makeLaunchOptions(Activity activity) {
- final ActivityCompat act = new ActivityCompat(activity);
- final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
- act.getDisplayId());
- if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
- return null;
- }
- boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
- return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft);
- }
-
- @Override
- protected boolean onActivityStarted(BaseDraggingActivity activity) {
- ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
- try {
- sysUiProxy.onSplitScreenInvoked();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
- return false;
- }
- activity.getUserEventDispatcher().logActionOnControl(TAP,
- LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
- return true;
- }
- }
-
- public static class Freeform extends MultiWindow {
- public Freeform() {
- super(R.drawable.ic_split_screen, R.string.recent_task_option_freeform);
- }
-
- @Override
- protected boolean isAvailable(BaseDraggingActivity activity, int displayId) {
- return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity);
- }
-
- @Override
- protected ActivityOptions makeLaunchOptions(Activity activity) {
- ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
- // Arbitrary bounds only because freeform is in dev mode right now
- Rect r = new Rect(50, 50, 200, 200);
- activityOptions.setLaunchBounds(r);
- return activityOptions;
- }
-
- @Override
- protected boolean onActivityStarted(BaseDraggingActivity activity) {
- activity.returnToHomescreen();
- return true;
- }
- }
-
- public static class Pin extends TaskSystemShortcut {
-
- private static final String TAG = Pin.class.getSimpleName();
-
- private Handler mHandler;
-
- public Pin() {
- super(R.drawable.ic_pin, R.string.recent_task_option_pin);
- mHandler = new Handler(Looper.getMainLooper());
- }
-
- @Override
- public View.OnClickListener getOnClickListener(
- BaseDraggingActivity activity, TaskView taskView) {
- ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
- if (sysUiProxy == null) {
- return null;
- }
- if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
- return null;
- }
- if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
- // We shouldn't be able to pin while an app is locked.
- return null;
- }
- return view -> {
- Consumer<Boolean> resultCallback = success -> {
- if (success) {
- try {
- sysUiProxy.startScreenPinning(taskView.getTask().key.id);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to start screen pinning: ", e);
- }
- } else {
- taskView.notifyTaskLaunchFailed(TAG);
- }
- };
- taskView.launchTask(true, resultCallback, mHandler);
- dismissTaskMenuView(activity);
- };
- }
- }
-
- public static class Install extends TaskSystemShortcut<SystemShortcut.Install> {
- public Install() {
- super(new SystemShortcut.Install());
- }
-
- @Override
- protected View.OnClickListener getOnClickListenerForTask(
- BaseDraggingActivity activity, Task task, ItemInfo itemInfo) {
- if (InstantAppResolver.newInstance(activity).isInstantApp(activity,
- task.getTopComponent().getPackageName())) {
- return mSystemShortcut.createOnClickListener(activity, itemInfo);
- }
- return null;
- }
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 6897c1e..38b86ce 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -15,12 +15,17 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.graphics.RectF;
@@ -30,14 +35,19 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Utilities;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.util.MultiValueUpdateListener;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
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.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Utility class for helpful methods related to {@link TaskView} objects and their tasks.
@@ -110,17 +120,23 @@
* @return Animator that controls the window of the opening targets for the recents launch
* animation.
*/
- public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
- RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper) {
+ public static Animator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ BackgroundBlurController backgroundBlurController,
+ final AppWindowAnimationHelper inOutHelper) {
SyncRtSurfaceTransactionApplierCompat applier =
new SyncRtSurfaceTransactionApplierCompat(v);
- ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
- .setSyncTransactionApplier(applier);
+ final RemoteAnimationTargets targets =
+ new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
+ targets.addDependentTransactionApplier(applier);
+ AppWindowAnimationHelper.TransformParams params =
+ new AppWindowAnimationHelper.TransformParams()
+ .setSyncTransactionApplier(applier)
+ .setTargetSet(targets)
+ .setLauncherOnTop(true);
- final RemoteAnimationTargetSet targetSet =
- new RemoteAnimationTargetSet(targets, MODE_OPENING);
- targetSet.addDependentTransactionApplier(applier);
-
+ AnimatorSet animatorSet = new AnimatorSet();
final RecentsView recentsView = v.getRecentsView();
final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
@@ -129,8 +145,6 @@
// Defer fading out the view until after the app window gets faded in
final FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
final FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
-
-
final RectF mThumbnailRect;
{
@@ -140,7 +154,7 @@
BaseActivity.fromContext(v.getContext()).getDeviceProfile(),
true /* isOpening */);
inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
- targetSet.apps.length == 0 ? null : targetSet.apps[0]);
+ targets.apps.length == 0 ? null : targets.apps[0]);
mThumbnailRect = new RectF(inOutHelper.getTargetRect());
mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
@@ -151,7 +165,36 @@
public void onUpdate(float percent) {
// TODO: Take into account the current fullscreen progress for animating the insets
params.setProgress(1 - percent);
- RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
+ RectF taskBounds;
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ List<SurfaceParams> surfaceParamsList = new ArrayList<>();
+ // Append the surface transform params for the app that's being opened.
+ Collections.addAll(surfaceParamsList, inOutHelper.computeSurfaceParams(params));
+
+ AppWindowAnimationHelper liveTileAnimationHelper =
+ v.getRecentsView().getClipAnimationHelper();
+ if (liveTileAnimationHelper != null) {
+ // Append the surface transform params for the live tile app.
+ AppWindowAnimationHelper.TransformParams liveTileParams =
+ v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
+ if (liveTileParams != null) {
+ SurfaceParams[] liveTileSurfaceParams =
+ liveTileAnimationHelper.computeSurfaceParams(liveTileParams);
+ if (liveTileSurfaceParams != null) {
+ Collections.addAll(surfaceParamsList, liveTileSurfaceParams);
+ }
+ }
+ }
+ // Apply surface transform using the surface params list.
+ AppWindowAnimationHelper.applySurfaceParams(params.getSyncTransactionApplier(),
+ surfaceParamsList.toArray(new SurfaceParams[surfaceParamsList.size()]));
+ // Get the task bounds for the app that's being opened after surface transform
+ // update.
+ taskBounds = inOutHelper.updateCurrentRect(params);
+ } else {
+ taskBounds = inOutHelper.applyTransform(params);
+ }
+
int taskIndex = recentsView.indexOfChild(v);
int centerTaskIndex = recentsView.getCurrentPage();
boolean parallaxCenterAndAdjacentTask = taskIndex != centerTaskIndex;
@@ -168,9 +211,17 @@
appAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- targetSet.release();
+ targets.release();
}
});
- return appAnimator;
+
+ if (backgroundBlurController != null) {
+ ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(backgroundBlurController,
+ BACKGROUND_BLUR, BACKGROUND_APP.getBackgroundBlurRadius(v.getContext()));
+ animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
+ } else {
+ animatorSet.play(appAnimator);
+ }
+ return animatorSet;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 2fa789f..25a3078 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -17,92 +17,74 @@
import static android.view.MotionEvent.ACTION_DOWN;
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
-import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
-import android.graphics.Point;
-import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
-import android.text.TextUtils;
import android.util.Log;
import android.view.Choreographer;
import android.view.InputEvent;
import android.view.MotionEvent;
-import android.view.Surface;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.BaseFlags;
-import com.android.launcher3.logging.EventLogArray;
+import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.launcher3.tracing.nano.LauncherTraceProto;
+import com.android.launcher3.tracing.nano.TouchInteractionServiceProto;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
-import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
+import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
-import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
-import com.android.quickstep.inputconsumers.InputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
+import com.android.quickstep.inputconsumers.OverscrollInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.plugins.OverscrollPlugin;
+import com.android.systemui.plugins.PluginListener;
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.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
+import com.android.systemui.shared.tracing.ProtoTraceable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -131,51 +113,47 @@
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.Q)
-public class TouchInteractionService extends Service implements
- NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener {
-
- /**
- * NOTE: This value should be kept same as
- * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
- */
- public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
-
-
- public static final EventLogArray TOUCH_INTERACTION_LOG =
- new EventLogArray("touch_interaction_log", 40);
+public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin>,
+ ProtoTraceable<LauncherTraceProto> {
private static final String TAG = "TouchInteractionService";
private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount";
private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
+ private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
private int mBackGestureNotificationCounter = -1;
+ @Nullable
+ private OverscrollPlugin mOverscrollPlugin;
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
- public void onActiveNavBarRegionChanges(Region region) {
- mActiveNavBarRegion = region;
- }
-
+ @BinderThread
public void onInitialize(Bundle bundle) {
- mISystemUiProxy = ISystemUiProxy.Stub
- .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
- MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
- MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
- MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
+ ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+ MAIN_EXECUTOR.execute(() -> {
+ SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
+ TouchInteractionService.this.initInputMonitor();
+ preloadOverview(true /* fromInit */);
+ });
sIsInitialized = true;
}
+ @BinderThread
@Override
public void onOverviewToggle() {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
mOverviewCommandHelper.onOverviewToggle();
}
+ @BinderThread
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
}
+ @BinderThread
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
@@ -184,42 +162,58 @@
}
}
+ @BinderThread
@Override
public void onTip(int actionType, int viewType) {
mOverviewCommandHelper.onTip(actionType, viewType);
}
+ @BinderThread
@Override
public void onAssistantAvailable(boolean available) {
- mAssistantAvailable = available;
+ MAIN_EXECUTOR.execute(() -> {
+ mDeviceState.setAssistantAvailable(available);
+ TouchInteractionService.this.onAssistantVisibilityChanged();
+ });
}
+ @BinderThread
@Override
public void onAssistantVisibilityChanged(float visibility) {
- mLastAssistantVisibility = visibility;
- MAIN_EXECUTOR.execute(
- TouchInteractionService.this::onAssistantVisibilityChanged);
+ MAIN_EXECUTOR.execute(() -> {
+ mDeviceState.setAssistantVisibility(visibility);
+ TouchInteractionService.this.onAssistantVisibilityChanged();
+ });
}
+ @BinderThread
public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
boolean gestureSwipeLeft) {
if (mOverviewComponentObserver == null) {
return;
}
- final ActivityControlHelper activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
+ final BaseActivityInterface activityInterface =
+ mOverviewComponentObserver.getActivityInterface();
UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
- isButton, gestureSwipeLeft, activityControl.getContainerType());
+ isButton, gestureSwipeLeft, activityInterface.getContainerType());
if (completed && !isButton && shouldNotifyBackGesture()) {
UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
}
}
+ @BinderThread
public void onSystemUiStateChanged(int stateFlags) {
- mSystemUiStateFlags = stateFlags;
- MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
+ MAIN_EXECUTOR.execute(() -> {
+ mDeviceState.setSystemUiFlags(stateFlags);
+ TouchInteractionService.this.onSystemUiFlagsChanged();
+ });
+ }
+
+ @BinderThread
+ public void onActiveNavBarRegionChanges(Region region) {
+ MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
}
/** Deprecated methods **/
@@ -242,8 +236,6 @@
private static boolean sConnected = false;
private static boolean sIsInitialized = false;
- private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
- private int mLogId;
public static boolean isConnected() {
return sConnected;
@@ -253,91 +245,39 @@
return sIsInitialized;
}
- public static SwipeSharedState getSwipeSharedState() {
- return sSwipeSharedState;
- }
-
- private final InputConsumer mResetGestureInputConsumer =
- new ResetGestureInputConsumer(sSwipeSharedState);
-
- private final BaseSwipeUpHandler.Factory mWindowTreansformFactory =
- this::createWindowTransformSwipeHandler;
- private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory =
- this::createFallbackNoButtonSwipeHandler;
+ private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
+ this::createLauncherSwipeHandler;
+ private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
+ this::createFallbackSwipeHandler;
private ActivityManagerWrapper mAM;
- private RecentsModel mRecentsModel;
- private ISystemUiProxy mISystemUiProxy;
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewComponentObserver mOverviewComponentObserver;
- private OverviewInteractionState mOverviewInteractionState;
- private OverviewCallbacks mOverviewCallbacks;
- private TaskOverlayFactory mTaskOverlayFactory;
private InputConsumerController mInputConsumer;
- private boolean mAssistantAvailable;
- private float mLastAssistantVisibility = 0;
- private @SystemUiStateFlags int mSystemUiStateFlags;
-
- private boolean mIsUserUnlocked;
- private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- initWhenUserUnlocked();
- }
- }
- };
+ private RecentsAnimationDeviceState mDeviceState;
+ private TaskAnimationManager mTaskAnimationManager;
private InputConsumer mUncheckedConsumer = InputConsumer.NO_OP;
private InputConsumer mConsumer = InputConsumer.NO_OP;
private Choreographer mMainChoreographer;
-
- private Region mActiveNavBarRegion = new Region();
+ private InputConsumer mResetGestureInputConsumer;
+ private GestureState mGestureState = new GestureState();
private InputMonitorCompat mInputMonitorCompat;
private InputEventReceiver mInputEventReceiver;
- private Mode mMode = Mode.THREE_BUTTONS;
- private int mDefaultDisplayId;
- private final RectF mSwipeTouchRegion = new RectF();
- private final RectF mAssistantLeftRegion = new RectF();
- private final RectF mAssistantRightRegion = new RectF();
-
- private ComponentName mGestureBlockingActivity;
-
- private Region mExclusionRegion;
- private SystemGestureExclusionListenerCompat mExclusionListener;
@Override
public void onCreate() {
super.onCreate();
-
// Initialize anything here that is needed in direct boot mode.
- // Everything else should be initialized in initWhenUserUnlocked() below.
+ // Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
+ mDeviceState = new RecentsAnimationDeviceState(this);
+ mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+ mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
+ ProtoTracer.INSTANCE.get(this).add(this);
- if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
- initWhenUserUnlocked();
- } else {
- mIsUserUnlocked = false;
- registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
- }
-
- mDefaultDisplayId = DefaultDisplay.INSTANCE.get(this).getInfo().id;
- String blockingActivity = getString(R.string.gesture_blocking_activity);
- mGestureBlockingActivity = TextUtils.isEmpty(blockingActivity) ? null :
- ComponentName.unflattenFromString(blockingActivity);
-
- mExclusionListener = new SystemGestureExclusionListenerCompat(mDefaultDisplayId) {
- @Override
- @BinderThread
- public void onExclusionChanged(Region region) {
- // Assignments are atomic, it should be safe on binder thread
- mExclusionRegion = region;
- }
- };
-
- onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this));
sConnected = true;
}
@@ -359,123 +299,43 @@
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
}
- if (!mMode.hasGestures || mISystemUiProxy == null) {
+ disposeEventHandlers();
+ if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
return;
}
- disposeEventHandlers();
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 2");
}
- try {
- mInputMonitorCompat = InputMonitorCompat.fromBundle(mISystemUiProxy
- .monitorGestureInput("swipe-up", mDefaultDisplayId), KEY_EXTRA_INPUT_MONITOR);
- mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
- mMainChoreographer, this::onInputEvent);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to create input monitor", e);
- }
- initTouchBounds();
- }
-
- private int getNavbarSize(String resName) {
- return ResourceUtils.getNavbarSize(resName, getResources());
- }
-
- private void initTouchBounds() {
- if (!mMode.hasGestures) {
- return;
- }
-
- DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(this).getInfo();
- Point realSize = new Point(displayInfo.realSize);
- mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
- if (mMode == Mode.NO_BUTTON) {
- int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
- mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - touchHeight;
-
- final int assistantWidth = getResources()
- .getDimensionPixelSize(R.dimen.gestures_assistant_width);
- final float assistantHeight = Math.max(touchHeight,
- QuickStepContract.getWindowCornerRadius(getResources()));
- mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeTouchRegion.bottom;
- mAssistantLeftRegion.top = mAssistantRightRegion.top =
- mSwipeTouchRegion.bottom - assistantHeight;
-
- mAssistantLeftRegion.left = 0;
- mAssistantLeftRegion.right = assistantWidth;
-
- mAssistantRightRegion.right = mSwipeTouchRegion.right;
- mAssistantRightRegion.left = mSwipeTouchRegion.right - assistantWidth;
- } else {
- mAssistantLeftRegion.setEmpty();
- mAssistantRightRegion.setEmpty();
- switch (displayInfo.rotation) {
- case Surface.ROTATION_90:
- mSwipeTouchRegion.left = mSwipeTouchRegion.right
- - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
- break;
- case Surface.ROTATION_270:
- mSwipeTouchRegion.right = mSwipeTouchRegion.left
- + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
- break;
- default:
- mSwipeTouchRegion.top = mSwipeTouchRegion.bottom
- - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
- }
- }
- }
-
- @Override
- public void onNavigationModeChanged(Mode newMode) {
+ Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
+ mDeviceState.getDisplayId());
+ mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
+ mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
+ mMainChoreographer, this::onInputEvent);
if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
}
- if (mMode.hasGestures != newMode.hasGestures) {
- if (newMode.hasGestures) {
- DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
- } else {
- DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
- }
- }
- mMode = newMode;
- disposeEventHandlers();
+ mDeviceState.updateGestureTouchRegions();
+ }
+
+ /**
+ * Called when the navigation mode changes, guaranteed to be after the device state has updated.
+ */
+ private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
initInputMonitor();
-
- if (mMode == Mode.NO_BUTTON) {
- mExclusionListener.register();
- } else {
- mExclusionListener.unregister();
- }
+ resetHomeBounceSeenOnQuickstepEnabledFirstTime();
}
- @Override
- public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
- if (info.id != mDefaultDisplayId) {
- return;
- }
-
- initTouchBounds();
- }
-
- private void initWhenUserUnlocked() {
- mRecentsModel = RecentsModel.INSTANCE.get(this);
- mOverviewComponentObserver = new OverviewComponentObserver(this);
-
- mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
- mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
- mOverviewCallbacks = OverviewCallbacks.get(this);
- mTaskOverlayFactory = TaskOverlayFactory.INSTANCE.get(this);
+ @UiThread
+ public void onUserUnlocked() {
+ mTaskAnimationManager = new TaskAnimationManager();
+ mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
+ mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
+ mOverviewComponentObserver);
+ mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
- mIsUserUnlocked = true;
-
- sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
mInputConsumer.registerInputConsumer();
- onSystemUiProxySet();
onSystemUiFlagsChanged();
onAssistantVisibilityChanged();
@@ -483,51 +343,68 @@
// new ModelPreload().start(this);
mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
.getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT));
+ resetHomeBounceSeenOnQuickstepEnabledFirstTime();
- Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
+ PluginManagerWrapper.INSTANCE.get(getBaseContext()).addPluginListener(this,
+ OverscrollPlugin.class, false /* allowMultiple */);
}
- @UiThread
- private void onSystemUiProxySet() {
- if (mIsUserUnlocked) {
- mRecentsModel.setSystemUiProxy(mISystemUiProxy);
- mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+ private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
+ if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
+ // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
+ // mode doesn't have gestures
+ return;
+ }
+
+ // Reset home bounce seen on quick step enabled for first time
+ SharedPreferences sharedPrefs = Utilities.getPrefs(this);
+ if (!sharedPrefs.getBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)) {
+ sharedPrefs.edit()
+ .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
+ .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
+ .apply();
}
}
@UiThread
private void onSystemUiFlagsChanged() {
- if (mIsUserUnlocked) {
- mOverviewInteractionState.setSystemUiStateFlags(mSystemUiStateFlags);
- mOverviewComponentObserver.onSystemUiStateChanged(mSystemUiStateFlags);
+ if (mDeviceState.isUserUnlocked()) {
+ SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(
+ mDeviceState.getSystemUiStateFlags());
+ mOverviewComponentObserver.onSystemUiStateChanged();
+
+ // Update the tracing state
+ if ((mDeviceState.getSystemUiStateFlags() & SYSUI_STATE_TRACING_ENABLED) != 0) {
+ ProtoTracer.INSTANCE.get(TouchInteractionService.this).start();
+ } else {
+ ProtoTracer.INSTANCE.get(TouchInteractionService.this).stop();
+ }
}
}
@UiThread
private void onAssistantVisibilityChanged() {
- if (mIsUserUnlocked) {
- mOverviewComponentObserver.getActivityControlHelper().onAssistantVisibilityChanged(
- mLastAssistantVisibility);
+ if (mDeviceState.isUserUnlocked()) {
+ mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
+ mDeviceState.getAssistantVisibility());
}
}
@Override
public void onDestroy() {
sIsInitialized = false;
- if (mIsUserUnlocked) {
+ if (mDeviceState.isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
mOverviewComponentObserver.onDestroy();
+ PluginManagerWrapper.INSTANCE.get(getBaseContext()).removePluginListener(this);
}
disposeEventHandlers();
- if (mMode.hasGestures) {
- DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
- }
+ mDeviceState.destroy();
+ SystemUiProxy.INSTANCE.get(this).setProxy(null);
+ ProtoTracer.INSTANCE.get(TouchInteractionService.this).stop();
+ ProtoTracer.INSTANCE.get(this).remove(this);
sConnected = false;
- Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
- SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
- mExclusionListener.unregister();
-
super.onDestroy();
}
@@ -545,206 +422,237 @@
Log.e(TAG, "Unknown event " + ev);
return;
}
-
MotionEvent event = (MotionEvent) ev;
- if (event.getAction() == ACTION_DOWN) {
- mLogId = TOUCH_INTERACTION_LOG.generateAndSetLogId();
- sSwipeSharedState.setLogTraceId(mLogId);
- if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
- boolean useSharedState = mConsumer.useSharedSwipeState();
+ TestLogging.recordMotionEvent(
+ TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
+
+ if (!mDeviceState.isUserUnlocked()) {
+ return;
+ }
+
+ Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
+ TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
+ mDeviceState.setOrientationTransformIfNeeded(event);
+
+ if (event.getAction() == ACTION_DOWN) {
+ GestureState newGestureState = new GestureState(mOverviewComponentObserver,
+ ActiveGestureLog.INSTANCE.generateAndSetLogId());
+ newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
+ () -> mAM.getRunningTask(0)));
+
+ if (mDeviceState.isInSwipeUpTouchRegion(event)) {
mConsumer.onConsumerAboutToBeSwitched();
- mConsumer = newConsumer(useSharedState, event);
- TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
+ mConsumer = newConsumer(mGestureState, newGestureState, event);
+
+ ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
mUncheckedConsumer = mConsumer;
- } else if (mIsUserUnlocked && mMode == Mode.NO_BUTTON
- && canTriggerAssistantAction(event)) {
+ } else if (mDeviceState.isUserUnlocked()
+ && mDeviceState.isFullyGesturalNavMode()
+ && mDeviceState.canTriggerAssistantAction(event)) {
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
// not interrupt it. QuickSwitch assumes that interruption can only happen if the
// next gesture is also quick switch.
- mUncheckedConsumer =
- new AssistantTouchConsumer(
- this,
- mISystemUiProxy,
- mOverviewComponentObserver.getActivityControlHelper(),
- InputConsumer.NO_OP,
- mInputMonitorCompat,
- mOverviewComponentObserver.assistantGestureIsConstrained());
+ mUncheckedConsumer = new AssistantInputConsumer(
+ this,
+ newGestureState,
+ InputConsumer.NO_OP, mInputMonitorCompat,
+ mOverviewComponentObserver.assistantGestureIsConstrained());
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
+
+ // Save the current gesture state
+ mGestureState = newGestureState;
}
- TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
+ ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
mUncheckedConsumer.onMotionEvent(event);
+ TraceHelper.INSTANCE.endFlagsOverride(traceToken);
}
- private boolean validSystemUiFlags() {
- return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
- && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
- && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
- && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
- || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
- }
+ private InputConsumer newConsumer(GestureState previousGestureState,
+ GestureState newGestureState, MotionEvent event) {
+ boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
- private boolean canTriggerAssistantAction(MotionEvent ev) {
- return mAssistantAvailable
- && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
- && (mAssistantLeftRegion.contains(ev.getX(), ev.getY()) ||
- mAssistantRightRegion.contains(ev.getX(), ev.getY()))
- && !ActivityManagerWrapper.getInstance().isLockToAppActive();
- }
-
- private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
- boolean isInValidSystemUiState = validSystemUiFlags();
-
- if (!mIsUserUnlocked) {
- if (isInValidSystemUiState) {
+ if (!mDeviceState.isUserUnlocked()) {
+ if (canStartSystemGesture) {
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
// launched while device is locked even after exiting direct boot mode (e.g. camera).
- return createDeviceLockedInputConsumer(mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
+ return createDeviceLockedInputConsumer(newGestureState);
} else {
return mResetGestureInputConsumer;
}
}
- // When using sharedState, bypass systemState check as this is a followup gesture and the
- // first gesture started in a valid system state.
- InputConsumer base = isInValidSystemUiState || useSharedState
- ? newBaseConsumer(useSharedState, event) : mResetGestureInputConsumer;
- if (mMode == Mode.NO_BUTTON) {
- final ActivityControlHelper activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
- if (canTriggerAssistantAction(event)) {
- base = new AssistantTouchConsumer(
- this,
- mISystemUiProxy,
- activityControl,
- base,
- mInputMonitorCompat,
- mOverviewComponentObserver.assistantGestureIsConstrained());
+ // When there is an existing recents animation running, bypass systemState check as this is
+ // a followup gesture and the first gesture started in a valid system state.
+ InputConsumer base = canStartSystemGesture
+ || previousGestureState.isRecentsAnimationRunning()
+ ? newBaseConsumer(previousGestureState, newGestureState, event)
+ : mResetGestureInputConsumer;
+ // TODO(b/149880412): 2 button landscape mode is wrecked. Fixit!
+ if (mDeviceState.isGesturalNavMode()) {
+ handleOrientationSetup(base);
+ }
+ if (mDeviceState.isFullyGesturalNavMode()) {
+ if (mDeviceState.canTriggerAssistantAction(event)) {
+ base = new AssistantInputConsumer(
+ this,
+ newGestureState,
+ base,
+ mInputMonitorCompat,
+ mOverviewComponentObserver.assistantGestureIsConstrained());
}
- if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
+ if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
+ OverscrollPlugin plugin = null;
+ if (FeatureFlags.FORCE_LOCAL_OVERSCROLL_PLUGIN.get()) {
+ TaskOverlayFactory factory =
+ TaskOverlayFactory.INSTANCE.get(getApplicationContext());
+ plugin = factory.getLocalOverscrollPlugin(); // may be null
+ }
+
+ // If not local plugin was forced, use the actual overscroll plugin if available.
+ if (plugin == null && mOverscrollPlugin != null && mOverscrollPlugin.isActive()) {
+ plugin = mOverscrollPlugin;
+ }
+
+ if (plugin != null) {
+ // Put the overscroll gesture as higher priority than the Assistant or base
+ // gestures
+ base = new OverscrollInputConsumer(this, newGestureState, base,
+ mInputMonitorCompat, plugin);
+ }
+ }
+
+ if (mDeviceState.isScreenPinningActive()) {
// Note: we only allow accessibility to wrap this, and it replaces the previous
// base input consumer (which should be NO_OP anyway since topTaskLocked == true).
- base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl);
+ base = new ScreenPinnedInputConsumer(this, newGestureState);
}
- if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
- base = new AccessibilityInputConsumer(this, mISystemUiProxy,
- (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
- mInputMonitorCompat, mSwipeTouchRegion);
+ if (mDeviceState.isAccessibilityMenuAvailable()) {
+ base = new AccessibilityInputConsumer(this, mDeviceState, base,
+ mInputMonitorCompat);
}
} else {
- if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
+ if (mDeviceState.isScreenPinningActive()) {
base = mResetGestureInputConsumer;
}
}
return base;
}
- private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
- RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
- if (!useSharedState) {
- sSwipeSharedState.clearAllState(false /* finishAnimation */);
+ private void handleOrientationSetup(InputConsumer baseInputConsumer) {
+ if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
+ return;
}
- if ((mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) {
- // This handles apps showing over the lockscreen (e.g. camera)
- return createDeviceLockedInputConsumer(runningTaskInfo);
+ mDeviceState.enableMultipleRegions(baseInputConsumer instanceof OtherActivityInputConsumer);
+ BaseDraggingActivity activity =
+ mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ if (activity == null || !(activity.getOverviewPanel() instanceof RecentsView)) {
+ return;
}
+ ((RecentsView) activity.getOverviewPanel())
+ .setLayoutRotation(mDeviceState.getCurrentActiveRotation(),
+ mDeviceState.getDisplayRotation());
+ activity.getDragLayer().recreateControllers();
+ }
- final ActivityControlHelper activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
+ private InputConsumer newBaseConsumer(GestureState previousGestureState,
+ GestureState gestureState, MotionEvent event) {
+ if (mDeviceState.isKeyguardShowingOccluded()) {
+ // This handles apps showing over the lockscreen (e.g. camera)
+ return createDeviceLockedInputConsumer(gestureState);
+ }
boolean forceOverviewInputConsumer = false;
- if (AssistantUtilities.isExcludedAssistant(runningTaskInfo)) {
+ if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
// In the case where we are in the excluded assistant state, ignore it and treat the
// running activity as the task behind the assistant
- runningTaskInfo = mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */);
+ gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant",
+ () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */)));
ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
- ComponentName runningComponent = runningTaskInfo.baseIntent.getComponent();
+ ComponentName runningComponent =
+ gestureState.getRunningTask().baseIntent.getComponent();
forceOverviewInputConsumer =
- runningComponent != null && runningComponent.equals(homeComponent);
+ runningComponent != null && runningComponent.equals(homeComponent);
}
- if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
- && !sSwipeSharedState.recentsAnimationFinishInterrupted) {
- return mResetGestureInputConsumer;
- } else if (sSwipeSharedState.recentsAnimationFinishInterrupted) {
+ if (previousGestureState.getFinishingRecentsAnimationTaskId() > 0) {
// If the finish animation was interrupted, then continue using the other activity input
// consumer but with the next task as the running task
RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
- info.id = sSwipeSharedState.nextRunningTaskId;
- return createOtherActivityInputConsumer(event, info);
- } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()
+ info.id = previousGestureState.getFinishingRecentsAnimationTaskId();
+ gestureState.updateRunningTask(info);
+ return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
+ } else if (gestureState.getRunningTask() == null) {
+ return mResetGestureInputConsumer;
+ } else if (previousGestureState.isRunningAnimationToLauncher()
+ || gestureState.getActivityInterface().isResumed()
|| forceOverviewInputConsumer) {
- return createOverviewInputConsumer(event, forceOverviewInputConsumer);
- } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
- return createOverviewInputConsumer(event, forceOverviewInputConsumer);
- } else if (mGestureBlockingActivity != null && runningTaskInfo != null
- && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
+ return createOverviewInputConsumer(
+ previousGestureState, gestureState, event, forceOverviewInputConsumer);
+ } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+ && gestureState.getActivityInterface().isInLiveTileMode()) {
+ return createOverviewInputConsumer(
+ previousGestureState, gestureState, event, forceOverviewInputConsumer);
+ } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
return mResetGestureInputConsumer;
} else {
- return createOtherActivityInputConsumer(event, runningTaskInfo);
+ return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
}
}
- private boolean disableHorizontalSwipe(MotionEvent event) {
- // mExclusionRegion can change on binder thread, use a local instance here.
- Region exclusionRegion = mExclusionRegion;
- return mMode == Mode.NO_BUTTON && exclusionRegion != null
- && exclusionRegion.contains((int) event.getX(), (int) event.getY());
- }
-
- private InputConsumer createOtherActivityInputConsumer(MotionEvent event,
- RunningTaskInfo runningTaskInfo) {
+ private InputConsumer createOtherActivityInputConsumer(GestureState previousGestureState,
+ GestureState gestureState, MotionEvent event) {
final boolean shouldDefer;
final BaseSwipeUpHandler.Factory factory;
- if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
- shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
- factory = mFallbackNoButtonFactory;
+ if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
+ shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
+ factory = mFallbackSwipeHandlerFactory;
} else {
- shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
- .deferStartingActivity(mActiveNavBarRegion, event);
- factory = mWindowTreansformFactory;
+ shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
+ event);
+ factory = mLauncherSwipeHandlerFactory;
}
- return new OtherActivityInputConsumer(this, runningTaskInfo,
- shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
- sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
- disableHorizontalSwipe(event), factory, mLogId);
+ final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
+ return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
+ gestureState, shouldDefer, this::onConsumerInactive,
+ mInputMonitorCompat, disableHorizontalSwipe, factory);
}
- private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
- if (mMode == Mode.NO_BUTTON && taskInfo != null) {
- return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat,
- mSwipeTouchRegion, taskInfo.taskId, mLogId);
+ private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
+ if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
+ return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
+ gestureState, mInputMonitorCompat);
} else {
return mResetGestureInputConsumer;
}
}
- public InputConsumer createOverviewInputConsumer(MotionEvent event,
+ public InputConsumer createOverviewInputConsumer(GestureState previousGestureState,
+ GestureState gestureState, MotionEvent event,
boolean forceOverviewInputConsumer) {
- final ActivityControlHelper activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
- BaseDraggingActivity activity = activityControl.getCreatedActivity();
+ BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity();
if (activity == null) {
return mResetGestureInputConsumer;
}
if (activity.getRootView().hasWindowFocus()
- || sSwipeSharedState.goingToLauncher
- || (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+ || previousGestureState.isRunningAnimationToLauncher()
+ || (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
&& forceOverviewInputConsumer)) {
- return new OverviewInputConsumer(activity, mInputMonitorCompat,
+ return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
- return new OverviewWithoutFocusInputConsumer(activity, mInputMonitorCompat,
- disableHorizontalSwipe(event));
+ final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
+ return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState,
+ mInputMonitorCompat, disableHorizontalSwipe);
}
}
@@ -759,10 +667,10 @@
}
private void preloadOverview(boolean fromInit) {
- if (!mIsUserUnlocked) {
+ if (!mDeviceState.isUserUnlocked()) {
return;
}
- if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+ if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
// Prevent the overview from being started before the real home on first boot.
return;
}
@@ -773,12 +681,12 @@
return;
}
- final ActivityControlHelper<BaseDraggingActivity> activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
- if (activityControl.getCreatedActivity() == null) {
+ final BaseActivityInterface<BaseDraggingActivity> activityInterface =
+ mOverviewComponentObserver.getActivityInterface();
+ if (activityInterface.getCreatedActivity() == null) {
// Make sure that UI states will be initialized.
- activityControl.createActivityInitListener((activity, wasVisible) -> {
- AppLaunchTracker.INSTANCE.get(activity);
+ activityInterface.createActivityInitListener((wasVisible) -> {
+ AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
return false;
}).register();
} else if (fromInit) {
@@ -788,19 +696,18 @@
return;
}
- // Pass null animation handler to indicate this start is preload.
- startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
- null);
+ mTaskAnimationManager.preloadRecentsAnimation(
+ mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (!mIsUserUnlocked) {
+ if (!mDeviceState.isUserUnlocked()) {
return;
}
- final ActivityControlHelper activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
- final BaseDraggingActivity activity = activityControl.getCreatedActivity();
+ final BaseActivityInterface activityInterface =
+ mOverviewComponentObserver.getActivityInterface();
+ final BaseDraggingActivity activity = activityInterface.getCreatedActivity();
if (activity == null || activity.isStarted()) {
// We only care about the existing background activity.
return;
@@ -828,32 +735,29 @@
}
} else {
// Dump everything
- pw.println("TouchState:");
- pw.println(" navMode=" + mMode);
- pw.println(" validSystemUiFlags=" + validSystemUiFlags());
- pw.println(" systemUiFlags=" + mSystemUiStateFlags);
- pw.println(" systemUiFlagsDesc="
- + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
- pw.println(" assistantAvailable=" + mAssistantAvailable);
- pw.println(" assistantDisabled="
- + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
- boolean resumed = mOverviewComponentObserver != null
- && mOverviewComponentObserver.getActivityControlHelper().isResumed();
- pw.println(" resumed=" + resumed);
- pw.println(" useSharedState=" + mConsumer.useSharedSwipeState());
- if (mConsumer.useSharedSwipeState()) {
- sSwipeSharedState.dump(" ", pw);
+ FeatureFlags.dump(pw);
+ if (mDeviceState.isUserUnlocked()) {
+ PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
}
+ mDeviceState.dump(pw);
+ if (mOverviewComponentObserver != null) {
+ mOverviewComponentObserver.dump(pw);
+ }
+ if (mGestureState != null) {
+ mGestureState.dump(pw);
+ }
+ pw.println("TouchState:");
+ BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
+ : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ boolean resumed = mOverviewComponentObserver != null
+ && mOverviewComponentObserver.getActivityInterface().isResumed();
+ pw.println(" createdOverviewActivity=" + createdOverviewActivity);
+ pw.println(" resumed=" + resumed);
pw.println(" mConsumer=" + mConsumer.getName());
- pw.println("FeatureFlags:");
- pw.println(" APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get());
- pw.println(" QUICKSTEP_SPRINGS=" + QUICKSTEP_SPRINGS.get());
- pw.println(" ADAPTIVE_ICON_WINDOW_ANIM=" + ADAPTIVE_ICON_WINDOW_ANIM.get());
- pw.println(" ENABLE_QUICKSTEP_LIVE_TILE=" + ENABLE_QUICKSTEP_LIVE_TILE.get());
- pw.println(" ENABLE_HINTS_IN_OVERVIEW=" + ENABLE_HINTS_IN_OVERVIEW.get());
- pw.println(" FAKE_LANDSCAPE_UI=" + FAKE_LANDSCAPE_UI.get());
- TOUCH_INTERACTION_LOG.dump("", pw);
-
+ ActiveGestureLog.INSTANCE.dump("", pw);
+ pw.println("ProtoTrace:");
+ pw.println(" file="
+ + ProtoTracer.INSTANCE.get(TouchInteractionService.this).getTraceFile());
}
}
@@ -865,26 +769,26 @@
private void onCommand(PrintWriter pw, ArgList args) {
switch (args.nextArg()) {
case "clear-touch-log":
- TOUCH_INTERACTION_LOG.clear();
+ ActiveGestureLog.INSTANCE.clear();
break;
}
}
- private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
+ private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
- return new WindowTransformSwipeHandler(runningTask, this, touchTimeMs,
- mOverviewComponentObserver, continuingLastGesture, mInputConsumer, mRecentsModel);
+ return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+ gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
- private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(RunningTaskInfo runningTask,
+ private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
- return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask,
- mRecentsModel, mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
+ return new FallbackSwipeHandler(this, mDeviceState, gestureState,
+ mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
}
protected boolean shouldNotifyBackGesture() {
return mBackGestureNotificationCounter > 0 &&
- mGestureBlockingActivity != null;
+ !mDeviceState.getGestureBlockedActivityPackages().isEmpty();
}
@WorkerThread
@@ -893,8 +797,8 @@
mBackGestureNotificationCounter--;
Utilities.getDevicePrefs(this).edit()
.putInt(KEY_BACK_NOTIFICATION_COUNT, mBackGestureNotificationCounter).apply();
- sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(
- mGestureBlockingActivity.getPackageName()));
+ mDeviceState.getGestureBlockedActivityPackages().forEach(blockedPackage ->
+ sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(blockedPackage)));
}
}
@@ -902,4 +806,23 @@
UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
.startRecentsActivity(intent, null, listener, null, null));
}
+
+ @Override
+ public void onPluginConnected(OverscrollPlugin overscrollPlugin, Context context) {
+ mOverscrollPlugin = overscrollPlugin;
+ }
+
+ @Override
+ public void onPluginDisconnected(OverscrollPlugin overscrollPlugin) {
+ mOverscrollPlugin = null;
+ }
+
+ @Override
+ public void writeToProto(LauncherTraceProto proto) {
+ if (proto.touchInteractionService == null) {
+ proto.touchInteractionService = new TouchInteractionServiceProto();
+ }
+ proto.touchInteractionService.serviceConnected = true;
+ proto.touchInteractionService.serviceConnected = true;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
new file mode 100644
index 0000000..cbb6ad4
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.graphics.Canvas;
+import android.view.View;
+
+import com.android.systemui.shared.system.WindowCallbacksCompat;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utility class for helpful methods related to {@link View} objects.
+ */
+public class ViewUtils {
+
+ /** See {@link #postDraw(View, Runnable, BooleanSupplier)}} */
+ public static boolean postDraw(View view, Runnable onFinishRunnable) {
+ return postDraw(view, onFinishRunnable, () -> false);
+ }
+
+ /**
+ * Inject some addition logic in order to make sure that the view is updated smoothly post
+ * draw, and allow addition task to be run after view update.
+ *
+ * @param onFinishRunnable runnable to be run right after the view finishes drawing.
+ */
+ public static boolean postDraw(View view, Runnable onFinishRunnable, BooleanSupplier canceled) {
+ // Defer finishing the animation until the next launcher frame with the
+ // new thumbnail
+ return new WindowCallbacksCompat(view) {
+ // The number of frames to defer until we actually finish the animation
+ private int mDeferFrameCount = 2;
+
+ @Override
+ public void onPostDraw(Canvas canvas) {
+ // If we were cancelled after this was attached, do not update
+ // the state.
+ if (canceled.getAsBoolean()) {
+ detach();
+ return;
+ }
+
+ if (mDeferFrameCount > 0) {
+ mDeferFrameCount--;
+ // Workaround, detach and reattach to invalidate the root node for
+ // another draw
+ detach();
+ attach();
+ view.invalidate();
+ return;
+ }
+
+ if (onFinishRunnable != null) {
+ onFinishRunnable.run();
+ }
+ detach();
+ }
+ }.attach();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
new file mode 100644
index 0000000..6f919c1
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.fallback;
+
+import android.view.MotionEvent;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+
+/**
+ * In 0-button mode, intercepts swipe up from the nav bar on FallbackRecentsView to go home.
+ */
+public class FallbackNavBarTouchController implements TouchController {
+
+ private final RecentsActivity mActivity;
+ @Nullable
+ private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
+
+ public FallbackNavBarTouchController(RecentsActivity activity) {
+ mActivity = activity;
+ SysUINavigationMode.Mode sysUINavigationMode = SysUINavigationMode.getMode(mActivity);
+ if (sysUINavigationMode == SysUINavigationMode.Mode.NO_BUTTON) {
+ NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
+ DefaultDisplay.INSTANCE.get(mActivity).getInfo());
+ mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
+ true /* disableHorizontalSwipe */, navBarPosition,
+ null /* onInterceptTouch */, this::onSwipeUp);
+ } else {
+ mTriggerSwipeUpTracker = null;
+ }
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
+ if (cameFromNavBar && mTriggerSwipeUpTracker != null) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mTriggerSwipeUpTracker.init();
+ }
+ onControllerTouchEvent(ev);
+ return mTriggerSwipeUpTracker.interceptedTouch();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ if (mTriggerSwipeUpTracker != null) {
+ mTriggerSwipeUpTracker.onMotionEvent(ev);
+ return true;
+ }
+ return false;
+ }
+
+ private void onSwipeUp(boolean wasFling) {
+ mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 7f1aae5..dc0c194 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -142,7 +142,7 @@
mZoomTranslationY = 0f;
} else {
TaskView dummyTask = getTaskViewAt(0);
- ScaleAndTranslation sat = getTempClipAnimationHelper()
+ ScaleAndTranslation sat = getTempAppWindowAnimationHelper()
.updateForFullscreenOverview(dummyTask)
.getScaleAndTranslation();
mZoomScale = sat.scale;
@@ -195,4 +195,9 @@
}
super.applyLoadPlan(tasks);
}
+
+ @Override
+ protected boolean supportsVerticalLandscape() {
+ return false;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 1820729..2c5d631 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -21,7 +21,6 @@
import android.graphics.Rect;
import android.util.AttributeSet;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TouchController;
@@ -31,13 +30,11 @@
public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
private static final int MIN_SIZE = 10;
- private final RecentsActivity mActivity;
private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
public RecentsRootView(Context context, AttributeSet attrs) {
super(context, attrs, 1 /* alphaChannelCount */);
- mActivity = BaseActivity.fromContext(context);
setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_LAYOUT_STABLE);
@@ -47,8 +44,12 @@
return mLastKnownSize;
}
- public void setup() {
- mControllers = new TouchController[] { new RecentsTaskController(mActivity) };
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[] {
+ new RecentsTaskController(mActivity),
+ new FallbackNavBarTouchController(mActivity),
+ };
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 1f73a28..5ad48eb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -23,31 +23,29 @@
import static android.view.MotionEvent.ACTION_UP;
import android.content.Context;
-import android.graphics.RectF;
-import android.os.RemoteException;
-import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.launcher3.R;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.MotionPauseDetector;
-import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
- * Touch consumer for two finger swipe actions for accessibility actions
+ * Input consumer for two finger swipe actions for accessibility actions
*/
public class AccessibilityInputConsumer extends DelegateInputConsumer {
private static final String TAG = "A11yInputConsumer";
- private final ISystemUiProxy mSystemUiProxy;
+ private final Context mContext;
private final VelocityTracker mVelocityTracker;
private final MotionPauseDetector mMotionPauseDetector;
- private final boolean mAllowLongClick;
- private final RectF mSwipeTouchRegion;
+ private final RecentsAnimationDeviceState mDeviceState;
private final float mMinGestureDistance;
private final float mMinFlingVelocity;
@@ -56,19 +54,17 @@
private float mDownY;
private float mTotalY;
- public AccessibilityInputConsumer(Context context, ISystemUiProxy systemUiProxy,
- boolean allowLongClick, InputConsumer delegate, InputMonitorCompat inputMonitor,
- RectF swipeTouchRegion) {
+ public AccessibilityInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+ InputConsumer delegate, InputMonitorCompat inputMonitor) {
super(delegate, inputMonitor);
- mSystemUiProxy = systemUiProxy;
+ mContext = context;
mVelocityTracker = VelocityTracker.obtain();
mMinGestureDistance = context.getResources()
.getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
- mSwipeTouchRegion = swipeTouchRegion;
+ mDeviceState = deviceState;
mMotionPauseDetector = new MotionPauseDetector(context);
- mAllowLongClick = allowLongClick;
}
@Override
@@ -103,7 +99,7 @@
case ACTION_POINTER_DOWN: {
if (mState == STATE_INACTIVE) {
int pointerIndex = ev.getActionIndex();
- if (mSwipeTouchRegion.contains(ev.getX(pointerIndex), ev.getY(pointerIndex))
+ if (mDeviceState.isInSwipeUpTouchRegion(ev, pointerIndex)
&& mDelegate.allowInterceptByParent()) {
setActive(ev);
@@ -116,34 +112,29 @@
break;
}
case ACTION_MOVE: {
- if (mState == STATE_ACTIVE && mAllowLongClick) {
+ if (mState == STATE_ACTIVE && mDeviceState.isAccessibilityMenuShortcutAvailable()) {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
}
-
- mMotionPauseDetector.addPosition(ev.getY(pointerIndex) - mDownY,
- ev.getEventTime());
+ mMotionPauseDetector.addPosition(ev, pointerIndex);
}
break;
}
case ACTION_UP:
if (mState == STATE_ACTIVE) {
- try {
- if (mAllowLongClick && mMotionPauseDetector.isPaused()) {
- mSystemUiProxy.notifyAccessibilityButtonLongClicked();
- } else {
- mTotalY += (ev.getY() - mDownY);
- mVelocityTracker.computeCurrentVelocity(1000);
+ if (mDeviceState.isAccessibilityMenuShortcutAvailable()
+ && mMotionPauseDetector.isPaused()) {
+ SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonLongClicked();
+ } else {
+ mTotalY += (ev.getY() - mDownY);
+ mVelocityTracker.computeCurrentVelocity(1000);
- if ((-mTotalY) > mMinGestureDistance
- || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
- mSystemUiProxy.notifyAccessibilityButtonClicked(
- Display.DEFAULT_DISPLAY);
- }
+ if ((-mTotalY) > mMinGestureDistance
+ || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
+ SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonClicked(
+ Display.DEFAULT_DISPLAY);
}
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to notify accessibility event", e);
}
}
// Follow through
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
similarity index 78%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index d4cdaf0..89e6931 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -37,9 +37,7 @@
import android.content.res.Resources;
import android.graphics.PointF;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.SystemClock;
-import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.HapticFeedbackConstants;
@@ -50,16 +48,18 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.quickstep.ActivityControlHelper;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
* Touch consumer for handling events to launch assistant from launcher
*/
-public class AssistantTouchConsumer extends DelegateInputConsumer {
+public class AssistantInputConsumer extends DelegateInputConsumer {
- private static final String TAG = "AssistantTouchConsumer";
+ private static final String TAG = "AssistantInputConsumer";
private static final long RETRACT_ANIMATION_DURATION_MS = 300;
// From //java/com/google/android/apps/gsa/search/shared/util/OpaContract.java.
@@ -81,25 +81,26 @@
private long mDragTime;
private float mLastProgress;
private int mDirection;
- private ActivityControlHelper mActivityControlHelper;
+ private BaseActivityInterface mActivityInterface;
private final float mDragDistThreshold;
private final float mFlingDistThreshold;
private final long mTimeThreshold;
private final int mAngleThreshold;
private final float mSquaredSlop;
- private final ISystemUiProxy mSysUiProxy;
private final Context mContext;
private final GestureDetector mGestureDetector;
private final boolean mIsAssistGestureConstrained;
- public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
- ActivityControlHelper activityControlHelper, InputConsumer delegate,
- InputMonitorCompat inputMonitor, boolean isAssistGestureConstrained) {
+ public AssistantInputConsumer(
+ Context context,
+ GestureState gestureState,
+ InputConsumer delegate,
+ InputMonitorCompat inputMonitor,
+ boolean isAssistGestureConstrained) {
super(delegate, inputMonitor);
final Resources res = context.getResources();
mContext = context;
- mSysUiProxy = systemUiProxy;
mIsAssistGestureConstrained = isAssistGestureConstrained;
mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
mFlingDistThreshold = res.getDimension(R.dimen.gestures_assistant_fling_threshold);
@@ -109,7 +110,7 @@
float slop = ViewConfiguration.get(context).getScaledTouchSlop();
mSquaredSlop = slop * slop;
- mActivityControlHelper = activityControlHelper;
+ mActivityInterface = gestureState.getActivityInterface();
mGestureDetector = new GestureDetector(context, new AssistantGestureListener());
}
@@ -133,8 +134,8 @@
case ACTION_POINTER_DOWN: {
if (mState != STATE_ACTIVE) {
mState = STATE_DELEGATE_ACTIVE;
- break;
}
+ break;
}
case ACTION_POINTER_UP: {
int ptrIdx = ev.getActionIndex();
@@ -200,13 +201,7 @@
SWIPE_NOOP, mDirection, NAVBAR);
animator.addUpdateListener(valueAnimator -> {
float progress = (float) valueAnimator.getAnimatedValue();
- try {
-
- mSysUiProxy.onAssistantProgress(progress);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
- + progress, e);
- }
+ SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(progress);
});
animator.setInterpolator(Interpolators.DEACCEL_2);
animator.start();
@@ -226,22 +221,17 @@
private void updateAssistantProgress() {
if (!mLaunchedAssistant) {
mLastProgress = Math.min(mDistance * 1f / mDragDistThreshold, 1) * mTimeFraction;
- try {
- if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
- mSysUiProxy.onAssistantGestureCompletion(0);
- startAssistantInternal(SWIPE);
+ if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
+ SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(0);
+ startAssistantInternal(SWIPE);
- Bundle args = new Bundle();
- args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
- args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
- mSysUiProxy.startAssistant(args);
- mLaunchedAssistant = true;
- } else {
- mSysUiProxy.onAssistantProgress(mLastProgress);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + mLastProgress,
- e);
+ Bundle args = new Bundle();
+ args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
+ args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
+ SystemUiProxy.INSTANCE.get(mContext).startAssistant(args);
+ mLaunchedAssistant = true;
+ } else {
+ SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(mLastProgress);
}
}
}
@@ -250,8 +240,7 @@
UserEventDispatcher.newInstance(mContext)
.logActionOnContainer(gestureType, mDirection, NAVBAR);
- BaseDraggingActivity launcherActivity = mActivityControlHelper
- .getCreatedActivity();
+ BaseDraggingActivity launcherActivity = mActivityInterface.getCreatedActivity();
if (launcherActivity != null) {
launcherActivity.getRootView().performHapticFeedback(
13, // HapticFeedbackConstants.GESTURE_END
@@ -276,25 +265,19 @@
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (!mIsAssistGestureConstrained
- && isValidAssistantGestureAngle(velocityX, -velocityY)
+ && isValidAssistantGestureAngle(velocityX, -velocityY)
&& mDistance >= mFlingDistThreshold
&& !mLaunchedAssistant
&& mState != STATE_DELEGATE_ACTIVE) {
mLastProgress = 1;
- try {
- mSysUiProxy.onAssistantGestureCompletion(
- (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
- startAssistantInternal(FLING);
+ SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(
+ (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
+ startAssistantInternal(FLING);
- Bundle args = new Bundle();
- args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
- mSysUiProxy.startAssistant(args);
- mLaunchedAssistant = true;
- } catch (RemoteException e) {
- Log.w(TAG,
- "Failed to send SysUI start/send assistant progress: " + mLastProgress,
- e);
- }
+ Bundle args = new Bundle();
+ args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
+ SystemUiProxy.INSTANCE.get(mContext).startAssistant(args);
+ mLaunchedAssistant = true;
}
return true;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 311ddd2..05c206f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -2,6 +2,9 @@
import android.view.MotionEvent;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.quickstep.InputConsumer;
import com.android.systemui.shared.system.InputMonitorCompat;
public abstract class DelegateInputConsumer implements InputConsumer {
@@ -22,11 +25,6 @@
}
@Override
- public boolean useSharedSwipeState() {
- return mDelegate.useSharedSwipeState();
- }
-
- @Override
public boolean allowInterceptByParent() {
return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
}
@@ -38,6 +36,7 @@
protected void setActive(MotionEvent ev) {
mState = STATE_ACTIVE;
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
mInputMonitor.pilferPointers();
// Send cancel event
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index b24c788..ba1d38c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -22,9 +22,8 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID;
-import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
import android.content.ComponentName;
import android.content.Context;
@@ -32,20 +31,26 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
import com.android.quickstep.LockScreenRecentsActivity;
import com.android.quickstep.MultiStateCallback;
-import com.android.quickstep.SwipeSharedState;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationTargets;
+import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -53,7 +58,7 @@
* A dummy input consumer used when the device is still locked, e.g. from secure camera.
*/
public class DeviceLockedInputConsumer implements InputConsumer,
- SwipeAnimationTargetSet.SwipeAnimationListener {
+ RecentsAnimationCallbacks.RecentsAnimationListener {
private static final float SCALE_DOWN = 0.75f;
@@ -71,45 +76,44 @@
getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
private final Context mContext;
+ private final RecentsAnimationDeviceState mDeviceState;
+ private final TaskAnimationManager mTaskAnimationManager;
+ private final GestureState mGestureState;
private final float mTouchSlopSquared;
- private final SwipeSharedState mSwipeSharedState;
private final InputMonitorCompat mInputMonitorCompat;
private final PointF mTouchDown = new PointF();
- private final ClipAnimationHelper mClipAnimationHelper;
- private int mLogId;
- private final ClipAnimationHelper.TransformParams mTransformParams;
+ private final AppWindowAnimationHelper mAppWindowAnimationHelper;
+ private final AppWindowAnimationHelper.TransformParams mTransformParams;
private final Point mDisplaySize;
private final MultiStateCallback mStateCallback;
- private final RectF mSwipeTouchRegion;
- public final int mRunningTaskId;
private VelocityTracker mVelocityTracker;
private float mProgress;
private boolean mThresholdCrossed = false;
- private SwipeAnimationTargetSet mTargetSet;
+ private RecentsAnimationController mRecentsAnimationController;
+ private RecentsAnimationTargets mRecentsAnimationTargets;
- public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState,
- InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId,
- int logId) {
+ public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager, GestureState gestureState,
+ InputMonitorCompat inputMonitorCompat) {
mContext = context;
+ mDeviceState = deviceState;
+ mTaskAnimationManager = taskAnimationManager;
+ mGestureState = gestureState;
mTouchSlopSquared = squaredTouchSlop(context);
- mSwipeSharedState = swipeSharedState;
- mClipAnimationHelper = new ClipAnimationHelper(context);
- mLogId = logId;
- mTransformParams = new ClipAnimationHelper.TransformParams();
+ mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
+ mTransformParams = new AppWindowAnimationHelper.TransformParams();
mInputMonitorCompat = inputMonitorCompat;
- mSwipeTouchRegion = swipeTouchRegion;
- mRunningTaskId = runningTaskId;
// Do not use DeviceProfile as the user data might be locked
mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
// Init states
mStateCallback = new MultiStateCallback(STATE_NAMES);
- mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
+ mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
this::endRemoteAnimation);
mVelocityTracker = VelocityTracker.obtain();
@@ -137,7 +141,7 @@
if (!mThresholdCrossed) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+ if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
finishTouchTracking(ev);
@@ -155,9 +159,7 @@
float dy = Math.max(mTouchDown.y - y, 0);
mProgress = dy / mDisplaySize.y;
mTransformParams.setProgress(mProgress);
- if (mTargetSet != null) {
- mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
- }
+ mAppWindowAnimationHelper.applyTransform(mTransformParams);
}
break;
}
@@ -202,45 +204,49 @@
private void startRecentsTransition() {
mThresholdCrossed = true;
- RecentsAnimationListenerSet newListenerSet =
- mSwipeSharedState.newRecentsAnimationListenerSet();
- newListenerSet.addListener(this);
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+ mInputMonitorCompat.pilferPointers();
+
Intent intent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_DEFAULT)
.setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
- .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
-
- mInputMonitorCompat.pilferPointers();
- startRecentsActivityAsync(intent, newListenerSet);
+ .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
+ mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this);
}
@Override
- public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- mTargetSet = targetSet;
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ mRecentsAnimationController = controller;
+ mRecentsAnimationTargets = targets;
Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
- RemoteAnimationTargetCompat targetCompat = targetSet.findTask(mRunningTaskId);
+ RemoteAnimationTargetCompat targetCompat = targets.findTask(
+ mGestureState.getRunningTaskId());
if (targetCompat != null) {
- mClipAnimationHelper.updateSource(displaySize, targetCompat);
+ mAppWindowAnimationHelper.updateSource(displaySize, targetCompat);
}
Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN);
displaySize.offsetTo(displaySize.left, 0);
- mClipAnimationHelper.updateTargetRect(displaySize);
- mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
+ mTransformParams.setTargetSet(mRecentsAnimationTargets)
+ .setLauncherOnTop(true);
+ mAppWindowAnimationHelper.updateTargetRect(displaySize);
+ mAppWindowAnimationHelper.applyTransform(mTransformParams);
mStateCallback.setState(STATE_TARGET_RECEIVED);
}
@Override
- public void onRecentsAnimationCanceled() {
- mTargetSet = null;
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
}
private void endRemoteAnimation() {
- if (mTargetSet != null) {
- mTargetSet.finishController(
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.finishController(
false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
deleted file mode 100644
index e0ff8af..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.inputconsumers;
-
-import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
-import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
-import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.os.Bundle;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
-import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.BaseSwipeUpHandler;
-import com.android.quickstep.MultiStateCallback;
-import com.android.quickstep.OverviewComponentObserver;
-import com.android.quickstep.RecentsActivity;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.SwipeSharedState;
-import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.quickstep.util.ObjectWrapper;
-import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.InputConsumerController;
-
-public class FallbackNoButtonInputConsumer extends
- BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
-
- private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
-
- private static int getFlagForIndex(int index, String name) {
- if (DEBUG_STATES) {
- STATE_NAMES[index] = name;
- }
- return 1 << index;
- }
-
- private static final int STATE_RECENTS_PRESENT =
- getFlagForIndex(0, "STATE_RECENTS_PRESENT");
- private static final int STATE_HANDLER_INVALIDATED =
- getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
-
- private static final int STATE_GESTURE_CANCELLED =
- getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
- private static final int STATE_GESTURE_COMPLETED =
- getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
- private static final int STATE_APP_CONTROLLER_RECEIVED =
- getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
-
- public enum GestureEndTarget {
- HOME(3, 100, 1),
- RECENTS(1, 300, 0),
- LAST_TASK(0, 150, 1),
- NEW_TASK(0, 150, 1);
-
- private final float mEndProgress;
- private final long mDurationMultiplier;
- private final float mLauncherAlpha;
-
- GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) {
- mEndProgress = endProgress;
- mDurationMultiplier = durationMultiplier;
- mLauncherAlpha = launcherAlpha;
- }
- }
-
- private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
-
- private boolean mIsMotionPaused = false;
- private GestureEndTarget mEndTarget;
-
- private final boolean mInQuickSwitchMode;
- private final boolean mContinuingLastGesture;
- private final boolean mRunningOverHome;
- private final boolean mSwipeUpOverHome;
-
- private final RunningTaskInfo mRunningTaskInfo;
-
- private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
- private RunningWindowAnim mFinishAnimation;
-
- public FallbackNoButtonInputConsumer(Context context,
- OverviewComponentObserver overviewComponentObserver,
- RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
- InputConsumerController inputConsumer,
- boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
- super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
- mLauncherAlpha.value = 1;
-
- mRunningTaskInfo = runningTaskInfo;
- mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
- mContinuingLastGesture = continuingLastGesture;
- mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo);
- mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
-
- if (mSwipeUpOverHome) {
- mClipAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
- } else {
- mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
- }
-
- initStateCallbacks();
- }
-
- private void initStateCallbacks() {
- mStateCallback = new MultiStateCallback(STATE_NAMES);
-
- mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
- this::onHandlerInvalidated);
- mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
- this::onHandlerInvalidatedWithRecents);
-
- mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
- this::finishAnimationTargetSetAnimationComplete);
-
- if (mInQuickSwitchMode) {
- mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
- | STATE_RECENTS_PRESENT,
- this::finishAnimationTargetSet);
- } else {
- mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
- this::finishAnimationTargetSet);
- }
- }
-
- private void onLauncherAlphaChanged() {
- if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
- applyTransformUnchecked();
- }
- }
-
- @Override
- protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
- mActivity = activity;
- mRecentsView = activity.getOverviewPanel();
- linkRecentsViewScroll();
- mRecentsView.setDisallowScrollToClearAll(true);
- mRecentsView.getClearAllButton().setVisibilityAlpha(0);
-
- mRecentsView.setZoomProgress(1);
-
- if (!mContinuingLastGesture) {
- if (mRunningOverHome) {
- mRecentsView.onGestureAnimationStart(mRunningTaskInfo);
- } else {
- mRecentsView.onGestureAnimationStart(mRunningTaskId);
- }
- }
- setStateOnUiThread(STATE_RECENTS_PRESENT);
- return true;
- }
-
- @Override
- protected boolean moveWindowWithRecentsScroll() {
- return mInQuickSwitchMode;
- }
-
- @Override
- public void initWhenReady() {
- if (mInQuickSwitchMode) {
- // Only init if we are in quickswitch mode
- super.initWhenReady();
- }
- }
-
- @Override
- public void updateDisplacement(float displacement) {
- if (!mInQuickSwitchMode) {
- super.updateDisplacement(displacement);
- }
- }
-
- @Override
- protected InputConsumer createNewInputProxyHandler() {
- // Just consume all input on the active task
- return InputConsumer.NO_OP;
- }
-
- @Override
- public void onMotionPauseChanged(boolean isPaused) {
- if (!mInQuickSwitchMode) {
- mIsMotionPaused = isPaused;
- mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
- .setDuration(150).start();
- performHapticFeedback();
- }
- }
-
- @Override
- public Intent getLaunchIntent() {
- if (mInQuickSwitchMode || mSwipeUpOverHome) {
- return mOverviewComponentObserver.getOverviewIntent();
- } else {
- return mOverviewComponentObserver.getHomeIntent();
- }
- }
-
- @Override
- public void updateFinalShift() {
- mTransformParams.setProgress(mCurrentShift.value);
- mRecentsAnimationWrapper.setWindowThresholdCrossed(!mInQuickSwitchMode
- && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
- if (mRecentsAnimationWrapper.targetSet != null) {
- applyTransformUnchecked();
- }
- }
-
- @Override
- public void onGestureCancelled() {
- updateDisplacement(0);
- mEndTarget = LAST_TASK;
- setStateOnUiThread(STATE_GESTURE_CANCELLED);
- }
-
- @Override
- public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
- mEndVelocityPxPerMs.set(0, velocity.y / 1000);
- if (mInQuickSwitchMode) {
- // For now set it to non-null, it will be reset before starting the animation
- mEndTarget = LAST_TASK;
- } else {
- float flingThreshold = mContext.getResources()
- .getDimension(R.dimen.quickstep_fling_threshold_velocity);
- boolean isFling = Math.abs(endVelocity) > flingThreshold;
-
- if (isFling) {
- mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
- } else if (mIsMotionPaused) {
- mEndTarget = RECENTS;
- } else {
- mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
- }
- }
- setStateOnUiThread(STATE_GESTURE_COMPLETED);
- }
-
- @Override
- public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
- if (mInQuickSwitchMode && mEndTarget != null) {
- sharedState.canGestureBeContinued = true;
- sharedState.goingToLauncher = false;
-
- mCanceled = true;
- mCurrentShift.cancelAnimation();
- if (mFinishAnimation != null) {
- mFinishAnimation.cancel();
- }
-
- if (mRecentsView != null) {
- if (mFinishingRecentsAnimationForNewTaskId != -1) {
- TaskView newRunningTaskView = mRecentsView.getTaskView(
- mFinishingRecentsAnimationForNewTaskId);
- int newRunningTaskId = newRunningTaskView != null
- ? newRunningTaskView.getTask().key.id
- : -1;
- mRecentsView.setCurrentTask(newRunningTaskId);
- sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
- }
- mRecentsView.setOnScrollChangeListener(null);
- }
- } else {
- setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- }
- }
-
- private void onHandlerInvalidated() {
- mActivityInitListener.unregister();
- if (mGestureEndCallback != null) {
- mGestureEndCallback.run();
- }
- if (mFinishAnimation != null) {
- mFinishAnimation.end();
- }
- }
-
- private void onHandlerInvalidatedWithRecents() {
- mRecentsView.onGestureAnimationEnd();
- mRecentsView.setDisallowScrollToClearAll(false);
- mRecentsView.getClearAllButton().setVisibilityAlpha(1);
- }
-
- private void finishAnimationTargetSetAnimationComplete() {
- switch (mEndTarget) {
- case HOME: {
- if (mSwipeUpOverHome) {
- mRecentsAnimationWrapper.finish(false, null, false);
- // Send a home intent to clear the task stack
- mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
- } else {
- mRecentsAnimationWrapper.finish(true, null, true);
- }
- break;
- }
- case LAST_TASK:
- mRecentsAnimationWrapper.finish(false, null, false);
- break;
- case RECENTS: {
- if (mSwipeUpOverHome) {
- mRecentsAnimationWrapper.finish(true, null, true);
- break;
- }
-
- ThumbnailData thumbnail =
- mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
- mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
- false /* screenshot */);
-
- ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
- ActivityOptionsCompat.setFreezeRecentTasksList(options);
-
- Bundle extras = new Bundle();
- extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
- extras.putInt(EXTRA_TASK_ID, mRunningTaskId);
-
- Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
- .putExtras(extras);
- mContext.startActivity(intent, options.toBundle());
- mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
- break;
- }
- case NEW_TASK: {
- startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
- break;
- }
- }
-
- setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- }
-
- private void finishAnimationTargetSet() {
- if (mInQuickSwitchMode) {
- // Recalculate the end target, some views might have been initialized after
- // gesture has ended.
- if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
- mEndTarget = LAST_TASK;
- } else {
- final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
- final int taskToLaunch = mRecentsView.getNextPage();
- mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
- ? NEW_TASK : LAST_TASK;
- }
- }
-
- float endProgress = mEndTarget.mEndProgress;
- long duration = (long) (mEndTarget.mDurationMultiplier *
- Math.abs(endProgress - mCurrentShift.value));
- if (mRecentsView != null) {
- duration = Math.max(duration, mRecentsView.getScroller().getDuration());
- }
- if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
- AnimationSuccessListener endListener = new AnimationSuccessListener() {
-
- @Override
- public void onAnimationSuccess(Animator animator) {
- finishAnimationTargetSetAnimationComplete();
- mFinishAnimation = null;
- }
- };
-
- if (mEndTarget == HOME && !mRunningOverHome) {
- RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
- anim.addAnimatorListener(endListener);
- anim.start(mEndVelocityPxPerMs);
- mFinishAnimation = RunningWindowAnim.wrap(anim);
- } else {
-
- AnimatorSet anim = new AnimatorSet();
- anim.play(mLauncherAlpha.animateToValue(
- mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
- anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
-
- anim.setDuration(duration);
- anim.addListener(endListener);
- anim.start();
- mFinishAnimation = RunningWindowAnim.wrap(anim);
- }
-
- } else {
- finishAnimationTargetSetAnimationComplete();
- }
- }
-
- @Override
- public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- super.onRecentsAnimationStart(targetSet);
- mRecentsAnimationWrapper.enableInputConsumer();
-
- if (mRunningOverHome) {
- mClipAnimationHelper.prepareAnimation(mDp, true);
- }
- applyTransformUnchecked();
-
- setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
- }
-
- @Override
- public void onRecentsAnimationCanceled() {
- mRecentsAnimationWrapper.setController(null);
- setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- }
-
- /**
- * Creates an animation that transforms the current app window into the home app.
- * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
- */
- private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
- HomeAnimationFactory factory = new HomeAnimationFactory() {
- @Override
- public RectF getWindowTargetRect() {
- return HomeAnimationFactory.getDefaultWindowTargetRect(mDp);
- }
-
- @Override
- public AnimatorPlaybackController createActivityAnimationToHome() {
- AnimatorSet anim = new AnimatorSet();
- anim.play(mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1));
- anim.setDuration(duration);
- return AnimatorPlaybackController.wrap(anim, duration);
- }
- };
- return createWindowAnimationToHome(startProgress, factory);
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e41880d..893868b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -22,45 +22,47 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
+
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.util.RaceConditionTracker.ENTER;
-import static com.android.launcher3.util.RaceConditionTracker.EXIT;
-import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
-import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
+import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.graphics.PointF;
-import android.graphics.RectF;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
+
import androidx.annotation.UiThread;
+
import com.android.launcher3.R;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.BaseSwipeUpHandler;
import com.android.quickstep.BaseSwipeUpHandler.Factory;
-import com.android.quickstep.OverviewCallbacks;
-import com.android.quickstep.SwipeSharedState;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.CachedEventDispatcher;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
+
import java.util.function.Consumer;
/**
@@ -75,21 +77,21 @@
// TODO: Move to quickstep contract
public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+ private final RecentsAnimationDeviceState mDeviceState;
+ private final NavBarPosition mNavBarPosition;
+ private final TaskAnimationManager mTaskAnimationManager;
+ private final GestureState mGestureState;
+ private RecentsAnimationCallbacks mActiveCallbacks;
private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
- private final RunningTaskInfo mRunningTask;
- private final OverviewCallbacks mOverviewCallbacks;
- private final SwipeSharedState mSwipeSharedState;
private final InputMonitorCompat mInputMonitorCompat;
- private final SysUINavigationMode.Mode mMode;
- private final RectF mSwipeTouchRegion;
+ private final BaseActivityInterface mActivityInterface;
private final BaseSwipeUpHandler.Factory mHandlerFactory;
- private final NavBarPosition mNavBarPosition;
-
private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
+
private VelocityTracker mVelocityTracker;
private BaseSwipeUpHandler mInteractionHandler;
@@ -118,36 +120,32 @@
ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
true /* restoreHomeStackPosition */);
};
- private int mLogId;
- public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
- boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
- Consumer<OtherActivityInputConsumer> onCompleteCallback,
- SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
- RectF swipeTouchRegion, boolean disableHorizontalSwipe,
- Factory handlerFactory, int logId) {
+ public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager, GestureState gestureState,
+ boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
+ InputMonitorCompat inputMonitorCompat, boolean disableHorizontalSwipe,
+ Factory handlerFactory) {
super(base);
- mLogId = logId;
-
+ mDeviceState = deviceState;
+ mNavBarPosition = mDeviceState.getNavBarPosition();
+ mTaskAnimationManager = taskAnimationManager;
+ mGestureState = gestureState;
mMainThreadHandler = new Handler(Looper.getMainLooper());
- mRunningTask = runningTaskInfo;
- mMode = SysUINavigationMode.getMode(base);
- mSwipeTouchRegion = swipeTouchRegion;
mHandlerFactory = handlerFactory;
+ mActivityInterface = mGestureState.getActivityInterface();
- mMotionPauseDetector = new MotionPauseDetector(base);
+ mMotionPauseDetector = new MotionPauseDetector(base, false,
+ mNavBarPosition.isLeftEdge() || mNavBarPosition.isRightEdge()
+ ? MotionEvent.AXIS_X : MotionEvent.AXIS_Y);
mMotionPauseMinDisplacement = base.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
mOnCompleteCallback = onCompleteCallback;
mVelocityTracker = VelocityTracker.obtain();
mInputMonitorCompat = inputMonitorCompat;
- boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
+ boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
- mOverviewCallbacks = overviewCallbacks;
- mSwipeSharedState = swipeSharedState;
-
- mNavBarPosition = new NavBarPosition(base);
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
@@ -178,8 +176,8 @@
// Proxy events to recents view
if (mPassedWindowMoveSlop && mInteractionHandler != null
&& !mRecentsViewDispatcher.hasConsumer()) {
- mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
- mNavBarPosition.getRotationMode()));
+ mRecentsViewDispatcher.setConsumer(mInteractionHandler
+ .getRecentsViewDispatcher(mNavBarPosition.getRotationMode()));
}
int edgeFlags = ev.getEdgeFlags();
ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -194,8 +192,8 @@
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
- RaceConditionTracker.onEvent(DOWN_EVT, ENTER);
- TraceHelper.beginSection("TouchInt");
+ Object traceToken = TraceHelper.INSTANCE.beginSection(DOWN_EVT,
+ FLAG_CHECK_FOR_RACE_CONDITIONS);
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
@@ -206,14 +204,14 @@
startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
}
- RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
+ TraceHelper.INSTANCE.endSection(traceToken);
break;
}
case ACTION_POINTER_DOWN: {
if (!mPassedPilferInputSlop) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+ if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
forceCancelGesture(ev);
}
}
@@ -289,10 +287,10 @@
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
}
- if (mMode == Mode.NO_BUTTON) {
+ if (mDeviceState.isFullyGesturalNavMode()) {
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|| isLikelyToStartNewTask);
- mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
+ mMotionPauseDetector.addPosition(ev);
mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask);
}
}
@@ -307,13 +305,14 @@
}
private void notifyGestureStarted() {
- TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+ ActiveGestureLog.INSTANCE.addLog("startQuickstep");
if (mInteractionHandler == null) {
return;
}
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
mInputMonitorCompat.pilferPointers();
- mOverviewCallbacks.closeAllWindows();
+ mActivityInterface.closeOverlay();
ActivityManagerWrapper.getInstance().closeSystemWindows(
CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
@@ -323,28 +322,24 @@
private void startTouchTrackingForWindowAnimation(
long touchTimeMs, boolean isLikelyToStartNewTask) {
- TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
+ ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
- RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
- final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
- listenerSet != null, isLikelyToStartNewTask);
+ mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
+ mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
+ mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
+ mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
+ mInteractionHandler.initWhenReady();
- mInteractionHandler = handler;
- handler.setGestureEndCallback(this::onInteractionGestureFinished);
- mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
- handler.initWhenReady();
-
- if (listenerSet != null) {
- listenerSet.addListener(handler);
- mSwipeSharedState.applyActiveRecentsAnimationState(handler);
+ if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+ mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
+ mActiveCallbacks.addListener(mInteractionHandler);
+ mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
notifyGestureStarted();
} else {
- RecentsAnimationListenerSet newListenerSet =
- mSwipeSharedState.newRecentsAnimationListenerSet();
- newListenerSet.addListener(handler);
- Intent intent = handler.getLaunchIntent();
- intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
- startRecentsActivityAsync(intent, newListenerSet);
+ Intent intent = mInteractionHandler.getLaunchIntent();
+ intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
+ mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
+ mInteractionHandler);
}
}
@@ -353,8 +348,8 @@
* the animation can still be running.
*/
private void finishTouchTracking(MotionEvent ev) {
- RaceConditionTracker.onEvent(UP_EVT, ENTER);
- TraceHelper.endSection("TouchInt");
+ Object traceToken = TraceHelper.INSTANCE.beginSection(UP_EVT,
+ FLAG_CHECK_FOR_RACE_CONDITIONS);
if (mPassedWindowMoveSlop && mInteractionHandler != null) {
if (ev.getActionMasked() == ACTION_CANCEL) {
@@ -364,10 +359,11 @@
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
- float velocity = mNavBarPosition.isRightEdge() ? velocityX
- : mNavBarPosition.isLeftEdge() ? -velocityX
+ float velocity = mNavBarPosition.isRightEdge()
+ ? velocityX
+ : mNavBarPosition.isLeftEdge()
+ ? -velocityX
: velocityY;
-
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
mDownPos);
@@ -388,7 +384,7 @@
mVelocityTracker.recycle();
mVelocityTracker = null;
mMotionPauseDetector.clear();
- RaceConditionTracker.onEvent(UP_EVT, EXIT);
+ TraceHelper.INSTANCE.endSection(traceToken);
}
@Override
@@ -399,7 +395,7 @@
// The consumer is being switched while we are active. Set up the shared state to be
// used by the next animation
removeListener();
- mInteractionHandler.onConsumerAboutToBeSwitched(mSwipeSharedState);
+ mInteractionHandler.onConsumerAboutToBeSwitched();
}
}
@@ -412,9 +408,8 @@
}
private void removeListener() {
- RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
- if (listenerSet != null) {
- listenerSet.removeListener(mInteractionHandler);
+ if (mActiveCallbacks != null) {
+ mActiveCallbacks.removeListener(mInteractionHandler);
}
}
@@ -429,11 +424,6 @@
}
@Override
- public boolean useSharedSwipeState() {
- return mInteractionHandler != null;
- }
-
- @Override
public boolean allowInterceptByParent() {
return !mPassedPilferInputSlop;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
new file mode 100644
index 0000000..0a21413
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.R;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.plugins.OverscrollPlugin;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Input consumer for handling events to pass to an {@code OverscrollPlugin}.
+ *
+ * @param <T> Draggable activity subclass used by RecentsView
+ */
+public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends DelegateInputConsumer {
+
+ private static final String TAG = "OverscrollInputConsumer";
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final PointF mStartDragPos = new PointF();
+ private final int mAngleThreshold;
+
+ private final float mFlingThresholdPx;
+ private int mActivePointerId = -1;
+ private boolean mPassedSlop = false;
+
+ private final float mSquaredSlop;
+
+ private final Context mContext;
+ private final GestureState mGestureState;
+ @Nullable
+ private final OverscrollPlugin mPlugin;
+ private final GestureDetector mGestureDetector;
+
+ private RecentsView mRecentsView;
+
+ public OverscrollInputConsumer(Context context, GestureState gestureState,
+ InputConsumer delegate, InputMonitorCompat inputMonitor, OverscrollPlugin plugin) {
+ super(delegate, inputMonitor);
+
+ mAngleThreshold = context.getResources()
+ .getInteger(R.integer.assistant_gesture_corner_deg_threshold);
+ mFlingThresholdPx = context.getResources()
+ .getDimension(R.dimen.gestures_overscroll_fling_threshold);
+ mContext = context;
+ mGestureState = gestureState;
+ mPlugin = plugin;
+
+ float slop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+ mSquaredSlop = slop * slop;
+ mGestureDetector = new GestureDetector(context, new FlingGestureListener());
+
+ gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
+ .register();
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_OVERSCROLL | mDelegate.getType();
+ }
+
+ private boolean onActivityInit(Boolean alreadyOnHome) {
+ mRecentsView = mGestureState.getActivityInterface().getCreatedActivity().getOverviewPanel();
+
+ return true;
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case ACTION_DOWN: {
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+
+ break;
+ }
+ case ACTION_POINTER_DOWN: {
+ if (mState != STATE_ACTIVE) {
+ mState = STATE_DELEGATE_ACTIVE;
+ }
+ break;
+ }
+ case ACTION_POINTER_UP: {
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ }
+ case ACTION_MOVE: {
+ if (mState == STATE_DELEGATE_ACTIVE) {
+ break;
+ }
+ if (!mDelegate.allowInterceptByParent()) {
+ mState = STATE_DELEGATE_ACTIVE;
+ break;
+ }
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ break;
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+
+ if (!mPassedSlop) {
+ // Normal gesture, ensure we pass the slop before we start tracking the gesture
+ if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+ > mSquaredSlop) {
+
+ mPassedSlop = true;
+ mStartDragPos.set(mLastPos.x, mLastPos.y);
+ if (isOverscrolled()) {
+ setActive(ev);
+
+ if (mPlugin != null) {
+ mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity());
+ }
+ } else {
+ mState = STATE_DELEGATE_ACTIVE;
+ }
+ }
+ }
+
+ if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled()
+ && mPlugin != null) {
+ mPlugin.onTouchTraveled(getDistancePx());
+ }
+
+ break;
+ }
+ case ACTION_CANCEL:
+ case ACTION_UP:
+ if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) {
+ mPlugin.onTouchEnd(getDistancePx());
+ }
+
+ mPassedSlop = false;
+ mState = STATE_INACTIVE;
+ break;
+ }
+
+ if (mState != STATE_DELEGATE_ACTIVE) {
+ mGestureDetector.onTouchEvent(ev);
+ }
+
+ if (mState != STATE_ACTIVE) {
+ mDelegate.onMotionEvent(ev);
+ }
+ }
+
+ private boolean isOverscrolled() {
+ // Make sure there isn't an app to quick switch to on our right
+ int maxIndex = 0;
+ if ((mRecentsView instanceof LauncherRecentsView)
+ && ((LauncherRecentsView) mRecentsView).hasRecentsExtraCard()) {
+ maxIndex = 1;
+ }
+
+ boolean atRightMostApp = (mRecentsView == null
+ || mRecentsView.getRunningTaskIndex() <= maxIndex);
+
+ // Check if the gesture is within our angle threshold of horizontal
+ float deltaY = Math.abs(mLastPos.y - mDownPos.y);
+ float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left
+ boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold;
+
+ return atRightMostApp && angleInBounds;
+ }
+
+ private String getDeviceState() {
+ String deviceState = OverscrollPlugin.DEVICE_STATE_UNKNOWN;
+ int consumerType = mDelegate.getType();
+ if (((consumerType & InputConsumer.TYPE_OVERVIEW) > 0)
+ || ((consumerType & InputConsumer.TYPE_OVERVIEW_WITHOUT_FOCUS)) > 0) {
+ deviceState = OverscrollPlugin.DEVICE_STATE_LAUNCHER;
+ } else if ((consumerType & InputConsumer.TYPE_OTHER_ACTIVITY) > 0) {
+ deviceState = OverscrollPlugin.DEVICE_STATE_APP;
+ } else if (((consumerType & InputConsumer.TYPE_RESET_GESTURE) > 0)
+ || ((consumerType & InputConsumer.TYPE_DEVICE_LOCKED) > 0)) {
+ deviceState = OverscrollPlugin.DEVICE_STATE_LOCKED;
+ }
+
+ return deviceState;
+ }
+
+ private int getDistancePx() {
+ return (int) Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y);
+ }
+
+ private String getUnderlyingActivity() {
+ return mGestureState.getRunningTask().topActivity.flattenToString();
+ }
+
+ private class FlingGestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ if (isValidAngle(velocityX, -velocityY)
+ && getDistancePx() >= mFlingThresholdPx
+ && mState != STATE_DELEGATE_ACTIVE) {
+
+ if (mPlugin != null) {
+ mPlugin.onFling(-velocityX);
+ }
+ }
+ return true;
+ }
+
+ private boolean isValidAngle(float deltaX, float deltaY) {
+ float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+ // normalize so that angle is measured clockwise from horizontal in the bottom right
+ // corner and counterclockwise from horizontal in the bottom left corner
+
+ angle = angle > 90 ? 180 - angle : angle;
+ return (angle < mAngleThreshold);
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index e553891..f161cc0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -16,10 +16,8 @@
package com.android.quickstep.inputconsumers;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -27,9 +25,13 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.OverviewCallbacks;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -42,6 +44,7 @@
implements InputConsumer {
private final T mActivity;
+ private final BaseActivityInterface<T> mActivityInterface;
private final BaseDragLayer mTarget;
private final InputMonitorCompat mInputMonitor;
@@ -52,11 +55,12 @@
private final boolean mStartingInActivityBounds;
private boolean mTargetHandledTouch;
- public OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
- boolean startingInActivityBounds) {
+ public OverviewInputConsumer(GestureState gestureState, T activity,
+ @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
mActivity = activity;
mInputMonitor = inputMonitor;
mStartingInActivityBounds = startingInActivityBounds;
+ mActivityInterface = gestureState.getActivityInterface();
mTarget = activity.getDragLayer();
if (startingInActivityBounds) {
@@ -98,12 +102,13 @@
if (!mTargetHandledTouch && handled) {
mTargetHandledTouch = true;
if (!mStartingInActivityBounds) {
- OverviewCallbacks.get(mActivity).closeAllWindows();
+ mActivityInterface.closeOverlay();
ActivityManagerWrapper.getInstance()
.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+ ActiveGestureLog.INSTANCE.addLog("startQuickstep");
}
if (mInputMonitor != null) {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
mInputMonitor.pilferPointers();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index f40d552..823b254 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,52 +15,37 @@
*/
package com.android.quickstep.inputconsumers;
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_UP;
-
-import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
-
import android.content.Context;
import android.content.Intent;
-import android.graphics.PointF;
import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogUtils;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
import com.android.systemui.shared.system.InputMonitorCompat;
public class OverviewWithoutFocusInputConsumer implements InputConsumer {
- private final InputMonitorCompat mInputMonitor;
- private final boolean mDisableHorizontalSwipe;
- private final PointF mDownPos = new PointF();
- private final float mSquaredTouchSlop;
private final Context mContext;
- private final NavBarPosition mNavBarPosition;
+ private final InputMonitorCompat mInputMonitor;
+ private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
- private boolean mInterceptedTouch;
- private VelocityTracker mVelocityTracker;
-
-
- public OverviewWithoutFocusInputConsumer(Context context, InputMonitorCompat inputMonitor,
- boolean disableHorizontalSwipe) {
- mInputMonitor = inputMonitor;
- mDisableHorizontalSwipe = disableHorizontalSwipe;
+ public OverviewWithoutFocusInputConsumer(Context context,
+ RecentsAnimationDeviceState deviceState, GestureState gestureState,
+ InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
mContext = context;
- mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
- mNavBarPosition = new NavBarPosition(context);
-
- mVelocityTracker = VelocityTracker.obtain();
+ mInputMonitor = inputMonitor;
+ mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
+ deviceState.getNavBarPosition(), this::onInterceptTouch, this::onSwipeUp);
}
@Override
@@ -70,94 +55,32 @@
@Override
public boolean allowInterceptByParent() {
- return !mInterceptedTouch;
- }
-
- private void endTouchTracking() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
+ return !mTriggerSwipeUpTracker.interceptedTouch();
}
@Override
public void onMotionEvent(MotionEvent ev) {
- if (mVelocityTracker == null) {
- return;
- }
+ mTriggerSwipeUpTracker.onMotionEvent(ev);
+ }
- mVelocityTracker.addMovement(ev);
- switch (ev.getActionMasked()) {
- case ACTION_DOWN: {
- mDownPos.set(ev.getX(), ev.getY());
- break;
- }
- case ACTION_MOVE: {
- if (!mInterceptedTouch) {
- float displacementX = ev.getX() - mDownPos.x;
- float displacementY = ev.getY() - mDownPos.y;
- if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
- if (mDisableHorizontalSwipe
- && Math.abs(displacementX) > Math.abs(displacementY)) {
- // Horizontal gesture is not allowed in this region
- endTouchTracking();
- break;
- }
-
- mInterceptedTouch = true;
-
- if (mInputMonitor != null) {
- mInputMonitor.pilferPointers();
- }
- }
- }
- break;
- }
-
- case ACTION_CANCEL:
- endTouchTracking();
- break;
-
- case ACTION_UP: {
- finishTouchTracking(ev);
- endTouchTracking();
- break;
- }
+ private void onInterceptTouch() {
+ if (mInputMonitor != null) {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+ mInputMonitor.pilferPointers();
}
}
- private void finishTouchTracking(MotionEvent ev) {
- mVelocityTracker.computeCurrentVelocity(100);
- float velocityX = mVelocityTracker.getXVelocity();
- float velocityY = mVelocityTracker.getYVelocity();
- float velocity = mNavBarPosition.isRightEdge()
- ? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY);
-
- final boolean triggerQuickstep;
- int touch = Touch.FLING;
- if (Math.abs(velocity) >= ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) {
- triggerQuickstep = velocity > 0;
- } else {
- float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
- float displacementY = ev.getY() - mDownPos.y;
- triggerQuickstep = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
- touch = Touch.SWIPE;
- }
-
- if (triggerQuickstep) {
- mContext.startActivity(new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- TOUCH_INTERACTION_LOG.addLog("startQuickstep");
- BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
- int pageIndex = -1; // This number doesn't reflect workspace page index.
- // It only indicates that launcher client screen was shown.
- int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
- activity.getUserEventDispatcher().logActionOnContainer(
- touch, Direction.UP, containerType, pageIndex);
- activity.getUserEventDispatcher().setPreviousHomeGesture(true);
- } else {
- // ignore
- }
+ private void onSwipeUp(boolean wasFling) {
+ mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ ActiveGestureLog.INSTANCE.addLog("startQuickstep");
+ BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+ int pageIndex = -1; // This number doesn't reflect workspace page index.
+ // It only indicates that launcher client screen was shown.
+ int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
+ activity.getUserEventDispatcher().logActionOnContainer(
+ wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex);
+ activity.getUserEventDispatcher().setPreviousHomeGesture(true);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
index 8eede81..d34b40b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
@@ -17,17 +17,18 @@
import android.view.MotionEvent;
-import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.TaskAnimationManager;
/**
* A NO_OP input consumer which also resets any pending gesture
*/
public class ResetGestureInputConsumer implements InputConsumer {
- private final SwipeSharedState mSwipeSharedState;
+ private final TaskAnimationManager mTaskAnimationManager;
- public ResetGestureInputConsumer(SwipeSharedState swipeSharedState) {
- mSwipeSharedState = swipeSharedState;
+ public ResetGestureInputConsumer(TaskAnimationManager taskAnimationManager) {
+ mTaskAnimationManager = taskAnimationManager;
}
@Override
@@ -38,8 +39,8 @@
@Override
public void onMotionEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN
- && mSwipeSharedState.getActiveListener() != null) {
- mSwipeSharedState.clearAllState(false /* finishAnimation */);
+ && mTaskAnimationManager.isRecentsAnimationRunning()) {
+ mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
}
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index a0e20f2..b9827ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -16,16 +16,15 @@
package com.android.quickstep.inputconsumers;
import android.content.Context;
-import android.os.RemoteException;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.MotionPauseDetector;
-import com.android.systemui.shared.recents.ISystemUiProxy;
/**
* An input consumer that detects swipe up and hold to exit screen pinning mode.
@@ -39,25 +38,21 @@
private float mTouchDownY;
- public ScreenPinnedInputConsumer(Context context, ISystemUiProxy sysuiProxy,
- ActivityControlHelper activityControl) {
+ public ScreenPinnedInputConsumer(Context context, GestureState gestureState) {
mMotionPauseMinDisplacement = context.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
if (isPaused) {
- try {
- sysuiProxy.stopScreenPinning();
- BaseDraggingActivity launcherActivity = activityControl.getCreatedActivity();
- if (launcherActivity != null) {
- launcherActivity.getRootView().performHapticFeedback(
- HapticFeedbackConstants.LONG_PRESS,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
- }
- mMotionPauseDetector.clear();
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to stop screen pinning ", e);
+ SystemUiProxy.INSTANCE.get(context).stopScreenPinning();
+ BaseDraggingActivity launcherActivity = gestureState.getActivityInterface()
+ .getCreatedActivity();
+ if (launcherActivity != null) {
+ launcherActivity.getRootView().performHapticFeedback(
+ HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
+ mMotionPauseDetector.clear();
}
});
}
@@ -77,7 +72,7 @@
case MotionEvent.ACTION_MOVE:
float displacement = mTouchDownY - y;
mMotionPauseDetector.setDisallowPause(displacement < mMotionPauseMinDisplacement);
- mMotionPauseDetector.addPosition(y, ev.getEventTime());
+ mMotionPauseDetector.addPosition(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
index b251f9e..b9ef57e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
@@ -23,6 +23,8 @@
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.userevent.nano.LauncherLogProto;
+import java.util.ArrayList;
+
/**
* This class handles AOSP MetricsLogger function calls and logging around
* quickstep interactions and app launches.
@@ -41,7 +43,7 @@
@Override
protected void onFillInLogContainerData(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
- @NonNull LauncherLogProto.Target targetParent) {
+ @NonNull ArrayList<LauncherLogProto.Target> targets) {
PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
new file mode 100644
index 0000000..fabfc4b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.content.Context;
+
+import com.android.launcher3.logging.EventLogArray;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
+/**
+ * A log to keep track of the active gesture.
+ */
+public class ActiveGestureLog extends EventLogArray {
+
+ public static final ActiveGestureLog INSTANCE = new ActiveGestureLog();
+
+ /**
+ * NOTE: This value should be kept same as
+ * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
+ */
+ public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
+
+ private ActiveGestureLog() {
+ super("touch_interaction_log", 40);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
new file mode 100644
index 0000000..681ce02
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.view.Surface;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.model.PagedViewOrientedState;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.utilities.RectFEvaluator;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+import com.android.systemui.shared.system.TransactionCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
+import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
+
+/**
+ * Utility class to handle window clip animation
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class AppWindowAnimationHelper {
+
+ // The bounds of the source app in device coordinates
+ private final Rect mSourceStackBounds = new Rect();
+ // The insets of the source app
+ private final Rect mSourceInsets = new Rect();
+ // The source app bounds with the source insets applied, in the device coordinates
+ private final RectF mSourceRect = new RectF();
+ // The bounds of the task view in device coordinates
+ private final RectF mTargetRect = new RectF();
+ // The bounds of the app window (between mSourceRect and mTargetRect) in device coordinates
+ private final RectF mCurrentRect = new RectF();
+ // The insets to be used for clipping the app window, which can be larger than mSourceInsets
+ // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
+ // app window coordinates.
+ private final RectF mSourceWindowClipInsets = new RectF();
+ // The clip rect in source app window coordinates. The app window surface will only be drawn
+ // within these bounds. This clip rect starts at the full mSourceStackBounds, and insets by
+ // mSourceWindowClipInsets as the transform progress goes to 1.
+ private final RectF mCurrentClipRectF = new RectF();
+
+ // The bounds of launcher (not including insets) in device coordinates
+ public final Rect mHomeStackBounds = new Rect();
+ private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
+ private final Matrix mTmpMatrix = new Matrix();
+ private final Rect mTmpRect = new Rect();
+ private final RectF mTmpRectF = new RectF();
+ private final RectF mCurrentRectWithInsets = new RectF();
+ private PagedViewOrientedState mOrientedState;
+ // Corner radius of windows, in pixels
+ private final float mWindowCornerRadius;
+ // Corner radius of windows when they're in overview mode.
+ private final float mTaskCornerRadius;
+ // If windows can have real time rounded corners.
+ private final boolean mSupportsRoundedCornersOnWindows;
+ // Whether or not to actually use the rounded cornders on windows
+ private boolean mUseRoundedCornersOnWindows;
+
+ // Corner radius currently applied to transformed window.
+ private float mCurrentCornerRadius;
+
+ // Whether to boost the opening animation target layers, or the closing
+ private int mBoostModeTargetLayers = -1;
+
+ private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
+ private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
+
+ public AppWindowAnimationHelper(PagedViewOrientedState orientedState, Context context) {
+ Resources res = context.getResources();
+ mOrientedState = orientedState;
+ mWindowCornerRadius = getWindowCornerRadius(res);
+ mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(res);
+ mTaskCornerRadius = TaskCornerRadius.get(context);
+ mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
+ }
+
+ public AppWindowAnimationHelper(Context context) {
+ this(null, context);
+ }
+
+ private void updateSourceStack(RemoteAnimationTargetCompat target) {
+ mSourceInsets.set(target.contentInsets);
+ mSourceStackBounds.set(target.sourceContainerBounds);
+
+ // TODO: Should sourceContainerBounds already have this offset?
+ mSourceStackBounds.offsetTo(target.position.x, target.position.y);
+ }
+
+ public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
+ updateSourceStack(target);
+ updateHomeBounds(homeStackBounds);
+ }
+
+ public void updateHomeBounds(Rect homeStackBounds) {
+ mHomeStackBounds.set(homeStackBounds);
+ }
+
+ public void updateTargetRect(Rect targetRect) {
+ mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
+ mSourceStackBounds.width() - mSourceInsets.right,
+ mSourceStackBounds.height() - mSourceInsets.bottom);
+ mTargetRect.set(targetRect);
+ mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
+ mHomeStackBounds.top - mSourceStackBounds.top);
+
+ // Calculate the clip based on the target rect (since the content insets and the
+ // launcher insets may differ, so the aspect ratio of the target rect can differ
+ // from the source rect. The difference between the target rect (scaled to the
+ // source rect) is the amount to clip on each edge.
+ RectF scaledTargetRect = new RectF(mTargetRect);
+ float scale = getSrcToTargetScale();
+ Utilities.scaleRectFAboutCenter(scaledTargetRect, scale);
+
+ scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
+ mSourceWindowClipInsets.set(
+ Math.max(scaledTargetRect.left, 0),
+ Math.max(scaledTargetRect.top, 0),
+ Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
+ Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
+ mSourceRect.set(scaledTargetRect);
+ }
+
+ private float getSrcToTargetScale() {
+ if (mOrientedState == null ||
+ (mOrientedState.getDisplayRotation() == Surface.ROTATION_0
+ || mOrientedState.getDisplayRotation() == Surface.ROTATION_180)) {
+ return mSourceRect.width() / mTargetRect.width();
+ } else {
+ return mSourceRect.height() / mTargetRect.height();
+ }
+ }
+
+ public void prepareAnimation(DeviceProfile dp, boolean isOpening) {
+ mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
+ mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
+ }
+
+ public RectF applyTransform(TransformParams params) {
+ SurfaceParams[] surfaceParams = computeSurfaceParams(params);
+ if (surfaceParams == null) {
+ return null;
+ }
+ applySurfaceParams(params.mSyncTransactionApplier, surfaceParams);
+ return mCurrentRect;
+ }
+
+ /**
+ * Updates this AppWindowAnimationHelper's state based on the given TransformParams, and returns
+ * the SurfaceParams to apply via {@link SyncRtSurfaceTransactionApplierCompat#applyParams}.
+ */
+ public SurfaceParams[] computeSurfaceParams(TransformParams params) {
+ if (params.mTargetSet == null) {
+ return null;
+ }
+
+ float progress = Utilities.boundToRange(params.mProgress, 0, 1);
+ updateCurrentRect(params);
+
+ SurfaceParams[] surfaceParams = new SurfaceParams[params.mTargetSet.unfilteredApps.length];
+ for (int i = 0; i < params.mTargetSet.unfilteredApps.length; i++) {
+ RemoteAnimationTargetCompat app = params.mTargetSet.unfilteredApps[i];
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
+ mTmpMatrix.setTranslate(app.position.x, app.position.y);
+ Rect crop = mTmpRect;
+ crop.set(app.sourceContainerBounds);
+ crop.offsetTo(0, 0);
+ float alpha;
+ float cornerRadius = 0f;
+ float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
+ int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
+ if (app.mode == params.mTargetSet.targetMode) {
+ alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha);
+ if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ mTmpMatrix.setRectToRect(mSourceRect, mCurrentRect, ScaleToFit.FILL);
+ mTmpMatrix.postTranslate(app.position.x, app.position.y);
+ mCurrentClipRectF.roundOut(crop);
+ if (mSupportsRoundedCornersOnWindows) {
+ if (params.mCornerRadius > -1) {
+ cornerRadius = params.mCornerRadius;
+ scale = mCurrentRect.width() / crop.width();
+ } else {
+ float windowCornerRadius = mUseRoundedCornersOnWindows
+ ? mWindowCornerRadius : 0;
+ cornerRadius = Utilities.mapRange(progress, windowCornerRadius,
+ mTaskCornerRadius);
+ }
+ mCurrentCornerRadius = cornerRadius;
+ }
+ // Fade out Assistant overlay.
+ if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
+ && app.isNotInRecents) {
+ alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
+ }
+ } else if (params.mTargetSet.hasRecents) {
+ // If home has a different target then recents, reverse anim the
+ // home target.
+ alpha = 1 - (progress * params.mTargetAlpha);
+ }
+ } else {
+ alpha = mBaseAlphaCallback.getAlpha(app, progress);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.mLauncherOnTop) {
+ crop = null;
+ layer = Integer.MAX_VALUE;
+ }
+ }
+ builder.withAlpha(alpha)
+ .withMatrix(mTmpMatrix)
+ .withWindowCrop(crop)
+ .withLayer(layer)
+ // Since radius is in Surface space, but we draw the rounded corners in screen
+ // space, we have to undo the scale
+ .withCornerRadius(cornerRadius / scale);
+ surfaceParams[i] = builder.build();
+ }
+ return surfaceParams;
+ }
+
+ public RectF updateCurrentRect(TransformParams params) {
+ if (params.mCurrentRect != null) {
+ mCurrentRect.set(params.mCurrentRect);
+ } else {
+ mTmpRectF.set(mTargetRect);
+ Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale);
+ mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF));
+ if (mOrientedState == null || mOrientedState.areMultipleLayoutOrientationsDisabled()) {
+ mCurrentRect.offset(params.mOffset, 0);
+ } else {
+ int displayRotation = mOrientedState.getDisplayRotation();
+ mOrientedState.getOrientationHandler().offsetTaskRect(mCurrentRect,
+ params.mOffset, displayRotation);
+ }
+ }
+
+ updateClipRect(params);
+ return mCurrentRect;
+ }
+
+ private void updateClipRect(TransformParams params) {
+ // Don't clip past progress > 1.
+ float progress = Math.min(1, params.mProgress);
+ mCurrentClipRectF.left = mSourceWindowClipInsets.left * progress;
+ mCurrentClipRectF.top = mSourceWindowClipInsets.top * progress;
+ mCurrentClipRectF.right =
+ mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
+ mCurrentClipRectF.bottom =
+ mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
+ }
+
+ public RectF getCurrentRectWithInsets() {
+ mTmpMatrix.mapRect(mCurrentRectWithInsets, mCurrentClipRectF);
+ return mCurrentRectWithInsets;
+ }
+
+ public static void applySurfaceParams(@Nullable SyncRtSurfaceTransactionApplierCompat
+ syncTransactionApplier, SurfaceParams[] params) {
+ if (syncTransactionApplier != null) {
+ syncTransactionApplier.scheduleApply(params);
+ } else {
+ TransactionCompat t = new TransactionCompat();
+ for (SurfaceParams param : params) {
+ SyncRtSurfaceTransactionApplierCompat.applyParams(t, param);
+ }
+ t.setEarlyWakeup();
+ t.apply();
+ }
+ }
+
+ public void setTaskAlphaCallback(TargetAlphaProvider callback) {
+ mTaskAlphaCallback = callback;
+ }
+
+ public void setBaseAlphaCallback(TargetAlphaProvider callback) {
+ mBaseAlphaCallback = callback;
+ }
+
+ public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
+ fromTaskThumbnailView(ttv, rv, null);
+ }
+
+ public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv,
+ @Nullable RemoteAnimationTargetCompat target) {
+ BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
+ BaseDragLayer dl = activity.getDragLayer();
+
+ int[] pos = new int[2];
+ dl.getLocationOnScreen(pos);
+ mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
+ mHomeStackBounds.offset(pos[0], pos[1]);
+
+ if (target != null) {
+ updateSourceStack(target);
+ } else if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
+ updateStackBoundsToMultiWindowTaskSize(activity);
+ } else {
+ mSourceStackBounds.set(mHomeStackBounds);
+ Rect fallback = dl.getInsets();
+ mSourceInsets.set(ttv.getInsets(fallback));
+ }
+
+ Rect targetRect = new Rect();
+ dl.getDescendantRectRelativeToSelf(ttv, targetRect);
+ updateTargetRect(targetRect);
+
+ if (target == null) {
+ // Transform the clip relative to the target rect. Only do this in the case where we
+ // aren't applying the insets to the app windows (where the clip should be in target app
+ // space)
+ float scale = mTargetRect.width() / mSourceRect.width();
+ mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
+ mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
+ mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
+ mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
+ }
+ }
+
+ /**
+ * Compute scale and translation y such that the specified task view fills the screen.
+ */
+ public AppWindowAnimationHelper updateForFullscreenOverview(TaskView v) {
+ TaskThumbnailView thumbnailView = v.getThumbnail();
+ RecentsView recentsView = v.getRecentsView();
+ fromTaskThumbnailView(thumbnailView, recentsView);
+ Rect taskSize = new Rect();
+ recentsView.getTaskSize(taskSize);
+ updateTargetRect(taskSize);
+ return this;
+ }
+
+ /**
+ * @return The source rect's scale and translation relative to the target rect.
+ */
+ public LauncherState.ScaleAndTranslation getScaleAndTranslation() {
+ float scale = getSrcToTargetScale();
+ float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
+ return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
+ }
+
+ private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
+ SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(activity);
+ if (proxy.isActive()) {
+ mSourceStackBounds.set(proxy.getNonMinimizedSplitScreenSecondaryBounds());
+ return;
+ }
+
+ // Assume that the task size is half screen size (minus the insets and the divider size)
+ DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
+ // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
+ // account for system insets
+ int taskWidth = fullDp.availableWidthPx;
+ int taskHeight = fullDp.availableHeightPx;
+ int halfDividerSize = activity.getResources()
+ .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+
+ Rect insets = new Rect();
+ WindowManagerWrapper.getInstance().getStableInsets(insets);
+ if (fullDp.isLandscape) {
+ taskWidth = taskWidth / 2 - halfDividerSize;
+ } else {
+ taskHeight = taskHeight / 2 - halfDividerSize;
+ }
+
+ // Align the task to bottom left/right edge (closer to nav bar).
+ int left = activity.getDeviceProfile().isSeascape() ? insets.left
+ : (insets.left + fullDp.availableWidthPx - taskWidth);
+ mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
+ mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
+ }
+
+ public RectF getTargetRect() {
+ return mTargetRect;
+ }
+
+ public float getCurrentCornerRadius() {
+ return mCurrentCornerRadius;
+ }
+
+ public interface TargetAlphaProvider {
+ float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha);
+ }
+
+ public static class TransformParams {
+ private float mProgress;
+ private float mOffset;
+ private float mOffsetScale;
+ private @Nullable RectF mCurrentRect;
+ private float mTargetAlpha;
+ private float mCornerRadius;
+ private boolean mLauncherOnTop;
+ private RemoteAnimationTargets mTargetSet;
+ private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
+
+ public TransformParams() {
+ mProgress = 0;
+ mOffset = 0;
+ mOffsetScale = 1;
+ mCurrentRect = null;
+ mTargetAlpha = 1;
+ mCornerRadius = -1;
+ mLauncherOnTop = false;
+ }
+
+ /**
+ * Sets the progress of the transformation, where 0 is the source and 1 is the target. We
+ * automatically adjust properties such as currentRect and cornerRadius based on this
+ * progress, unless they are manually overridden by setting them on this TransformParams.
+ */
+ public TransformParams setProgress(float progress) {
+ mProgress = progress;
+ return this;
+ }
+
+ /**
+ * Sets the corner radius of the transformed window, in pixels. If unspecified (-1), we
+ * simply interpolate between the window's corner radius to the task view's corner radius,
+ * based on {@link #mProgress}.
+ */
+ public TransformParams setCornerRadius(float cornerRadius) {
+ mCornerRadius = cornerRadius;
+ return this;
+ }
+
+ /**
+ * Sets the current rect to show the transformed window, in device coordinates. This gives
+ * the caller manual control of where to show the window. If unspecified (null), we
+ * interpolate between {@link AppWindowAnimationHelper#mSourceRect} and
+ * {@link AppWindowAnimationHelper#mTargetRect}, based on {@link #mProgress}.
+ */
+ public TransformParams setCurrentRect(RectF currentRect) {
+ mCurrentRect = currentRect;
+ return this;
+ }
+
+ /**
+ * Specifies the alpha of the transformed window. Default is 1.
+ */
+ public TransformParams setTargetAlpha(float targetAlpha) {
+ mTargetAlpha = targetAlpha;
+ return this;
+ }
+
+ /**
+ * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
+ * the default), then offset the current rect by this amount after computing the rect based
+ * on {@link #mProgress}.
+ */
+ public TransformParams setOffset(float offset) {
+ mOffset = offset;
+ return this;
+ }
+
+ /**
+ * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
+ * the default), then scale the current rect by this amount after computing the rect based
+ * on {@link #mProgress}.
+ */
+ public TransformParams setOffsetScale(float offsetScale) {
+ mOffsetScale = offsetScale;
+ return this;
+ }
+
+ /**
+ * If true, sets the crop = null and layer = Integer.MAX_VALUE for targets that don't match
+ * {@link #mTargetSet}.targetMode. (Currently only does this when live tiles are enabled.)
+ */
+ public TransformParams setLauncherOnTop(boolean launcherOnTop) {
+ mLauncherOnTop = launcherOnTop;
+ return this;
+ }
+
+ /**
+ * Specifies the set of RemoteAnimationTargetCompats that are included in the transformation
+ * that these TransformParams help compute. These TransformParams generally only apply to
+ * the targetSet.apps which match the targetSet.targetMode (e.g. the MODE_CLOSING app when
+ * swiping to home).
+ */
+ public TransformParams setTargetSet(RemoteAnimationTargets targetSet) {
+ mTargetSet = targetSet;
+ return this;
+ }
+
+ /**
+ * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that
+ * are computed based on these TransformParams.
+ */
+ public TransformParams setSyncTransactionApplier(
+ SyncRtSurfaceTransactionApplierCompat applier) {
+ mSyncTransactionApplier = applier;
+ return this;
+ }
+
+ // Pubic getters so outside packages can read the values.
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ public float getOffset() {
+ return mOffset;
+ }
+
+ public float getOffsetScale() {
+ return mOffsetScale;
+ }
+
+ @Nullable
+ public RectF getCurrentRect() {
+ return mCurrentRect;
+ }
+
+ public float getTargetAlpha() {
+ return mTargetAlpha;
+ }
+
+ public float getCornerRadius() {
+ return mCornerRadius;
+ }
+
+ public boolean isLauncherOnTop() {
+ return mLauncherOnTop;
+ }
+
+ public RemoteAnimationTargets getTargetSet() {
+ return mTargetSet;
+ }
+
+ public SyncRtSurfaceTransactionApplierCompat getSyncTransactionApplier() {
+ return mSyncTransactionApplier;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java
similarity index 100%
rename from quickstep/src/com/android/quickstep/util/AssistantUtilities.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
deleted file mode 100644
index cb83b9a..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ /dev/null
@@ -1,441 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
-import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Build;
-import android.os.RemoteException;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
-import com.android.quickstep.views.TaskView;
-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.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-import com.android.systemui.shared.system.TransactionCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-/**
- * Utility class to handle window clip animation
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class ClipAnimationHelper {
-
- // The bounds of the source app in device coordinates
- private final Rect mSourceStackBounds = new Rect();
- // The insets of the source app
- private final Rect mSourceInsets = new Rect();
- // The source app bounds with the source insets applied, in the source app window coordinates
- private final RectF mSourceRect = new RectF();
- // The bounds of the task view in launcher window coordinates
- private final RectF mTargetRect = new RectF();
- // The insets to be used for clipping the app window, which can be larger than mSourceInsets
- // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
- // app window coordinates.
- private final RectF mSourceWindowClipInsets = new RectF();
- // The insets to be used for clipping the app window. For live tile, we don't transform the clip
- // relative to the target rect.
- private final RectF mSourceWindowClipInsetsForLiveTile = new RectF();
-
- // The bounds of launcher (not including insets) in device coordinates
- public final Rect mHomeStackBounds = new Rect();
-
- // The clip rect in source app window coordinates
- private final RectF mClipRectF = new RectF();
- private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
- private final Matrix mTmpMatrix = new Matrix();
- private final Rect mTmpRect = new Rect();
- private final RectF mTmpRectF = new RectF();
- private final RectF mCurrentRectWithInsets = new RectF();
- // Corner radius of windows, in pixels
- private final float mWindowCornerRadius;
- // Corner radius of windows when they're in overview mode.
- private final float mTaskCornerRadius;
- // If windows can have real time rounded corners.
- private final boolean mSupportsRoundedCornersOnWindows;
- // Whether or not to actually use the rounded cornders on windows
- private boolean mUseRoundedCornersOnWindows;
-
- // Corner radius currently applied to transformed window.
- private float mCurrentCornerRadius;
-
- // Whether to boost the opening animation target layers, or the closing
- private int mBoostModeTargetLayers = -1;
-
- private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
- private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
-
- public ClipAnimationHelper(Context context) {
- mWindowCornerRadius = getWindowCornerRadius(context.getResources());
- mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
- mTaskCornerRadius = TaskCornerRadius.get(context);
- mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
- }
-
- private void updateSourceStack(RemoteAnimationTargetCompat target) {
- mSourceInsets.set(target.contentInsets);
- mSourceStackBounds.set(target.sourceContainerBounds);
-
- // TODO: Should sourceContainerBounds already have this offset?
- mSourceStackBounds.offsetTo(target.position.x, target.position.y);
-
- }
-
- public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
- updateSourceStack(target);
- updateHomeBounds(homeStackBounds);
- }
-
- public void updateHomeBounds(Rect homeStackBounds) {
- mHomeStackBounds.set(homeStackBounds);
- }
-
- public void updateTargetRect(Rect targetRect) {
- mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
- mSourceStackBounds.width() - mSourceInsets.right,
- mSourceStackBounds.height() - mSourceInsets.bottom);
- mTargetRect.set(targetRect);
- mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
- mHomeStackBounds.top - mSourceStackBounds.top);
-
- // Calculate the clip based on the target rect (since the content insets and the
- // launcher insets may differ, so the aspect ratio of the target rect can differ
- // from the source rect. The difference between the target rect (scaled to the
- // source rect) is the amount to clip on each edge.
- RectF scaledTargetRect = new RectF(mTargetRect);
- Utilities.scaleRectFAboutCenter(scaledTargetRect,
- mSourceRect.width() / mTargetRect.width());
- scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
- mSourceWindowClipInsets.set(
- Math.max(scaledTargetRect.left, 0),
- Math.max(scaledTargetRect.top, 0),
- Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
- Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
- mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets);
- mSourceRect.set(scaledTargetRect);
- }
-
- public void prepareAnimation(DeviceProfile dp, boolean isOpening) {
- mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
- mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
- }
-
- public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) {
- return applyTransform(targetSet, params, true /* launcherOnTop */);
- }
-
- public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params,
- boolean launcherOnTop) {
- float progress = params.progress;
- if (params.currentRect == null) {
- RectF currentRect;
- mTmpRectF.set(mTargetRect);
- Utilities.scaleRectFAboutCenter(mTmpRectF, params.offsetScale);
- currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
- currentRect.offset(params.offsetX, 0);
-
- // Don't clip past progress > 1.
- progress = Math.min(1, progress);
- final RectF sourceWindowClipInsets = params.forLiveTile
- ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
- mClipRectF.left = sourceWindowClipInsets.left * progress;
- mClipRectF.top = sourceWindowClipInsets.top * progress;
- mClipRectF.right =
- mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
- mClipRectF.bottom =
- mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
- params.setCurrentRectAndTargetAlpha(currentRect, 1);
- }
-
- SurfaceParams[] surfaceParams = 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 = mTmpRect;
- crop.set(app.sourceContainerBounds);
- crop.offsetTo(0, 0);
- float alpha;
- int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
- float cornerRadius = 0f;
- float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
- if (app.mode == targetSet.targetMode) {
- alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha);
- if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
- mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
- mTmpMatrix.postTranslate(app.position.x, app.position.y);
- mClipRectF.roundOut(crop);
- if (mSupportsRoundedCornersOnWindows) {
- if (params.cornerRadius > -1) {
- cornerRadius = params.cornerRadius;
- scale = params.currentRect.width() / crop.width();
- } else {
- float windowCornerRadius = mUseRoundedCornersOnWindows
- ? mWindowCornerRadius : 0;
- cornerRadius = Utilities.mapRange(progress, windowCornerRadius,
- mTaskCornerRadius);
- }
- mCurrentCornerRadius = cornerRadius;
- }
- // Fade out Assistant overlay.
- if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
- && app.isNotInRecents) {
- alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
- }
- } else if (targetSet.hasRecents) {
- // If home has a different target then recents, reverse anim the
- // home target.
- alpha = 1 - (progress * params.targetAlpha);
- }
- } else {
- alpha = mBaseAlphaCallback.getAlpha(app, progress);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
- crop = null;
- layer = Integer.MAX_VALUE;
- }
- }
-
- // Since radius is in Surface space, but we draw the rounded corners in screen space, we
- // have to undo the scale.
- surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer,
- cornerRadius / scale);
- }
- applySurfaceParams(params.syncTransactionApplier, surfaceParams);
- return params.currentRect;
- }
-
- public RectF getCurrentRectWithInsets() {
- mTmpMatrix.mapRect(mCurrentRectWithInsets, mClipRectF);
- return mCurrentRectWithInsets;
- }
-
- private void applySurfaceParams(@Nullable SyncRtSurfaceTransactionApplierCompat
- syncTransactionApplier, SurfaceParams[] params) {
- if (syncTransactionApplier != null) {
- syncTransactionApplier.scheduleApply(params);
- } else {
- TransactionCompat t = new TransactionCompat();
- for (SurfaceParams param : params) {
- SyncRtSurfaceTransactionApplierCompat.applyParams(t, param);
- }
- t.setEarlyWakeup();
- t.apply();
- }
- }
-
- public void setTaskAlphaCallback(TargetAlphaProvider callback) {
- mTaskAlphaCallback = callback;
- }
-
- public void setBaseAlphaCallback(TargetAlphaProvider callback) {
- mBaseAlphaCallback = callback;
- }
-
- public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
- fromTaskThumbnailView(ttv, rv, null);
- }
-
- public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv,
- @Nullable RemoteAnimationTargetCompat target) {
- BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
- BaseDragLayer dl = activity.getDragLayer();
-
- int[] pos = new int[2];
- dl.getLocationOnScreen(pos);
- mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
- mHomeStackBounds.offset(pos[0], pos[1]);
-
- if (target != null) {
- updateSourceStack(target);
- } else if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
- updateStackBoundsToMultiWindowTaskSize(activity);
- } else {
- mSourceStackBounds.set(mHomeStackBounds);
- Rect fallback = dl.getInsets();
- mSourceInsets.set(ttv.getInsets(fallback));
- }
-
- Rect targetRect = new Rect();
- dl.getDescendantRectRelativeToSelf(ttv, targetRect);
- updateTargetRect(targetRect);
-
- if (target == null) {
- // Transform the clip relative to the target rect. Only do this in the case where we
- // aren't applying the insets to the app windows (where the clip should be in target app
- // space)
- float scale = mTargetRect.width() / mSourceRect.width();
- mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
- mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
- mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
- mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
- }
- }
-
- /**
- * Compute scale and translation y such that the specified task view fills the screen.
- */
- public ClipAnimationHelper updateForFullscreenOverview(TaskView v) {
- TaskThumbnailView thumbnailView = v.getThumbnail();
- RecentsView recentsView = v.getRecentsView();
- fromTaskThumbnailView(thumbnailView, recentsView);
- Rect taskSize = new Rect();
- recentsView.getTaskSize(taskSize);
- updateTargetRect(taskSize);
- return this;
- }
-
- /**
- * @return The source rect's scale and translation relative to the target rect.
- */
- public LauncherState.ScaleAndTranslation getScaleAndTranslation() {
- float scale = mSourceRect.width() / mTargetRect.width();
- float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
- return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
- }
-
- private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
- ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
- if (sysUiProxy != null) {
- try {
- mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
- return;
- } catch (RemoteException e) {
- // Use half screen size
- }
- }
-
- // Assume that the task size is half screen size (minus the insets and the divider size)
- DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
- // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
- // account for system insets
- int taskWidth = fullDp.availableWidthPx;
- int taskHeight = fullDp.availableHeightPx;
- int halfDividerSize = activity.getResources()
- .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
-
- Rect insets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(insets);
- if (fullDp.isLandscape) {
- taskWidth = taskWidth / 2 - halfDividerSize;
- } else {
- taskHeight = taskHeight / 2 - halfDividerSize;
- }
-
- // Align the task to bottom left/right edge (closer to nav bar).
- int left = activity.getDeviceProfile().isSeascape() ? insets.left
- : (insets.left + fullDp.availableWidthPx - taskWidth);
- mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
- mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
- }
-
- public RectF getTargetRect() {
- return mTargetRect;
- }
-
- public float getCurrentCornerRadius() {
- return mCurrentCornerRadius;
- }
-
- public interface TargetAlphaProvider {
- float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha);
- }
-
- public static class TransformParams {
- float progress;
- public float offsetX;
- public float offsetScale;
- @Nullable RectF currentRect;
- float targetAlpha;
- boolean forLiveTile;
- float cornerRadius;
-
- SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
-
- public TransformParams() {
- progress = 0;
- offsetX = 0;
- offsetScale = 1;
- currentRect = null;
- targetAlpha = 0;
- forLiveTile = false;
- cornerRadius = -1;
- }
-
- public TransformParams setProgress(float progress) {
- this.progress = progress;
- this.currentRect = null;
- return this;
- }
-
- public float getProgress() {
- return progress;
- }
-
- public TransformParams setCornerRadius(float cornerRadius) {
- this.cornerRadius = cornerRadius;
- return this;
- }
-
- public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) {
- this.currentRect = currentRect;
- this.targetAlpha = targetAlpha;
- return this;
- }
-
- public TransformParams setOffsetX(float offsetX) {
- this.offsetX = offsetX;
- return this;
- }
-
- public TransformParams setOffsetScale(float offsetScale) {
- this.offsetScale = offsetScale;
- return this;
- }
-
- public TransformParams setForLiveTile(boolean forLiveTile) {
- this.forLiveTile = forLiveTile;
- return this;
- }
-
- public TransformParams setSyncTransactionApplier(
- SyncRtSurfaceTransactionApplierCompat applier) {
- this.syncTransactionApplier = applier;
- return this;
- }
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
deleted file mode 100644
index bbb318a..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_LANDSCAPE;
-import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
-
-import android.content.Context;
-import android.view.Surface;
-
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.quickstep.SysUINavigationMode;
-
-/**
- * Utility class to check nav bar position
- */
-public class NavBarPosition {
-
- private final SysUINavigationMode.Mode mMode;
- private final int mDisplayRotation;
-
- public NavBarPosition(Context context) {
- mMode = SysUINavigationMode.getMode(context);
- mDisplayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
- }
-
- public boolean isRightEdge() {
- return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
- }
-
- public boolean isLeftEdge() {
- return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
- }
-
- public RotationMode getRotationMode() {
- return isLeftEdge() ? ROTATION_SEASCAPE
- : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL);
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java
new file mode 100644
index 0000000..190763a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.tracing.nano.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_H;
+import static com.android.launcher3.tracing.nano.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_L;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import com.android.launcher3.tracing.nano.LauncherTraceProto;
+import com.android.launcher3.tracing.nano.LauncherTraceEntryProto;
+import com.android.launcher3.tracing.nano.LauncherTraceFileProto;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.shared.tracing.FrameProtoTracer;
+import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
+import com.android.systemui.shared.tracing.ProtoTraceable;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Queue;
+
+
+/**
+ * Controller for coordinating winscope proto tracing.
+ */
+public class ProtoTracer implements ProtoTraceParams<MessageNano,
+ LauncherTraceFileProto, LauncherTraceEntryProto, LauncherTraceProto> {
+
+ public static final MainThreadInitializedObject<ProtoTracer> INSTANCE =
+ new MainThreadInitializedObject<>(ProtoTracer::new);
+
+ private static final String TAG = "ProtoTracer";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Context mContext;
+ private final FrameProtoTracer<MessageNano,
+ LauncherTraceFileProto, LauncherTraceEntryProto, LauncherTraceProto> mProtoTracer;
+
+ public ProtoTracer(Context context) {
+ mContext = context;
+ mProtoTracer = new FrameProtoTracer<>(this);
+ }
+
+ @Override
+ public File getTraceFile() {
+ return new File(mContext.getFilesDir(), "launcher_trace.pb");
+ }
+
+ @Override
+ public LauncherTraceFileProto getEncapsulatingTraceProto() {
+ return new LauncherTraceFileProto();
+ }
+
+ @Override
+ public LauncherTraceEntryProto updateBufferProto(LauncherTraceEntryProto reuseObj,
+ ArrayList<ProtoTraceable<LauncherTraceProto>> traceables) {
+ LauncherTraceEntryProto proto = reuseObj != null
+ ? reuseObj
+ : new LauncherTraceEntryProto();
+ proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
+ proto.launcher = proto.launcher != null ? proto.launcher : new LauncherTraceProto();
+ for (ProtoTraceable t : traceables) {
+ t.writeToProto(proto.launcher);
+ }
+ return proto;
+ }
+
+ @Override
+ public byte[] serializeEncapsulatingProto(LauncherTraceFileProto encapsulatingProto,
+ Queue<LauncherTraceEntryProto> buffer) {
+ encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE;
+ encapsulatingProto.entry = buffer.toArray(new LauncherTraceEntryProto[0]);
+ return MessageNano.toByteArray(encapsulatingProto);
+ }
+
+ @Override
+ public byte[] getProtoBytes(MessageNano proto) {
+ return MessageNano.toByteArray(proto);
+ }
+
+ @Override
+ public int getProtoSize(MessageNano proto) {
+ return proto.getCachedSize();
+ }
+
+ public void start() {
+ mProtoTracer.start();
+ }
+
+ public void stop() {
+ mProtoTracer.stop();
+ }
+
+ public void add(ProtoTraceable<LauncherTraceProto> traceable) {
+ mProtoTracer.add(traceable);
+ }
+
+ public void remove(ProtoTraceable<LauncherTraceProto> traceable) {
+ mProtoTracer.remove(traceable);
+ }
+
+ public void scheduleFrameUpdate() {
+ mProtoTracer.scheduleFrameUpdate();
+ }
+
+ public void update() {
+ mProtoTracer.update();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
deleted file mode 100644
index b1999d7..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.graphics.Rect;
-import android.util.ArraySet;
-
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * Wrapper around {@link RecentsAnimationListener} which delegates callbacks to multiple listeners
- * on the main thread
- */
-public class RecentsAnimationListenerSet implements RecentsAnimationListener {
-
- // The actual app surface is replaced by a screenshot upon recents animation cancelation when
- // the thumbnailData exists. Launcher takes the responsibility to clean up this screenshot
- // after app transition is finished. This delay is introduced to cover the app transition
- // period of time.
- private final int TRANSITION_DELAY = 100;
-
- private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
- private final boolean mShouldMinimizeSplitScreen;
- private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
- private RecentsAnimationControllerCompat mController;
-
- private boolean mCancelled;
-
- public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
- Consumer<SwipeAnimationTargetSet> onFinishListener) {
- mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
- mOnFinishListener = onFinishListener;
- }
-
- @UiThread
- public void addListener(SwipeAnimationListener listener) {
- Preconditions.assertUIThread();
- mListeners.add(listener);
- }
-
- @UiThread
- public void removeListener(SwipeAnimationListener listener) {
- Preconditions.assertUIThread();
- mListeners.remove(listener);
- }
-
- @Override
- public final void onAnimationStart(RecentsAnimationControllerCompat controller,
- RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
- Rect minimizedHomeBounds) {
- mController = controller;
- SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
- homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
- mOnFinishListener);
-
- if (mCancelled) {
- targetSet.cancelAnimation();
- } else {
- Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
- for (SwipeAnimationListener listener : getListeners()) {
- listener.onRecentsAnimationStart(targetSet);
- }
- });
- }
- }
-
- @Override
- public final void onAnimationCanceled(ThumbnailData thumbnailData) {
- Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
- for (SwipeAnimationListener listener : getListeners()) {
- listener.onRecentsAnimationCanceled();
- }
- });
- // TODO: handle the transition better instead of simply using a transition delay.
- if (thumbnailData != null) {
- MAIN_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
- TRANSITION_DELAY);
- }
- }
-
- private SwipeAnimationListener[] getListeners() {
- return mListeners.toArray(new SwipeAnimationListener[mListeners.size()]);
- }
-
- public void cancelListener() {
- mCancelled = true;
- onAnimationCanceled(null);
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index c6eafe6..dde7605 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -16,7 +16,7 @@
package com.android.quickstep.util;
import android.animation.Animator;
-import android.content.res.Resources;
+import android.content.Context;
import android.graphics.PointF;
import android.graphics.RectF;
@@ -28,6 +28,8 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.FlingSpringAnim;
+import com.android.launcher3.util.DynamicResource;
+import com.android.systemui.plugins.ResourceProvider;
import java.util.ArrayList;
import java.util.List;
@@ -103,7 +105,7 @@
private float mMinVisChange;
private float mYOvershoot;
- public RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources) {
+ public RectFSpringAnim(RectF startRect, RectF targetRect, Context context) {
mStartRect = startRect;
mTargetRect = targetRect;
mCurrentCenterX = mStartRect.centerX();
@@ -111,8 +113,9 @@
mTrackingBottomY = startRect.bottom < targetRect.bottom;
mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top;
- mMinVisChange = resources.getDimensionPixelSize(R.dimen.swipe_up_fling_min_visible_change);
- mYOvershoot = resources.getDimensionPixelSize(R.dimen.swipe_up_y_overshoot);
+ ResourceProvider rp = DynamicResource.provider(context);
+ mMinVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change);
+ mYOvershoot = rp.getDimension(R.dimen.swipe_up_y_overshoot);
}
public void onTargetPositionChanged() {
@@ -137,7 +140,12 @@
mAnimatorListeners.add(animatorListener);
}
- public void start(PointF velocityPxPerMs) {
+ /**
+ * Starts the fling/spring animation.
+ * @param context The activity context.
+ * @param velocityPxPerMs Velocity of swipe in px/ms.
+ */
+ public void start(Context context, PointF velocityPxPerMs) {
// Only tell caller that we ended if both x and y animations have ended.
OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
mRectXAnimEnded = true;
@@ -152,7 +160,7 @@
float endX = mTargetRect.centerX();
float minXValue = Math.min(startX, endX);
float maxXValue = Math.max(startX, endX);
- mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, startX, endX,
+ mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
velocityPxPerMs.x * 1000, mMinVisChange, minXValue, maxXValue, 1f, onXEndListener);
float startVelocityY = velocityPxPerMs.y * 1000;
@@ -162,14 +170,18 @@
float endY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top;
float minYValue = Math.min(startY, endY - mYOvershoot);
float maxYValue = Math.max(startY, endY);
- mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY,
+ mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, startVelocityY,
mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener);
- float minVisibleChange = 1f / mStartRect.height();
+ float minVisibleChange = Math.abs(1f / mStartRect.height());
+ ResourceProvider rp = DynamicResource.provider(context);
+ float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
+ float stiffness = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+
mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
.setSpring(new SpringForce(1f)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
- .setStiffness(SpringForce.STIFFNESS_LOW))
+ .setDampingRatio(damping)
+ .setStiffness(stiffness))
.setStartVelocity(velocityPxPerMs.y * minVisibleChange)
.setMaxValue(1f)
.setMinimumVisibleChange(minVisibleChange)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
index 83bc416..217eca5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -25,12 +25,12 @@
import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.states.OverviewState;
/**
* Animates the shelf between states HIDE, PEEK, and OVERVIEW.
*/
-
public class ShelfPeekAnim {
public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
@@ -49,7 +49,7 @@
* Animates to the given state, canceling the previous animation if it was still running.
*/
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
- if (mShelfState == shelfState) {
+ if (mShelfState == shelfState || FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
return;
}
mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 958ef7d..7e17fbf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -18,12 +18,13 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.view.View;
import android.view.ViewGroup;
@@ -32,19 +33,17 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.util.DynamicResource;
import com.android.quickstep.views.RecentsView;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.systemui.plugins.ResourceProvider;
/**
* Creates an animation where all the workspace items are moved into their final location,
@@ -58,13 +57,10 @@
private static final float MAX_VELOCITY_PX_PER_S = 22f;
- private static final float DAMPING_RATIO = 0.7f;
- private static final float STIFFNESS = 150f;
-
private final float mVelocity;
private final float mSpringTransY;
- private final List<Animator> mAnimators = new ArrayList<>();
+ private final AnimatorSet mAnimators = new AnimatorSet();
public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) {
prepareToAnimate(launcher);
@@ -130,16 +126,9 @@
addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
}
- AnimatorListener resetClipListener = new AnimatorListenerAdapter() {
- int numAnimations = mAnimators.size();
-
+ mAnimators.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- numAnimations--;
- if (numAnimations > 0) {
- return;
- }
-
workspace.setClipChildren(workspaceClipChildren);
workspace.setClipToPadding(workspaceClipToPadding);
cellLayout.setClipChildren(cellLayoutClipChildren);
@@ -147,22 +136,17 @@
hotseat.setClipChildren(hotseatClipChildren);
hotseat.setClipToPadding(hotseatClipToPadding);
}
- };
-
- for (Animator a : mAnimators) {
- a.addListener(resetClipListener);
- }
+ });
}
/**
* Setup workspace with 0 duration to prepare for our staggered animation.
*/
private void prepareToAnimate(Launcher launcher) {
- LauncherStateManager stateManager = launcher.getStateManager();
AnimatorSetBuilder builder = new AnimatorSetBuilder();
// setRecentsAttachedToAppWindow() will animate recents out.
- builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
- stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
+ launcher.getStateManager().createAtomicAnimation(
+ BACKGROUND_APP, NORMAL, builder, ANIM_ALL_COMPONENTS | SKIP_OVERVIEW, 0);
builder.build().start();
// Stop scrolling so that it doesn't interfere with the translation offscreen.
@@ -173,13 +157,7 @@
* Starts the animation.
*/
public void start() {
- for (Animator a : mAnimators) {
- if (a instanceof SpringObjectAnimator) {
- ((SpringObjectAnimator) a).startSpring(1f, mVelocity, null);
- } else {
- a.start();
- }
- }
+ mAnimators.start();
}
/**
@@ -196,17 +174,26 @@
long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS);
v.setTranslationY(mSpringTransY);
- SpringObjectAnimator springTransY = new SpringObjectAnimator<>(v, VIEW_TRANSLATE_Y,
- 1f, DAMPING_RATIO, STIFFNESS, mSpringTransY, 0);
+
+ ResourceProvider rp = DynamicResource.provider(v.getContext());
+ float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
+ float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
+ ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y)
+ .setStiffness(stiffness)
+ .setDampingRatio(damping)
+ .setMinimumVisibleChange(1f)
+ .setEndValue(0)
+ .setStartVelocity(mVelocity)
+ .build(v.getContext());
springTransY.setStartDelay(startDelay);
- mAnimators.add(springTransY);
+ mAnimators.play(springTransY);
v.setAlpha(0);
ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
alpha.setInterpolator(LINEAR);
alpha.setDuration(ALPHA_DURATION_MS);
alpha.setStartDelay(startDelay);
- mAnimators.add(alpha);
+ mAnimators.play(alpha);
}
private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
@@ -215,11 +202,11 @@
scrimAnimConfig.duration = duration;
PropertySetter scrimPropertySetter = scrimAnimConfig.getPropertySetter(scrimAnimBuilder);
launcher.getWorkspace().getStateTransitionAnimation().setScrim(scrimPropertySetter, state);
- mAnimators.add(scrimAnimBuilder.build());
+ mAnimators.play(scrimAnimBuilder.build());
Animator fadeOverviewScrim = ObjectAnimator.ofFloat(
launcher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
state.getOverviewScrimAlpha(launcher));
fadeOverviewScrim.setDuration(duration);
- mAnimators.add(fadeOverviewScrim);
+ mAnimators.play(fadeOverviewScrim);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
deleted file mode 100644
index 3619d3a..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.graphics.Rect;
-
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.function.Consumer;
-
-/**
- * Extension of {@link RemoteAnimationTargetSet} with additional information about swipe
- * up animation
- */
-public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet {
-
- private final boolean mShouldMinimizeSplitScreen;
- private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
-
- public final RecentsAnimationControllerCompat controller;
- public final Rect homeContentInsets;
- public final Rect minimizedHomeBounds;
-
- public SwipeAnimationTargetSet(RecentsAnimationControllerCompat controller,
- RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
- Rect minimizedHomeBounds, boolean shouldMinimizeSplitScreen,
- Consumer<SwipeAnimationTargetSet> onFinishListener) {
- super(targets, MODE_CLOSING);
- this.controller = controller;
- this.homeContentInsets = homeContentInsets;
- this.minimizedHomeBounds = minimizedHomeBounds;
- this.mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
- this.mOnFinishListener = onFinishListener;
- }
-
- public boolean hasTargets() {
- return unfilteredApps.length != 0;
- }
-
- /**
- * Clones the target set without any actual targets. Used only when continuing a gesture after
- * the actual recents animation has finished.
- */
- public SwipeAnimationTargetSet cloneWithoutTargets() {
- return new SwipeAnimationTargetSet(controller, new RemoteAnimationTargetCompat[0],
- homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
- mOnFinishListener);
- }
-
- public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
- mOnFinishListener.accept(this);
- UI_HELPER_EXECUTOR.execute(() -> {
- controller.setInputConsumerEnabled(false);
- controller.finish(toRecents, sendUserLeaveHint);
-
- if (callback != null) {
- MAIN_EXECUTOR.execute(callback);
- }
- });
- }
-
- public void enableInputConsumer() {
- UI_HELPER_EXECUTOR.submit(() -> {
- controller.hideCurrentInputMethod();
- controller.setInputConsumerEnabled(true);
- });
- }
-
- public void setWindowThresholdCrossed(boolean thresholdCrossed) {
- UI_HELPER_EXECUTOR.execute(() -> {
- controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed);
- if (mShouldMinimizeSplitScreen && thresholdCrossed) {
- // NOTE: As a workaround for conflicting animations (Launcher animating the task
- // leash, and SystemUI resizing the docked stack, which resizes the task), we
- // currently only set the minimized mode, and not the inverse.
- // TODO: Synchronize the minimize animation with the launcher animation
- controller.setSplitScreenMinimized(thresholdCrossed);
- }
- });
- }
-
- public ThumbnailData screenshotTask(int taskId) {
- return controller != null ? controller.screenshotTask(taskId) : null;
- }
-
- public void cancelAnimation() {
- finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
- }
-
- public void finishAnimation() {
- finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
- }
-
- public interface SwipeAnimationListener {
-
- void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet);
-
- void onRecentsAnimationCanceled();
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
new file mode 100644
index 0000000..c71258b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Tracks motion events to determine whether a gesture on the nav bar is a swipe up.
+ */
+public class TriggerSwipeUpTouchTracker {
+
+ private final PointF mDownPos = new PointF();
+ private final float mSquaredTouchSlop;
+ private final float mMinFlingVelocity;
+ private final boolean mDisableHorizontalSwipe;
+ private final NavBarPosition mNavBarPosition;
+ private final Runnable mOnInterceptTouch;
+ private final OnSwipeUpListener mOnSwipeUp;
+
+ private boolean mInterceptedTouch;
+ private VelocityTracker mVelocityTracker;
+
+ public TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe,
+ NavBarPosition navBarPosition, Runnable onInterceptTouch,
+ OnSwipeUpListener onSwipeUp) {
+ mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
+ mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+ mNavBarPosition = navBarPosition;
+ mDisableHorizontalSwipe = disableHorizontalSwipe;
+ mOnInterceptTouch = onInterceptTouch;
+ mOnSwipeUp = onSwipeUp;
+
+ init();
+ }
+
+ /**
+ * Reset some initial values to prepare for the next gesture.
+ */
+ public void init() {
+ mInterceptedTouch = false;
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+
+ /**
+ * @return Whether we have passed the touch slop and are still tracking the gesture.
+ */
+ public boolean interceptedTouch() {
+ return mInterceptedTouch;
+ }
+
+ /**
+ * Track motion events to determine whether an atomic swipe up has occurred.
+ */
+ public void onMotionEvent(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ return;
+ }
+
+ mVelocityTracker.addMovement(ev);
+ switch (ev.getActionMasked()) {
+ case ACTION_DOWN: {
+ mDownPos.set(ev.getX(), ev.getY());
+ break;
+ }
+ case ACTION_MOVE: {
+ if (!mInterceptedTouch) {
+ float displacementX = ev.getX() - mDownPos.x;
+ float displacementY = ev.getY() - mDownPos.y;
+ if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
+ if (mDisableHorizontalSwipe
+ && Math.abs(displacementX) > Math.abs(displacementY)) {
+ // Horizontal gesture is not allowed in this region
+ endTouchTracking();
+ break;
+ }
+
+ mInterceptedTouch = true;
+
+ if (mOnInterceptTouch != null) {
+ mOnInterceptTouch.run();
+ }
+ }
+ }
+ break;
+ }
+
+ case ACTION_CANCEL:
+ endTouchTracking();
+ break;
+
+ case ACTION_UP: {
+ onGestureEnd(ev);
+ endTouchTracking();
+ break;
+ }
+ }
+ }
+
+ private void endTouchTracking() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void onGestureEnd(MotionEvent ev) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float velocityX = mVelocityTracker.getXVelocity();
+ float velocityY = mVelocityTracker.getYVelocity();
+ float velocity = mNavBarPosition.isRightEdge()
+ ? -velocityX
+ : mNavBarPosition.isLeftEdge()
+ ? velocityX
+ : -velocityY;
+
+ final boolean wasFling = Math.abs(velocity) >= mMinFlingVelocity;
+ final boolean isSwipeUp;
+ if (wasFling) {
+ isSwipeUp = velocity > 0;
+ } else {
+ float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
+ float displacementY = ev.getY() - mDownPos.y;
+ isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
+ }
+
+ if (isSwipeUp && mOnSwipeUp != null) {
+ mOnSwipeUp.onSwipeUp(wasFling);
+ }
+ }
+
+ /**
+ * Callback when the gesture ends and was determined to be a swipe from the nav bar.
+ */
+ public interface OnSwipeUpListener {
+ /**
+ * Called on touch up if a swipe up was detected.
+ * @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity.
+ */
+ void onSwipeUp(boolean wasFling);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
index 9db0c09..763f5be 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
@@ -18,25 +18,25 @@
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Property;
+import android.util.FloatProperty;
import android.widget.Button;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
public class ClearAllButton extends Button implements PageCallbacks {
- public static final Property<ClearAllButton, Float> VISIBILITY_ALPHA =
- new Property<ClearAllButton, Float>(Float.class, "visibilityAlpha") {
+ public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
+ new FloatProperty<ClearAllButton>("visibilityAlpha") {
@Override
public Float get(ClearAllButton view) {
return view.mVisibilityAlpha;
}
@Override
- public void set(ClearAllButton view, Float visibilityAlpha) {
- view.setVisibilityAlpha(visibilityAlpha);
+ public void setValue(ClearAllButton view, float v) {
+ view.setVisibilityAlpha(v);
}
};
@@ -44,21 +44,26 @@
private float mContentAlpha = 1;
private float mVisibilityAlpha = 1;
- private final boolean mIsRtl;
+ private boolean mIsRtl;
private int mScrollOffset;
+ private RecentsView mParent;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mIsRtl = Utilities.isRtl(context.getResources());
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ mScrollOffset = mIsRtl ? mParent.getPaddingRight() / 2 : - mParent.getPaddingLeft() / 2;
+ }
- RecentsView parent = (RecentsView) getParent();
- mScrollOffset = mIsRtl ? parent.getPaddingRight() / 2 : - parent.getPaddingLeft() / 2;
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mParent = (RecentsView) getParent();
+ mIsRtl = !mParent.getPagedOrientationHandler().getRecentsRtlSetting(getResources());
}
@Override
@@ -73,6 +78,21 @@
}
}
+ public void onLayoutChanged() {
+ if (mParent == null) {
+ return;
+ }
+ setRotation(mParent.getPagedOrientationHandler().getDegreesRotated());
+ }
+
+ public void setRtl(boolean rtl) {
+ if (mIsRtl == rtl) {
+ return;
+ }
+ mIsRtl = rtl;
+ invalidate();
+ }
+
public void setVisibilityAlpha(float alpha) {
if (mVisibilityAlpha != alpha) {
mVisibilityAlpha = alpha;
@@ -82,14 +102,16 @@
@Override
public void onPageScroll(ScrollState scrollState) {
- float width = getWidth();
- if (width == 0) {
+ PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler();
+ float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
+ if (orientationSize == 0) {
return;
}
- float shift = Math.min(scrollState.scrollFromEdge, width);
- setTranslationX(mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift));
- mScrollAlpha = 1 - shift / width;
+ float shift = Math.min(scrollState.scrollFromEdge, orientationSize);
+ float translation = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift);
+ orientationHandler.setPrimaryAndResetSecondaryTranslate(this, translation);
+ mScrollAlpha = 1 - shift / orientationSize;
updateAlpha();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index c2cb720..3ed7530 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -46,11 +46,15 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
import com.android.quickstep.util.LayoutUtils;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.RecentsExtraCard;
@@ -84,6 +88,10 @@
}
};
+ private RotationHelper.ForcedRotationChangedListener mForcedRotationChangedListener =
+ isForcedRotation -> LauncherRecentsView.this
+ .disableMultipleLayoutRotations(!isForcedRotation);
+
public LauncherRecentsView(Context context) {
this(context, null);
}
@@ -100,7 +108,13 @@
@Override
public void startHome() {
- mActivity.getStateManager().goToState(NORMAL);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ switchToScreenshot(null,
+ () -> finishRecentsAnimation(true /* toRecents */,
+ () -> mActivity.getStateManager().goToState(NORMAL)));
+ } else {
+ mActivity.getStateManager().goToState(NORMAL);
+ }
}
@Override
@@ -137,7 +151,7 @@
*/
@Override
public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv,
- ClipAnimationHelper helper) {
+ AppWindowAnimationHelper helper) {
AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
@@ -173,30 +187,31 @@
* @return The translationX to apply to this view so that the first task is just offscreen.
*/
public float getOffscreenTranslationX(float recentsScale) {
- float offscreenX = NORMAL.getOverviewScaleAndTranslation(mActivity).translationX;
+ LauncherState.ScaleAndTranslation overviewScaleAndTranslation =
+ NORMAL.getOverviewScaleAndTranslation(mActivity);
+ float offscreen = mOrientationHandler.getTranslationValue(overviewScaleAndTranslation);
// Offset since scale pushes tasks outwards.
getTaskSize(sTempRect);
- int taskWidth = sTempRect.width();
- offscreenX += taskWidth * (recentsScale - 1) / 2;
+ int taskSize = mOrientationHandler.getPrimarySize(sTempRect);
+ offscreen += taskSize * (recentsScale - 1) / 2;
if (mRunningTaskTileHidden) {
// The first task is hidden, so offset by its width.
- offscreenX -= (taskWidth + getPageSpacing()) * recentsScale;
+ offscreen -= (taskSize + getPageSpacing()) * recentsScale;
}
if (isRtl()) {
- offscreenX = -offscreenX;
+ offscreen = -offscreen;
}
- return offscreenX;
+ return offscreen;
}
@Override
protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- if (mRecentsAnimationWrapper.targetSet != null && tv.isRunningTask()) {
+ if (tv.isRunningTask()) {
mTransformParams.setProgress(1 - progress)
- .setSyncTransactionApplier(mSyncTransactionApplier)
- .setForLiveTile(true);
- mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
- mTransformParams);
+ .setCurrentRect(null)
+ .setSyncTransactionApplier(mSyncTransactionApplier);
+ mAppWindowAnimationHelper.applyTransform(mTransformParams);
} else {
redrawLiveTile(true);
}
@@ -216,7 +231,7 @@
@Override
public boolean shouldUseMultiWindowTaskSizeStrategy() {
- return mActivity.isInMultiWindowMode();
+ return TraceHelper.whitelistIpcs("isInMultiWindowMode", mActivity::isInMultiWindowMode);
}
@Override
@@ -229,9 +244,18 @@
@Override
public void redrawLiveTile(boolean mightNeedToRefill) {
- if (!mEnableDrawingLiveTile || mRecentsAnimationWrapper == null
- || mClipAnimationHelper == null) {
- return;
+ AppWindowAnimationHelper.TransformParams transformParams = getLiveTileParams(mightNeedToRefill);
+ if (transformParams != null) {
+ mAppWindowAnimationHelper.applyTransform(transformParams);
+ }
+ }
+
+ @Override
+ public AppWindowAnimationHelper.TransformParams getLiveTileParams(
+ boolean mightNeedToRefill) {
+ if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
+ || mRecentsAnimationTargets == null || mAppWindowAnimationHelper == null) {
+ return null;
}
TaskView taskView = getRunningTaskView();
if (taskView != null) {
@@ -252,13 +276,19 @@
}
mTempRectF.set(mTempRect);
mTransformParams.setProgress(1f)
- .setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha())
- .setSyncTransactionApplier(mSyncTransactionApplier);
- if (mRecentsAnimationWrapper.targetSet != null) {
- mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
- mTransformParams);
- }
+ .setCurrentRect(mTempRectF)
+ .setTargetAlpha(taskView.getAlpha())
+ .setSyncTransactionApplier(mSyncTransactionApplier)
+ .setTargetSet(mRecentsAnimationTargets)
+ .setLauncherOnTop(true);
}
+ return mTransformParams;
+ }
+
+ @Override
+ protected boolean supportsVerticalLandscape() {
+ return FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()
+ && !mOrientationState.areMultipleLayoutOrientationsDisabled();
}
@Override
@@ -312,8 +342,9 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- PluginManagerWrapper.INSTANCE.get(getContext())
- .addPluginListener(mRecentsExtraCardPluginListener, RecentsExtraCard.class);
+ PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(
+ mRecentsExtraCardPluginListener, RecentsExtraCard.class);
+ mActivity.getRotationHelper().addForcedRotationCallback(mForcedRotationChangedListener);
}
@Override
@@ -321,22 +352,23 @@
super.onDetachedFromWindow();
PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(
mRecentsExtraCardPluginListener);
+ mActivity.getRotationHelper().removeForcedRotationCallback(mForcedRotationChangedListener);
}
@Override
- protected int computeMinScrollX() {
+ protected int computeMinScroll() {
if (canComputeScrollX() && !mIsRtl) {
return computeScrollX();
}
- return super.computeMinScrollX();
+ return super.computeMinScroll();
}
@Override
- protected int computeMaxScrollX() {
+ protected int computeMaxScroll() {
if (canComputeScrollX() && mIsRtl) {
return computeScrollX();
}
- return super.computeMaxScrollX();
+ return super.computeMaxScroll();
}
private boolean canComputeScrollX() {
@@ -364,10 +396,20 @@
}
@Override
- public void resetTaskVisuals() {
- super.resetTaskVisuals();
+ public boolean hasRecentsExtraCard() {
+ return mRecentsExtraViewContainer != null;
+ }
+
+ @Override
+ public void setContentAlpha(float alpha) {
+ super.setContentAlpha(alpha);
if (mRecentsExtraViewContainer != null) {
- mRecentsExtraViewContainer.setAlpha(mContentAlpha);
+ mRecentsExtraViewContainer.setAlpha(alpha);
}
}
+
+ @Override
+ protected BackgroundBlurController getBackgroundBlurController() {
+ return mActivity.getBackgroundBlurController();
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
index a838797..30c9f77 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -16,6 +16,7 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
+import android.view.ViewOverlay;
import com.android.launcher3.anim.Interpolators;
@@ -36,9 +37,11 @@
}
};
- private final Paint mPaint = new Paint();
+ public static final LiveTileOverlay INSTANCE = new LiveTileOverlay();
- private Rect mBoundsRect = new Rect();
+ private final Paint mPaint = new Paint();
+ private final Rect mBoundsRect = new Rect();
+
private RectF mCurrentRect;
private float mCornerRadius;
private Drawable mIcon;
@@ -46,8 +49,9 @@
private boolean mDrawEnabled = true;
private float mIconAnimationProgress = 0f;
+ private boolean mIsAttached;
- public LiveTileOverlay() {
+ private LiveTileOverlay() {
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
@@ -124,6 +128,23 @@
return PixelFormat.TRANSLUCENT;
}
+ public boolean attach(ViewOverlay overlay) {
+ if (overlay != null && !mIsAttached) {
+ overlay.add(this);
+ mIsAttached = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ public void detach(ViewOverlay overlay) {
+ if (overlay != null) {
+ overlay.remove(this);
+ mIsAttached = false;
+ }
+ }
+
private void setIconAnimationProgress(float progress) {
mIconAnimationProgress = progress;
invalidateSelf();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 6ad3cc6..b5e6af4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -16,13 +16,10 @@
package com.android.quickstep.views;
-import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
-
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
@@ -30,8 +27,9 @@
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.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
@@ -39,12 +37,10 @@
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -52,7 +48,6 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -60,54 +55,73 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
+import android.os.UserHandle;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.util.Property;
import android.util.SparseBooleanArray;
+import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.OrientationEventListener;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
import android.widget.ListView;
import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.SpringForce;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
+import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
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.PendingAnimation;
+import com.android.launcher3.anim.PendingAnimation.EndState;
import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.anim.SpringProperty;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.OverScroller;
-import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.ViewPool;
-import com.android.quickstep.RecentsAnimationWrapper;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
+import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.systemui.plugins.ResourceProvider;
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -125,7 +139,7 @@
@TargetApi(Build.VERSION_CODES.P)
public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
- InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener {
+ InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener {
private static final String TAG = RecentsView.class.getSimpleName();
@@ -155,8 +169,10 @@
}
};
- protected RecentsAnimationWrapper mRecentsAnimationWrapper;
- protected ClipAnimationHelper mClipAnimationHelper;
+ private int mPreviousRotation;
+ protected RecentsAnimationController mRecentsAnimationController;
+ protected RecentsAnimationTargets mRecentsAnimationTargets;
+ protected AppWindowAnimationHelper mAppWindowAnimationHelper;
protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
protected int mTaskWidth;
protected int mTaskHeight;
@@ -173,10 +189,11 @@
private final float mFastFlingVelocity;
private final RecentsModel mModel;
private final int mTaskTopMargin;
+ private final int mTaskBottomMargin;
private final ClearAllButton mClearAllButton;
private final Rect mClearAllButtonDeadZoneRect = new Rect();
private final Rect mTaskViewDeadZoneRect = new Rect();
- protected final ClipAnimationHelper mTempClipAnimationHelper;
+ protected final AppWindowAnimationHelper mTempAppWindowAnimationHelper;
private final ScrollState mScrollState = new ScrollState();
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
@@ -189,7 +206,7 @@
private boolean mDwbToastShown;
protected boolean mDisallowScrollToClearAll;
private boolean mOverlayEnabled;
- private boolean mFreezeViewVisibility;
+ protected boolean mFreezeViewVisibility;
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -257,15 +274,11 @@
}
});
}
-
- @Override
- public void onPinnedStackAnimationStarted() {
- // Needed for activities that auto-enter PiP, which will not trigger a remote
- // animation to be created
- mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
- }
};
+ private final PinnedStackAnimationListener mIPinnedStackAnimationListener =
+ new PinnedStackAnimationListener();
+
// Used to keep track of the last requested task list id, so that we do not request to load the
// tasks again if we have already requested it and the task list has not changed
private int mTaskListChangeId = -1;
@@ -303,11 +316,14 @@
private final Point mLastMeasureSize = new Point();
private final int mEmptyMessagePadding;
private boolean mShowEmptyMessage;
+ private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
private Layout mEmptyTextLayout;
- private LiveTileOverlay mLiveTileOverlay;
+ private boolean mLiveTileOverlayAttached;
// Keeps track of the index where the first TaskView should be
private int mTaskViewStartIndex = 0;
+ private View mActionsView;
+ private boolean mGestureRunning = false;
private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
(inMultiWindowMode) -> {
@@ -327,7 +343,8 @@
mActivity = (T) BaseActivity.fromContext(context);
mModel = RecentsModel.INSTANCE.get(context);
mIdp = InvariantDeviceProfile.INSTANCE.get(context);
- mTempClipAnimationHelper = new ClipAnimationHelper(context);
+ mTempAppWindowAnimationHelper =
+ new AppWindowAnimationHelper(getPagedViewOrientedState(), context);
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
@@ -335,10 +352,11 @@
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
10 /* initial size */);
- mIsRtl = !Utilities.isRtl(getResources());
+ mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mTaskTopMargin = getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(context);
mSquaredTouchSlop = squaredTouchSlop(context);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -354,6 +372,7 @@
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
updateEmptyMessage();
+ disableMultipleLayoutRotations(!supportsVerticalLandscape());
// Initialize quickstep specific cache params here, as this is constructed only once
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
@@ -380,14 +399,38 @@
return null;
}
- public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+ @Override
+ public void onTaskIconChanged(String pkg, UserHandle user) {
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ TaskView tv = getTaskViewAt(i);
+ Task task = tv.getTask();
+ if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
+ && task.key.userId == user.getIdentifier()) {
+ task.icon = null;
+ if (tv.getIconView().getDrawable() != null) {
+ tv.onTaskListVisibilityChanged(true /* visible */);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the thumbnail of the task.
+ * @param refreshNow Refresh immediately if it's true.
+ */
+ public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
TaskView taskView = getTaskView(taskId);
if (taskView != null) {
- taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
+ taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
}
return taskView;
}
+ /** See {@link #updateThumbnail(int, ThumbnailData, boolean)} */
+ public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+ return updateThumbnail(taskId, thumbnailData, true /* refreshNow */);
+ }
+
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
@@ -413,6 +456,10 @@
mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIdp.addOnChangeListener(this);
+ mIPinnedStackAnimationListener.setActivity(mActivity);
+ SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
+ mIPinnedStackAnimationListener);
+ setActionsView();
}
@Override
@@ -425,6 +472,8 @@
mSyncTransactionApplier = null;
RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
mIdp.removeOnChangeListener(this);
+ SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
+ mIPinnedStackAnimationListener.setActivity(null);
}
@Override
@@ -457,6 +506,11 @@
public void setOverviewStateEnabled(boolean enabled) {
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
+ if (!enabled) {
+ // Reset the running task when leaving overview since it can still have a reference to
+ // its thumbnail
+ mTmpRunningTask = null;
+ }
}
public void onDigitalWellbeingToastShown() {
@@ -527,13 +581,20 @@
return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev);
}
+ @Override
+ protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
+ // Enables swiping to the left or right only if the task overlay is not modal.
+ if (getCurrentPageTaskView() == null || !getCurrentPageTaskView().isTaskOverlayModal()) {
+ super.determineScrollingStart(ev, touchSlopScale);
+ }
+ }
protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
return true;
}
protected void applyLoadPlan(ArrayList<Task> tasks) {
if (mPendingAnimation != null) {
- mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks));
+ mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks));
return;
}
@@ -553,6 +614,7 @@
if (getTaskViewCount() != requiredTaskCount) {
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
+ hideActionsView();
}
for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
addView(mTaskViewPool.getView());
@@ -562,6 +624,7 @@
}
if (requiredTaskCount > 0) {
addView(mClearAllButton);
+ showActionsView();
}
}
@@ -570,7 +633,7 @@
final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
- taskView.bind(task);
+ taskView.bind(task, mLayoutRotation);
}
if (mNextPage == INVALID_PAGE) {
@@ -601,6 +664,7 @@
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
}
+ hideActionsView();
}
public int getTaskViewCount() {
@@ -611,6 +675,21 @@
return taskViewCount;
}
+ /**
+ * Updates UI for a modal task, including hiding other tasks.
+ */
+ public void updateUiForModalTask(TaskView taskView, boolean isTaskOverlayModal) {
+ int currentIndex = indexOfChild(taskView);
+ TaskView previousTask = getTaskViewAt(currentIndex - 1);
+ TaskView nextTask = getTaskViewAt(currentIndex + 1);
+ if (previousTask != null) {
+ previousTask.setVisibility(isTaskOverlayModal ? View.INVISIBLE : View.VISIBLE);
+ }
+ if (nextTask != null) {
+ nextTask.setVisibility(isTaskOverlayModal ? View.INVISIBLE : View.VISIBLE);
+ }
+ }
+
protected void onTaskStackUpdated() { }
public void resetTaskVisuals() {
@@ -663,6 +742,7 @@
mTaskHeight = mTempRect.height();
mTempRect.top -= mTaskTopMargin;
+ mTempRect.bottom += mTaskBottomMargin;
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
@@ -701,19 +781,21 @@
if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
return;
}
- int scrollX = getScrollX();
- final int halfPageWidth = getNormalChildWidth() / 2;
- final int screenCenter = mInsets.left + getPaddingLeft() + scrollX + halfPageWidth;
- final int halfScreenWidth = getMeasuredWidth() / 2;
+ CurveProperties curveProperties = mOrientationHandler
+ .getCurveProperties(this, mInsets);
+ int scroll = curveProperties.scroll;
+ final int halfPageSize = curveProperties.halfPageSize;
+ final int screenCenter = curveProperties.screenCenter;
+ final int halfScreenSize = curveProperties.halfScreenSize;
final int pageSpacing = mPageSpacing;
- mScrollState.scrollFromEdge = mIsRtl ? scrollX : (mMaxScrollX - scrollX);
+ mScrollState.scrollFromEdge = mIsRtl ? scroll : (mMaxScroll - scroll);
final int pageCount = getPageCount();
for (int i = 0; i < pageCount; i++) {
View page = getPageAt(i);
- float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
+ float pageCenter = mOrientationHandler.getViewCenterPosition(page) + halfPageSize;
float distanceFromScreenCenter = screenCenter - pageCenter;
- float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+ float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
mScrollState.linearInterpolation = Math.min(1,
Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
((PageCallbacks) page).onPageScroll(mScrollState);
@@ -793,13 +875,19 @@
public abstract void startHome();
+ /** `true` if there is a +1 space available in overview. */
+ public boolean hasRecentsExtraCard() {
+ return false;
+ }
+
public void reset() {
setCurrentTask(-1);
mIgnoreResetTaskId = -1;
mTaskListChangeId = -1;
- mRecentsAnimationWrapper = null;
- mClipAnimationHelper = null;
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ mAppWindowAnimationHelper = null;
unloadVisibleTaskData();
setCurrentPage(0);
@@ -839,6 +927,7 @@
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
setRunningTaskIconScaledDown(true);
+ mGestureRunning = true;
}
/**
@@ -847,14 +936,23 @@
*/
public void onSwipeUpAnimationSuccess() {
if (getRunningTaskView() != null) {
- float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null
- ? mLiveTileOverlay.cancelIconAnimation()
+ float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached
+ ? LiveTileOverlay.INSTANCE.cancelIconAnimation()
: 0f;
animateUpRunningTaskIconScale(startProgress);
}
setSwipeDownShouldLaunchApp(true);
}
+ abstract protected boolean supportsVerticalLandscape();
+
+ private void rotateAllChildTasks() {
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ TaskView taskView = getTaskViewAt(i);
+ taskView.setOverviewRotation(mLayoutRotation);
+ }
+ }
+
/**
* Called when a gesture from an app has finished.
*/
@@ -862,9 +960,12 @@
setEnableFreeScroll(true);
setEnableDrawingLiveTile(true);
setOnScrollChangeListener(null);
- setRunningTaskViewShowScreenshot(true);
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ setRunningTaskViewShowScreenshot(true);
+ }
setRunningTaskHidden(false);
animateUpRunningTaskIconScale();
+ mGestureRunning = false;
}
/**
@@ -881,6 +982,7 @@
addView(taskView, mTaskViewStartIndex);
if (wasEmpty) {
addView(mClearAllButton);
+ showActionsView();
}
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
@@ -888,7 +990,7 @@
new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
false, true, false, false, new ActivityManager.TaskDescription(), 0,
new ComponentName("", ""), false);
- taskView.bind(mTmpRunningTask);
+ taskView.bind(mTmpRunningTask, mLayoutRotation);
}
boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -927,6 +1029,10 @@
TaskView runningTask = getRunningTaskView();
if (runningTask != null) {
runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
+ if (!isHidden) {
+ AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask,
+ AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
+ }
}
}
@@ -1058,30 +1164,29 @@
}
}
- private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
- addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
- if (QUICKSTEP_SPRINGS.get() && taskView instanceof TaskView)
- addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y,
- MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
- SpringForce.STIFFNESS_MEDIUM,
- 0, -taskView.getHeight()),
- duration, LINEAR, anim);
- else {
- addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
- duration, LINEAR, anim);
- }
+ private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) {
+ anim.add(ObjectAnimator.ofFloat(taskView, ALPHA, 0).setDuration(duration), ACCEL_2);
+ FloatProperty<View> secondaryViewTranslate =
+ mOrientationHandler.getSecondaryViewTranslate();
+ int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
+ int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor();
+
+ ResourceProvider rp = DynamicResource.provider(mActivity);
+ SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
+ .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
+ .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
+
+ anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
+ verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp);
}
- private void removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener,
- boolean shouldLog) {
+ private void removeTask(Task task, int index, EndState endState) {
if (task != null) {
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
- if (shouldLog) {
- ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
- mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
- onEndListener.logAction, Direction.UP, index, componentKey);
- mActivity.getStatsLogManager().logTaskDismiss(this, componentKey);
- }
+ ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
+ mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
+ endState.logAction, Direction.UP, index, componentKey);
+ mActivity.getStatsLogManager().logTaskDismiss(this, componentKey);
}
}
@@ -1090,20 +1195,17 @@
if (mPendingAnimation != null) {
mPendingAnimation.finish(false, Touch.SWIPE);
}
- AnimatorSet anim = new AnimatorSet();
- PendingAnimation pendingAnimation = new PendingAnimation(anim);
+ PendingAnimation anim = new PendingAnimation();
int count = getPageCount();
if (count == 0) {
- return pendingAnimation;
+ return anim;
}
int[] oldScroll = new int[count];
- getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
-
int[] newScroll = new int[count];
+ getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
-
int taskCount = getTaskViewCount();
int scrollDiffPerPage = 0;
if (count > 1) {
@@ -1116,7 +1218,7 @@
View child = getChildAt(i);
if (child == taskView) {
if (animateTaskView) {
- addDismissedTaskAnimations(taskView, anim, duration);
+ addDismissedTaskAnimations(taskView, duration, anim);
}
} else {
// If we just take newScroll - oldScroll, everything to the right of dragged task
@@ -1140,16 +1242,15 @@
}
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
- if (QUICKSTEP_SPRINGS.get() && child instanceof TaskView) {
- addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X,
- MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
- SpringForce.STIFFNESS_MEDIUM,
- 0, scrollDiff), duration, ACCEL, anim);
- } else {
- addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration,
- ACCEL, anim);
- }
+ Property translationProperty = mOrientationHandler.getPrimaryViewTranslate();
+ ResourceProvider rp = DynamicResource.provider(mActivity);
+ SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
+ .setDampingRatio(
+ rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio))
+ .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness));
+ anim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff)
+ .setDuration(duration), ACCEL, sp);
needsCurveUpdates = true;
}
}
@@ -1158,7 +1259,7 @@
if (needsCurveUpdates) {
ValueAnimator va = ValueAnimator.ofFloat(0, 1);
va.addUpdateListener((a) -> updateCurveProperties());
- anim.play(va);
+ anim.add(va);
}
// Add a tiny bit of translation Z, so that it draws on top of other views
@@ -1166,22 +1267,22 @@
taskView.setTranslationZ(0.1f);
}
- mPendingAnimation = pendingAnimation;
- mPendingAnimation.addEndListener(new Consumer<PendingAnimation.OnEndListener>() {
+ mPendingAnimation = anim;
+ mPendingAnimation.addEndListener(new Consumer<EndState>() {
@Override
- public void accept(PendingAnimation.OnEndListener onEndListener) {
+ public void accept(EndState endState) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
- taskView.isRunningTask() && onEndListener.isSuccess) {
- finishRecentsAnimation(true /* toHome */, () -> onEnd(onEndListener));
+ taskView.isRunningTask() && endState.isSuccess) {
+ finishRecentsAnimation(true /* toHome */, () -> onEnd(endState));
} else {
- onEnd(onEndListener);
+ onEnd(endState);
}
}
- private void onEnd(PendingAnimation.OnEndListener onEndListener) {
- if (onEndListener.isSuccess) {
+ private void onEnd(EndState endState) {
+ if (endState.isSuccess) {
if (shouldRemoveTask) {
- removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
+ removeTask(taskView.getTask(), draggedIndex, endState);
}
int pageToSnapTo = mCurrentPage;
@@ -1193,6 +1294,7 @@
if (getTaskViewCount() == 0) {
removeView(mClearAllButton);
+ hideActionsView();
startHome();
} else {
snapToPageImmediately(pageToSnapTo);
@@ -1202,24 +1304,23 @@
mPendingAnimation = null;
}
});
- return pendingAnimation;
+ return anim;
}
public PendingAnimation createAllTasksDismissAnimation(long duration) {
- if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+ if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
throw new IllegalStateException("Another pending animation is still running");
}
- AnimatorSet anim = new AnimatorSet();
- PendingAnimation pendingAnimation = new PendingAnimation(anim);
+ PendingAnimation anim = new PendingAnimation();
int count = getTaskViewCount();
for (int i = 0; i < count; i++) {
- addDismissedTaskAnimations(getTaskViewAt(i), anim, duration);
+ addDismissedTaskAnimations(getTaskViewAt(i), duration, anim);
}
- mPendingAnimation = pendingAnimation;
- mPendingAnimation.addEndListener((onEndListener) -> {
- if (onEndListener.isSuccess) {
+ mPendingAnimation = anim;
+ mPendingAnimation.addEndListener((endState) -> {
+ if (endState.isSuccess) {
// Remove all the task views now
ActivityManagerWrapper.getInstance().removeAllRecentTasks();
removeTasksViewsAndClearAllButton();
@@ -1227,13 +1328,7 @@
}
mPendingAnimation = null;
});
- return pendingAnimation;
- }
-
- private static void addAnim(Animator anim, long duration,
- TimeInterpolator interpolator, AnimatorSet set) {
- anim.setDuration(duration).setInterpolator(interpolator);
- set.play(anim);
+ return anim;
}
private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) {
@@ -1250,8 +1345,8 @@
}
private void runDismissAnimation(PendingAnimation pendingAnim) {
- AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
- pendingAnim.anim, DISMISS_TASK_DURATION);
+ AnimatorPlaybackController controller =
+ AnimatorPlaybackController.wrap(pendingAnim, DISMISS_TASK_DURATION);
controller.dispatchOnStart();
controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
@@ -1270,7 +1365,7 @@
}
private void dismissCurrentTask() {
- TaskView taskView = getTaskView(getNextPage());
+ TaskView taskView = getNextPageTaskView();
if (taskView != null) {
dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
}
@@ -1337,15 +1432,17 @@
}
}
mClearAllButton.setContentAlpha(mContentAlpha);
-
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
-
if (alpha > 0) {
setVisibility(VISIBLE);
+ if (!mGestureRunning) {
+ showActionsView();
+ }
} else if (!mFreezeViewVisibility) {
setVisibility(GONE);
+ hideActionsView();
}
}
@@ -1359,11 +1456,28 @@
if (!mFreezeViewVisibility) {
setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
+ if (mContentAlpha > 0) {
+ showActionsView();
+ } else {
+ hideActionsView();
+ }
}
}
}
@Override
+ public void setLayoutRotation(int touchRotation, int displayRotation) {
+ if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
+ return;
+ }
+
+ super.setLayoutRotation(touchRotation, displayRotation);
+ mClearAllButton.onLayoutChanged();
+ mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
+ setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+ }
+
+ @Override
public void onViewAdded(View child) {
super.onViewAdded(child);
child.setAlpha(mContentAlpha);
@@ -1411,6 +1525,10 @@
return null;
}
+ public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
+ mOnEmptyMessageUpdatedListener = listener;
+ }
+
public void updateEmptyMessage() {
boolean isEmpty = getTaskViewCount() == 0;
boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
@@ -1422,6 +1540,10 @@
mShowEmptyMessage = isEmpty;
updateEmptyStateUi(hasSizeChanged);
invalidate();
+
+ if (mOnEmptyMessageUpdatedListener != null) {
+ mOnEmptyMessageUpdatedListener.onEmptyMessageUpdated(mShowEmptyMessage);
+ }
}
@Override
@@ -1431,7 +1553,7 @@
// Set the pivot points to match the task preview center
setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin)
- + (getHeight() - mInsets.bottom - getPaddingBottom())) / 2);
+ + (getHeight() - mInsets.bottom - getPaddingBottom() - mTaskBottomMargin)) / 2);
setPivotX(((mInsets.left + getPaddingLeft())
+ (getWidth() - mInsets.right - getPaddingRight())) / 2);
}
@@ -1508,14 +1630,14 @@
* to the right.
*/
public AnimatorSet createAdjacentPageAnimForTaskLaunch(
- TaskView tv, ClipAnimationHelper clipAnimationHelper) {
+ TaskView tv, AppWindowAnimationHelper appWindowAnimationHelper) {
AnimatorSet anim = new AnimatorSet();
int taskIndex = indexOfChild(tv);
int centerTaskIndex = getCurrentPage();
boolean launchingCenterTask = taskIndex == centerTaskIndex;
- LauncherState.ScaleAndTranslation toScaleAndTranslation = clipAnimationHelper
+ LauncherState.ScaleAndTranslation toScaleAndTranslation = appWindowAnimationHelper
.getScaleAndTranslation();
float toScale = toScaleAndTranslation.scale;
float toTranslationY = toScaleAndTranslation.translationY;
@@ -1541,20 +1663,20 @@
return anim;
}
- public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
- if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+ public PendingAnimation createTaskLaunchAnimation(
+ TaskView tv, long duration, Interpolator interpolator) {
+ if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
throw new IllegalStateException("Another pending animation is still running");
}
int count = getTaskViewCount();
if (count == 0) {
- return new PendingAnimation(new AnimatorSet());
+ return new PendingAnimation();
}
int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
final boolean[] passedOverviewThreshold = new boolean[] {false};
ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
- progressAnim.setInterpolator(LINEAR);
progressAnim.addUpdateListener(animator -> {
// Once we pass a certain threshold, update the sysui flags to match the target
// tasks' flags
@@ -1575,20 +1697,28 @@
}
});
- ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(mActivity);
- clipAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
- clipAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
- AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, clipAnimationHelper);
+ AppWindowAnimationHelper appWindowAnimationHelper = new AppWindowAnimationHelper(
+ getPagedViewOrientedState(), mActivity);
+ appWindowAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
+ appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
+ AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
+
+ BackgroundBlurController blurController = getBackgroundBlurController();
+ if (blurController != null) {
+ ObjectAnimator backgroundBlur = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
+ BACKGROUND_APP.getBackgroundBlurRadius(mActivity));
+ anim.play(backgroundBlur);
+ }
anim.play(progressAnim);
- anim.setDuration(duration);
+ anim.setDuration(duration)
+ .setInterpolator(interpolator);
- Consumer<Boolean> onTaskLaunchFinish = this::onTaskLaunched;
-
- mPendingAnimation = new PendingAnimation(anim);
- mPendingAnimation.addEndListener((onEndListener) -> {
- if (onEndListener.isSuccess) {
+ mPendingAnimation = new PendingAnimation();
+ mPendingAnimation.add(anim);
+ mPendingAnimation.addEndListener((endState) -> {
+ if (endState.isSuccess) {
Consumer<Boolean> onLaunchResult = (result) -> {
- onTaskLaunchFinish.accept(result);
+ onTaskLaunched(result);
if (!result) {
tv.notifyTaskLaunchFailed(TAG);
}
@@ -1597,11 +1727,11 @@
Task task = tv.getTask();
if (task != null) {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
- onEndListener.logAction, Direction.DOWN, indexOfChild(tv),
+ endState.logAction, Direction.DOWN, indexOfChild(tv),
TaskUtils.getLaunchComponentKeyForTask(task.key));
}
} else {
- onTaskLaunchFinish.accept(false);
+ onTaskLaunched(false);
}
mPendingAnimation = null;
});
@@ -1658,8 +1788,8 @@
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
final int[] visibleTasks = getVisibleChildrenRange();
- event.setFromIndex(taskViewCount - visibleTasks[1] - 1);
- event.setToIndex(taskViewCount - visibleTasks[0] - 1);
+ event.setFromIndex(taskViewCount - visibleTasks[1]);
+ event.setToIndex(taskViewCount - visibleTasks[0]);
event.setItemCount(taskViewCount);
}
}
@@ -1681,33 +1811,47 @@
public void redrawLiveTile(boolean mightNeedToRefill) { }
- public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) {
- mRecentsAnimationWrapper = recentsAnimationWrapper;
+ // TODO: To be removed in a follow up CL
+ public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
+ RecentsAnimationTargets recentsAnimationTargets) {
+ mRecentsAnimationController = recentsAnimationController;
+ mRecentsAnimationTargets = recentsAnimationTargets;
}
- public void setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper) {
- mClipAnimationHelper = clipAnimationHelper;
+ // TODO: To be removed in a follow up CL
+ public void setAppWindowAnimationHelper(AppWindowAnimationHelper appWindowAnimationHelper) {
+ mAppWindowAnimationHelper = appWindowAnimationHelper;
}
- public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
- mLiveTileOverlay = liveTileOverlay;
+ public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
+ mLiveTileOverlayAttached = liveTileOverlayAttached;
}
public void updateLiveTileIcon(Drawable icon) {
- if (mLiveTileOverlay != null) {
- mLiveTileOverlay.setIcon(icon);
+ if (mLiveTileOverlayAttached) {
+ LiveTileOverlay.INSTANCE.setIcon(icon);
}
}
public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
- if (mRecentsAnimationWrapper == null) {
+ if (mRecentsAnimationController == null) {
if (onFinishComplete != null) {
onFinishComplete.run();
}
return;
}
- mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
+ mRecentsAnimationController.finish(toRecents, () -> {
+ if (onFinishComplete != null) {
+ onFinishComplete.run();
+ // After we finish the recents animation, the current task id should be correctly
+ // reset so that when the task is launched from Overview later, it goes through the
+ // flow of starting a new task instead of finishing recents animation to app. A
+ // typical example of this is (1) user swipes up from app to Overview (2) user
+ // taps on QSB (3) user goes back to Overview and launch the most recent task.
+ setCurrentTask(-1);
+ }
+ });
}
public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
@@ -1718,7 +1862,7 @@
}
@Override
- protected int computeMinScrollX() {
+ protected int computeMinScroll() {
if (getTaskViewCount() > 0) {
if (mDisallowScrollToClearAll) {
// We aren't showing the clear all button,
@@ -1733,11 +1877,11 @@
}
return getScrollForPage(mTaskViewStartIndex);
}
- return super.computeMinScrollX();
+ return super.computeMinScroll();
}
@Override
- protected int computeMaxScrollX() {
+ protected int computeMaxScroll() {
if (getTaskViewCount() > 0) {
if (mDisallowScrollToClearAll) {
// We aren't showing the clear all button,
@@ -1752,7 +1896,7 @@
}
return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
}
- return super.computeMaxScrollX();
+ return super.computeMaxScroll();
}
public ClearAllButton getClearAllButton() {
@@ -1767,42 +1911,58 @@
return 0;
}
int startScroll = getScrollForPage(getRunningTaskIndex());
- int offsetX = startScroll - getScrollX();
- offsetX *= getScaleX();
+ int offsetX = startScroll - mOrientationHandler.getPrimaryScroll(this);
+ offsetX *= mOrientationHandler.getPrimaryScale(this);
return offsetX;
}
- public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) {
- if (rotationMode.isTransposed) {
- Matrix transform = new Matrix();
- transform.setRotate(-rotationMode.surfaceRotation);
-
- if (getWidth() > 0 && getHeight() > 0) {
- float scale = ((float) getWidth()) / getHeight();
- transform.postScale(scale, 1 / scale);
- }
-
- Matrix inverse = new Matrix();
- transform.invert(inverse);
- return e -> {
- e.transform(transform);
- super.onTouchEvent(e);
- e.transform(inverse);
- };
+ public Consumer<MotionEvent> getEventDispatcher(RotationMode navBarRotationMode) {
+ float degreesRotated;
+ if (navBarRotationMode == RotationMode.NORMAL) {
+ degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 :
+ RotationHelper.getDegreesFromRotation(mLayoutRotation);
} else {
+ degreesRotated = -navBarRotationMode.surfaceRotation;
+ }
+ if (degreesRotated == 0) {
return super::onTouchEvent;
}
+
+ // At this point the event coordinates have already been transformed, so we need to
+ // undo that transformation since PagedView also accommodates for the transformation via
+ // PagedOrientationHandler
+ return e -> {
+ if (navBarRotationMode != RotationMode.NORMAL
+ && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
+ RotationHelper.transformEventForNavBar(e, true);
+ super.onTouchEvent(e);
+ RotationHelper.transformEventForNavBar(e, false);
+ return;
+ }
+ RotationHelper.transformEvent(-degreesRotated, e, true);
+ super.onTouchEvent(e);
+ RotationHelper.transformEvent(-degreesRotated, e, false);
+ };
}
- public ClipAnimationHelper getTempClipAnimationHelper() {
- return mTempClipAnimationHelper;
+ public AppWindowAnimationHelper getClipAnimationHelper() {
+ return mAppWindowAnimationHelper;
+ }
+
+ public AppWindowAnimationHelper getTempAppWindowAnimationHelper() {
+ return mTempAppWindowAnimationHelper;
+ }
+
+ public AppWindowAnimationHelper.TransformParams getLiveTileParams(
+ boolean mightNeedToRefill) {
+ return null;
}
private void updateEnabledOverlays() {
int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
int taskCount = getTaskViewCount();
- for (int i = 0; i < taskCount; i++) {
- getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
+ for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) {
+ getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage);
}
}
@@ -1813,18 +1973,29 @@
}
}
- public int getLeftGestureMargin() {
- final WindowInsets insets = getRootWindowInsets();
- return Math.max(insets.getSystemGestureInsets().left, insets.getSystemWindowInsetLeft());
- }
-
- public int getRightGestureMargin() {
- final WindowInsets insets = getRootWindowInsets();
- return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
+ /** If it's in the live tile mode, switch the running task into screenshot mode. */
+ public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
+ TaskView taskView = getRunningTaskView();
+ if (taskView != null) {
+ taskView.setShowScreenshot(true);
+ if (thumbnailData != null) {
+ taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
+ } else {
+ taskView.getThumbnail().refresh();
+ }
+ ViewUtils.postDraw(taskView, onFinishRunnable);
+ } else {
+ onFinishRunnable.run();
+ }
}
@Override
public void addView(View child, int index) {
+ // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
+ // child direction back to match system settings.
+ child.setLayoutDirection(
+ Utilities.isRtl(getResources())
+ ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
super.addView(child, index);
if (isExtraCardView(child, index)) {
mTaskViewStartIndex++;
@@ -1839,8 +2010,71 @@
super.removeView(view);
}
+ @Nullable
+ protected BackgroundBlurController getBackgroundBlurController() {
+ return null;
+ }
+
private boolean isExtraCardView(View view, int index) {
return !(view instanceof TaskView) && !(view instanceof ClearAllButton)
&& index <= mTaskViewStartIndex;
}
+
+ /**
+ * Used to register callbacks for when our empty message state changes.
+ *
+ * @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener)
+ * @see #updateEmptyMessage()
+ */
+ public interface OnEmptyMessageUpdatedListener {
+ /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */
+ void onEmptyMessageUpdated(boolean isEmpty);
+ }
+
+
+ private static class PinnedStackAnimationListener<T extends BaseActivity> extends
+ IPinnedStackAnimationListener.Stub {
+ private T mActivity;
+
+ public void setActivity(T activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void onPinnedStackAnimationStarted() {
+ // Needed for activities that auto-enter PiP, which will not trigger a remote
+ // animation to be created
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ }
+ }
+
+ private void showActionsView() {
+ if (mActionsView != null && getTaskViewCount() > 0) {
+ mActionsView.setVisibility(VISIBLE);
+ }
+ }
+
+ private void hideActionsView() {
+ if (mActionsView != null) {
+ mActionsView.setVisibility(GONE);
+ }
+ }
+
+ private void setActionsView() {
+ if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
+ && SysUINavigationMode.removeShelfFromOverview(mActivity)) {
+ mActionsView = ((ViewGroup) getParent()).findViewById(R.id.overview_actions_view);
+ if (mActionsView != null) {
+ Rect rect = new Rect();
+ getTaskSize(rect);
+ InsettableFrameLayout.LayoutParams layoutParams =
+ new InsettableFrameLayout.LayoutParams(rect.width(),
+ getResources().getDimensionPixelSize(
+ R.dimen.overview_actions_height));
+ layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ mActionsView.setLayoutParams(layoutParams);
+ showActionsView();
+ }
+ }
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 07d0796..80022b4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,6 @@
package com.android.quickstep.views;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
import android.animation.Animator;
@@ -26,7 +25,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -41,16 +39,13 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.IconView.OnScaleUpdateListener;
-import java.util.List;
-
/**
* Contains options for a recent task when long-pressing its icon.
*/
@@ -197,22 +192,15 @@
params.topMargin = (int) -mThumbnailTopMargin;
mTaskIcon.setLayoutParams(params);
- final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
- final List<TaskSystemShortcut> shortcuts =
- TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView);
- final int count = shortcuts.size();
- for (int i = 0; i < count; ++i) {
- final TaskSystemShortcut menuOption = shortcuts.get(i);
- addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView));
- }
+ TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption);
}
- private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
+ private void addMenuOption(SystemShortcut menuOption) {
ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate(
R.layout.task_view_menu_option, this, false);
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
- menuOptionView.setOnClickListener(onClickListener);
+ menuOptionView.setOnClickListener(menuOption);
mOptionLayout.addView(menuOptionView);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 044292a..178ff32 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -20,7 +20,6 @@
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
@@ -37,15 +36,15 @@
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.util.Log;
import android.util.Property;
+import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
@@ -56,15 +55,16 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ConfigurationCompat;
/**
* A task in the Recents view.
*/
public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
- private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
- private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
- private final static RectF EMPTY_RECT_F = new RectF();
+ private static final ColorMatrix COLOR_MATRIX = new ColorMatrix();
+ private static final ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
+ private static final RectF EMPTY_RECT_F = new RectF();
public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -103,7 +103,7 @@
private float mSaturation = 1f;
private boolean mOverlayEnabled;
- private boolean mRotated;
+ private boolean mIsOrientationChanged;
private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
public TaskThumbnailView(Context context) {
@@ -135,16 +135,35 @@
}
/**
- * Updates this thumbnail.
+ * Updates the thumbnail.
+ * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
+ * In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
+ * version with {@code refreshNow} is true. The only exception is
+ * in the live tile case that we grab a screenshot when user enters Overview
+ * upon swipe up so that a usable screenshot is accessible immediately when
+ * recents animation needs to be finished / cancelled.
*/
- public void setThumbnail(Task task, ThumbnailData thumbnailData) {
+ public void setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow) {
mTask = task;
- if (thumbnailData != null && thumbnailData.thumbnail != null) {
- Bitmap bm = thumbnailData.thumbnail;
+ mThumbnailData =
+ (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
+ if (refreshNow) {
+ refresh();
+ }
+ }
+
+ /** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
+ public void setThumbnail(Task task, ThumbnailData thumbnailData) {
+ setThumbnail(task, thumbnailData, true /* refreshNow */);
+ }
+
+ /** Updates the shader, paint, matrix to redraw. */
+ public void refresh() {
+ if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
+ Bitmap bm = mThumbnailData.thumbnail;
bm.prepareToDraw();
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(mBitmapShader);
- mThumbnailData = thumbnailData;
updateThumbnailMatrix();
} else {
mBitmapShader = null;
@@ -152,7 +171,6 @@
mPaint.setShader(null);
mOverlay.reset();
}
-
if (mOverviewScreenshotActionsPlugin != null) {
mOverviewScreenshotActionsPlugin
.setupActions((ViewGroup) getTaskView(), getThumbnail(), mActivity);
@@ -180,6 +198,10 @@
updateThumbnailPaintFilter();
}
+ public TaskOverlay getTaskOverlay() {
+ return mOverlay;
+ }
+
public float getDimAlpha() {
return mDimAlpha;
}
@@ -302,7 +324,7 @@
private void updateOverlay() {
// The overlay doesn't really work when the screenshot is rotated, so don't add it.
- if (mOverlayEnabled && !mRotated && mBitmapShader != null && mThumbnailData != null) {
+ if (mOverlayEnabled && !mIsOrientationChanged && mBitmapShader != null && mThumbnailData != null) {
mOverlay.initOverlay(mTask, mThumbnailData, mMatrix);
} else {
mOverlay.reset();
@@ -325,6 +347,7 @@
private void updateThumbnailMatrix() {
boolean isRotated = false;
+ boolean isOrientationDifferent = false;
mClipBottom = -1;
if (mBitmapShader != null && mThumbnailData != null) {
float scale = mThumbnailData.scale;
@@ -335,52 +358,41 @@
(thumbnailInsets.top + thumbnailInsets.bottom) * scale;
final float thumbnailScale;
- final DeviceProfile profile = mActivity.getDeviceProfile();
-
+ int thumbnailRotation = mThumbnailData.rotation;
+ int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
+ getResources().getConfiguration());
+ int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+ // Landscape vs portrait change
+ boolean windowingModeSupportsRotation = !mActivity.isInMultiWindowMode()
+ && mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
+ isOrientationDifferent = isOrientationChange(deltaRotate)
+ && windowingModeSupportsRotation;
if (getMeasuredWidth() == 0) {
// If we haven't measured , skip the thumbnail drawing and only draw the background
// color
thumbnailScale = 0f;
} else {
- final Configuration configuration =
- getContext().getResources().getConfiguration();
// Rotate the screenshot if not in multi-window mode
- isRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
- configuration.orientation != mThumbnailData.orientation &&
- !mActivity.isInMultiWindowMode() &&
- mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
+ isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
// Scale the screenshot to always fit the width of the card.
- thumbnailScale = isRotated
+
+ thumbnailScale = isOrientationDifferent
? getMeasuredWidth() / thumbnailHeight
: getMeasuredWidth() / thumbnailWidth;
}
- if (isRotated) {
- int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
- mMatrix.setRotate(90 * rotationDir);
- int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top;
- int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right;
- mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
- if (rotationDir == -1) {
- // Crop the right/bottom side of the screenshot rather than left/top
- float excessHeight = thumbnailWidth * thumbnailScale - getMeasuredHeight();
- mClippedInsets.offset(0, excessHeight);
- }
- mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
- // Move the screenshot to the thumbnail window (rotation moved it out).
- if (rotationDir == 1) {
- mMatrix.postTranslate(mThumbnailData.thumbnail.getHeight(), 0);
- } else {
- mMatrix.postTranslate(0, mThumbnailData.thumbnail.getWidth());
- }
- } else {
- mClippedInsets.offsetTo(thumbnailInsets.left * scale, thumbnailInsets.top * scale);
+ if (!isRotated) {
+ // No Rotation
+ mClippedInsets.offsetTo(thumbnailInsets.left * scale,
+ thumbnailInsets.top * scale);
mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top);
+ } else {
+ setThumbnailRotation(deltaRotate, thumbnailInsets, scale);
}
final float widthWithInsets;
final float heightWithInsets;
- if (isRotated) {
+ if (isOrientationDifferent) {
widthWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale;
heightWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale;
} else {
@@ -395,7 +407,7 @@
mMatrix.postScale(thumbnailScale, thumbnailScale);
mBitmapShader.setLocalMatrix(mMatrix);
- float bitmapHeight = Math.max((isRotated ? thumbnailWidth : thumbnailHeight)
+ float bitmapHeight = Math.max((isOrientationDifferent ? thumbnailWidth : thumbnailHeight)
* thumbnailScale, 0);
if (Math.round(bitmapHeight) < getMeasuredHeight()) {
mClipBottom = bitmapHeight;
@@ -403,7 +415,7 @@
mPaint.setShader(mBitmapShader);
}
- mRotated = isRotated;
+ mIsOrientationChanged = isOrientationDifferent;
invalidate();
// Update can be called from {@link #onSizeChanged} during layout, post handling of overlay
@@ -411,6 +423,51 @@
post(this::updateOverlay);
}
+ private int getRotationDelta(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
+ /**
+ * @param deltaRotation the number of 90 degree turns from the current orientation
+ * @return {@code true} if the change in rotation results in a shift from landscape to portrait
+ * or vice versa, {@code false} otherwise
+ */
+ private boolean isOrientationChange(int deltaRotation) {
+ return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+ }
+
+ private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale) {
+ int newLeftInset = 0;
+ int newTopInset = 0;
+ int translateX = 0;
+ int translateY = 0;
+
+ mMatrix.setRotate(90 * deltaRotate);
+ switch (deltaRotate) { /* Counter-clockwise */
+ case Surface.ROTATION_90:
+ newLeftInset = thumbnailInsets.bottom;
+ newTopInset = thumbnailInsets.left;
+ translateX = mThumbnailData.thumbnail.getHeight();
+ break;
+ case Surface.ROTATION_270:
+ newLeftInset = thumbnailInsets.top;
+ newTopInset = thumbnailInsets.right;
+ translateY = mThumbnailData.thumbnail.getWidth();
+ break;
+ case Surface.ROTATION_180:
+ newLeftInset = -thumbnailInsets.top;
+ newTopInset = -thumbnailInsets.left;
+ translateX = mThumbnailData.thumbnail.getWidth();
+ translateY = mThumbnailData.thumbnail.getHeight();
+ break;
+ }
+ mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
+ mMatrix.postTranslate(translateX - mClippedInsets.left,
+ translateY - mClippedInsets.top);
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 51802df..0e1640e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -16,11 +16,18 @@
package com.android.quickstep.views;
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.view.Gravity.CENTER_VERTICAL;
+import static android.view.Gravity.END;
+import static android.view.Gravity.START;
+import static android.view.Gravity.TOP;
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import android.animation.Animator;
@@ -30,7 +37,6 @@
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -40,7 +46,7 @@
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
-import android.view.Gravity;
+import android.view.Surface;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -52,19 +58,23 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
@@ -215,7 +225,7 @@
mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
- mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
+ mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
setOutlineProvider(mOutlineProvider);
}
@@ -224,6 +234,22 @@
super.onFinishInflate();
mSnapshotView = findViewById(R.id.snapshot);
mIconView = findViewById(R.id.icon);
+ final Context context = getContext();
+
+ TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams();
+ thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(context);
+ mSnapshotView.setLayoutParams(thumbnailParams);
+ }
+
+ public boolean isTaskOverlayModal() {
+ return mSnapshotView.getTaskOverlay().isOverlayModal();
+ }
+
+ /** Updates UI based on whether the task is modal. */
+ public void updateUiForModalTask() {
+ if (getRecentsView() != null) {
+ getRecentsView().updateUiForModalTask(this, isTaskOverlayModal());
+ }
}
public TaskMenuView getMenuView() {
@@ -237,10 +263,11 @@
/**
* Updates this task view to the given {@param task}.
*/
- public void bind(Task task) {
+ public void bind(Task task, int recentsRotation) {
cancelPendingLoadTasks();
mTask = task;
mSnapshotView.bind(task);
+ setOverviewRotation(recentsRotation);
}
public Task getTask() {
@@ -256,11 +283,10 @@
}
public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
- final PendingAnimation pendingAnimation =
- getRecentsView().createTaskLauncherAnimation(this, RECENTS_LAUNCH_DURATION);
- pendingAnimation.anim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
- AnimatorPlaybackController currentAnimation = AnimatorPlaybackController
- .wrap(pendingAnimation.anim, RECENTS_LAUNCH_DURATION, null);
+ final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
+ this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
+ AnimatorPlaybackController currentAnimation =
+ AnimatorPlaybackController.wrap(pendingAnimation, RECENTS_LAUNCH_DURATION);
currentAnimation.setEndAction(() -> {
pendingAnimation.finish(true, Touch.SWIPE);
launchTask(false);
@@ -288,11 +314,19 @@
public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ RecentsView recentsView = getRecentsView();
if (isRunningTask()) {
- getRecentsView().finishRecentsAnimation(false /* toRecents */,
+ recentsView.finishRecentsAnimation(false /* toRecents */,
() -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
} else {
- launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
+ // This is a workaround against the WM issue that app open is not correctly animated
+ // when recents animation is being cleaned up (b/143774568). When that's possible,
+ // we should rely on the framework side to cancel the recents animation, and we will
+ // clean up the screenshot on the launcher side while we launch the next task.
+ recentsView.switchToScreenshot(null,
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+ () -> launchTaskInternal(animate, freezeTaskList, resultCallback,
+ resultCallbackHandler)));
}
} else {
launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
@@ -303,6 +337,8 @@
Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
if (mTask != null) {
final ActivityOptions opts;
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
if (animate) {
opts = mActivity.getActivityLaunchOptions(this);
if (freezeTaskList) {
@@ -401,6 +437,42 @@
}
}
+ void setOverviewRotation(int iconRotation) {
+ PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+ boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources());
+ LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
+ snapshotParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getContext());
+ int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+ LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
+ int rotation = RotationHelper.getDegreesFromRotation(iconRotation);
+ switch (iconRotation) {
+ case Surface.ROTATION_90:
+ iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
+ iconParams.rightMargin = -thumbnailPadding;
+ iconParams.leftMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+ break;
+ case Surface.ROTATION_180:
+ iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+ iconParams.bottomMargin = -thumbnailPadding;
+ iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
+ break;
+ case Surface.ROTATION_270:
+ iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
+ iconParams.leftMargin = -thumbnailPadding;
+ iconParams.rightMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+ break;
+ case Surface.ROTATION_0:
+ default:
+ iconParams.gravity = TOP | CENTER_HORIZONTAL;
+ iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin =
+ iconParams.bottomMargin = 0;
+ break;
+ }
+ mSnapshotView.setLayoutParams(snapshotParams);
+ mIconView.setLayoutParams(iconParams);
+ mIconView.setRotation(rotation);
+ }
+
private void setIconAndDimTransitionProgress(float progress, boolean invert) {
if (invert) {
progress = 1 - progress;
@@ -487,9 +559,11 @@
public void onPageScroll(ScrollState scrollState) {
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
+ float curveScaleForCurveInterpolation = getCurveScaleForCurveInterpolation(
+ curveInterpolation);
mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
- setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
+ setCurveScale(curveScaleForCurveInterpolation);
mFooterAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
for (FooterWrapper footer : mFooters) {
@@ -505,7 +579,6 @@
}
}
-
/**
* Sets the footer at the specific index and returns the previously set footer.
*/
@@ -533,8 +606,10 @@
}
addView(view, indexToAdd);
- ((LayoutParams) view.getLayoutParams()).gravity =
- Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+ layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+ layoutParams.bottomMargin =
+ ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin;
view.setAlpha(mFooterAlpha);
mFooters[index] = new FooterWrapper(view);
if (shouldAnimateEntry) {
@@ -611,10 +686,13 @@
private static final class TaskOutlineProvider extends ViewOutlineProvider {
private final int mMarginTop;
+ private final int mMarginBottom;
private FullscreenDrawParams mFullscreenParams;
- TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) {
- mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
+ mMarginTop = context.getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_top_margin);
+ mMarginBottom = LayoutUtils.thumbnailBottomMargin(context);
mFullscreenParams = fullscreenParams;
}
@@ -629,7 +707,7 @@
outline.setRoundRect(0,
(int) (mMarginTop * scale),
(int) ((insets.left + view.getWidth() + insets.right) * scale),
- (int) ((insets.top + view.getHeight() + insets.bottom) * scale),
+ (int) ((insets.top + view.getHeight() + insets.bottom - mMarginBottom) * scale),
mFullscreenParams.mCurrentDrawnCornerRadius);
}
}
@@ -714,15 +792,8 @@
getContext().getText(R.string.accessibility_close_task)));
final Context context = getContext();
- final List<TaskSystemShortcut> shortcuts =
- TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this);
- final int count = shortcuts.size();
- for (int i = 0; i < count; ++i) {
- final TaskSystemShortcut menuOption = shortcuts.get(i);
- OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
- if (onClickListener != null) {
- info.addAction(menuOption.createAccessibilityAction(context));
- }
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ info.addAction(s.createAccessibilityAction(context));
}
if (mDigitalWellBeingToast.hasLimit()) {
@@ -735,8 +806,8 @@
final RecentsView recentsView = getRecentsView();
final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
AccessibilityNodeInfo.CollectionItemInfo.obtain(
- 0, 1, recentsView.getChildCount() - recentsView.indexOfChild(this) - 1, 1,
- false);
+ 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1,
+ 1, false);
info.setCollectionItemInfo(itemInfo);
}
@@ -753,16 +824,9 @@
return true;
}
- final List<TaskSystemShortcut> shortcuts =
- TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this);
- final int count = shortcuts.size();
- for (int i = 0; i < count; ++i) {
- final TaskSystemShortcut menuOption = shortcuts.get(i);
- if (menuOption.hasHandlerForAction(action)) {
- OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
- if (onClickListener != null) {
- onClickListener.onClick(this);
- }
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ if (s.hasHandlerForAction(action)) {
+ s.onClick(this);
return true;
}
}
@@ -785,6 +849,7 @@
/**
* Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
+ *
* @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
*/
public void setFullscreenProgress(float progress) {
diff --git a/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml b/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml
new file mode 100644
index 0000000..cd30ef7
--- /dev/null
+++ b/quickstep/res/drawable-v28/back_gesture_tutorial_action_button_background.xml
@@ -0,0 +1,20 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="?android:attr/dialogCornerRadius"/>
+ <solid android:color="@color/back_gesture_tutorial_primary_color"/>
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture.xml b/quickstep/res/drawable/back_gesture.xml
new file mode 100644
index 0000000..a5c57b4
--- /dev/null
+++ b/quickstep/res/drawable/back_gesture.xml
@@ -0,0 +1,367 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="206dp"
+ android:height="435dp"
+ android:viewportWidth="206"
+ android:viewportHeight="435">
+ <group android:name="edgeGroup"
+ android:translateX="197"
+ android:translateY="0">
+ <path
+ android:name="edge"
+ android:fillAlpha="0"
+ android:fillType="nonZero"
+ android:fillColor="#1a73eb"
+ android:pathData=" M0,0 h9 v435 h-9 z " />
+ </group>
+ <group
+ android:name="trailGroup"
+ android:translateX="226"
+ android:translateY="200">
+ <path
+ android:name="trail"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M0,0 h55 v36 h-55 z ">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="0"
+ android:endX="55"
+ android:type="linear">
+ <item
+ android:color="#991a73eb"
+ android:offset="0" />
+ <item
+ android:color="#401a73eb"
+ android:offset="0.5" />
+ <item
+ android:color="#001a73eb"
+ android:offset="1" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ </group>
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:rotation="11"
+ android:scaleX="0.9"
+ android:scaleY="0.9"
+ android:translateX="309"
+ android:translateY="422.5">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="-145"
+ android:translateY="-208">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#d2e3fc"
+ android:fillType="nonZero"
+ android:pathData=" M12.5 -47 C-7.93,-41.24 -3,-20.5 -1.5,-7 C0,6.5 2.5,22 9,39.5 C13.52,51.67 17.06,63.52 19,113 C21,164 53.5,243.5 53.5,243.5 C53.5,243.5 59,275.5 123.5,326 C188,376.5 283.5,236 290.5,199 C297.5,162 194.5,80 149,73 C103.5,66 90.5,57.5 77,50 C63.5,42.5 57,27 54.5,13.5 C52,0 43.5,-15 40,-25 C36.5,-35 32,-52.5 12.5,-47c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:pathData=" M4.45 -34.66 C4.45,-34.66 10.5,-12.66 10.5,-12.66 C11.24,-9.98 13.98,-8.38 16.67,-9.04 C16.67,-9.04 29.72,-12.27 29.72,-12.27 C32.39,-12.93 34.05,-15.59 33.47,-18.28 C33.47,-18.28 32.11,-24.57 32.11,-24.57 "
+ android:strokeWidth="4"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:pathData=" M18.35 21.81 C21.41,17.24 36.97,10.77 44.63,13.55 "
+ android:strokeWidth="4"
+ android:strokeAlpha="1"
+ android:strokeColor="#a0c2f9" />
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="edge">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0.2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="917"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="0.2"
+ android:valueTo="0.2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="583"
+ android:propertyName="fillAlpha"
+ android:startOffset="1250"
+ android:valueFrom="0.2"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="trail">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="2000"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="850"
+ android:propertyName="fillAlpha"
+ android:startOffset="2000"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="trailGroup">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="translateX"
+ android:startOffset="1250"
+ android:valueFrom="226"
+ android:valueTo="226"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1000"
+ android:propertyName="translateX"
+ android:startOffset="1333"
+ android:valueFrom="226"
+ android:valueTo="151"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="517"
+ android:propertyName="translateX"
+ android:startOffset="2333"
+ android:valueFrom="151"
+ android:valueTo="151"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="translateX"
+ android:startOffset="2850"
+ android:valueFrom="226"
+ android:valueTo="226"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1833"
+ android:propertyName="fillAlpha"
+ android:startOffset="1250"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="3083"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1833"
+ android:propertyName="strokeAlpha"
+ android:startOffset="1250"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="strokeAlpha"
+ android:startOffset="3083"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1833"
+ android:propertyName="strokeAlpha"
+ android:startOffset="1250"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="strokeAlpha"
+ android:startOffset="3083"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="translateX"
+ android:startOffset="1250"
+ android:valueFrom="309"
+ android:valueTo="309"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1417"
+ android:propertyName="translateX"
+ android:startOffset="1333"
+ android:valueFrom="309"
+ android:valueTo="251"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.285,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="rotation"
+ android:startOffset="1250"
+ android:valueFrom="11"
+ android:valueTo="11"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.277,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="1417"
+ android:propertyName="rotation"
+ android:startOffset="1333"
+ android:valueFrom="11"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.277,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="2183"
+ android:propertyName="translateX"
+ android:startOffset="1250"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml b/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml
new file mode 100644
index 0000000..d7b9102
--- /dev/null
+++ b/quickstep/res/drawable/back_gesture_tutorial_action_button_background.xml
@@ -0,0 +1,20 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/default_dialog_corner_radius"/>
+ <solid android:color="@color/back_gesture_tutorial_primary_color"/>
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/back_gesture_tutorial_close_button.xml b/quickstep/res/drawable/back_gesture_tutorial_close_button.xml
new file mode 100644
index 0000000..0702042
--- /dev/null
+++ b/quickstep/res/drawable/back_gesture_tutorial_close_button.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="13dp"
+ android:viewportHeight="14"
+ android:viewportWidth="14"
+ android:width="13dp">
+ <path
+ android:fillColor="#000000"
+ android:fillType="evenOdd"
+ android:pathData="M14,1.41L12.59,0L7,5.59L1.41,0L0,1.41L5.59,7L0,12.59L1.41,14L7,8.41L12.59,14L14,12.59L8.41,7L14,1.41Z"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/layout/back_gesture_tutorial_activity.xml b/quickstep/res/layout/back_gesture_tutorial_activity.xml
new file mode 100644
index 0000000..e894e89
--- /dev/null
+++ b/quickstep/res/layout/back_gesture_tutorial_activity.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/back_gesture_tutorial_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/back_gesture_tutorial_fragment.xml b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
new file mode 100644
index 0000000..294e46e
--- /dev/null
+++ b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
@@ -0,0 +1,121 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layerType="software"
+ android:background="@color/back_gesture_tutorial_background_color">
+ <!--The layout is rendered on the software layer to avoid b/136158117-->
+
+ <ImageView
+ android:id="@+id/back_gesture_tutorial_fragment_hand_coaching"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"/>
+
+ <ImageButton
+ android:id="@+id/back_gesture_tutorial_fragment_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="18dp"
+ android:layout_marginTop="30dp"
+ android:layout_marginStart="4dp"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:background="@android:color/transparent"
+ android:accessibilityTraversalAfter="@id/back_gesture_tutorial_fragment_titles_container"
+ android:contentDescription="@string/back_gesture_tutorial_close_button_content_description"
+ android:src="@drawable/back_gesture_tutorial_close_button"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="70dp"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/back_gesture_tutorial_fragment_titles_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:focusable="true">
+
+ <TextView
+ android:id="@+id/back_gesture_tutorial_fragment_title_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginStart="@dimen/back_gesture_tutorial_title_margin_start_end"
+ android:layout_marginEnd="@dimen/back_gesture_tutorial_title_margin_start_end"
+ style="@style/TextAppearance.BackGestureTutorial.Title"/>
+
+ <TextView
+ android:id="@+id/back_gesture_tutorial_fragment_subtitle_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="10dp"
+ android:layout_marginStart="@dimen/back_gesture_tutorial_subtitle_margin_start_end"
+ android:layout_marginEnd="@dimen/back_gesture_tutorial_subtitle_margin_start_end"
+ style="@style/TextAppearance.BackGestureTutorial.Subtitle"/>
+
+ </LinearLayout>
+
+ <Space
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:layout_height="0dp"
+ android:layout_marginTop="48dp"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"/>
+
+ <!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
+ of elevation and shadow) which is replaced by ripple effect in android:foreground -->
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="46dp"
+ android:layout_marginBottom="48dp"
+ android:layout_gravity="center_horizontal">
+
+ <Button
+ android:id="@+id/back_gesture_tutorial_fragment_action_button"
+ android:layout_width="142dp"
+ android:layout_height="49dp"
+ android:layout_marginEnd="@dimen/back_gesture_tutorial_button_margin_start_end"
+ android:layout_alignParentEnd="true"
+ android:stateListAnimator="@null"
+ android:background="@drawable/back_gesture_tutorial_action_button_background"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ style="@style/TextAppearance.BackGestureTutorial.ButtonLabel"/>
+
+ <Button
+ android:id="@+id/back_gesture_tutorial_fragment_action_text_button"
+ android:layout_width="142dp"
+ android:layout_height="49dp"
+ android:layout_marginStart="@dimen/back_gesture_tutorial_button_margin_start_end"
+ android:layout_alignParentStart="true"
+ android:stateListAnimator="@null"
+ android:background="@null"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ style="@style/TextAppearance.BackGestureTutorial.TextButtonLabel"/>
+
+ </RelativeLayout>
+
+ </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 60cfa0c..f9bb2f2 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -31,7 +31,6 @@
android:id="@+id/icon"
android:layout_width="@dimen/task_thumbnail_icon_size"
android:layout_height="@dimen/task_thumbnail_icon_size"
- android:layout_gravity="top|center_horizontal"
android:focusable="false"
android:importantForAccessibility="no"/>
</com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
deleted file mode 100644
index 2d1418e..0000000
--- a/quickstep/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Split screen"</string>
- <string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
- <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
- <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
- <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
- <string name="accessibility_close_task" msgid="5354563209433803643">"Close"</string>
- <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
- <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
- <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
- <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
- <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minute"</string>
- <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
- <string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
- <string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
- <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
-</resources>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
deleted file mode 100644
index bb186db..0000000
--- a/quickstep/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Split screen"</string>
- <string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
- <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
- <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
- <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
- <string name="accessibility_close_task" msgid="5354563209433803643">"Close"</string>
- <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
- <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
- <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
- <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
- <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minute"</string>
- <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
- <string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
- <string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
- <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
-</resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index e476880..329286b 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -23,7 +23,7 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fijar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
<string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Aplicaciones recientes"</string>
- <string name="recents_empty_message" msgid="7040467240571714191">"No hay nada reciente"</string>
+ <string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
<string name="accessibility_close_task" msgid="5354563209433803643">"Cerrar"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ajustes de uso de la aplicación"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 8ca743f..9f11521 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -26,7 +26,7 @@
<string name="recents_empty_message" msgid="7040467240571714191">"Нема неодамнешни ставки"</string>
<string name="accessibility_close_task" msgid="5354563209433803643">"Затвори"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Поставки за користење на апликациите"</string>
- <string name="recents_clear_all" msgid="5328176793634888831">"Избриши ги сите"</string>
+ <string name="recents_clear_all" msgid="5328176793634888831">"Исчисти ги сите"</string>
<string name="accessibility_recent_apps" msgid="4058661986695117371">"Неодамнешни апликации"</string>
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 минута"</string>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 292eaaa..a688f9a 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -13,13 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <string name="task_overlay_factory_class" translatable="false"></string>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="task_overlay_factory_class" translatable="false"/>
- <string name="overview_callbacks_class" translatable="false"></string>
-
- <!-- Activity which blocks home gesture -->
- <string name="gesture_blocking_activity" translatable="false"></string>
+ <!-- Activities which block home gesture -->
+ <string-array name="gesture_blocking_activities" tools:ignore="InconsistentArrays">
+ <item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
+ </string-array>
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
@@ -33,4 +33,6 @@
<!-- Assistant Gesture -->
<integer name="assistant_gesture_min_time_threshold">200</integer>
<integer name="assistant_gesture_corner_deg_threshold">20</integer>
+
+ <string name="wellbeing_provider_pkg" translatable="false"/>
</resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 78424ca..988c78d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -22,6 +22,9 @@
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
+ <!-- Overrideable in overlay that provides the Overview Actions. -->
+ <dimen name="overview_actions_height">0dp</dimen>
+
<dimen name="recents_page_spacing">10dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
<dimen name="overview_peek_distance">96dp</dimen>
@@ -56,6 +59,7 @@
<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="portrait_task_card_horz_space_big_overview">24dp</dimen>
<dimen name="landscape_task_card_horz_space">200dp</dimen>
<dimen name="multi_window_task_card_horz_space">100dp</dimen>
<!-- Copied from framework resource:
@@ -73,4 +77,12 @@
<!-- Distance to move elements when swiping up to go home from launcher -->
<dimen name="home_pullback_distance">28dp</dimen>
+
+ <!-- Overscroll Gesture -->
+ <dimen name="gestures_overscroll_fling_threshold">40dp</dimen>
+
+ <!-- Tips Gesture Tutorial -->
+ <dimen name="back_gesture_tutorial_title_margin_start_end">40dp</dimen>
+ <dimen name="back_gesture_tutorial_subtitle_margin_start_end">16dp</dimen>
+ <dimen name="back_gesture_tutorial_button_margin_start_end">18dp</dimen>
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 4319b5d..90d4245 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -29,9 +29,6 @@
<!-- Title for an option to enter freeform mode for a given app -->
<string name="recent_task_option_freeform">Freeform</string>
- <!-- Content description for the recent apps panel (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_desc_recent_apps">Overview</string>
-
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent items</string>
@@ -66,5 +63,54 @@
<!-- Text of the tip when user lands in all apps view for the first time, indicating where the tip toast points to is the predicted apps section. [CHAR_LIMIT=50] -->
<string name="all_apps_prediction_tip">Your predicted apps</string>
+ <!-- Content description for a close button. [CHAR LIMIT=NONE] -->
+ <string name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
+
+ <!-- Hotseat migration notification title -->
+ <string translatable="false" name="hotseat_edu_prompt_title">Get app suggestions based on your routines</string>
+ <!-- Hotseat migration notification content -->
+ <string translatable="false" name="hotseat_edu_prompt_content">Tap to set up</string>
+
+
+ <!-- Hotseat educational strings for users who don't qualify for migration -->
+ <string translatable="false" name="hotseat_edu_title_migrate">Suggested apps replace the bottom row of apps</string>
+ <string translatable="false" name="hotseat_edu_message_migrate">Your hotseat items will be moved up on the homescreen</string>
+ <string translatable="false" name="hotseat_edu_message_migrate_alt">Your hotseat items will be moved to the last page of your workspace</string>
+
+
+ <!-- Hotseat educational strings for users who don't qualify -->
+ <string translatable="false" name="hotseat_edu_title_no_migrate">Suggested apps will be found at the bottom row of your home screen</string>
+ <string translatable="false" name="hotseat_edu_message_no_migrate">Drag one or many apps off the bottom row of home screen to see app suggestions</string>
+
+ <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
+ <string translatable="false" name="hotseat_items_migrated">Bottom row of apps moved up.</string>
+ <string translatable="false" name="hotseat_items_migrated_alt">Bottom row of apps moved to last page.</string>
+ <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
+ <string translatable="false" name="hotseat_no_migration">Bottom row won\'t be replaced. Manually drag apps for predictions.</string>
+ <!-- Button text to opt in for fully predicted hotseat -->
+ <string translatable="false" name="hotseat_edu_accept">Got it</string>
+ <!-- Button text to dismiss opt in for fully predicted hotseat -->
+ <string translatable="false" name="hotseat_edu_dismiss">No thanks</string>
+
+
+ <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
+ <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
+ <!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
+ <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge" translatable="false">Start at the right edge and swipe toward the middle</string>
+
+ <!-- Title shown during interactive part of Back gesture tutorial for left edge. [CHAR LIMIT=30] -->
+ <string name="back_gesture_tutorial_playground_title_swipe_inward_left_edge" translatable="false">Try the other side</string>
+ <!-- Subtitle shown during interactive parts of Back gesture tutorial for left edge. [CHAR LIMIT=60] -->
+ <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge" translatable="false">That\'s it! Now try swiping from the left edge.</string>
+
+ <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
+ <string name="back_gesture_tutorial_confirm_title" translatable="false">All set</string>
+ <!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
+ <string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
+
+ <!-- Button text shown on a button on the confirm screen. [CHAR LIMIT=14] -->
+ <string name="back_gesture_tutorial_action_button_label" translatable="false">Done</string>
+ <!-- Button text shown on a text button on the confirm screen. [CHAR LIMIT=14] -->
+ <string name="back_gesture_tutorial_action_text_button_label" translatable="false">Settings</string>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index bb364ff..c8d7777 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -25,4 +25,39 @@
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
+
+ <style name="TextAppearance.BackGestureTutorial"
+ parent="android:TextAppearance.Material.Body1" />
+
+ <style name="TextAppearance.BackGestureTutorial.CallToAction"
+ parent="android:TextAppearance.Material.Body2" />
+
+ <style name="TextAppearance.BackGestureTutorial.Title"
+ parent="TextAppearance.BackGestureTutorial">
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">@color/back_gesture_tutorial_title_color</item>
+ <item name="android:textSize">28sp</item>
+ </style>
+
+ <style name="TextAppearance.BackGestureTutorial.Subtitle"
+ parent="TextAppearance.BackGestureTutorial">
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">@color/back_gesture_tutorial_subtitle_color</item>
+ <item name="android:letterSpacing">0.03</item>
+ <item name="android:textSize">21sp</item>
+ </style>
+
+ <style name="TextAppearance.BackGestureTutorial.ButtonLabel"
+ parent="TextAppearance.BackGestureTutorial.CallToAction">
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">@color/back_gesture_tutorial_action_button_label_color</item>
+ <item name="android:letterSpacing">0.02</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textAllCaps">false</item>
+ </style>
+
+ <style name="TextAppearance.BackGestureTutorial.TextButtonLabel"
+ parent="TextAppearance.BackGestureTutorial.ButtonLabel">
+ <item name="android:textColor">@color/back_gesture_tutorial_primary_color</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
new file mode 100644
index 0000000..53f37c1
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.quickstep;
+
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.util.DefaultDisplay;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class OrientationTouchTransformerTest {
+ private static final int SIZE_WIDTH = 1080;
+ private static final int SIZE_HEIGHT = 2280;
+ private static final float DENSITY_DISPLAY_METRICS = 3.0f;
+
+ private OrientationTouchTransformer mTouchTransformer;
+
+ Resources mResources;
+ private DefaultDisplay.Info mInfo;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mResources = mock(Resources.class);
+ when(mResources.getBoolean(anyInt())).thenReturn(true);
+ when(mResources.getDimension(anyInt())).thenReturn(10.0f);
+ DisplayMetrics mockDisplayMetrics = new DisplayMetrics();
+ mockDisplayMetrics.density = DENSITY_DISPLAY_METRICS;
+ when(mResources.getDisplayMetrics()).thenReturn(mockDisplayMetrics);
+ mInfo = createDisplayInfo(Surface.ROTATION_0);
+ mTouchTransformer = new OrientationTouchTransformer(mResources, NO_BUTTON, () -> 0);
+ }
+
+ @Test
+ public void disabledMultipeRegions_shouldOverrideFirstRegion() {
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
+ mTouchTransformer.createOrAddTouchRegion(info2);
+
+ float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ MotionEvent inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+ mTouchTransformer.transform(inOldRegion);
+ assertFalse(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+
+ // Override region
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+ mTouchTransformer.transform(inOldRegion);
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+ }
+
+ @Test
+ public void allowMultipeRegions_shouldOverrideFirstRegion() {
+ DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
+ mTouchTransformer.createOrAddTouchRegion(info2);
+ // We have to add 0 rotation second so that gets set as the current rotation, otherwise
+ // matrix transform will fail (tests only work in Portrait at the moment)
+ mTouchTransformer.enableMultipleRegions(true, mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+
+ float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ MotionEvent inNewRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+ mTouchTransformer.transform(inNewRegion);
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(inNewRegion.getX(), inNewRegion.getY()));
+ }
+
+ @Test
+ public void applyTransform_taskNotFrozen_notInRegion() {
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
+ mTouchTransformer.transform(outOfRegion);
+ assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+ }
+
+ @Test
+ public void applyTransform_taskFrozen_noRotate_outOfRegion() {
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.enableMultipleRegions(true, mInfo);
+ MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
+ mTouchTransformer.transform(outOfRegion);
+ assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+ }
+
+ @Test
+ public void applyTransform_taskFrozen_noRotate_inRegion() {
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.enableMultipleRegions(true, mInfo);
+ float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+ mTouchTransformer.transform(inRegion);
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+ }
+
+ @Test
+ public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() {
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+ mTouchTransformer.transform(inRegion);
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+ }
+
+ @Test
+ public void applyTransform_taskNotFrozen_90Rotate_inRegion() {
+ mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ float y = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+ mTouchTransformer.transform(inRegion);
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+ }
+
+ @Test
+ @Ignore("There's too much that goes into needing to mock a real motion event so the "
+ + "transforms in native code get applied correctly. Once that happens then maybe we can"
+ + " write slightly more complex unit tests")
+ public void applyTransform_taskNotFrozen_90Rotate_inTwoRegions() {
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.enableMultipleRegions(true, mInfo);
+ mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ // Landscape point
+ float y1 = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1);
+ MotionEvent inRegion1_up = generateMotionEvent(MotionEvent.ACTION_UP, 10, y1);
+ // Portrait point in landscape orientation axis
+ MotionEvent inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 10);
+ mTouchTransformer.transform(inRegion1_down);
+ mTouchTransformer.transform(inRegion2);
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion1_down.getX(), inRegion1_down.getY()));
+ // We only process one gesture region until we see a MotionEvent.ACTION_UP
+ assertFalse(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
+
+ mTouchTransformer.transform(inRegion1_up);
+
+ // Set the new region with this MotionEvent.ACTION_DOWN
+ inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
+ mTouchTransformer.transform(inRegion2);
+ assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
+ }
+
+ private DefaultDisplay.Info createDisplayInfo(int rotation) {
+ Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT);
+ if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+ p = new Point(SIZE_HEIGHT, SIZE_WIDTH);
+ }
+ return new DefaultDisplay.Info(0, rotation, 0, p, p, p, null);
+ }
+
+ private float generateTouchRegionHeight(int rotation) {
+ float height = SIZE_HEIGHT;
+ if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+ height = SIZE_WIDTH;
+ }
+ return height - ResourceUtils.DEFAULT_NAVBAR_VALUE * DENSITY_DISPLAY_METRICS;
+ }
+
+ private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
+ return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
new file mode 100644
index 0000000..809543a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
+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.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
+import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
+import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
+import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
+import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
+
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.accessibility.SystemActions;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WellbeingModel;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.proxy.ProxyActivityStarter;
+import com.android.launcher3.proxy.StartActivityParams;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.uioverrides.BackButtonAlphaHandler;
+import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.stream.Stream;
+
+/**
+ * Extension of Launcher activity to provide quickstep specific functionality
+ */
+public abstract class BaseQuickstepLauncher extends Launcher
+ implements NavigationModeChangeListener {
+
+ protected SystemActions mSystemActions;
+
+ /**
+ * Reusable command for applying the back button alpha on the background thread.
+ */
+ public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
+ (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
+ Float.intBitsToFloat(arg1), arg2 != 0);
+
+ private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSystemActions = new SystemActions(this);
+
+ SysUINavigationMode.Mode mode = SysUINavigationMode.INSTANCE.get(this)
+ .addModeChangeListener(this);
+ getRotationHelper().setRotationHadDifferentUI(mode != Mode.NO_BUTTON);
+
+ if (!getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
+ getStateManager().addStateListener(new LauncherStateManager.StateListener() {
+ @Override
+ public void onStateTransitionStart(LauncherState toState) { }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ boolean swipeUpEnabled = SysUINavigationMode.INSTANCE
+ .get(BaseQuickstepLauncher.this).getMode().hasGestures;
+ LauncherState prevState = getStateManager().getLastState();
+
+ if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
+ && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT
+ <= getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) {
+ getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
+ getStateManager().removeStateListener(this);
+ }
+ }
+ });
+ }
+
+ if (!getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
+ getStateManager().addStateListener(new LauncherStateManager.StateListener() {
+ @Override
+ public void onStateTransitionStart(LauncherState toState) { }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ LauncherState prevState = getStateManager().getLastState();
+
+ if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT
+ <= getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) {
+ getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
+ getStateManager().removeStateListener(this);
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onNavigationModeChanged(Mode newMode) {
+ getDragLayer().recreateControllers();
+ getRotationHelper().setRotationHadDifferentUI(newMode != Mode.NO_BUTTON);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mSystemActions.onActivityResult(requestCode);
+ }
+
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+ // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+ // overview
+ RecentsModel.INSTANCE.get(this).getThumbnailCache()
+ .getHighResLoadingState().setVisible(true);
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ RecentsModel.INSTANCE.get(this).onTrimMemory(level);
+ }
+
+ @Override
+ protected void onUiChangedWhileSleeping() {
+ // Remove the snapshot because the content view may have obvious changes.
+ ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this);
+ }
+
+ @Override
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
+ if (requestCode != -1) {
+ mPendingActivityRequestCode = requestCode;
+ StartActivityParams params = new StartActivityParams(this, requestCode);
+ params.intentSender = intent;
+ params.fillInIntent = fillInIntent;
+ params.flagsMask = flagsMask;
+ params.flagsValues = flagsValues;
+ params.extraFlags = extraFlags;
+ params.options = options;
+ startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+ } else {
+ super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ }
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
+ if (requestCode != -1) {
+ mPendingActivityRequestCode = requestCode;
+ StartActivityParams params = new StartActivityParams(this, requestCode);
+ params.intent = intent;
+ params.options = options;
+ startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+ } else {
+ super.startActivityForResult(intent, requestCode, options);
+ }
+ }
+
+ @Override
+ protected void onDeferredResumed() {
+ if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
+ // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
+ onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
+ // ProxyActivityStarter is started with clear task to reset the task after which it
+ // removes the task itself.
+ startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
+ }
+
+ // Register all system actions once they are available
+ mSystemActions.register();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mSystemActions.unregister();
+ }
+
+ @Override
+ protected void setupViews() {
+ super.setupViews();
+
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
+ // Overview is above all other launcher elements, including qsb, so move it to the top.
+ getOverviewPanel().bringToFront();
+ if (getActionsView() != null) {
+ getActionsView().bringToFront();
+ }
+ }
+ }
+
+ @Override
+ protected void closeOpenViews(boolean animate) {
+ super.closeOpenViews(animate);
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+ }
+
+ @Override
+ protected StateHandler[] createStateHandlers() {
+ return new StateHandler[] {
+ getAllAppsController(),
+ getWorkspace(),
+ getBackgroundBlurController(),
+ new RecentsViewStateController(this),
+ new BackButtonAlphaHandler(this)};
+ }
+
+ @Override
+ protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
+ if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) {
+ PagedOrientationHandler layoutVertical =
+ ((RecentsView)getOverviewPanel()).getPagedViewOrientedState().getOrientationHandler();
+ return layoutVertical.getScaleAndTranslation(getDeviceProfile(),
+ getOverviewPanel());
+ }
+ return super.getOverviewScaleAndTranslationForNormalState();
+ }
+
+ @Override
+ public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
+ QuickstepAppTransitionManagerImpl appTransitionManager =
+ (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
+ appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
+
+ // On the first call clear the reference.
+ signal.cancel();
+
+ ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
+ fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
+ wallpaperTargets));
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(fadeAnimation);
+ return anim;
+ }, signal);
+ }
+
+ @Override
+ public void onDragLayerHierarchyChanged() {
+ onLauncherStateOrFocusChanged();
+ }
+
+ @Override
+ protected void onActivityFlagsChanged(int changeBits) {
+ if ((changeBits
+ & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
+ onLauncherStateOrFocusChanged();
+ }
+
+ super.onActivityFlagsChanged(changeBits);
+ }
+
+ /**
+ * Sets the back button visibility based on the current state/window focus.
+ */
+ private void onLauncherStateOrFocusChanged() {
+ Mode mode = SysUINavigationMode.getMode(this);
+ boolean shouldBackButtonBeHidden = mode.hasGestures
+ && getStateManager().getState().hideBackButton
+ && hasWindowFocus()
+ && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0;
+ if (shouldBackButtonBeHidden) {
+ // Show the back button if there is a floating view visible.
+ shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this,
+ TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
+ }
+ UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
+ shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+ if (getDragLayer() != null) {
+ getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
+ }
+ }
+
+ @Override
+ public void finishBindingItems(int pageBoundFirst) {
+ super.finishBindingItems(pageBoundFirst);
+ // Instantiate and initialize WellbeingModel now that its loading won't interfere with
+ // populating workspace.
+ // TODO: Find a better place for this
+ WellbeingModel.INSTANCE.get(this);
+ }
+
+ @Override
+ public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
+ return Stream.concat(super.getSupportedShortcuts(),
+ Stream.of(WellbeingModel.SHORTCUT_FACTORY));
+ }
+
+ public ShelfPeekAnim getShelfPeekAnim() {
+ return mShelfPeekAnim;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index a8e2956..31c1acf 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,8 +17,7 @@
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
-import static com.android.systemui.shared.recents.utilities.Utilities
- .postAtFrontOfQueueAsynchronously;
+import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -28,12 +27,12 @@
import android.os.Build;
import android.os.Handler;
-import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
import androidx.annotation.BinderThread;
import androidx.annotation.UiThread;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
@TargetApi(Build.VERSION_CODES.P)
public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
@@ -50,13 +49,17 @@
mStartAtFrontOfQueue = startAtFrontOfQueue;
}
+ // Called only in R+ platform
@BinderThread
- @Override
- public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
+ public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) {
Runnable r = () -> {
finishExistingAnimation();
- mAnimationResult = new AnimationResult(runnable);
- onCreateAnimation(targetCompats, mAnimationResult);
+ mAnimationResult = new AnimationResult(() -> {
+ runnable.run();
+ mAnimationResult = null;
+ });
+ onCreateAnimation(appTargets, wallpaperTargets, mAnimationResult);
};
if (mStartAtFrontOfQueue) {
postAtFrontOfQueueAsynchronously(mHandler, r);
@@ -65,13 +68,21 @@
}
}
+ // Called only in Q platform
+ @BinderThread
+ @Deprecated
+ public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets, Runnable runnable) {
+ onAnimationStart(appTargets, new RemoteAnimationTargetCompat[0], runnable);
+ }
+
/**
* Called on the UI thread when the animation targets are received. The implementation must
* call {@link AnimationResult#setAnimation} with the target animation to be run.
*/
@UiThread
public abstract void onCreateAnimation(
- RemoteAnimationTargetCompat[] targetCompats, AnimationResult result);
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result);
@UiThread
private void finishExistingAnimation() {
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index b9ce1ce..96340b2 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -19,30 +19,31 @@
import android.content.Context;
import android.content.Intent;
import android.os.Build;
-import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
-import com.android.quickstep.OverviewCallbacks;
+import com.android.launcher3.util.ActivityTracker;
+import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import java.util.function.BiPredicate;
@TargetApi(Build.VERSION_CODES.P)
-public class LauncherInitListener extends InternalStateHandler implements ActivityInitListener {
-
- private final BiPredicate<Launcher, Boolean> mOnInitListener;
+public class LauncherInitListener extends ActivityInitListener<Launcher> {
private RemoteAnimationProvider mRemoteAnimationProvider;
+ /**
+ * @param onInitListener a callback made when the activity is initialized. The callback should
+ * return true to continue receiving callbacks (ie. for if the activity is
+ * recreated).
+ */
public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
- mOnInitListener = onInitListener;
+ super(onInitListener, Launcher.ACTIVITY_TRACKER);
}
@Override
- protected boolean init(Launcher launcher, boolean alreadyOnHome) {
+ public boolean init(Launcher launcher, boolean alreadyOnHome) {
if (mRemoteAnimationProvider != null) {
QuickstepAppTransitionManagerImpl appTransitionManager =
(QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
@@ -50,7 +51,7 @@
// Set a one-time animation provider. After the first call, this will get cleared.
// TODO: Probably also check the intended target id.
CancellationSignal cancellationSignal = new CancellationSignal();
- appTransitionManager.setRemoteAnimationProvider((targets) -> {
+ appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
// On the first call clear the reference.
cancellationSignal.cancel();
@@ -58,34 +59,25 @@
mRemoteAnimationProvider = null;
if (provider != null && launcher.getStateManager().getState().overviewUi) {
- return provider.createWindowAnimation(targets);
+ return provider.createWindowAnimation(appTargets, wallpaperTargets);
}
return null;
}, cancellationSignal);
}
- OverviewCallbacks.get(launcher).onInitOverviewTransition();
- return mOnInitListener.test(launcher, alreadyOnHome);
- }
-
- @Override
- public void register() {
- initWhenReady();
+ launcher.deferOverlayCallbacksUntilNextResumeOrStop();
+ return super.init(launcher, alreadyOnHome);
}
@Override
public void unregister() {
mRemoteAnimationProvider = null;
- clearReference();
+ super.unregister();
}
@Override
public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
Context context, Handler handler, long duration) {
mRemoteAnimationProvider = animProvider;
-
- register();
-
- Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
- context.startActivity(addToIntent(new Intent((intent))), options);
+ super.registerAndStartActivity(intent, animProvider, context, handler, duration);
}
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index a91410c..f691359 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -21,6 +21,7 @@
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.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -29,6 +30,7 @@
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
+import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
@@ -66,13 +68,13 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.util.ShelfPeekAnim;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -84,9 +86,11 @@
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.lang.ref.WeakReference;
+
/**
* {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
- * home and/or all-apps.
+ * home and/or all-apps. Not used for 3p launchers.
*/
@TargetApi(Build.VERSION_CODES.O)
@SuppressWarnings("unused")
@@ -135,7 +139,7 @@
// 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;
- protected final Launcher mLauncher;
+ protected final BaseQuickstepLauncher mLauncher;
private final DragLayer mDragLayer;
private final AlphaProperty mDragLayerAlpha;
@@ -150,8 +154,9 @@
private DeviceProfile mDeviceProfile;
private RemoteAnimationProvider mRemoteAnimationProvider;
-
- private final ShelfPeekAnim mShelfPeekAnim;
+ // Strong refs to runners which are cleared when the launcher activity is destroyed
+ private WrappedAnimationRunnerImpl mWallpaperOpenRunner;
+ private WrappedAnimationRunnerImpl mAppLaunchRunner;
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
@Override
@@ -166,7 +171,7 @@
};
public QuickstepAppTransitionManagerImpl(Context context) {
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = Launcher.cast(Launcher.getLauncher(context));
mDragLayer = mLauncher.getDragLayer();
mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
mHandler = new Handler(Looper.getMainLooper());
@@ -179,13 +184,6 @@
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
mLauncher.addOnDeviceProfileChangeListener(this);
- registerRemoteAnimations();
-
- mShelfPeekAnim = new ShelfPeekAnim(mLauncher);
- }
-
- public ShelfPeekAnim getShelfPeekAnim() {
- return mShelfPeekAnim;
}
@Override
@@ -207,30 +205,9 @@
public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission()) {
boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
- RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
- true /* startAtFrontOfQueue */) {
-
- @Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
- AnimationResult result) {
- AnimatorSet anim = new AnimatorSet();
-
- boolean launcherClosing =
- launcherIsATargetWithMode(targetCompats, MODE_CLOSING);
-
- if (isLaunchingFromRecents(v, targetCompats)) {
- composeRecentsLaunchAnimator(anim, v, targetCompats, launcherClosing);
- } else {
- composeIconLaunchAnimator(anim, v, targetCompats, launcherClosing);
- }
-
- if (launcherClosing) {
- anim.addListener(mForceInvisibleListener);
- }
-
- result.setAnimation(anim, mLauncher);
- }
- };
+ mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v);
+ RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
+ mAppLaunchRunner, true /* startAtFrontOfQueue */);
// Note that this duration is a guess as we do not know if the animation will be a
// recents launch or not for sure until we know the opening app targets.
@@ -264,36 +241,39 @@
*
* @param anim the animator set to add to
* @param v the launching view
- * @param targets the apps that are opening/closing
+ * @param appTargets the apps that are opening/closing
* @param launcherClosing true if the launcher app is closing
*/
protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
- @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing);
+ @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
/**
* Compose the animations for a launch from the app icon.
*
* @param anim the animation to add to
* @param v the launching view with the icon
- * @param targets the list of opening/closing apps
+ * @param appTargets the list of opening/closing apps
* @param launcherClosing true if launcher is closing
*/
private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
- @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+ @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ boolean launcherClosing) {
// Set the state animation first so that any state listeners are called
// before our internal listeners.
mLauncher.getStateManager().setCurrentAnimation(anim);
- Rect windowTargetBounds = getWindowTargetBounds(targets);
+ Rect windowTargetBounds = getWindowTargetBounds(appTargets);
boolean isAllOpeningTargetTrs = true;
- for (int i = 0; i < targets.length; i++) {
- RemoteAnimationTargetCompat target = targets[i];
+ for (int i = 0; i < appTargets.length; i++) {
+ RemoteAnimationTargetCompat target = appTargets[i];
if (target.mode == MODE_OPENING) {
isAllOpeningTargetTrs &= target.isTranslucent;
}
if (!isAllOpeningTargetTrs) break;
}
- anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds,
+ anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds,
!isAllOpeningTargetTrs));
if (launcherClosing) {
Pair<AnimatorSet, Runnable> launcherContentAnimator =
@@ -314,10 +294,10 @@
* In multiwindow mode, we need to get the final size of the opening app window target to help
* figure out where the floating view should animate to.
*/
- private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] targets) {
+ private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets) {
Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
if (mLauncher.isInMultiWindowMode()) {
- for (RemoteAnimationTargetCompat target : targets) {
+ for (RemoteAnimationTargetCompat target : appTargets) {
if (target.mode == MODE_OPENING) {
bounds.set(target.sourceContainerBounds);
bounds.offsetTo(target.position.x, target.position.y);
@@ -425,9 +405,11 @@
float[] alphas, float[] trans);
/**
- * @return Animator that controls the window of the opening targets.
+ * @return Animator that controls the window of the opening targets from app icons.
*/
- private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
+ private Animator getOpeningWindowAnimators(View v,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
Rect windowTargetBounds, boolean toggleVisibility) {
RectF bounds = new RectF();
FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
@@ -435,8 +417,8 @@
Rect crop = new Rect();
Matrix matrix = new Matrix();
- RemoteAnimationTargetSet openingTargets = new RemoteAnimationTargetSet(targets,
- MODE_OPENING);
+ RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
+ wallpaperTargets, MODE_OPENING);
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(floatingView);
openingTargets.addDependentTransactionApplier(surfaceApplier);
@@ -479,6 +461,7 @@
RectF currentBounds = new RectF();
RectF temp = new RectF();
+ AnimatorSet animatorSet = new AnimatorSet();
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(APP_LAUNCH_DURATION);
appAnimator.setInterpolator(LINEAR);
@@ -560,18 +543,13 @@
float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale;
float croppedWidth = (windowTargetBounds.width() - crop.width()) * scale;
- SurfaceParams[] params = new SurfaceParams[targets.length];
- for (int i = targets.length - 1; i >= 0; i--) {
- RemoteAnimationTargetCompat target = targets[i];
- Rect targetCrop;
- final float alpha;
- final float cornerRadius;
+ SurfaceParams[] params = new SurfaceParams[appTargets.length];
+ for (int i = appTargets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = appTargets[i];
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
if (target.mode == MODE_OPENING) {
matrix.setScale(scale, scale);
matrix.postTranslate(transX0, transY0);
- targetCrop = crop;
- alpha = 1f - mIconAlpha.value;
- cornerRadius = mWindowRadius.value;
matrix.mapRect(currentBounds, targetBounds);
if (mDeviceProfile.isVerticalBarLayout()) {
currentBounds.right -= croppedWidth;
@@ -579,39 +557,80 @@
currentBounds.bottom -= croppedHeight;
}
floatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
- cornerRadius * scale, true /* isOpening */);
+ mWindowRadius.value * scale, true /* isOpening */);
+ builder.withMatrix(matrix)
+ .withWindowCrop(crop)
+ .withAlpha(1f - mIconAlpha.value)
+ .withCornerRadius(mWindowRadius.value);
} else {
matrix.setTranslate(target.position.x, target.position.y);
- targetCrop = target.sourceContainerBounds;
- alpha = 1f;
- cornerRadius = 0;
+ builder.withMatrix(matrix)
+ .withWindowCrop(target.sourceContainerBounds)
+ .withAlpha(1f);
}
-
- params[i] = new SurfaceParams(target.leash, alpha, matrix, targetCrop,
- RemoteAnimationProvider.getLayer(target, MODE_OPENING),
- cornerRadius);
+ builder.withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING));
+ params[i] = builder.build();
}
surfaceApplier.scheduleApply(params);
}
});
- return appAnimator;
+
+ // When launching an app from overview that doesn't map to a task, we still want to just
+ // blur the wallpaper instead of the launcher surface as well
+ boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
+ BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
+ ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
+ BACKGROUND_APP.getBackgroundBlurRadius(mLauncher))
+ .setDuration(APP_LAUNCH_DURATION);
+ if (allowBlurringLauncher) {
+ blurController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
+ appTargets, MODE_OPENING));
+ backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ blurController.setSurfaceToLauncher(mLauncher.getDragLayer());
+ }
+ });
+ }
+
+ animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
+ return animatorSet;
}
/**
* Registers remote animations used when closing apps to home screen.
*/
- private void registerRemoteAnimations() {
- // Unregister this
+ @Override
+ public void registerRemoteAnimations() {
if (hasControlRemoteAppTransitionPermission()) {
+ mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
+
RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
- new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(false /* fromUnlock */),
+ new RemoteAnimationAdapterCompat(
+ new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
+ false /* startAtFrontOfQueue */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
}
}
+ /**
+ * Unregisters all remote animations.
+ */
+ @Override
+ public void unregisterRemoteAnimations() {
+ if (hasControlRemoteAppTransitionPermission()) {
+ new ActivityCompat(mLauncher).unregisterRemoteAnimations();
+
+ // Also clear strong references to the runners registered with the remote animation
+ // definition so we don't have to wait for the system gc
+ mWallpaperOpenRunner = null;
+ mAppLaunchRunner = null;
+ }
+ }
+
private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
}
@@ -620,15 +639,15 @@
* @return Runner that plays when user goes to Launcher
* ie. pressing home, swiping up from nav bar.
*/
- RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
- return new WallpaperOpenLauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */,
- fromUnlock);
+ WrappedAnimationRunnerImpl createWallpaperOpenRunner(boolean fromUnlock) {
+ return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock);
}
/**
* Animator that controls the transformations of the windows when unlocking the device.
*/
- private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] targets) {
+ private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets) {
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
@@ -638,12 +657,15 @@
unlockAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- SurfaceParams[] params = new SurfaceParams[targets.length];
- for (int i = targets.length - 1; i >= 0; i--) {
- RemoteAnimationTargetCompat target = targets[i];
- params[i] = new SurfaceParams(target.leash, 1f, null,
- target.sourceContainerBounds,
- RemoteAnimationProvider.getLayer(target, MODE_OPENING), cornerRadius);
+ SurfaceParams[] params = new SurfaceParams[appTargets.length];
+ for (int i = appTargets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = appTargets[i];
+ params[i] = new SurfaceParams.Builder(target.leash)
+ .withAlpha(1f)
+ .withWindowCrop(target.sourceContainerBounds)
+ .withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING))
+ .withCornerRadius(cornerRadius)
+ .build();
}
surfaceApplier.scheduleApply(params);
}
@@ -654,7 +676,8 @@
/**
* Animator that controls the transformations of the windows the targets that are closing.
*/
- private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
+ private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets) {
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
Matrix matrix = new Matrix();
@@ -670,28 +693,28 @@
@Override
public void onUpdate(float percent) {
- SurfaceParams[] params = new SurfaceParams[targets.length];
- for (int i = targets.length - 1; i >= 0; i--) {
- RemoteAnimationTargetCompat target = targets[i];
- final float alpha;
- final float cornerRadius;
+ SurfaceParams[] params = new SurfaceParams[appTargets.length];
+ for (int i = appTargets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = appTargets[i];
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
if (target.mode == MODE_CLOSING) {
matrix.setScale(mScale.value, mScale.value,
target.sourceContainerBounds.centerX(),
target.sourceContainerBounds.centerY());
matrix.postTranslate(0, mDy.value);
matrix.postTranslate(target.position.x, target.position.y);
- alpha = mAlpha.value;
- cornerRadius = windowCornerRadius;
+ builder.withMatrix(matrix)
+ .withAlpha(mAlpha.value)
+ .withCornerRadius(windowCornerRadius);
} else {
matrix.setTranslate(target.position.x, target.position.y);
- alpha = 1f;
- cornerRadius = 0f;
+ builder.withMatrix(matrix)
+ .withAlpha(1f);
}
- params[i] = new SurfaceParams(target.leash, alpha, matrix,
- target.sourceContainerBounds,
- RemoteAnimationProvider.getLayer(target, MODE_CLOSING),
- cornerRadius);
+ params[i] = builder
+ .withWindowCrop(target.sourceContainerBounds)
+ .withLayer(RemoteAnimationProvider.getLayer(target, MODE_CLOSING))
+ .build();
}
surfaceApplier.scheduleApply(params);
}
@@ -701,7 +724,8 @@
}
/**
- * Creates an animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}.
+ * Creates an animator that modifies Launcher as a result from
+ * {@link #createWallpaperOpenRunner}.
*/
private void createLauncherResumeAnimation(AnimatorSet anim) {
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
@@ -761,25 +785,85 @@
}
/**
+ * Used with WrappedLauncherAnimationRunner as an interface for the runner to call back to the
+ * implementation.
+ */
+ protected interface WrappedAnimationRunnerImpl {
+ Handler getHandler();
+ void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ LauncherAnimationRunner.AnimationResult result);
+ }
+
+ /**
+ * This class is needed to wrap any animation runner that is a part of the
+ * RemoteAnimationDefinition:
+ * - Launcher creates a new instance of the LauncherAppTransitionManagerImpl whenever it is
+ * created, which in turn registers a new definition
+ * - When the definition is registered, window manager retains a strong binder reference to the
+ * runner passed in
+ * - If the Launcher activity is recreated, the new definition registered will replace the old
+ * reference in the system's activity record, but until the system server is GC'd, the binder
+ * reference will still exist, which references the runner in the Launcher process, which
+ * references the (old) Launcher activity through this class
+ *
+ * Instead we make the runner provided to the definition static only holding a weak reference to
+ * the runner implementation. When this animation manager is destroyed, we remove the Launcher
+ * reference to the runner, leaving only the weak ref from the runner.
+ */
+ protected static class WrappedLauncherAnimationRunner<R extends WrappedAnimationRunnerImpl>
+ extends LauncherAnimationRunner {
+ private WeakReference<R> mImpl;
+
+ public WrappedLauncherAnimationRunner(R animationRunnerImpl, boolean startAtFrontOfQueue) {
+ super(animationRunnerImpl.getHandler(), startAtFrontOfQueue);
+ mImpl = new WeakReference<>(animationRunnerImpl);
+ }
+
+ @Override
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+ R animationRunnerImpl = mImpl.get();
+ if (animationRunnerImpl != null) {
+ animationRunnerImpl.onCreateAnimation(appTargets, wallpaperTargets, result);
+ }
+ }
+ }
+
+ /**
* Remote animation runner for animation from the app to Launcher, including recents.
*/
- class WallpaperOpenLauncherAnimationRunner extends LauncherAnimationRunner {
+ protected class WallpaperOpenLauncherAnimationRunner implements WrappedAnimationRunnerImpl {
+
+ private final Handler mHandler;
private final boolean mFromUnlock;
- public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
- boolean fromUnlock) {
- super(handler, startAtFrontOfQueue);
+ public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) {
+ mHandler = handler;
mFromUnlock = fromUnlock;
}
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
LauncherAnimationRunner.AnimationResult result) {
+ if (mLauncher.isDestroyed()) {
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(getClosingWindowAnimators(appTargets, wallpaperTargets));
+ result.setAnimation(anim, mLauncher.getApplicationContext());
+ return;
+ }
+
if (!mLauncher.hasBeenResumed()) {
// If launcher is not resumed, wait until new async-frame after resume
mLauncher.addOnResumeCallback(() ->
postAsyncCallback(mHandler, () ->
- onCreateAnimation(targetCompats, result)));
+ onCreateAnimation(appTargets, wallpaperTargets, result)));
return;
}
@@ -791,14 +875,14 @@
AnimatorSet anim = null;
RemoteAnimationProvider provider = mRemoteAnimationProvider;
if (provider != null) {
- anim = provider.createWindowAnimation(targetCompats);
+ anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
}
if (anim == null) {
anim = new AnimatorSet();
anim.play(mFromUnlock
- ? getUnlockWindowAnimator(targetCompats)
- : getClosingWindowAnimators(targetCompats));
+ ? getUnlockWindowAnimator(appTargets, wallpaperTargets)
+ : getClosingWindowAnimators(appTargets, wallpaperTargets));
// Normally, we run the launcher content animation when we are transitioning
// home, but if home is already visible, then we don't want to animate the
@@ -808,7 +892,7 @@
// targets list because it is already visible). In that case, we force
// invisibility on touch down, and only reset it after the animation to home
// is initialized.
- if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
+ if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
|| mLauncher.isForceInvisible()) {
// Only register the content animation for cancellation when state changes
mLauncher.getStateManager().setCurrentAnimation(anim);
@@ -834,4 +918,47 @@
result.setAnimation(anim, mLauncher);
}
}
+
+ /**
+ * Remote animation runner for animation to launch an app.
+ */
+ private class AppLaunchAnimationRunner implements WrappedAnimationRunnerImpl {
+
+ private final Handler mHandler;
+ private final View mV;
+
+ AppLaunchAnimationRunner(Handler handler, View v) {
+ mHandler = handler;
+ mV = v;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ LauncherAnimationRunner.AnimationResult result) {
+ AnimatorSet anim = new AnimatorSet();
+
+ boolean launcherClosing =
+ launcherIsATargetWithMode(appTargets, MODE_CLOSING);
+
+ if (isLaunchingFromRecents(mV, appTargets)) {
+ composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
+ launcherClosing);
+ } else {
+ composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
+ launcherClosing);
+ }
+
+ if (launcherClosing) {
+ anim.addListener(mForceInvisibleListener);
+ }
+
+ result.setAnimation(anim, mLauncher);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/accessibility/SystemActions.java b/quickstep/src/com/android/launcher3/accessibility/SystemActions.java
new file mode 100644
index 0000000..669877f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/accessibility/SystemActions.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.accessibility;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
+import android.view.accessibility.AccessibilityManager;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.R;
+
+/**
+ * Manages the launcher system actions presented to accessibility services.
+ */
+public class SystemActions {
+
+ /**
+ * System Action ID to show all apps. This ID should follow the ones in
+ * com.android.systemui.accessibility.SystemActions.
+ */
+ private static final int SYSTEM_ACTION_ID_ALL_APPS = 100;
+
+ private Launcher mLauncher;
+ private AccessibilityManager mAccessibilityManager;
+ private RemoteAction mAllAppsAction;
+ private boolean mRegistered;
+
+ public SystemActions(Launcher launcher) {
+ mLauncher = launcher;
+ mAccessibilityManager = (AccessibilityManager) launcher.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ mAllAppsAction = new RemoteAction(
+ Icon.createWithResource(launcher, R.drawable.ic_apps),
+ launcher.getString(R.string.all_apps_label),
+ launcher.getString(R.string.all_apps_label),
+ launcher.createPendingResult(SYSTEM_ACTION_ID_ALL_APPS, new Intent(),
+ 0 /* flags */));
+ }
+
+ public void register() {
+ if (mRegistered) {
+ return;
+ }
+ mAccessibilityManager.registerSystemAction(mAllAppsAction, SYSTEM_ACTION_ID_ALL_APPS);
+ mRegistered = true;
+ }
+
+ public void unregister() {
+ if (!mRegistered) {
+ return;
+ }
+ mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
+ mRegistered = false;
+ }
+
+ public void onActivityResult(int requestCode) {
+ if (requestCode == SYSTEM_ACTION_ID_ALL_APPS) {
+ showAllApps();
+ }
+ }
+
+ private void showAllApps() {
+ LauncherStateManager stateManager = mLauncher.getStateManager();
+ stateManager.goToState(NORMAL);
+ stateManager.goToState(ALL_APPS);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
new file mode 100644
index 0000000..92c8573
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static android.content.ContentResolver.SCHEME_CONTENT;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
+import android.annotation.TargetApi;
+import android.app.RemoteAction;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.LauncherApps;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.MainThread;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.popup.RemoteActionShortcut;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Data model for digital wellbeing status of apps.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public final class WellbeingModel {
+ private static final String TAG = "WellbeingModel";
+ private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
+ private static final boolean DEBUG = false;
+
+ private static final int MSG_PACKAGE_ADDED = 1;
+ private static final int MSG_PACKAGE_REMOVED = 2;
+ private static final int MSG_FULL_REFRESH = 3;
+
+ // Welbeing contract
+ private static final String METHOD_GET_ACTIONS = "get_actions";
+ private static final String EXTRA_ACTIONS = "actions";
+ private static final String EXTRA_ACTION = "action";
+ private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
+ private static final String EXTRA_PACKAGES = "packages";
+
+ public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
+ new MainThreadInitializedObject<>(WellbeingModel::new);
+
+ private final Context mContext;
+ private final String mWellbeingProviderPkg;
+ private final Handler mWorkerHandler;
+
+ private final ContentObserver mContentObserver;
+
+ private final Object mModelLock = new Object();
+ // Maps the action Id to the corresponding RemoteAction
+ private final Map<String, RemoteAction> mActionIdMap = new ArrayMap<>();
+ private final Map<String, String> mPackageToActionId = new HashMap<>();
+
+ private boolean mIsInTest;
+
+ private WellbeingModel(final Context context) {
+ mContext = context;
+ mWorkerHandler =
+ new Handler(createAndStartNewLooper("WellbeingHandler"), this::handleMessage);
+
+ mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
+ mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ // Wellbeing reports that app actions have changed.
+ if (DEBUG || mIsInTest) {
+ Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange
+ + "], uri = [" + uri + "]");
+ }
+ Preconditions.assertUIThread();
+ updateWellbeingData();
+ }
+ };
+
+ if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
+ context.registerReceiver(
+ new SimpleBroadcastReceiver(this::onWellbeingProviderChanged),
+ PackageManagerHelper.getPackageFilter(mWellbeingProviderPkg,
+ Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED,
+ Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED,
+ Intent.ACTION_PACKAGE_RESTARTED));
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ context.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
+ filter);
+
+ restartObserver();
+ }
+ }
+
+ public void setInTest(boolean inTest) {
+ mIsInTest = inTest;
+ }
+
+ protected void onWellbeingProviderChanged(Intent intent) {
+ if (DEBUG || mIsInTest) {
+ Log.d(TAG, "Changes to Wellbeing package: intent = [" + intent + "]");
+ }
+ restartObserver();
+ }
+
+ private void restartObserver() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(mContentObserver);
+ Uri actionsUri = apiBuilder().path("actions").build();
+ try {
+ resolver.registerContentObserver(
+ actionsUri, true /* notifyForDescendants */, mContentObserver);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
+ if (mIsInTest) throw new RuntimeException(e);
+ }
+ updateWellbeingData();
+ }
+
+ @MainThread
+ private SystemShortcut getShortcutForApp(String packageName, int userId,
+ BaseDraggingActivity activity, ItemInfo info) {
+ Preconditions.assertUIThread();
+ // Work profile apps are not recognized by digital wellbeing.
+ if (userId != UserHandle.myUserId()) {
+ if (DEBUG || mIsInTest) {
+ Log.d(TAG, "getShortcutForApp [" + packageName + "]: not current user");
+ }
+ return null;
+ }
+
+ synchronized (mModelLock) {
+ String actionId = mPackageToActionId.get(packageName);
+ final RemoteAction action = actionId != null ? mActionIdMap.get(actionId) : null;
+ if (action == null) {
+ if (DEBUG || mIsInTest) {
+ Log.d(TAG, "getShortcutForApp [" + packageName + "]: no action");
+ }
+ return null;
+ }
+ if (DEBUG || mIsInTest) {
+ Log.d(TAG,
+ "getShortcutForApp [" + packageName + "]: action: '" + action.getTitle()
+ + "'");
+ }
+ return new RemoteActionShortcut(action, activity, info);
+ }
+ }
+
+ private void updateWellbeingData() {
+ mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
+ }
+
+ private Uri.Builder apiBuilder() {
+ return new Uri.Builder()
+ .scheme(SCHEME_CONTENT)
+ .authority(mWellbeingProviderPkg + ".api");
+ }
+
+ private boolean updateActions(String... packageNames) {
+ if (packageNames.length == 0) {
+ return true;
+ }
+ if (DEBUG || mIsInTest) {
+ Log.d(TAG, "retrieveActions() called with: packageNames = [" + String.join(", ",
+ packageNames) + "]");
+ }
+ Preconditions.assertNonUiThread();
+
+ Uri contentUri = apiBuilder().build();
+ final Bundle remoteActionBundle;
+ try (ContentProviderClient client = mContext.getContentResolver()
+ .acquireUnstableContentProviderClient(contentUri)) {
+ if (client == null) {
+ if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): null provider");
+ return false;
+ }
+
+ // Prepare wellbeing call parameters.
+ final Bundle params = new Bundle();
+ params.putStringArray(EXTRA_PACKAGES, packageNames);
+ params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1);
+ // Perform wellbeing call .
+ remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params);
+
+ synchronized (mModelLock) {
+ // Remove the entries for requested packages, and then update the fist with what we
+ // got from service
+ Arrays.stream(packageNames).forEach(mPackageToActionId::remove);
+
+ // The result consists of sub-bundles, each one is per a remote action. Each
+ // sub-bundle has a RemoteAction and a list of packages to which the action applies.
+ for (String actionId :
+ remoteActionBundle.getStringArray(EXTRA_ACTIONS)) {
+ final Bundle actionBundle = remoteActionBundle.getBundle(actionId);
+ mActionIdMap.put(actionId,
+ actionBundle.getParcelable(EXTRA_ACTION));
+
+ final String[] packagesForAction =
+ actionBundle.getStringArray(EXTRA_PACKAGES);
+ if (DEBUG || mIsInTest) {
+ Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ",
+ packagesForAction));
+ }
+ for (String packageName : packagesForAction) {
+ mPackageToActionId.put(packageName, actionId);
+ }
+ }
+ }
+ } catch (DeadObjectException e) {
+ Log.i(TAG, "retrieveActions(): DeadObjectException");
+ return false;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e);
+ if (mIsInTest) throw new RuntimeException(e);
+ return true;
+ }
+ if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): finished");
+ return true;
+ }
+
+ private boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PACKAGE_REMOVED: {
+ String packageName = (String) msg.obj;
+ mWorkerHandler.removeCallbacksAndMessages(packageName);
+ synchronized (mModelLock) {
+ mPackageToActionId.remove(packageName);
+ }
+ return true;
+ }
+ case MSG_PACKAGE_ADDED: {
+ String packageName = (String) msg.obj;
+ mWorkerHandler.removeCallbacksAndMessages(packageName);
+ if (!updateActions(packageName)) {
+ scheduleRefreshRetry(msg);
+ }
+ return true;
+ }
+
+ case MSG_FULL_REFRESH: {
+ // Remove all existing messages
+ mWorkerHandler.removeCallbacksAndMessages(null);
+ final String[] packageNames = mContext.getSystemService(LauncherApps.class)
+ .getActivityList(null, Process.myUserHandle()).stream()
+ .map(li -> li.getApplicationInfo().packageName).distinct()
+ .toArray(String[]::new);
+ if (!updateActions(packageNames)) {
+ scheduleRefreshRetry(msg);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void scheduleRefreshRetry(Message originalMsg) {
+ int retryCount = originalMsg.arg1;
+ if (retryCount >= RETRY_TIMES_MS.length) {
+ // To many retries, skip
+ return;
+ }
+
+ Message msg = Message.obtain(originalMsg);
+ msg.arg1 = retryCount + 1;
+ mWorkerHandler.sendMessageDelayed(msg, RETRY_TIMES_MS[retryCount]);
+ }
+
+ private void onAppPackageChanged(Intent intent) {
+ if (DEBUG || mIsInTest) Log.d(TAG, "Changes in apps: intent = [" + intent + "]");
+ Preconditions.assertUIThread();
+
+ final String packageName = intent.getData().getSchemeSpecificPart();
+ if (packageName == null || packageName.length() == 0) {
+ // they sent us a bad intent
+ return;
+ }
+
+ final String action = intent.getAction();
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ Message.obtain(mWorkerHandler, MSG_PACKAGE_REMOVED, packageName).sendToTarget();
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ Message.obtain(mWorkerHandler, MSG_PACKAGE_ADDED, packageName).sendToTarget();
+ }
+ }
+
+ /**
+ * Shortcut factory for generating wellbeing action
+ */
+ public static final SystemShortcut.Factory SHORTCUT_FACTORY =
+ (activity, info) -> (info.getTargetComponent() == null) ? null : INSTANCE.get(activity)
+ .getShortcutForApp(
+ info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
+ activity, info);
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
new file mode 100644
index 0000000..1e03b05
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import android.app.Person;
+import android.content.pm.ShortcutInfo;
+
+import com.android.launcher3.Utilities;
+
+public class ApiWrapper {
+
+ public static Person[] getPersons(ShortcutInfo si) {
+ Person[] persons = si.getPersons();
+ return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 693ae60..671aab0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -16,54 +16,50 @@
package com.android.launcher3.uioverrides;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SystemUiProxy;
public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
- private static final String TAG = "BackButtonAlphaHandler";
+ private final BaseQuickstepLauncher mLauncher;
- private final Launcher mLauncher;
- private final OverviewInteractionState mOverviewInteractionState;
-
- public BackButtonAlphaHandler(Launcher launcher) {
+ public BackButtonAlphaHandler(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
- mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(mLauncher);
}
@Override
- public void setState(LauncherState state) {
- UiFactory.onLauncherStateOrFocusChanged(mLauncher);
- }
+ public void setState(LauncherState state) { }
@Override
public void setStateWithAnimation(LauncherState toState,
AnimatorSetBuilder builder, LauncherStateManager.AnimationConfig config) {
- if (!config.playNonAtomicComponent()) {
+ if (config.onlyPlayAtomicComponent()) {
return;
}
- float fromAlpha = mOverviewInteractionState.getBackButtonAlpha();
+
+ if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
+ // If the nav mode is not gestural, then force back button alpha to be 1
+ UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
+ BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */);
+ return;
+ }
+
+ float fromAlpha = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
float toAlpha = toState.hideBackButton ? 0 : 1;
if (Float.compare(fromAlpha, toAlpha) != 0) {
ValueAnimator anim = ValueAnimator.ofFloat(fromAlpha, toAlpha);
anim.setDuration(config.duration);
anim.addUpdateListener(valueAnimator -> {
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);
- }
+ UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
+ BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, alpha, false /* animate */);
});
builder.play(anim);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java b/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
new file mode 100644
index 0000000..022a5f7
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.util.IntProperty;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SurfaceControlCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+/**
+ * Controls the blur, for the Launcher surface only.
+ */
+public class BackgroundBlurController implements LauncherStateManager.StateHandler {
+
+ public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
+ new IntProperty<BackgroundBlurController>("backgroundBlur") {
+ @Override
+ public void setValue(BackgroundBlurController blurController, int blurRadius) {
+ blurController.setBackgroundBlurRadius(blurRadius);
+ }
+
+ @Override
+ public Integer get(BackgroundBlurController blurController) {
+ return blurController.mBackgroundBlurRadius;
+ }
+ };
+
+ /**
+ * A property that updates the background blur within a given range of values (ie. even if the
+ * animator goes beyond 0..1, the interpolated value will still be bounded).
+ */
+ public static class ClampedBlurProperty extends IntProperty<BackgroundBlurController> {
+ private final int mMinValue;
+ private final int mMaxValue;
+
+ public ClampedBlurProperty(int minValue, int maxValue) {
+ super(("backgroundBlurClamped"));
+ mMinValue = minValue;
+ mMaxValue = maxValue;
+ }
+
+ @Override
+ public void setValue(BackgroundBlurController blurController, int blurRadius) {
+ blurController.setBackgroundBlurRadius(Utilities.boundToRange(blurRadius,
+ mMinValue, mMaxValue));
+ }
+
+ @Override
+ public Integer get(BackgroundBlurController blurController) {
+ return blurController.mBackgroundBlurRadius;
+ }
+ }
+
+ private final Launcher mLauncher;
+ private SurfaceControlCompat mSurface;
+ private int mBackgroundBlurRadius;
+
+ public BackgroundBlurController(Launcher l) {
+ mLauncher = l;
+ }
+
+ /**
+ * @return the background blur adjustment for folders
+ */
+ public int getFolderBackgroundBlurAdjustment() {
+ return mLauncher.getResources().getInteger(
+ R.integer.folder_background_blur_radius_adjustment);
+ }
+
+ /**
+ * Sets the specified app target surface to apply the blur to.
+ */
+ public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
+ if (target != null) {
+ setSurface(target.leash);
+ }
+ }
+
+ /**
+ * Sets the surface to apply the blur to as the launcher surface.
+ */
+ public void setSurfaceToLauncher(View v) {
+ setSurface(v != null ? new SurfaceControlCompat(v) : null);
+ }
+
+ private void setSurface(SurfaceControlCompat surface) {
+ if (mSurface != surface) {
+ mSurface = surface;
+ if (surface != null) {
+ setBackgroundBlurRadius(mBackgroundBlurRadius);
+ } else {
+ // If there is no surface, then reset the blur radius
+ setBackgroundBlurRadius(0);
+ }
+ }
+ }
+
+ @Override
+ public void setState(LauncherState toState) {
+ if (mSurface == null) {
+ return;
+ }
+
+ int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
+ if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
+ setBackgroundBlurRadius(toBackgroundBlurRadius);
+ }
+ }
+
+ @Override
+ public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
+ LauncherStateManager.AnimationConfig config) {
+ if (mSurface == null || config.onlyPlayAtomicComponent()) {
+ return;
+ }
+
+ int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
+ if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
+ PropertySetter propertySetter = config.getPropertySetter(builder);
+ propertySetter.setInt(this, BACKGROUND_BLUR, toBackgroundBlurRadius, LINEAR);
+ }
+ }
+
+ private void setBackgroundBlurRadius(int blurRadius) {
+ // TODO: Do nothing if the shadows are not enabled
+ // Always update the background blur as it will be reapplied when a surface is next
+ // available
+ mBackgroundBlurRadius = blurRadius;
+ if (mSurface == null || !mSurface.isValid()) {
+ return;
+ }
+ new TransactionCompat()
+ .setBackgroundBlurRadius(mSurface, blurRadius)
+ .apply();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 174e49b..94e67f0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,12 +17,17 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
@@ -52,10 +57,12 @@
implements StateHandler {
protected final T mRecentsView;
protected final Launcher mLauncher;
+ protected final View mActionsView;
public BaseRecentsViewStateController(@NonNull Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
+ mActionsView = launcher.getActionsView();
}
@Override
@@ -72,18 +79,20 @@
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
+ if (mActionsView != null) {
+ mActionsView.setTranslationX(translationX);
+ mActionsView.setAlpha(state.overviewUi ? 1f : 0);
+ }
}
@Override
public final void setStateWithAnimation(@NonNull final LauncherState toState,
@NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
- boolean playAtomicOverviewComponent = config.playAtomicOverviewScaleComponent()
- || config.playAtomicOverviewPeekComponent();
- if (!playAtomicOverviewComponent) {
+ if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
// The entire recents animation is played atomically.
return;
}
- if (builder.hasFlag(FLAG_DONT_ANIMATE_OVERVIEW)) {
+ if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
return;
}
setStateWithAnimationInternal(toState, builder, config);
@@ -110,14 +119,19 @@
if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
translationX = -translationX;
}
- setter.setFloat(mRecentsView, View.TRANSLATION_X, translationX, translateXInterpolator);
- setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleAndTranslation.translationY,
+ setter.setFloat(mRecentsView, VIEW_TRANSLATE_X, translationX, translateXInterpolator);
+ setter.setFloat(mRecentsView, VIEW_TRANSLATE_Y, scaleAndTranslation.translationY,
translateYInterpolator);
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
builder.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
+ if (mActionsView != null) {
+ setter.setFloat(mActionsView, VIEW_TRANSLATE_X, translationX, translateXInterpolator);
+ setter.setFloat(mActionsView, VIEW_ALPHA, toState.overviewUi ? 1 : 0,
+ builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+ }
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
new file mode 100644
index 0000000..5836ebd
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.provider.DeviceConfig;
+
+import com.android.launcher3.config.FeatureFlags.DebugFlag;
+
+import java.util.ArrayList;
+
+@TargetApi(Build.VERSION_CODES.P)
+public class DeviceFlag extends DebugFlag {
+
+ public static final String NAMESPACE_LAUNCHER = "launcher";
+
+ private final boolean mDefaultValueInCode;
+ ArrayList<Runnable> mListeners;
+
+ public DeviceFlag(String key, boolean defaultValue, String description) {
+ super(key, getDeviceValue(key, defaultValue), description);
+ mDefaultValueInCode = defaultValue;
+ }
+
+ @Override
+ protected StringBuilder appendProps(StringBuilder src) {
+ return super.appendProps(src).append(", mDefaultValueInCode=").append(mDefaultValueInCode);
+ }
+
+ @Override
+ public void initialize(Context context) {
+ super.initialize(context);
+ if (mListeners == null) {
+ mListeners = new ArrayList<>();
+ registerDeviceConfigChangedListener(context);
+ }
+ }
+
+ @Override
+ public void addChangeListener(Context context, Runnable r) {
+ mListeners.add(r);
+ }
+
+ private void registerDeviceConfigChangedListener(Context context) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_LAUNCHER,
+ context.getMainExecutor(),
+ properties -> {
+ if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())
+ || !properties.getKeyset().contains(key)) {
+ return;
+ }
+ defaultValue = getDeviceValue(key, mDefaultValueInCode);
+ initialize(context);
+ for (Runnable r: mListeners) {
+ r.run();
+ }
+ });
+ }
+
+ protected static boolean getDeviceValue(String key, boolean defaultValue) {
+ return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValue);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java b/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
deleted file mode 100644
index 2d9a161..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import android.content.Context;
-import android.os.Handler;
-
-import com.android.systemui.shared.system.RotationWatcher;
-
-/**
- * Utility class for listening for rotation changes
- */
-public class DisplayRotationListener extends RotationWatcher {
-
- private final Runnable mCallback;
- private Handler mHandler;
-
- public DisplayRotationListener(Context context, Runnable callback) {
- super(context);
- mCallback = callback;
- }
-
- @Override
- public void enable() {
- if (mHandler == null) {
- mHandler = new Handler();
- }
- super.enable();
- }
-
- @Override
- protected void onRotationChanged(int i) {
- mHandler.post(mCallback);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
new file mode 100644
index 0000000..548223a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Size;
+import android.view.View;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.systemui.shared.system.SurfaceViewRequestReceiver;
+
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer {
+
+ /** Handle a received surface view request. */
+ public static void render(Context context, Bundle bundle) {
+ final String gridName = bundle.getString("name");
+ bundle.remove("name");
+ final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName);
+
+ MAIN_EXECUTOR.execute(() -> {
+ View view = new LauncherPreviewRenderer(context, idp).getRenderedView();
+ new SurfaceViewRequestReceiver().onReceive(context, bundle, view,
+ new Size(view.getMeasuredWidth(), view.getMeasuredHeight()));
+ });
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
deleted file mode 100644
index 853a1c6..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import android.content.Context;
-import android.provider.DeviceConfig;
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
-
-public class TogglableFlag extends BaseTogglableFlag {
- public static final String NAMESPACE_LAUNCHER = "launcher";
- public static final String TAG = "TogglableFlag";
-
- public TogglableFlag(String key, boolean defaultValue, String description) {
- super(key, defaultValue, description);
- }
-
- @Override
- public boolean getOverridenDefaultValue(boolean value) {
- return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, getKey(), value);
- }
-
- @Override
- public void addChangeListener(Context context, Runnable r) {
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE_LAUNCHER,
- context.getMainExecutor(),
- (properties) -> {
- if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())) {
- return;
- }
- initialize(context);
- r.run();
- });
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
deleted file mode 100644
index eb58b94..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import static android.app.Activity.RESULT_CANCELED;
-
-import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
-import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
-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.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
-import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
-import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
-
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.app.Activity;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ShortcutInfo;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.util.Base64;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.proxy.ProxyActivityStarter;
-import com.android.launcher3.proxy.StartActivityParams;
-import com.android.quickstep.OverviewInteractionState;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
-import com.android.quickstep.util.RemoteFadeOutAnimationListener;
-import com.android.systemui.shared.system.ActivityCompat;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
-import java.util.zip.Deflater;
-
-public class UiFactory extends RecentsUiFactory {
-
- public static Runnable enableLiveUIChanges(Launcher launcher) {
- NavigationModeChangeListener listener = m -> {
- launcher.getDragLayer().recreateControllers();
- launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
- };
- SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(launcher);
- SysUINavigationMode.Mode m = mode.addModeChangeListener(listener);
- launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
- return () -> mode.removeModeChangeListener(listener);
- }
-
- public static StateHandler[] getStateHandler(Launcher launcher) {
- return new StateHandler[] {
- launcher.getAllAppsController(),
- launcher.getWorkspace(),
- createRecentsViewStateController(launcher),
- new BackButtonAlphaHandler(launcher)};
- }
-
- /**
- * Sets the back button visibility based on the current state/window focus.
- */
- public static void onLauncherStateOrFocusChanged(Launcher launcher) {
- boolean shouldBackButtonBeHidden = launcher != null
- && launcher.getStateManager().getState().hideBackButton
- && launcher.hasWindowFocus();
- if (shouldBackButtonBeHidden) {
- // Show the back button if there is a floating view visible.
- shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher,
- TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
- }
- OverviewInteractionState.INSTANCE.get(launcher)
- .setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
- if (launcher != null && launcher.getDragLayer() != null) {
- launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
- }
- }
-
- public static void onCreate(Launcher launcher) {
- if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
- launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
- }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- boolean swipeUpEnabled = SysUINavigationMode.INSTANCE.get(launcher).getMode()
- .hasGestures;
- LauncherState prevState = launcher.getStateManager().getLastState();
-
- if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
- && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT <=
- launcher.getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) {
- launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
- launcher.getStateManager().removeStateListener(this);
- }
- }
- });
- }
-
- if (!launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
- launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
- }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- LauncherState prevState = launcher.getStateManager().getLastState();
-
- if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT <=
- launcher.getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) {
- launcher.getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
- launcher.getStateManager().removeStateListener(this);
- }
- }
- });
- }
- }
-
- public static void onEnterAnimationComplete(Context context) {
- // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
- // as a part of quickstep, so that high-res thumbnails can load the next time we enter
- // overview
- RecentsModel.INSTANCE.get(context).getThumbnailCache()
- .getHighResLoadingState().setVisible(true);
- }
-
- public static void onTrimMemory(Context context, int level) {
- RecentsModel model = RecentsModel.INSTANCE.get(context);
- if (model != null) {
- model.onTrimMemory(level);
- }
- }
-
- public static void useFadeOutAnimationForLauncherStart(Launcher launcher,
- CancellationSignal cancellationSignal) {
- QuickstepAppTransitionManagerImpl appTransitionManager =
- (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
- appTransitionManager.setRemoteAnimationProvider((targets) -> {
-
- // On the first call clear the reference.
- cancellationSignal.cancel();
-
- ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
- fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(targets));
- AnimatorSet anim = new AnimatorSet();
- anim.play(fadeAnimation);
- return anim;
- }, cancellationSignal);
- }
-
- public static boolean dumpActivity(Activity activity, PrintWriter writer) {
- if (!Utilities.IS_DEBUG_DEVICE) {
- return false;
- }
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) {
- return false;
- }
-
- Deflater deflater = new Deflater();
- deflater.setInput(out.toByteArray());
- deflater.finish();
-
- out.reset();
- byte[] buffer = new byte[1024];
- while (!deflater.finished()) {
- int count = deflater.deflate(buffer); // returns the generated code... index
- out.write(buffer, 0, count);
- }
-
- writer.println("--encoded-view-dump-v0--");
- writer.println(Base64.encodeToString(
- out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
- return true;
- }
-
- public static boolean startIntentSenderForResult(Activity activity, IntentSender intent,
- int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
- Bundle options) {
- StartActivityParams params = new StartActivityParams(activity, requestCode);
- params.intentSender = intent;
- params.fillInIntent = fillInIntent;
- params.flagsMask = flagsMask;
- params.flagsValues = flagsValues;
- params.extraFlags = extraFlags;
- params.options = options;
- ((Context) activity).startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
- return true;
- }
-
- public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode,
- Bundle options) {
- StartActivityParams params = new StartActivityParams(activity, requestCode);
- params.intent = intent;
- params.options = options;
- activity.startActivity(ProxyActivityStarter.getLaunchIntent(activity, params));
- return true;
- }
-
- /**
- * Removes any active ProxyActivityStarter task and sends RESULT_CANCELED to Launcher.
- *
- * ProxyActivityStarter is started with clear task to reset the task after which it removes the
- * task itself.
- */
- public static void resetPendingActivityResults(Launcher launcher, int requestCode) {
- launcher.onActivityResult(requestCode, RESULT_CANCELED, null);
- launcher.startActivity(ProxyActivityStarter.getLaunchIntent(launcher, null));
- }
-
- public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) {
- if (SysUINavigationMode.getMode(l) == Mode.NO_BUTTON) {
- float offscreenTranslationX = l.getDeviceProfile().widthPx
- - l.getOverviewPanel().getPaddingStart();
- return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
- }
- return new ScaleAndTranslation(1.1f, 0f, 0f);
- }
-
- public static Person[] getPersons(ShortcutInfo si) {
- Person[] persons = si.getPersons();
- return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
- }
-
- public static void closeSystemWindows() {}
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
index 711e59a..36c0e34 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.Looper;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.shared.system.TonalCompat;
import com.android.systemui.shared.system.TonalCompat.ExtractionInfo;
@@ -39,17 +40,8 @@
private static final int MAIN_COLOR_DARK = 0xff202124;
private static final int MAIN_COLOR_REGULAR = 0xff000000;
- private static final Object sInstanceLock = new Object();
- private static WallpaperColorInfo sInstance;
-
- public static WallpaperColorInfo getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- sInstance = new WallpaperColorInfo(context.getApplicationContext());
- }
- return sInstance;
- }
- }
+ public static final MainThreadInitializedObject<WallpaperColorInfo> INSTANCE =
+ new MainThreadInitializedObject<>(WallpaperColorInfo::new);
private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
private final WallpaperManager mWallpaperManager;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 6e7c087..2e422b7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -14,8 +14,12 @@
package com.android.launcher3.uioverrides.plugins;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
@@ -24,6 +28,9 @@
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
public class PluginManagerWrapper {
@@ -75,4 +82,27 @@
public static boolean hasPlugins(Context context) {
return PluginPrefs.hasPlugins(context);
}
+
+ public void dump(PrintWriter pw) {
+ final List<ComponentName> enabledPlugins = new ArrayList<>();
+ final List<ComponentName> disabledPlugins = new ArrayList<>();
+ for (String action : getPluginActions()) {
+ for (ResolveInfo resolveInfo : mContext.getPackageManager().queryIntentServices(
+ new Intent(action), MATCH_DISABLED_COMPONENTS)) {
+ ComponentName installedPlugin = new ComponentName(
+ resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+ if (mPluginEnabler.isEnabled(installedPlugin)) {
+ enabledPlugins.add(installedPlugin);
+ } else {
+ disabledPlugins.add(installedPlugin);
+ }
+ }
+ }
+
+ pw.println("PluginManager:");
+ pw.println(" numEnabledPlugins=" + enabledPlugins.size());
+ pw.println(" numDisabledPlugins=" + disabledPlugins.size());
+ pw.println(" enabledPlugins=" + enabledPlugins);
+ pw.println(" disabledPlugins=" + disabledPlugins);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 85a9545..971d917 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -15,12 +15,14 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import android.content.Context;
+
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -40,7 +42,12 @@
};
public AllAppsState(int id) {
- super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, STATE_FLAGS);
+ super(id, ContainerType.ALLAPPS, STATE_FLAGS);
+ }
+
+ @Override
+ public int getTransitionDuration(Launcher launcher) {
+ return 320;
}
@Override
@@ -50,6 +57,12 @@
}
@Override
+ public void onStateDisabled(Launcher launcher) {
+ super.onStateDisabled(launcher);
+ AbstractFloatingView.closeAllOpenViews(launcher);
+ }
+
+ @Override
public String getDescription(Launcher launcher) {
AllAppsContainerView appsView = launcher.getAppsView();
return appsView.getDescription();
@@ -75,6 +88,11 @@
}
@Override
+ public int getBackgroundBlurRadius(Context context) {
+ return context.getResources().getInteger(R.integer.allapps_background_blur_radius);
+ }
+
+ @Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
return PAGE_ALPHA_PROVIDER;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 39b0f8d..3d0fc56 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -9,12 +9,12 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
/**
* Touch controller for handling edge swipes in landscape/seascape UI
@@ -56,7 +56,7 @@
}
@Override
- protected float initCurrentAnimation(@AnimationComponents int animComponent) {
+ protected float initCurrentAnimation(@AnimationFlags int animComponent) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
@@ -73,7 +73,7 @@
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mStartState == NORMAL && targetState == OVERVIEW) {
- RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
+ SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index db6a40f..a060d64 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -25,7 +25,7 @@
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 static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.TimeInterpolator;
@@ -37,7 +37,7 @@
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.LauncherStateManager.AnimationFlags;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
@@ -47,8 +47,7 @@
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.OverviewInteractionState;
-import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.LayoutUtils;
@@ -88,8 +87,7 @@
protected boolean canInterceptTouch(MotionEvent ev) {
if (mCurrentAnimation != null) {
if (mFinishFastOnSecondTouch) {
- // TODO: Animate to finish instead.
- mCurrentAnimation.skipToEnd();
+ mCurrentAnimation.getAnimationPlayer().end();
}
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
@@ -137,8 +135,7 @@
} else if (fromState == OVERVIEW) {
return isDragTowardPositive ? ALL_APPS : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
- int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher)
- .getSystemUiStateFlags();
+ int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mAllowDragToOverview && TouchInteractionService.isConnected()
&& (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0
? OVERVIEW : ALL_APPS;
@@ -210,7 +207,8 @@
}
@Override
- protected float initCurrentAnimation(@AnimationComponents int animComponents) {
+ protected float initCurrentAnimation(@AnimationFlags int animComponents) {
+ animComponents = updateAnimComponentsOnReinit(animComponents);
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
@@ -221,7 +219,6 @@
final AnimatorSetBuilder builder = totalShift == 0 ? new AnimatorSetBuilder()
: getAnimatorSetBuilderForStates(mFromState, mToState);
- updateAnimatorBuilderOnReinit(builder);
cancelPendingAnim();
@@ -230,15 +227,13 @@
// Reset the state manager, when changing the interaction mode
mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
mPendingAnimation = mOverviewPortraitStateTouchHelper
- .createSwipeDownToTaskAppAnimation(maxAccuracy);
- mPendingAnimation.anim.setInterpolator(Interpolators.LINEAR);
-
+ .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR);
Runnable onCancelRunnable = () -> {
cancelPendingAnim();
clearState();
};
- mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
- onCancelRunnable);
+ mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation, maxAccuracy)
+ .setOnCancelRunnable(onCancelRunnable);
mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
mLauncher.getDeviceProfile());
@@ -258,7 +253,9 @@
/**
* Give subclasses the chance to update the animation when we re-initialize towards a new state.
*/
- protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+ @AnimationFlags
+ protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
+ return animComponents;
}
private void cancelPendingAnim() {
@@ -279,7 +276,7 @@
private void handleFirstSwipeToOverview(final ValueAnimator animator,
final long expectedDuration, final LauncherState targetState, final float velocity,
final boolean isFling) {
- if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
+ if (UNSTABLE_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
&& targetState == OVERVIEW) {
mFinishFastOnSecondTouch = true;
} else if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
@@ -301,7 +298,7 @@
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mStartState == NORMAL && targetState == OVERVIEW) {
- RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
+ SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 11a8043..16bd9ed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -21,8 +21,6 @@
import static android.view.MotionEvent.ACTION_CANCEL;
import android.graphics.PointF;
-import android.os.RemoteException;
-import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -37,9 +35,8 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TouchController;
-import com.android.quickstep.RecentsModel;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.SystemUiProxy;
import java.io.PrintWriter;
/**
@@ -62,9 +59,9 @@
*/
private static final int FLAG_SLIPPERY = 0x20000000;
- protected final Launcher mLauncher;
+ private final Launcher mLauncher;
+ private final SystemUiProxy mSystemUiProxy;
private final float mTouchSlop;
- private ISystemUiProxy mSysUiProxy;
private int mLastAction;
private final SparseArray<PointF> mDownEvents;
@@ -73,6 +70,7 @@
public StatusBarTouchController(Launcher l) {
mLauncher = l;
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher);
// Guard against TAPs by increasing the touch slop.
mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
mDownEvents = new SparseArray<>();
@@ -82,17 +80,14 @@
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "mCanIntercept:" + mCanIntercept);
writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
- writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
+ writer.println(prefix + "mSysUiProxy available:"
+ + SystemUiProxy.INSTANCE.get(mLauncher).isActive());
}
private void dispatchTouchEvent(MotionEvent ev) {
- try {
- if (mSysUiProxy != null) {
- mLastAction = ev.getActionMasked();
- mSysUiProxy.onStatusBarMotionEvent(ev);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Remote exception on sysUiProxy.", e);
+ if (mSystemUiProxy.isActive()) {
+ mLastAction = ev.getActionMasked();
+ mSystemUiProxy.onStatusBarMotionEvent(ev);
}
}
@@ -170,7 +165,6 @@
return false;
}
}
- mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
- return mSysUiProxy != null;
+ return SystemUiProxy.INSTANCE.get(mLauncher).isActive();
}
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
deleted file mode 100644
index 110cc23..0000000
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ /dev/null
@@ -1,157 +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;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.os.Build;
-import android.os.Handler;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.util.ShelfPeekAnim;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.function.BiPredicate;
-import java.util.function.Consumer;
-
-/**
- * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public interface ActivityControlHelper<T extends BaseDraggingActivity> {
-
- void onTransitionCancelled(T activity, boolean activityVisible);
-
- int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
-
- void onSwipeUpToRecentsComplete(T activity);
-
- default void onSwipeUpToHomeComplete(T activity) { }
- void onAssistantVisibilityChanged(float visibility);
-
- @NonNull HomeAnimationFactory prepareHomeUI(T activity);
-
- AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
- boolean animateActivity, Consumer<AnimatorPlaybackController> callback);
-
- ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
-
- @Nullable
- T getCreatedActivity();
-
- default boolean isResumed() {
- BaseDraggingActivity activity = getCreatedActivity();
- return activity != null && activity.hasBeenResumed();
- }
-
- @UiThread
- @Nullable
- <T extends View> T getVisibleRecentsView();
-
- @UiThread
- boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
-
- Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
-
- boolean shouldMinimizeSplitScreen();
-
- default boolean deferStartingActivity(Region activeNavBarRegion, MotionEvent ev) {
- return true;
- }
-
- /**
- * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
- */
- int getContainerType();
-
- boolean isInLiveTileMode();
-
- void onLaunchTaskFailed(T activity);
-
- void onLaunchTaskSuccess(T activity);
-
- interface ActivityInitListener {
-
- void register();
-
- void unregister();
-
- void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
- Context context, Handler handler, long duration);
- }
-
- interface AnimationFactory {
-
- default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
-
- void createActivityController(long transitionLength);
-
- default void adjustActivityControllerInterpolators() { }
-
- default void onTransitionCancelled() { }
-
- default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
- Interpolator interpolator, long duration) { }
-
- /**
- * @param attached Whether to show RecentsView alongside the app window. If false, recents
- * will be hidden by some property we can animate, e.g. alpha.
- * @param animate Whether to animate recents to/from its new attached state.
- */
- default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
- }
-
- interface HomeAnimationFactory {
-
- /** Return the floating view that will animate in sync with the closing window. */
- default @Nullable View getFloatingView() {
- return null;
- }
-
- @NonNull RectF getWindowTargetRect();
-
- @NonNull AnimatorPlaybackController createActivityAnimationToHome();
-
- default void playAtomicAnimation(float velocity) {
- // No-op
- }
-
- static RectF getDefaultWindowTargetRect(DeviceProfile dp) {
- final int halfIconSize = dp.iconSizePx / 2;
- final float targetCenterX = dp.availableWidthPx / 2f;
- final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
- // Fallback to animate to center of screen.
- return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
- targetCenterX + halfIconSize, targetCenterY + halfIconSize);
- }
-
- }
-}
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index 84dfdbd..c3b90e3 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -25,17 +25,18 @@
*/
public class AnimatedFloat {
- public static FloatProperty<AnimatedFloat> VALUE = new FloatProperty<AnimatedFloat>("value") {
- @Override
- public void setValue(AnimatedFloat obj, float v) {
- obj.updateValue(v);
- }
+ public static final FloatProperty<AnimatedFloat> VALUE =
+ new FloatProperty<AnimatedFloat>("value") {
+ @Override
+ public void setValue(AnimatedFloat obj, float v) {
+ obj.updateValue(v);
+ }
- @Override
- public Float get(AnimatedFloat obj) {
- return obj.value;
- }
- };
+ @Override
+ public Float get(AnimatedFloat obj) {
+ return obj.value;
+ }
+ };
private final Runnable mUpdateCallback;
private ObjectAnimator mValueAnimator;
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
new file mode 100644
index 0000000..be0bdd8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -0,0 +1,182 @@
+/*
+ * 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;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.Pair;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public interface BaseActivityInterface<T extends BaseDraggingActivity> {
+
+ void onTransitionCancelled(boolean activityVisible);
+
+ int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
+
+ /**
+ * @return The progress of the swipe where we start resisting the user, where 0 is fullscreen
+ * and 1 is recents. These values should probably be greater than 1 to let the user swipe past
+ * recents before we start resisting them.
+ */
+ default Pair<Float, Float> getSwipeUpPullbackStartAndMaxProgress() {
+ return new Pair<>(1.4f, 1.8f);
+ }
+
+ void onSwipeUpToRecentsComplete();
+
+ default void onSwipeUpToHomeComplete() { }
+ void onAssistantVisibilityChanged(float visibility);
+
+ @NonNull HomeAnimationFactory prepareHomeUI();
+
+ AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity,
+ Consumer<AnimatorPlaybackController> callback);
+
+ ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener);
+
+ /**
+ * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
+ */
+ default void setOnDeferredActivityLaunchCallback(Runnable r) {}
+
+ @Nullable
+ T getCreatedActivity();
+
+ default @Nullable BackgroundBlurController getBackgroundBlurController() {
+ return null;
+ }
+
+ default boolean isResumed() {
+ BaseDraggingActivity activity = getCreatedActivity();
+ return activity != null && activity.hasBeenResumed();
+ }
+
+ @UiThread
+ @Nullable
+ <T extends View> T getVisibleRecentsView();
+
+ @UiThread
+ boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
+
+ Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
+
+ boolean shouldMinimizeSplitScreen();
+
+ default boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ return true;
+ }
+
+ /**
+ * Updates the prediction state to the overview state.
+ */
+ default void updateOverviewPredictionState() {
+ // By default overview predictions are not supported
+ }
+
+ /**
+ * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
+ */
+ int getContainerType();
+
+ boolean isInLiveTileMode();
+
+ void onLaunchTaskFailed();
+
+ void onLaunchTaskSuccess();
+
+ default void closeOverlay() { }
+
+ default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
+ Runnable runnable) {}
+
+ interface AnimationFactory {
+
+ default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
+
+ void createActivityInterface(long transitionLength);
+
+ default void adjustActivityControllerInterpolators() { }
+
+ default void onTransitionCancelled() { }
+
+ default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
+ Interpolator interpolator, long duration) { }
+
+ /**
+ * @param attached Whether to show RecentsView alongside the app window. If false, recents
+ * will be hidden by some property we can animate, e.g. alpha.
+ * @param animate Whether to animate recents to/from its new attached state.
+ */
+ default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
+ }
+
+ interface HomeAnimationFactory {
+
+ /** Return the floating view that will animate in sync with the closing window. */
+ default @Nullable View getFloatingView() {
+ return null;
+ }
+
+ @NonNull RectF getWindowTargetRect();
+
+ @NonNull AnimatorPlaybackController createActivityAnimationToHome();
+
+ default void playAtomicAnimation(float velocity) {
+ // No-op
+ }
+
+ static RectF getDefaultWindowTargetRect(PagedOrientationHandler orientationHandler,
+ DeviceProfile dp) {
+ final int halfIconSize = dp.iconSizePx / 2;
+ float primaryDimension = orientationHandler
+ .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
+ float secondaryDimension = orientationHandler
+ .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
+ final float targetX = primaryDimension / 2f;
+ final float targetY = secondaryDimension - dp.hotseatBarSizePx;
+ // Fallback to animate to center of screen.
+ return new RectF(targetX - halfIconSize, targetY - halfIconSize,
+ targetX + halfIconSize, targetY + halfIconSize);
+ }
+
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
index 1ac7ed4..1b9158b 100644
--- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
@@ -27,7 +27,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
@@ -43,6 +43,8 @@
*/
public abstract class BaseRecentsActivity extends BaseDraggingActivity {
+ public static final ActivityTracker<BaseRecentsActivity> ACTIVITY_TRACKER =
+ new ActivityTracker<>();
private Configuration mOldConfig;
@Override
@@ -55,7 +57,7 @@
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
- RecentsActivityTracker.onRecentsActivityCreate(this);
+ ACTIVITY_TRACKER.handleCreate(this);
}
/**
@@ -120,25 +122,29 @@
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
- UiFactory.onEnterAnimationComplete(this);
+ // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+ // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+ // overview
+ RecentsModel.INSTANCE.get(this).getThumbnailCache()
+ .getHighResLoadingState().setVisible(true);
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
- UiFactory.onTrimMemory(this, level);
+ RecentsModel.INSTANCE.get(this).onTrimMemory(level);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
- RecentsActivityTracker.onRecentsActivityNewIntent(this);
+ ACTIVITY_TRACKER.handleNewIntent(this, intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
- RecentsActivityTracker.onRecentsActivityDestroy(this);
+ ACTIVITY_TRACKER.onActivityDestroyed(this);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
new file mode 100644
index 0000000..501c6f0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+
+import android.app.ActivityManager;
+import android.content.Intent;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Manages the state for an active system gesture, listens for events from the system and Launcher,
+ * and fires events when the states change.
+ */
+public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
+
+ /**
+ * Defines the end targets of a gesture and the associated state.
+ */
+ public enum GestureEndTarget {
+ HOME(true, ContainerType.WORKSPACE, false),
+
+ RECENTS(true, ContainerType.TASKSWITCHER, true),
+
+ NEW_TASK(false, ContainerType.APP, true),
+
+ LAST_TASK(false, ContainerType.APP, false);
+
+ GestureEndTarget(boolean isLauncher, int containerType,
+ boolean recentsAttachedToAppWindow) {
+ this.isLauncher = isLauncher;
+ this.containerType = containerType;
+ this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
+ }
+
+ /** Whether the target is in the launcher activity. Implicitly, if the end target is going
+ to Launcher, then we can not interrupt the animation to start another gesture. */
+ public final boolean isLauncher;
+ /** Used to log where the user ended up after the gesture ends */
+ public final int containerType;
+ /** Whether RecentsView should be attached to the window as we animate to this target */
+ public final boolean recentsAttachedToAppWindow;
+ }
+
+ private static final String TAG = "GestureState";
+
+ private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+ private static int FLAG_COUNT = 0;
+ private static int getFlagForIndex(String name) {
+ if (DEBUG_STATES) {
+ STATE_NAMES.add(name);
+ }
+ int index = 1 << FLAG_COUNT;
+ FLAG_COUNT++;
+ return index;
+ }
+
+ // Called when the end target as been set
+ public static final int STATE_END_TARGET_SET =
+ getFlagForIndex("STATE_END_TARGET_SET");
+
+ // Called when the end target animation has finished
+ public static final int STATE_END_TARGET_ANIMATION_FINISHED =
+ getFlagForIndex("STATE_END_TARGET_ANIMATION_FINISHED");
+
+ // Called when the recents animation has been requested to start
+ public static final int STATE_RECENTS_ANIMATION_INITIALIZED =
+ getFlagForIndex("STATE_RECENTS_ANIMATION_INITIALIZED");
+
+ // Called when the recents animation is started and the TaskAnimationManager has been updated
+ // with the controller and targets
+ public static final int STATE_RECENTS_ANIMATION_STARTED =
+ getFlagForIndex("STATE_RECENTS_ANIMATION_STARTED");
+
+ // Called when the recents animation is canceled
+ public static final int STATE_RECENTS_ANIMATION_CANCELED =
+ getFlagForIndex("STATE_RECENTS_ANIMATION_CANCELED");
+
+ // Called when the recents animation finishes
+ public static final int STATE_RECENTS_ANIMATION_FINISHED =
+ getFlagForIndex("STATE_RECENTS_ANIMATION_FINISHED");
+
+ // Always called when the recents animation ends (regardless of cancel or finish)
+ public static final int STATE_RECENTS_ANIMATION_ENDED =
+ getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
+
+
+ // Needed to interact with the current activity
+ private final Intent mHomeIntent;
+ private final Intent mOverviewIntent;
+ private final BaseActivityInterface mActivityInterface;
+ private final MultiStateCallback mStateCallback;
+ private final int mGestureId;
+
+ private ActivityManager.RunningTaskInfo mRunningTask;
+ private GestureEndTarget mEndTarget;
+ // TODO: This can be removed once we stop finishing the animation when starting a new task
+ private int mFinishingRecentsAnimationTaskId = -1;
+
+ public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
+ mHomeIntent = componentObserver.getHomeIntent();
+ mOverviewIntent = componentObserver.getOverviewIntent();
+ mActivityInterface = componentObserver.getActivityInterface();
+ mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+ mGestureId = gestureId;
+ }
+
+ public GestureState() {
+ // Do nothing, only used for initializing the gesture state prior to user unlock
+ mHomeIntent = new Intent();
+ mOverviewIntent = new Intent();
+ mActivityInterface = null;
+ mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+ mGestureId = -1;
+ }
+
+ /**
+ * @return whether the gesture state has the provided {@param stateMask} flags set.
+ */
+ public boolean hasState(int stateMask) {
+ return mStateCallback.hasStates(stateMask);
+ }
+
+ /**
+ * Sets the given {@param stateFlag}s.
+ */
+ public void setState(int stateFlag) {
+ mStateCallback.setState(stateFlag);
+ }
+
+ /**
+ * Adds a callback for when the states matching the given {@param stateMask} is set.
+ */
+ public void runOnceAtState(int stateMask, Runnable callback) {
+ mStateCallback.runOnceAtState(stateMask, callback);
+ }
+
+ /**
+ * @return the intent for the Home component.
+ */
+ public Intent getHomeIntent() {
+ return mHomeIntent;
+ }
+
+ /**
+ * @return the intent for the Overview component.
+ */
+ public Intent getOverviewIntent() {
+ return mOverviewIntent;
+ }
+
+ /**
+ * @return the interface to the activity handing the UI updates for this gesture.
+ */
+ public <T extends BaseDraggingActivity> BaseActivityInterface<T> getActivityInterface() {
+ return mActivityInterface;
+ }
+
+ /**
+ * @return the id for this particular gesture.
+ */
+ public int getGestureId() {
+ return mGestureId;
+ }
+
+ /**
+ * @return the running task for this gesture.
+ */
+ public ActivityManager.RunningTaskInfo getRunningTask() {
+ return mRunningTask;
+ }
+
+ /**
+ * @return the running task id for this gesture.
+ */
+ public int getRunningTaskId() {
+ return mRunningTask != null ? mRunningTask.taskId : -1;
+ }
+
+ /**
+ * Updates the running task for the gesture to be the given {@param runningTask}.
+ */
+ public void updateRunningTask(ActivityManager.RunningTaskInfo runningTask) {
+ mRunningTask = runningTask;
+ }
+
+ /**
+ * @return the end target for this gesture (if known).
+ */
+ public GestureEndTarget getEndTarget() {
+ return mEndTarget;
+ }
+
+ /**
+ * Sets the end target of this gesture and immediately notifies the state changes.
+ */
+ public void setEndTarget(GestureEndTarget target) {
+ setEndTarget(target, true /* isAtomic */);
+ }
+
+ /**
+ * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the
+ * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves.
+ */
+ public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
+ mEndTarget = target;
+ mStateCallback.setState(STATE_END_TARGET_SET);
+ if (isAtomic) {
+ mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+ }
+ }
+
+ /**
+ * @return the id for the task that was about to be launched following the finish of the recents
+ * animation. Only defined between when the finish-recents call was made and the launch
+ * activity call is made.
+ */
+ public int getFinishingRecentsAnimationTaskId() {
+ return mFinishingRecentsAnimationTaskId;
+ }
+
+ /**
+ * Sets the id for the task will be launched after the recents animation is finished. Once the
+ * animation has finished then the id will be reset to -1.
+ */
+ public void setFinishingRecentsAnimationTaskId(int taskId) {
+ mFinishingRecentsAnimationTaskId = taskId;
+ mStateCallback.runOnceAtState(STATE_RECENTS_ANIMATION_FINISHED, () -> {
+ mFinishingRecentsAnimationTaskId = -1;
+ });
+ }
+
+ /**
+ * @return whether the current gesture is still running a recents animation to a state in the
+ * Launcher or Recents activity.
+ * Updates the running task for the gesture to be the given {@param runningTask}.
+ */
+ public boolean isRunningAnimationToLauncher() {
+ return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher;
+ }
+
+ /**
+ * @return whether the recents animation is started but not yet ended
+ */
+ public boolean isRecentsAnimationRunning() {
+ return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_INITIALIZED) &&
+ !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED);
+ }
+
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED);
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED);
+ mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED);
+ mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("GestureState:");
+ pw.println(" gestureID=" + mGestureId);
+ pw.println(" runningTask=" + mRunningTask);
+ pw.println(" endTarget=" + mEndTarget);
+ pw.println(" finishingRecentsAnimationTaskId=" + mFinishingRecentsAnimationTaskId);
+ pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
similarity index 91%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
rename to quickstep/src/com/android/quickstep/InputConsumer.java
index a1e5d47..3e84e7d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep.inputconsumers;
+package com.android.quickstep;
import android.annotation.TargetApi;
import android.os.Build;
@@ -33,6 +33,7 @@
int TYPE_SCREEN_PINNED = 1 << 6;
int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
int TYPE_RESET_GESTURE = 1 << 8;
+ int TYPE_OVERSCROLL = 1 << 9;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -44,16 +45,13 @@
"TYPE_SCREEN_PINNED", // 6
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
"TYPE_RESET_GESTURE", // 8
+ "TYPE_OVERSCROLL", // 9
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
int getType();
- default boolean useSharedSwipeState() {
- return false;
- }
-
/**
* Returns true if the user has crossed the threshold for it to be an explicit action.
*/
@@ -63,6 +61,8 @@
/**
* Called by the event queue when the consumer is about to be switched to a new consumer.
+ * Consumers should update the state accordingly here before the state is passed to the new
+ * consumer.
*/
default void onConsumerAboutToBeSwitched() { }
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
new file mode 100644
index 0000000..b3875ae
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.os.Looper;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to help manage multiple callbacks based on different states.
+ */
+public class MultiStateCallback {
+
+ private static final String TAG = "MultiStateCallback";
+ public static final boolean DEBUG_STATES = false;
+
+ private final SparseArray<LinkedList<Runnable>> mCallbacks = new SparseArray<>();
+ private final SparseArray<ArrayList<Consumer<Boolean>>> mStateChangeListeners =
+ new SparseArray<>();
+
+ private final String[] mStateNames;
+
+ private int mState = 0;
+
+ public MultiStateCallback(String[] stateNames) {
+ mStateNames = DEBUG_STATES ? stateNames : null;
+ }
+
+ /**
+ * Adds the provided state flags to the global state on the UI thread and executes any callbacks
+ * as a result.
+ */
+ public void setStateOnUiThread(int stateFlag) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ setState(stateFlag);
+ } else {
+ postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> setState(stateFlag));
+ }
+ }
+
+ /**
+ * Adds the provided state flags to the global state and executes any callbacks as a result.
+ */
+ public void setState(int stateFlag) {
+ if (DEBUG_STATES) {
+ Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
+ + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
+ }
+
+ final int oldState = mState;
+ mState = mState | stateFlag;
+
+ int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ int state = mCallbacks.keyAt(i);
+
+ if ((mState & state) == state) {
+ LinkedList<Runnable> callbacks = mCallbacks.valueAt(i);
+ while (!callbacks.isEmpty()) {
+ callbacks.pollFirst().run();
+ }
+ }
+ }
+ notifyStateChangeListeners(oldState);
+ }
+
+ /**
+ * Adds the provided state flags to the global state and executes any change handlers
+ * as a result.
+ */
+ public void clearState(int stateFlag) {
+ if (DEBUG_STATES) {
+ Log.d(TAG, "[" + System.identityHashCode(this) + "] Removing "
+ + convertToFlagNames(stateFlag) + " from " + convertToFlagNames(mState));
+ }
+
+ int oldState = mState;
+ mState = mState & ~stateFlag;
+ notifyStateChangeListeners(oldState);
+ }
+
+ private void notifyStateChangeListeners(int oldState) {
+ int count = mStateChangeListeners.size();
+ for (int i = 0; i < count; i++) {
+ int state = mStateChangeListeners.keyAt(i);
+ boolean wasOn = (state & oldState) == state;
+ boolean isOn = (state & mState) == state;
+
+ if (wasOn != isOn) {
+ ArrayList<Consumer<Boolean>> listeners = mStateChangeListeners.valueAt(i);
+ for (Consumer<Boolean> listener : listeners) {
+ listener.accept(isOn);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets a callback to be run when the provided states in the given {@param stateMask} is
+ * enabled. The callback is only run *once*, and if the states are already set at the time of
+ * this call then the callback will be made immediately.
+ */
+ public void runOnceAtState(int stateMask, Runnable callback) {
+ if ((mState & stateMask) == stateMask) {
+ callback.run();
+ } else {
+ final LinkedList<Runnable> callbacks;
+ if (mCallbacks.indexOfKey(stateMask) >= 0) {
+ callbacks = mCallbacks.get(stateMask);
+ if (FeatureFlags.IS_STUDIO_BUILD && callbacks.contains(callback)) {
+ throw new IllegalStateException("Existing callback for state found");
+ }
+ } else {
+ callbacks = new LinkedList<>();
+ mCallbacks.put(stateMask, callbacks);
+ }
+ callbacks.add(callback);
+ }
+ }
+
+ /**
+ * Adds a persistent listener to be called states in the given {@param stateMask} are enabled
+ * or disabled.
+ */
+ public void addChangeListener(int stateMask, Consumer<Boolean> listener) {
+ final ArrayList<Consumer<Boolean>> listeners;
+ if (mStateChangeListeners.indexOfKey(stateMask) >= 0) {
+ listeners = mStateChangeListeners.get(stateMask);
+ } else {
+ listeners = new ArrayList<>();
+ mStateChangeListeners.put(stateMask, listeners);
+ }
+ listeners.add(listener);
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ public boolean hasStates(int stateMask) {
+ return (mState & stateMask) == stateMask;
+ }
+
+ private String convertToFlagNames(int flags) {
+ StringJoiner joiner = new StringJoiner(", ", "[", " (" + flags + ")]");
+ for (int i = 0; i < mStateNames.length; i++) {
+ if ((flags & (1 << i)) != 0) {
+ joiner.add(mStateNames[i]);
+ }
+ }
+ return joiner.toString();
+ }
+
+}
diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
deleted file mode 100644
index bd6204a..0000000
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ /dev/null
@@ -1,99 +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;
-
-import android.annotation.TargetApi;
-import android.app.ActivityManager.TaskDescription;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.UserHandle;
-import android.util.LruCache;
-import android.util.SparseArray;
-
-import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.graphics.DrawableFactory;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.systemui.shared.recents.model.IconLoader;
-import com.android.systemui.shared.recents.model.TaskKeyLruCache;
-
-/**
- * Extension of {@link IconLoader} with icon normalization support
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class NormalizedIconLoader extends IconLoader {
-
- private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
- private final DrawableFactory mDrawableFactory;
- private final boolean mDisableColorExtraction;
-
- public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
- LruCache<ComponentName, ActivityInfo> activityInfoCache,
- boolean disableColorExtraction) {
- super(context, iconCache, activityInfoCache);
- mDrawableFactory = DrawableFactory.INSTANCE.get(context);
- mDisableColorExtraction = disableColorExtraction;
- }
-
- @Override
- public Drawable getDefaultIcon(int userId) {
- synchronized (mDefaultIcons) {
- BitmapInfo info = mDefaultIcons.get(userId);
- if (info == null) {
- info = getBitmapInfo(Resources.getSystem()
- .getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false);
- mDefaultIcons.put(userId, info);
- }
-
- return new FastBitmapDrawable(info);
- }
- }
-
- @Override
- protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
- return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
- false));
- }
-
- private BitmapInfo getBitmapInfo(Drawable drawable, int userId,
- int primaryColor, boolean isInstantApp) {
- try (LauncherIcons la = LauncherIcons.obtain(mContext)) {
- if (mDisableColorExtraction) {
- la.disableColorExtraction();
- }
- la.setWrapperBackgroundColor(primaryColor);
-
- // User version code O, so that the icon is always wrapped in an adaptive icon container
- return la.createBadgedIconBitmap(drawable, UserHandle.of(userId),
- Build.VERSION_CODES.O, isInstantApp);
- }
- }
-
- @Override
- protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId,
- TaskDescription desc) {
- BitmapInfo bitmapInfo = getBitmapInfo(
- activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
- userId,
- desc.getPrimaryColor(),
- activityInfo.applicationInfo.isInstantApp());
- return mDrawableFactory.newIcon(mContext, bitmapInfo, activityInfo);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
new file mode 100644
index 0000000..3e73f49
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.util.DefaultDisplay;
+
+import java.io.PrintWriter;
+
+/**
+ * Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
+ * See {@link OrientationRectF#applyTransform(MotionEvent, boolean)} for transformation of
+ * MotionEvents from one orientation's coordinate space to another's.
+ *
+ * This class only supports single touch/pointer gesture tracking for touches started in a supported
+ * nav bar region.
+ */
+class OrientationTouchTransformer {
+
+ private static final String TAG = "OrientationTouchTransformer";
+ private static final boolean DEBUG = false;
+ private static final int MAX_ORIENTATIONS = 4;
+
+ private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
+ private final RectF mAssistantLeftRegion = new RectF();
+ private final RectF mAssistantRightRegion = new RectF();
+ private int mCurrentRotation;
+ private boolean mEnableMultipleRegions;
+ private Resources mResources;
+ private OrientationRectF mLastRectTouched;
+ private SysUINavigationMode.Mode mMode;
+ private QuickStepContractInfo mContractInfo;
+ private int mQuickStepStartingRotation = -1;
+
+ /** For testability */
+ interface QuickStepContractInfo {
+ float getWindowCornerRadius();
+ }
+
+ OrientationTouchTransformer(Resources resources, SysUINavigationMode.Mode mode,
+ QuickStepContractInfo contractInfo) {
+ mResources = resources;
+ mMode = mode;
+ mContractInfo = contractInfo;
+ }
+
+ void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+ if (mMode == newMode) {
+ return;
+ }
+ this.mMode = newMode;
+ resetSwipeRegions(info);
+ }
+
+ /**
+ * Sets the current nav bar region to listen to events for as determined by
+ * {@param info}. If multiple nav bar regions are enabled, then this region will be added
+ * alongside other regions.
+ * Ok to call multiple times
+ *
+ * @see #enableMultipleRegions(boolean, DefaultDisplay.Info)
+ */
+ void createOrAddTouchRegion(DefaultDisplay.Info info) {
+ mCurrentRotation = info.rotation;
+ if (mQuickStepStartingRotation > -1 && mCurrentRotation == mQuickStepStartingRotation) {
+ // Ignore nav bars in other rotations except for the one we started out in
+ resetSwipeRegions(info);
+ return;
+ }
+
+ OrientationRectF region = mSwipeTouchRegions.get(mCurrentRotation);
+ if (region != null) {
+ return;
+ }
+
+ if (mEnableMultipleRegions) {
+ mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(info));
+ } else {
+ resetSwipeRegions(info);
+ }
+ }
+
+ /**
+ * Call when we want to start tracking nav bar touch regions in multiple orientations.
+ * ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done.
+ *
+ * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
+ * @param info The current displayInfo
+ */
+ void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
+ mEnableMultipleRegions = enableMultipleRegions;
+ if (!enableMultipleRegions) {
+ mQuickStepStartingRotation = -1;
+ resetSwipeRegions(info);
+ } else {
+ if (mLastRectTouched != null) {
+ // mLastRectTouched can be null if gesture type is changed (ex. from settings)
+ // but nav bar hasn't been interacted with yet.
+ mQuickStepStartingRotation = mLastRectTouched.mRotation;
+ }
+ }
+ }
+
+ /**
+ * Only saves the swipe region represented by {@param region}, clears the
+ * rest from {@link #mSwipeTouchRegions}
+ * To be called whenever we want to stop tracking more than one swipe region.
+ * Ok to call multiple times.
+ */
+ private void resetSwipeRegions(DefaultDisplay.Info region) {
+ if (DEBUG) {
+ Log.d(TAG, "clearing all regions except rotation: " + mCurrentRotation);
+ }
+
+ mCurrentRotation = region.rotation;
+ mSwipeTouchRegions.clear();
+ mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(region));
+ }
+
+ private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
+ if (DEBUG) {
+ Log.d(TAG, "creating rotation region for: " + mCurrentRotation);
+ }
+
+ Point size = display.realSize;
+ int rotation = display.rotation;
+ OrientationRectF orientationRectF =
+ new OrientationRectF(0, 0, size.x, size.y, rotation);
+ if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
+ int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+ orientationRectF.top = orientationRectF.bottom - touchHeight;
+
+ final int assistantWidth = mResources
+ .getDimensionPixelSize(R.dimen.gestures_assistant_width);
+ final float assistantHeight = Math.max(touchHeight,
+ mContractInfo.getWindowCornerRadius());
+ mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
+ mAssistantLeftRegion.top = mAssistantRightRegion.top =
+ orientationRectF.bottom - assistantHeight;
+
+ mAssistantLeftRegion.left = 0;
+ mAssistantLeftRegion.right = assistantWidth;
+
+ mAssistantRightRegion.right = orientationRectF.right;
+ mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
+ } else {
+ mAssistantLeftRegion.setEmpty();
+ mAssistantRightRegion.setEmpty();
+ switch (rotation) {
+ case Surface.ROTATION_90:
+ orientationRectF.left = orientationRectF.right
+ - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+ break;
+ case Surface.ROTATION_270:
+ orientationRectF.right = orientationRectF.left
+ + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+ break;
+ default:
+ orientationRectF.top = orientationRectF.bottom
+ - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+ }
+ }
+
+ return orientationRectF;
+ }
+
+ boolean touchInAssistantRegion(MotionEvent ev) {
+ return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
+ || mAssistantRightRegion.contains(ev.getX(), ev.getY());
+
+ }
+
+ private int getNavbarSize(String resName) {
+ return ResourceUtils.getNavbarSize(resName, mResources);
+ }
+
+ boolean touchInValidSwipeRegions(float x, float y) {
+ if (mLastRectTouched != null) {
+ return mLastRectTouched.contains(x, y);
+ }
+ return false;
+ }
+
+ int getCurrentActiveRotation() {
+ if (mLastRectTouched == null) {
+ return 0;
+ } else {
+ return mLastRectTouched.mRotation;
+ }
+ }
+
+ public void transform(MotionEvent event) {
+ int eventAction = event.getActionMasked();
+ switch (eventAction) {
+ case ACTION_MOVE: {
+ if (mLastRectTouched == null) {
+ return;
+ }
+ mLastRectTouched.applyTransform(event, true);
+ break;
+ }
+ case ACTION_CANCEL:
+ case ACTION_UP: {
+ if (mLastRectTouched == null) {
+ return;
+ }
+ mLastRectTouched.applyTransform(event, true);
+ mLastRectTouched = null;
+ break;
+ }
+ case ACTION_POINTER_DOWN:
+ case ACTION_DOWN: {
+ if (mLastRectTouched != null) {
+ return;
+ }
+
+ for (int i = 0; i < MAX_ORIENTATIONS; i++) {
+ OrientationRectF rect = mSwipeTouchRegions.get(i);
+ if (rect == null) {
+ continue;
+ }
+ if (rect.applyTransform(event, false)) {
+ mLastRectTouched = rect;
+ if (DEBUG) {
+ Log.d(TAG, "set active region: " + rect);
+ }
+ return;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("OrientationTouchTransformerState: ");
+ pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
+ pw.println(" lastTouchedRegion=" + mLastRectTouched);
+ pw.println(" multipleRegionsEnabled=" + mEnableMultipleRegions);
+ StringBuilder regions = new StringBuilder(" currentTouchableRotations=");
+ for(int i = 0; i < mSwipeTouchRegions.size(); i++) {
+ OrientationRectF rectF = mSwipeTouchRegions.get(mSwipeTouchRegions.keyAt(i));
+ regions.append(rectF.mRotation).append(" ");
+ }
+ pw.println(regions.toString());
+ }
+
+ private class OrientationRectF extends RectF {
+
+ /**
+ * Delta to subtract width and height by because if we report the translated touch
+ * bounds as the width and height, calling {@link RectF#contains(float, float)} will
+ * be false
+ */
+ private float maxDelta = 0.001f;
+
+ private int mRotation;
+ private float mHeight;
+ private float mWidth;
+
+ OrientationRectF(float left, float top, float right, float bottom, int rotation) {
+ super(left, top, right, bottom);
+ this.mRotation = rotation;
+ mHeight = bottom - maxDelta;
+ mWidth = right - maxDelta;
+ }
+
+ @Override
+ public String toString() {
+ String s = super.toString();
+ s += " rotation: " + mRotation;
+ return s;
+ }
+
+ boolean applyTransform(MotionEvent event, boolean forceTransform) {
+ // TODO(b/149658423): See if we can use RotationHelper.getRotationMatrix here
+ MotionEvent tmp = MotionEvent.obtain(event);
+ Matrix outMatrix = new Matrix();
+ int delta = RotationHelper.deltaRotation(mCurrentRotation, mRotation);
+ switch (delta) {
+ case Surface.ROTATION_0:
+ outMatrix.reset();
+ break;
+ case Surface.ROTATION_90:
+ outMatrix.setRotate(270);
+ outMatrix.postTranslate(0, mHeight);
+ break;
+ case Surface.ROTATION_180:
+ outMatrix.setRotate(180);
+ outMatrix.postTranslate(mHeight, mWidth);
+ break;
+ case Surface.ROTATION_270:
+ outMatrix.setRotate(90);
+ outMatrix.postTranslate(mWidth, 0);
+ break;
+ }
+
+ tmp.transform(outMatrix);
+ if (DEBUG) {
+ Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
+ + " new: " + tmp.getX() + ", " + tmp.getY()
+ + " rect: " + this + " forceTransform: " + forceTransform
+ + " contains: " + contains(tmp.getX(), tmp.getY()));
+ }
+
+ if (forceTransform || contains(tmp.getX(), tmp.getY())) {
+ event.transform(outMatrix);
+ tmp.recycle();
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewCallbacks.java b/quickstep/src/com/android/quickstep/OverviewCallbacks.java
deleted file mode 100644
index f5573ba..0000000
--- a/quickstep/src/com/android/quickstep/OverviewCallbacks.java
+++ /dev/null
@@ -1,43 +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;
-
-import android.content.Context;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Callbacks related to overview/quicksteps.
- */
-public class OverviewCallbacks implements ResourceBasedOverride {
-
- private static OverviewCallbacks sInstance;
-
- public static OverviewCallbacks get(Context context) {
- Preconditions.assertUIThread();
- if (sInstance == null) {
- sInstance = Overrides.getObject(OverviewCallbacks.class,
- context.getApplicationContext(), R.string.overview_callbacks_class);
- }
- return sInstance;
- }
-
- public void onInitOverviewTransition() { }
-
- public void closeAllWindows() { }
-}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 93a6127..9edc86e 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -23,7 +23,6 @@
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -35,8 +34,10 @@
import android.content.pm.ResolveInfo;
import android.util.SparseIntArray;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.systemui.shared.system.PackageManagerWrapper;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
@@ -45,32 +46,27 @@
* and provide callers the relevant classes.
*/
public final class OverviewComponentObserver {
- private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateOverviewTargets();
- }
- };
- private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateOverviewTargets();
- }
- };
+ private final BroadcastReceiver mUserPreferenceChangeReceiver =
+ new SimpleBroadcastReceiver(this::updateOverviewTargets);
+ private final BroadcastReceiver mOtherHomeAppUpdateReceiver =
+ new SimpleBroadcastReceiver(this::updateOverviewTargets);
+
private final Context mContext;
+ private final RecentsAnimationDeviceState mDeviceState;
private final Intent mCurrentHomeIntent;
private final Intent mMyHomeIntent;
private final Intent mFallbackIntent;
private final SparseIntArray mConfigChangesMap = new SparseIntArray();
private String mUpdateRegisteredPackage;
- private ActivityControlHelper mActivityControlHelper;
+ private BaseActivityInterface mActivityInterface;
private Intent mOverviewIntent;
- private int mSystemUiStateFlags;
private boolean mIsHomeAndOverviewSame;
private boolean mIsDefaultHome;
+ private boolean mIsHomeDisabled;
- public OverviewComponentObserver(Context context) {
+ public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
mContext = context;
+ mDeviceState = deviceState;
mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
@@ -99,53 +95,51 @@
updateOverviewTargets();
}
- public void onSystemUiStateChanged(int stateFlags) {
- boolean homeDisabledChanged = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED)
- != (stateFlags & SYSUI_STATE_HOME_DISABLED);
- mSystemUiStateFlags = stateFlags;
- if (homeDisabledChanged) {
+ public void onSystemUiStateChanged() {
+ if (mDeviceState.isHomeDisabled() != mIsHomeDisabled) {
updateOverviewTargets();
}
}
+ private void updateOverviewTargets(Intent unused) {
+ updateOverviewTargets();
+ }
+
public boolean assistantGestureIsConstrained() {
- return (mSystemUiStateFlags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
+ return (mDeviceState.getSystemUiStateFlags() & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
}
/**
- * Update overview intent and {@link ActivityControlHelper} based off the current launcher home
+ * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
* component.
*/
private void updateOverviewTargets() {
ComponentName defaultHome = PackageManagerWrapper.getInstance()
.getHomeActivities(new ArrayList<>());
+ mIsHomeDisabled = mDeviceState.isHomeDisabled();
mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
// Set assistant visibility to 0 from launcher's perspective, ensures any elements that
// launcher made invisible become visible again before the new activity control helper
// becomes active.
- if (mActivityControlHelper != null) {
- mActivityControlHelper.onAssistantVisibilityChanged(0.f);
+ if (mActivityInterface != null) {
+ mActivityInterface.onAssistantVisibilityChanged(0.f);
}
- if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
- && (defaultHome == null || mIsDefaultHome)) {
+ if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
// User default home is same as out home app. Use Overview integrated in Launcher.
- mActivityControlHelper = new LauncherActivityControllerHelper();
+ mActivityInterface = new LauncherActivityInterface();
mIsHomeAndOverviewSame = true;
mOverviewIntent = mMyHomeIntent;
mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
- if (mUpdateRegisteredPackage != null) {
- // Remove any update listener as we don't care about other packages.
- mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
- mUpdateRegisteredPackage = null;
- }
+ // Remove any update listener as we don't care about other packages.
+ unregisterOtherHomeAppUpdateReceiver();
} else {
// The default home app is a different launcher. Use the fallback Overview instead.
- mActivityControlHelper = new FallbackActivityControllerHelper();
+ mActivityInterface = new FallbackActivityInterface();
mIsHomeAndOverviewSame = false;
mOverviewIntent = mFallbackIntent;
mCurrentHomeIntent.setComponent(defaultHome);
@@ -155,13 +149,9 @@
// Listen for package updates of this app (and remove any previously attached
// package listener).
if (defaultHome == null) {
- if (mUpdateRegisteredPackage != null) {
- mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
- }
+ unregisterOtherHomeAppUpdateReceiver();
} else if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
- if (mUpdateRegisteredPackage != null) {
- mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
- }
+ unregisterOtherHomeAppUpdateReceiver();
mUpdateRegisteredPackage = defaultHome.getPackageName();
mContext.registerReceiver(mOtherHomeAppUpdateReceiver, getPackageFilter(
@@ -176,7 +166,10 @@
*/
public void onDestroy() {
mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
+ unregisterOtherHomeAppUpdateReceiver();
+ }
+ private void unregisterOtherHomeAppUpdateReceiver() {
if (mUpdateRegisteredPackage != null) {
mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
mUpdateRegisteredPackage = null;
@@ -237,7 +230,16 @@
*
* @return the current activity control helper
*/
- public ActivityControlHelper getActivityControlHelper() {
- return mActivityControlHelper;
+ public BaseActivityInterface getActivityInterface() {
+ return mActivityInterface;
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("OverviewComponentObserver:");
+ pw.println(" isDefaultHome=" + mIsDefaultHome);
+ pw.println(" isHomeDisabled=" + mIsHomeDisabled);
+ pw.println(" homeAndOverviewSame=" + mIsHomeAndOverviewSame);
+ pw.println(" overviewIntent=" + mOverviewIntent);
+ pw.println(" homeIntent=" + mCurrentHomeIntent);
}
}
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
deleted file mode 100644
index 858c3b6..0000000
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ /dev/null
@@ -1,146 +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;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-
-/**
- * Sets alpha for the back button
- */
-public class OverviewInteractionState {
-
- private static final String TAG = "OverviewFlags";
-
- private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
-
- // We do not need any synchronization for this variable as its only written on UI thread.
- public static final MainThreadInitializedObject<OverviewInteractionState> INSTANCE =
- new MainThreadInitializedObject<>(OverviewInteractionState::new);
-
- private static final int MSG_SET_PROXY = 200;
- private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
-
- private final Context mContext;
- private final Handler mUiHandler;
- private final Handler mBgHandler;
-
- // These are updated on the background thread
- private ISystemUiProxy mISystemUiProxy;
- private float mBackButtonAlpha = 1;
-
- private int mSystemUiStateFlags;
-
- private OverviewInteractionState(Context context) {
- mContext = context;
-
- // Data posted to the uihandler will be sent to the bghandler. Data is sent to uihandler
- // because of its high send frequency and data may be very different than the previous value
- // For example, send back alpha on uihandler to avoid flickering when setting its visibility
- mUiHandler = new Handler(this::handleUiMessage);
- mBgHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleBgMessage);
-
- onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
- .addModeChangeListener(this::onNavigationModeChanged));
- }
-
- public float getBackButtonAlpha() {
- return mBackButtonAlpha;
- }
-
- public void setBackButtonAlpha(float alpha, boolean animate) {
- if (!modeSupportsGestures()) {
- alpha = 1;
- }
- mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA);
- mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_ALPHA, animate ? 1 : 0, 0, alpha)
- .sendToTarget();
- }
-
- public void setSystemUiProxy(ISystemUiProxy proxy) {
- mBgHandler.obtainMessage(MSG_SET_PROXY, proxy).sendToTarget();
- }
-
- public void setSystemUiStateFlags(int stateFlags) {
- mSystemUiStateFlags = stateFlags;
- }
-
- public int getSystemUiStateFlags() {
- return mSystemUiStateFlags;
- }
-
- private boolean handleUiMessage(Message msg) {
- if (msg.what == MSG_SET_BACK_BUTTON_ALPHA) {
- mBackButtonAlpha = (float) msg.obj;
- }
- mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2, msg.obj).sendToTarget();
- return true;
- }
-
- private boolean handleBgMessage(Message msg) {
- switch (msg.what) {
- case MSG_SET_PROXY:
- mISystemUiProxy = (ISystemUiProxy) msg.obj;
- break;
- case MSG_SET_BACK_BUTTON_ALPHA:
- applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
- return true;
- }
- return true;
- }
-
- @WorkerThread
- private void applyBackButtonAlpha(float alpha, boolean animate) {
- if (mISystemUiProxy == null) {
- return;
- }
- try {
- mISystemUiProxy.setBackButtonAlpha(alpha, animate);
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to update overview back button alpha", e);
- }
- }
-
- private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
- resetHomeBounceSeenOnQuickstepEnabledFirstTime();
- }
-
- private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
- if (modeSupportsGestures() && !Utilities.getPrefs(mContext).getBoolean(
- HAS_ENABLED_QUICKSTEP_ONCE, true)) {
- Utilities.getPrefs(mContext).edit()
- .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
- .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
- .apply();
- }
- }
-
- private boolean modeSupportsGestures() {
- return SysUINavigationMode.getMode(mContext).hasGestures;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index befeee0..32268a4 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -49,6 +49,7 @@
super.init(context);
// Elevate GPU priority for Quickstep and Remote animations.
- ThreadedRendererCompat.setContextPriority(ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+ ThreadedRendererCompat.setContextPriority(
+ ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
deleted file mode 100644
index 4d1d9ef..0000000
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ /dev/null
@@ -1,130 +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;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
-
-import java.lang.ref.WeakReference;
-import java.util.function.BiPredicate;
-
-/**
- * Utility class to track create/destroy for some {@link BaseRecentsActivity}.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class RecentsActivityTracker<T extends BaseRecentsActivity> implements ActivityInitListener {
-
- private static WeakReference<BaseRecentsActivity> sCurrentActivity =
- new WeakReference<>(null);
- private static final Scheduler sScheduler = new Scheduler();
-
- private final BiPredicate<T, Boolean> mOnInitListener;
-
- public RecentsActivityTracker(BiPredicate<T, Boolean> onInitListener) {
- mOnInitListener = onInitListener;
- }
-
- @Override
- public void register() {
- sScheduler.schedule(this);
- }
-
- @Override
- public void unregister() {
- sScheduler.clearReference(this);
- }
-
- private boolean init(T activity, boolean visible) {
- return mOnInitListener.test(activity, visible);
- }
-
- public static <T extends BaseRecentsActivity> T getCurrentActivity() {
- return (T) sCurrentActivity.get();
- }
-
- @Override
- public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
- Context context, Handler handler, long duration) {
- register();
-
- Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
- context.startActivity(intent, options);
- }
-
- public static void onRecentsActivityCreate(BaseRecentsActivity activity) {
- sCurrentActivity = new WeakReference<>(activity);
- sScheduler.initIfPending(activity, false);
- }
-
-
- public static void onRecentsActivityNewIntent(BaseRecentsActivity activity) {
- sScheduler.initIfPending(activity, activity.isStarted());
- }
-
- public static void onRecentsActivityDestroy(BaseRecentsActivity activity) {
- if (sCurrentActivity.get() == activity) {
- sCurrentActivity.clear();
- }
- }
-
-
- private static class Scheduler implements Runnable {
-
- private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null);
-
- public synchronized void schedule(RecentsActivityTracker tracker) {
- mPendingTracker = new WeakReference<>(tracker);
- MAIN_EXECUTOR.execute(this);
- }
-
- @Override
- public void run() {
- BaseRecentsActivity activity = sCurrentActivity.get();
- if (activity != null) {
- initIfPending(activity, activity.isStarted());
- }
- }
-
- public synchronized boolean initIfPending(BaseRecentsActivity activity,
- boolean alreadyOnHome) {
- RecentsActivityTracker tracker = mPendingTracker.get();
- if (tracker != null) {
- if (!tracker.init(activity, alreadyOnHome)) {
- mPendingTracker.clear();
- }
- return true;
- }
- return false;
- }
-
- public synchronized boolean clearReference(RecentsActivityTracker tracker) {
- if (mPendingTracker.get() == tracker) {
- mPendingTracker.clear();
- return true;
- }
- return false;
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
new file mode 100644
index 0000000..0f98b32
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.graphics.Rect;
+import android.util.ArraySet;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.Set;
+
+/**
+ * Wrapper around {@link com.android.systemui.shared.system.RecentsAnimationListener} which
+ * delegates callbacks to multiple listeners on the main thread
+ */
+public class RecentsAnimationCallbacks implements
+ com.android.systemui.shared.system.RecentsAnimationListener {
+
+ private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
+ private final boolean mShouldMinimizeSplitScreen;
+
+ // TODO(141886704): Remove these references when they are no longer needed
+ private RecentsAnimationController mController;
+
+ private boolean mCancelled;
+
+ public RecentsAnimationCallbacks(boolean shouldMinimizeSplitScreen) {
+ mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+ }
+
+ @UiThread
+ public void addListener(RecentsAnimationListener listener) {
+ Preconditions.assertUIThread();
+ mListeners.add(listener);
+ }
+
+ @UiThread
+ public void removeListener(RecentsAnimationListener listener) {
+ Preconditions.assertUIThread();
+ mListeners.remove(listener);
+ }
+
+ @UiThread
+ public void removeAllListeners() {
+ Preconditions.assertUIThread();
+ mListeners.clear();
+ }
+
+ public void notifyAnimationCanceled() {
+ mCancelled = true;
+ onAnimationCanceled(null);
+ }
+
+ // Called only in Q platform
+ @BinderThread
+ @Deprecated
+ public final void onAnimationStart(RecentsAnimationControllerCompat controller,
+ RemoteAnimationTargetCompat[] appTargets, Rect homeContentInsets,
+ Rect minimizedHomeBounds) {
+ onAnimationStart(controller, appTargets, new RemoteAnimationTargetCompat[0],
+ homeContentInsets, minimizedHomeBounds);
+ }
+
+ // Called only in R+ platform
+ @BinderThread
+ public final void onAnimationStart(RecentsAnimationControllerCompat animationController,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ Rect homeContentInsets, Rect minimizedHomeBounds) {
+ RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
+ wallpaperTargets, homeContentInsets, minimizedHomeBounds);
+ mController = new RecentsAnimationController(animationController,
+ mShouldMinimizeSplitScreen, this::onAnimationFinished);
+
+ if (mCancelled) {
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
+ mController::finishAnimationToApp);
+ } else {
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+ for (RecentsAnimationListener listener : getListeners()) {
+ listener.onRecentsAnimationStart(mController, targets);
+ }
+ });
+ }
+ }
+
+ @BinderThread
+ @Override
+ public final void onAnimationCanceled(ThumbnailData thumbnailData) {
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+ for (RecentsAnimationListener listener : getListeners()) {
+ listener.onRecentsAnimationCanceled(thumbnailData);
+ }
+ });
+ }
+
+ private final void onAnimationFinished(RecentsAnimationController controller) {
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+ for (RecentsAnimationListener listener : getListeners()) {
+ listener.onRecentsAnimationFinished(controller);
+ }
+ });
+ }
+
+ private RecentsAnimationListener[] getListeners() {
+ return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
+ }
+
+ /**
+ * Listener for the recents animation callbacks.
+ */
+ public interface RecentsAnimationListener {
+ default void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {}
+
+ /**
+ * Callback from the system when the recents animation is canceled. {@param thumbnailData}
+ * is passed back for rendering screenshot to replace live tile.
+ */
+ default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {}
+
+ /**
+ * Callback made whenever the recents animation is finished.
+ */
+ default void onRecentsAnimationFinished(RecentsAnimationController controller) {}
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
new file mode 100644
index 0000000..8dd4aa4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -0,0 +1,250 @@
+/*
+ * 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;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Wrapper around RecentsAnimationControllerCompat to help with some synchronization
+ */
+public class RecentsAnimationController {
+
+ private static final String TAG = "RecentsAnimationController";
+
+ private final RecentsAnimationControllerCompat mController;
+ private final Consumer<RecentsAnimationController> mOnFinishedListener;
+ private final boolean mShouldMinimizeSplitScreen;
+
+ private InputConsumerController mInputConsumerController;
+ private Supplier<InputConsumer> mInputProxySupplier;
+ private InputConsumer mInputConsumer;
+ private boolean mWindowThresholdCrossed = false;
+ private boolean mTouchInProgress;
+ private boolean mFinishPending;
+
+ public RecentsAnimationController(RecentsAnimationControllerCompat controller,
+ boolean shouldMinimizeSplitScreen,
+ Consumer<RecentsAnimationController> onFinishedListener) {
+ mController = controller;
+ mOnFinishedListener = onFinishedListener;
+ mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+ }
+
+ /**
+ * Synchronously takes a screenshot of the task with the given {@param taskId} if the task is
+ * currently being animated.
+ */
+ public ThumbnailData screenshotTask(int taskId) {
+ return mController.screenshotTask(taskId);
+ }
+
+ /**
+ * Indicates that the gesture has crossed the window boundary threshold and system UI can be
+ * update the represent the window behind
+ */
+ public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
+ if (mWindowThresholdCrossed != windowThresholdCrossed) {
+ mWindowThresholdCrossed = windowThresholdCrossed;
+ UI_HELPER_EXECUTOR.execute(() -> {
+ mController.setAnimationTargetsBehindSystemBars(!windowThresholdCrossed);
+ SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
+ if (p != null && mShouldMinimizeSplitScreen) {
+ p.setSplitScreenMinimized(windowThresholdCrossed);
+ }
+ });
+ }
+ }
+
+ /**
+ * Notifies the controller that we want to defer cancel until the next app transition starts.
+ * If {@param screenshot} is set, then we will receive a screenshot on the next
+ * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)} and we must also call
+ * {@link #cleanupScreenshot()} when that screenshot is no longer used.
+ */
+ public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+ mController.setDeferCancelUntilNextTransition(defer, screenshot);
+ }
+
+ /**
+ * Cleans up the screenshot previously returned from
+ * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)}.
+ */
+ public void cleanupScreenshot() {
+ UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
+ }
+
+ @UiThread
+ public void finishAnimationToHome() {
+ finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+ }
+
+ @UiThread
+ public void finishAnimationToApp() {
+ finishAndClear(false /* toRecents */, null, false /* sendUserLeaveHint */);
+ }
+
+ /** See {@link #finish(boolean, Runnable, boolean)} */
+ @UiThread
+ public void finish(boolean toRecents, Runnable onFinishComplete) {
+ finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
+ }
+
+ /**
+ * @param onFinishComplete A callback that runs on the main thread after the animation
+ * controller has finished on the background thread.
+ * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
+ * activity. If userLeaveHint is true, the activity will enter into
+ * picture-in-picture mode upon being paused.
+ */
+ @UiThread
+ public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
+ Preconditions.assertUIThread();
+ if (!toRecents) {
+ finishAndClear(false, onFinishComplete, sendUserLeaveHint);
+ } else {
+ if (mTouchInProgress) {
+ mFinishPending = true;
+ // Execute the callback
+ if (onFinishComplete != null) {
+ onFinishComplete.run();
+ }
+ } else {
+ finishAndClear(true, onFinishComplete, sendUserLeaveHint);
+ }
+ }
+ }
+
+ private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
+ boolean sendUserLeaveHint) {
+ disableInputProxy();
+ finishController(toRecents, onFinishComplete, sendUserLeaveHint);
+ }
+
+ @UiThread
+ public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
+ mOnFinishedListener.accept(this);
+ UI_HELPER_EXECUTOR.execute(() -> {
+ mController.setInputConsumerEnabled(false);
+ mController.finish(toRecents, sendUserLeaveHint);
+ if (callback != null) {
+ MAIN_EXECUTOR.execute(callback);
+ }
+ });
+ }
+
+ /**
+ * Enables the input consumer to start intercepting touches in the app window.
+ */
+ public void enableInputConsumer() {
+ UI_HELPER_EXECUTOR.submit(() -> {
+ mController.hideCurrentInputMethod();
+ mController.setInputConsumerEnabled(true);
+ });
+ }
+
+ public void enableInputProxy(InputConsumerController inputConsumerController,
+ Supplier<InputConsumer> inputProxySupplier) {
+ mInputProxySupplier = inputProxySupplier;
+ mInputConsumerController = inputConsumerController;
+ mInputConsumerController.setInputListener(this::onInputConsumerEvent);
+ }
+
+ /** @return wrapper controller. */
+ public RecentsAnimationControllerCompat getController() {
+ return mController;
+ }
+
+ private void disableInputProxy() {
+ if (mInputConsumer != null && mTouchInProgress) {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent dummyCancel = MotionEvent.obtain(now, now, ACTION_CANCEL, 0, 0, 0);
+ mInputConsumer.onMotionEvent(dummyCancel);
+ dummyCancel.recycle();
+ }
+ if (mInputConsumerController != null) {
+ mInputConsumerController.setInputListener(null);
+ }
+ mInputProxySupplier = null;
+ }
+
+ private boolean onInputConsumerEvent(InputEvent ev) {
+ if (ev instanceof MotionEvent) {
+ onInputConsumerMotionEvent((MotionEvent) ev);
+ } else if (ev instanceof KeyEvent) {
+ if (mInputConsumer == null) {
+ mInputConsumer = mInputProxySupplier.get();
+ }
+ mInputConsumer.onKeyEvent((KeyEvent) ev);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean onInputConsumerMotionEvent(MotionEvent ev) {
+ int action = ev.getAction();
+
+ // Just to be safe, verify that ACTION_DOWN comes before any other action,
+ // and ignore any ACTION_DOWN after the first one (though that should not happen).
+ if (!mTouchInProgress && action != ACTION_DOWN) {
+ Log.w(TAG, "Received non-down motion before down motion: " + action);
+ return false;
+ }
+ if (mTouchInProgress && action == ACTION_DOWN) {
+ Log.w(TAG, "Received down motion while touch was already in progress");
+ return false;
+ }
+
+ if (action == ACTION_DOWN) {
+ mTouchInProgress = true;
+ if (mInputConsumer == null) {
+ mInputConsumer = mInputProxySupplier.get();
+ }
+ } else if (action == ACTION_CANCEL || action == ACTION_UP) {
+ // Finish any pending actions
+ mTouchInProgress = false;
+ if (mFinishPending) {
+ mFinishPending = false;
+ finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+ }
+ }
+ if (mInputConsumer != null) {
+ mInputConsumer.onMotionEvent(ev);
+ }
+
+ return true;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
new file mode 100644
index 0000000..1299a53
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static android.content.Intent.ACTION_USER_UNLOCKED;
+
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Region;
+import android.os.Process;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.annotation.BinderThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Manages the state of the system during a swipe up gesture.
+ */
+public class RecentsAnimationDeviceState implements
+ NavigationModeChangeListener,
+ DefaultDisplay.DisplayInfoChangeListener {
+
+ private final Context mContext;
+ private final SysUINavigationMode mSysUiNavMode;
+ private final DefaultDisplay mDefaultDisplay;
+ private final int mDisplayId;
+ private int mDisplayRotation;
+
+ private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
+
+ private @SystemUiStateFlags int mSystemUiStateFlags;
+ private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
+ private NavBarPosition mNavBarPosition;
+
+ private final Region mDeferredGestureRegion = new Region();
+ private boolean mAssistantAvailable;
+ private float mAssistantVisibility;
+
+ private boolean mIsUserUnlocked;
+ private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
+ private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+ mIsUserUnlocked = true;
+ notifyUserUnlocked();
+ }
+ }
+ };
+
+ private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
+ @Override
+ public void onRecentTaskListFrozenChanged(boolean frozen) {
+ if (frozen) {
+ return;
+ }
+ mOrientationTouchTransformer.enableMultipleRegions(false, mDefaultDisplay.getInfo());
+ }
+ };
+
+ private OrientationTouchTransformer mOrientationTouchTransformer;
+
+ private Region mExclusionRegion;
+ private SystemGestureExclusionListenerCompat mExclusionListener;
+
+ private final List<ComponentName> mGestureBlockedActivities;
+ private Runnable mOnDestroyFrozenTaskRunnable;
+
+ public RecentsAnimationDeviceState(Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ mContext = context;
+ mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
+ mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
+ mDisplayId = mDefaultDisplay.getInfo().id;
+ runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
+
+ // Register for user unlocked if necessary
+ mIsUserUnlocked = context.getSystemService(UserManager.class)
+ .isUserUnlocked(Process.myUserHandle());
+ if (!mIsUserUnlocked) {
+ mContext.registerReceiver(mUserUnlockedReceiver,
+ new IntentFilter(ACTION_USER_UNLOCKED));
+ }
+ runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver));
+
+ // Register for exclusion updates
+ mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
+ @Override
+ @BinderThread
+ public void onExclusionChanged(Region region) {
+ // Assignments are atomic, it should be safe on binder thread
+ mExclusionRegion = region;
+ }
+ };
+ runOnDestroy(mExclusionListener::unregister);
+
+ Resources resources = mContext.getResources();
+ mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+ () -> QuickStepContract.getWindowCornerRadius(resources));
+
+ // Register for navigation mode changes
+ onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
+ runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
+
+ // Add any blocked activities
+ String[] blockingActivities;
+ try {
+ blockingActivities =
+ context.getResources().getStringArray(R.array.gesture_blocking_activities);
+ } catch (Resources.NotFoundException e) {
+ blockingActivities = new String[0];
+ }
+ mGestureBlockedActivities = new ArrayList<>(blockingActivities.length);
+ for (String blockingActivity : blockingActivities) {
+ if (!TextUtils.isEmpty(blockingActivity)) {
+ mGestureBlockedActivities.add(
+ ComponentName.unflattenFromString(blockingActivity));
+ }
+ }
+ }
+
+ private void setupOrientationSwipeHandler() {
+ if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
+ return;
+ }
+
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
+ mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
+ .unregisterTaskStackListener(mFrozenTaskListener);
+ runOnDestroy(mOnDestroyFrozenTaskRunnable);
+ }
+
+ private void destroyOrientationSwipeHandlerCallback() {
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
+ mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
+ }
+
+ private void runOnDestroy(Runnable action) {
+ mOnDestroyActions.add(action);
+ }
+
+ /**
+ * Cleans up all the registered listeners and receivers.
+ */
+ public void destroy() {
+ for (Runnable r : mOnDestroyActions) {
+ r.run();
+ }
+ }
+
+ /**
+ * Adds a listener for the nav mode change, guaranteed to be called after the device state's
+ * mode has changed.
+ */
+ public void addNavigationModeChangedCallback(NavigationModeChangeListener listener) {
+ listener.onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(listener));
+ runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
+ }
+
+ @Override
+ public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
+ }
+ mDefaultDisplay.removeChangeListener(this);
+ if (newMode.hasGestures) {
+ mDefaultDisplay.addChangeListener(this);
+ }
+
+ if (newMode == NO_BUTTON) {
+ mExclusionListener.register();
+ } else {
+ mExclusionListener.unregister();
+ }
+
+ mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
+
+ mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
+ if (!mMode.hasGestures && newMode.hasGestures) {
+ setupOrientationSwipeHandler();
+ } else if (mMode.hasGestures && !newMode.hasGestures){
+ destroyOrientationSwipeHandlerCallback();
+ }
+
+ mMode = newMode;
+ }
+
+ @Override
+ public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+ if (info.id != getDisplayId()) {
+ return;
+ }
+
+ mDisplayRotation = info.rotation;
+ mNavBarPosition = new NavBarPosition(mMode, info);
+ updateGestureTouchRegions();
+ mOrientationTouchTransformer.createOrAddTouchRegion(info);
+ }
+
+ /**
+ * @return the current navigation mode for the device.
+ */
+ public SysUINavigationMode.Mode getNavMode() {
+ return mMode;
+ }
+
+ /**
+ * @return the nav bar position for the current nav bar mode and display rotation.
+ */
+ public NavBarPosition getNavBarPosition() {
+ return mNavBarPosition;
+ }
+
+ /**
+ * @return whether the current nav mode is fully gestural.
+ */
+ public boolean isFullyGesturalNavMode() {
+ return mMode == NO_BUTTON;
+ }
+
+ /**
+ * @return whether the current nav mode has some gestures (either 2 or 0 button mode).
+ */
+ public boolean isGesturalNavMode() {
+ return mMode == TWO_BUTTONS || mMode == NO_BUTTON;
+ }
+
+ /**
+ * @return whether the current nav mode is button-based.
+ */
+ public boolean isButtonNavMode() {
+ return mMode == THREE_BUTTONS;
+ }
+
+ /**
+ * @return the display id for the display that Launcher is running on.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
+ * will be called back immediately.
+ */
+ public void runOnUserUnlocked(Runnable action) {
+ if (mIsUserUnlocked) {
+ action.run();
+ } else {
+ mUserUnlockedActions.add(action);
+ }
+ }
+
+ /**
+ * @return whether the user is unlocked.
+ */
+ public boolean isUserUnlocked() {
+ return mIsUserUnlocked;
+ }
+
+ private void notifyUserUnlocked() {
+ for (Runnable action : mUserUnlockedActions) {
+ action.run();
+ }
+ mUserUnlockedActions.clear();
+ Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver);
+ }
+
+ /**
+ * @return whether the given running task info matches the gesture-blocked activity.
+ */
+ public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) {
+ return runningTaskInfo != null
+ && mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
+ }
+
+ /**
+ * @return the packages of gesture-blocked activities.
+ */
+ public List<String> getGestureBlockedActivityPackages() {
+ return mGestureBlockedActivities.stream().map(ComponentName::getPackageName)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Updates the system ui state flags from SystemUI.
+ */
+ public void setSystemUiFlags(int stateFlags) {
+ mSystemUiStateFlags = stateFlags;
+ }
+
+ /**
+ * @return the system ui state flags.
+ */
+ // TODO(141886704): See if we can remove this
+ public @SystemUiStateFlags int getSystemUiStateFlags() {
+ return mSystemUiStateFlags;
+ }
+
+ /**
+ * @return whether SystemUI is in a state where we can start a system gesture.
+ */
+ public boolean canStartSystemGesture() {
+ return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+ && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
+ && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
+ && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+ || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
+ }
+
+ /**
+ * @return whether the keyguard is showing and is occluded by an app showing above the keyguard
+ * (like camera or maps)
+ */
+ public boolean isKeyguardShowingOccluded() {
+ return (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
+ }
+
+ /**
+ * @return whether screen pinning is enabled and active
+ */
+ public boolean isScreenPinningActive() {
+ return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+ }
+
+ /**
+ * @return whether lock-task mode is active
+ */
+ public boolean isLockToAppActive() {
+ return ActivityManagerWrapper.getInstance().isLockToAppActive();
+ }
+
+ /**
+ * @return whether the accessibility menu is available.
+ */
+ public boolean isAccessibilityMenuAvailable() {
+ return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ }
+
+ /**
+ * @return whether the accessibility menu shortcut is available.
+ */
+ public boolean isAccessibilityMenuShortcutAvailable() {
+ return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+ }
+
+ /**
+ * @return whether home is disabled (either by SUW/SysUI/device policy)
+ */
+ public boolean isHomeDisabled() {
+ return (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+ }
+
+ /**
+ * @return whether overview is disabled (either by SUW/SysUI/device policy)
+ */
+ public boolean isOverviewDisabled() {
+ return (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+ }
+
+ /**
+ * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
+ */
+ public void updateGestureTouchRegions() {
+ if (!mMode.hasGestures) {
+ return;
+ }
+
+ mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
+ }
+
+ /**
+ * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
+ */
+ public boolean isInSwipeUpTouchRegion(MotionEvent event) {
+ return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
+ }
+
+ /**
+ * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
+ * is in the swipe up gesture region.
+ */
+ public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+ return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
+ event.getY(pointerIndex));
+ }
+
+ /**
+ * Sets the region in screen space where the gestures should be deferred (ie. due to specific
+ * nav bar ui).
+ */
+ public void setDeferredGestureRegion(Region deferredGestureRegion) {
+ mDeferredGestureRegion.set(deferredGestureRegion);
+ }
+
+ /**
+ * @return whether the given {@param event} is in the deferred gesture region indicating that
+ * the Launcher should not immediately start the recents animation until the gesture
+ * passes a certain threshold.
+ */
+ public boolean isInDeferredGestureRegion(MotionEvent event) {
+ return mDeferredGestureRegion.contains((int) event.getX(), (int) event.getY());
+ }
+
+ /**
+ * @return whether the given {@param event} is in the app-requested gesture-exclusion region.
+ * This is only used for quickswitch, and not swipe up.
+ */
+ public boolean isInExclusionRegion(MotionEvent event) {
+ // mExclusionRegion can change on binder thread, use a local instance here.
+ Region exclusionRegion = mExclusionRegion;
+ return mMode == NO_BUTTON && exclusionRegion != null
+ && exclusionRegion.contains((int) event.getX(), (int) event.getY());
+ }
+
+ /**
+ * Sets whether the assistant is available.
+ */
+ public void setAssistantAvailable(boolean assistantAvailable) {
+ mAssistantAvailable = assistantAvailable;
+ }
+
+ /**
+ * Sets the visibility fraction of the assistant.
+ */
+ public void setAssistantVisibility(float visibility) {
+ mAssistantVisibility = visibility;
+ }
+
+ /**
+ * @return the visibility fraction of the assistant.
+ */
+ public float getAssistantVisibility() {
+ return mAssistantVisibility;
+ }
+
+ /**
+ * @param ev An ACTION_DOWN motion event
+ * @return whether the given motion event can trigger the assistant.
+ */
+ public boolean canTriggerAssistantAction(MotionEvent ev) {
+ return mAssistantAvailable
+ && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
+ && mOrientationTouchTransformer.touchInAssistantRegion(ev)
+ && !isLockToAppActive();
+ }
+
+ /**
+ * *May* apply a transform on the motion event if it lies in the nav bar region for another
+ * orientation that is currently being tracked as a part of quickstep
+ */
+ public void setOrientationTransformIfNeeded(MotionEvent event) {
+ // negative coordinates bug b/143901881
+ if (event.getX() < 0 || event.getY() < 0) {
+ event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
+ }
+ mOrientationTouchTransformer.transform(event);
+ }
+
+ public void enableMultipleRegions(boolean enable) {
+ mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+ }
+
+ public int getCurrentActiveRotation() {
+ return mOrientationTouchTransformer.getCurrentActiveRotation();
+ }
+
+ public int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("DeviceState:");
+ pw.println(" canStartSystemGesture=" + canStartSystemGesture());
+ pw.println(" systemUiFlags=" + mSystemUiStateFlags);
+ pw.println(" systemUiFlagsDesc="
+ + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
+ pw.println(" assistantAvailable=" + mAssistantAvailable);
+ pw.println(" assistantDisabled="
+ + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
+ pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
+ pw.println(" displayRotation=" + getDisplayRotation());
+ pw.println(" isUserUnlocked=" + mIsUserUnlocked);
+ mOrientationTouchTransformer.dump(pw);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
new file mode 100644
index 0000000..718c5ba
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.graphics.Rect;
+
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Extension of {@link RemoteAnimationTargets} with additional information about swipe
+ * up animation
+ */
+public class RecentsAnimationTargets extends RemoteAnimationTargets {
+
+ public final Rect homeContentInsets;
+ public final Rect minimizedHomeBounds;
+
+ public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
+ RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
+ Rect minimizedHomeBounds) {
+ super(apps, wallpapers, MODE_CLOSING);
+ this.homeContentInsets = homeContentInsets;
+ this.minimizedHomeBounds = minimizedHomeBounds;
+ }
+
+ public boolean hasTargets() {
+ return unfilteredApps.length != 0;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 2e59ed5..517501a 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -28,14 +28,10 @@
import android.os.Build;
import android.os.Looper;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Log;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
+import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -52,17 +48,13 @@
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel extends TaskStackChangeListener {
- private static final String TAG = "RecentsModel";
-
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
new MainThreadInitializedObject<>(RecentsModel::new);
- private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
+ private final List<TaskVisualsChangeListener> mThumbnailChangeListeners = new ArrayList<>();
private final Context mContext;
- private ISystemUiProxy mSystemUiProxy;
-
private final RecentTasksList mTaskList;
private final TaskIconCache mIconCache;
private final TaskThumbnailCache mThumbnailCache;
@@ -75,8 +67,10 @@
new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
mIconCache = new TaskIconCache(context, looper);
mThumbnailCache = new TaskThumbnailCache(context, looper);
+
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
- setupPackageListener();
+ IconProvider.registerIconChangeListener(context,
+ this::onPackageIconChanged, MAIN_EXECUTOR.getHandler());
}
public TaskIconCache getIconCache() {
@@ -178,14 +172,6 @@
mIconCache.onTaskRemoved(dummyKey);
}
- public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
- mSystemUiProxy = systemUiProxy;
- }
-
- public ISystemUiProxy getSystemUiProxy() {
- return mSystemUiProxy;
- }
-
public void onTrimMemory(int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
mThumbnailCache.getHighResLoadingState().setVisible(false);
@@ -197,44 +183,40 @@
}
}
- public void onOverviewShown(boolean fromHome, String tag) {
- if (mSystemUiProxy == null) {
- return;
- }
- try {
- mSystemUiProxy.onOverviewShown(fromHome);
- } catch (RemoteException e) {
- Log.w(tag,
- "Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app")
- + ": ", e);
+ private void onPackageIconChanged(String pkg, UserHandle user) {
+ mIconCache.invalidateCacheEntries(pkg, user);
+ for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
+ mThumbnailChangeListeners.get(i).onTaskIconChanged(pkg, user);
}
}
- private void setupPackageListener() {
- LauncherAppsCompat.getInstance(mContext)
- .addOnAppsChangedCallback(new OnAppsChangedCallbackCompat() {
- @Override
- public void onPackageRemoved(String packageName, UserHandle user) {
- mIconCache.invalidatePackage(packageName);
- }
-
- @Override
- public void onPackageChanged(String packageName, UserHandle user) {
- mIconCache.invalidatePackage(packageName);
- }
- });
- }
-
- public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {
+ /**
+ * Adds a listener for visuals changes
+ */
+ public void addThumbnailChangeListener(TaskVisualsChangeListener listener) {
mThumbnailChangeListeners.add(listener);
}
- public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) {
+ /**
+ * Removes a previously added listener
+ */
+ public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) {
mThumbnailChangeListeners.remove(listener);
}
- public interface TaskThumbnailChangeListener {
+ /**
+ * Listener for receiving various task properties changes
+ */
+ public interface TaskVisualsChangeListener {
+ /**
+ * Called whn the task thumbnail changes
+ */
Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
+
+ /**
+ * Called when the icon for a task changes
+ */
+ void onTaskIconChanged(String pkg, UserHandle user);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
similarity index 86%
rename from quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
rename to quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index 1229293..5fa6bc7 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep.util;
+package com.android.quickstep;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -25,17 +25,19 @@
/**
* Holds a collection of RemoteAnimationTargets, filtered by different properties.
*/
-public class RemoteAnimationTargetSet {
+public class RemoteAnimationTargets {
private final Queue<SyncRtSurfaceTransactionApplierCompat> mDependentTransactionAppliers =
new ArrayDeque<>(1);
public final RemoteAnimationTargetCompat[] unfilteredApps;
public final RemoteAnimationTargetCompat[] apps;
+ public final RemoteAnimationTargetCompat[] wallpapers;
public final int targetMode;
public final boolean hasRecents;
- public RemoteAnimationTargetSet(RemoteAnimationTargetCompat[] apps, int targetMode) {
+ public RemoteAnimationTargets(RemoteAnimationTargetCompat[] apps,
+ RemoteAnimationTargetCompat[] wallpapers, int targetMode) {
ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
boolean hasRecents = false;
if (apps != null) {
@@ -51,6 +53,7 @@
this.unfilteredApps = apps;
this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
+ this.wallpapers = wallpapers;
this.targetMode = targetMode;
this.hasRecents = hasRecents;
}
@@ -83,6 +86,9 @@
for (RemoteAnimationTargetCompat target : unfilteredApps) {
target.release();
}
+ for (RemoteAnimationTargetCompat target : wallpapers) {
+ target.release();
+ }
} else {
applier.addAfterApplyCallback(this::release);
}
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index b67c6f8..375e589 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -52,12 +52,12 @@
return INSTANCE.get(context).getMode();
}
- public static MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
+ public static final MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
new MainThreadInitializedObject<>(SysUINavigationMode::new);
private static final String TAG = "SysUINavigationMode";
- private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+ private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
"config_navBarInteractionMode";
@@ -122,6 +122,12 @@
}
}
+ /** @return Whether we can remove the shelf from overview. */
+ public static boolean removeShelfFromOverview(Context context) {
+ // The shelf is core to the two-button mode model, so we need to continue supporting it.
+ return getMode(context) != Mode.TWO_BUTTONS;
+ }
+
public interface NavigationModeChangeListener {
void onNavigationModeChanged(Mode newMode);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
new file mode 100644
index 0000000..0210a81
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+
+/**
+ * Holds the reference to SystemUI.
+ */
+public class SystemUiProxy implements ISystemUiProxy {
+ private static final String TAG = SystemUiProxy.class.getSimpleName();
+
+ public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
+ new MainThreadInitializedObject<>(SystemUiProxy::new);
+
+ private ISystemUiProxy mSystemUiProxy;
+ private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
+ MAIN_EXECUTOR.execute(() -> setProxy(null));
+ };
+
+ // Used to dedupe calls to SystemUI
+ private int mLastShelfHeight;
+ private boolean mLastShelfVisible;
+ private float mLastBackButtonAlpha;
+ private boolean mLastBackButtonAnimate;
+
+ // TODO(141886704): Find a way to remove this
+ private int mLastSystemUiStateFlags;
+
+ public SystemUiProxy(Context context) {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // Do nothing
+ return null;
+ }
+
+ public void setProxy(ISystemUiProxy proxy) {
+ unlinkToDeath();
+ mSystemUiProxy = proxy;
+ linkToDeath();
+ }
+
+ // TODO(141886704): Find a way to remove this
+ public void setLastSystemUiStateFlags(int stateFlags) {
+ mLastSystemUiStateFlags = stateFlags;
+ }
+
+ // TODO(141886704): Find a way to remove this
+ public int getLastSystemUiStateFlags() {
+ return mLastSystemUiStateFlags;
+ }
+
+ public boolean isActive() {
+ return mSystemUiProxy != null;
+ }
+
+ private void linkToDeath() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.asBinder().linkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to link sysui proxy death recipient");
+ }
+ }
+ }
+
+ private void unlinkToDeath() {
+ if (mSystemUiProxy != null) {
+ mSystemUiProxy.asBinder().unlinkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
+ }
+ }
+
+ @Override
+ public void startScreenPinning(int taskId) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.startScreenPinning(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startScreenPinning", e);
+ }
+ }
+ }
+
+ @Override
+ public void onSplitScreenInvoked() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onSplitScreenInvoked();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onSplitScreenInvoked", e);
+ }
+ }
+ }
+
+ @Override
+ public void onOverviewShown(boolean fromHome) {
+ onOverviewShown(fromHome, TAG);
+ }
+
+ public void onOverviewShown(boolean fromHome, String tag) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onOverviewShown(fromHome);
+ } catch (RemoteException e) {
+ Log.w(tag, "Failed call onOverviewShown from: " + (fromHome ? "home" : "app"), e);
+ }
+ }
+ }
+
+ @Override
+ public Rect getNonMinimizedSplitScreenSecondaryBounds() {
+ if (mSystemUiProxy != null) {
+ try {
+ return mSystemUiProxy.getNonMinimizedSplitScreenSecondaryBounds();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call getNonMinimizedSplitScreenSecondaryBounds", e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void setBackButtonAlpha(float alpha, boolean animate) {
+ boolean changed = Float.compare(alpha, mLastBackButtonAlpha) != 0
+ || animate != mLastBackButtonAnimate;
+ if (mSystemUiProxy != null && changed) {
+ mLastBackButtonAlpha = alpha;
+ mLastBackButtonAnimate = animate;
+ try {
+ mSystemUiProxy.setBackButtonAlpha(alpha, animate);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setBackButtonAlpha", e);
+ }
+ }
+ }
+
+ public float getLastBackButtonAlpha() {
+ return mLastBackButtonAlpha;
+ }
+
+ @Override
+ public void setNavBarButtonAlpha(float alpha, boolean animate) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+ }
+ }
+ }
+
+ @Override
+ public void onStatusBarMotionEvent(MotionEvent event) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onStatusBarMotionEvent(event);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onStatusBarMotionEvent", e);
+ }
+ }
+ }
+
+ @Override
+ public void onAssistantProgress(float progress) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onAssistantProgress(progress);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onAssistantProgress with progress: " + progress, e);
+ }
+ }
+ }
+
+ @Override
+ public void onAssistantGestureCompletion(float velocity) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onAssistantGestureCompletion(velocity);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onAssistantGestureCompletion", e);
+ }
+ }
+ }
+
+ @Override
+ public void startAssistant(Bundle args) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.startAssistant(args);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startAssistant", e);
+ }
+ }
+ }
+
+ @Override
+ public Bundle monitorGestureInput(String name, int displayId) {
+ if (mSystemUiProxy != null) {
+ try {
+ return mSystemUiProxy.monitorGestureInput(name, displayId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call monitorGestureInput: " + name, e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void notifyAccessibilityButtonClicked(int displayId) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.notifyAccessibilityButtonClicked(displayId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call notifyAccessibilityButtonClicked", e);
+ }
+ }
+ }
+
+ @Override
+ public void notifyAccessibilityButtonLongClicked() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.notifyAccessibilityButtonLongClicked();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call notifyAccessibilityButtonLongClicked", e);
+ }
+ }
+ }
+
+ @Override
+ public void stopScreenPinning() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.stopScreenPinning();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopScreenPinning", e);
+ }
+ }
+ }
+
+ @Override
+ public void setShelfHeight(boolean visible, int shelfHeight) {
+ boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
+ if (mSystemUiProxy != null && changed) {
+ mLastShelfVisible = visible;
+ mLastShelfHeight = shelfHeight;
+ try {
+ mSystemUiProxy.setShelfHeight(visible, shelfHeight);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setShelfHeight visible: " + visible
+ + " height: " + shelfHeight, e);
+ }
+ }
+ }
+
+ @Override
+ public void handleImageAsScreenshot(Bitmap bitmap, Rect rect, Insets insets, int i) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.handleImageAsScreenshot(bitmap, rect, insets, i);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call handleImageAsScreenshot", e);
+ }
+ }
+ }
+
+ @Override
+ public void setSplitScreenMinimized(boolean minimized) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.setSplitScreenMinimized(minimized);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopScreenPinning", e);
+ }
+ }
+ }
+
+ /**
+ * Notifies that swipe-to-home action is finished.
+ */
+ @Override
+ public void notifySwipeToHomeFinished() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.notifySwipeToHomeFinished();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setPinnedStackAnimationType", e);
+ }
+ }
+ }
+
+ /**
+ * Sets listener to get pinned stack animation callbacks.
+ */
+ @Override
+ public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.setPinnedStackAnimationListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
+ }
+ }
+ }
+
+ public void onQuickSwitchToNewTask() {
+ //TODO(b/150250451) add back in after big CL goes through
+// if (mSystemUiProxy != null) {
+// try {
+// mSystemUiProxy.onQuickSwitchToNewTask();
+// } catch (RemoteException e) {
+// Log.w(TAG, "Failed call onQuickstepStarted", e);
+// }
+// }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
new file mode 100644
index 0000000..6902e37
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
+
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
+
+ private RecentsAnimationController mController;
+ private RecentsAnimationCallbacks mCallbacks;
+ private RecentsAnimationTargets mTargets;
+ // Temporary until we can hook into gesture state events
+ private GestureState mLastGestureState;
+
+ /**
+ * Preloads the recents animation.
+ */
+ public void preloadRecentsAnimation(Intent intent) {
+ // Pass null animation handler to indicate this start is for preloading
+ UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+ .startRecentsActivity(intent, null, null, null, null));
+ }
+
+ /**
+ * Starts a new recents animation for the activity with the given {@param intent}.
+ */
+ @UiThread
+ public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
+ Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
+ // Notify if recents animation is still running
+ if (mController != null) {
+ String msg = "New recents animation started before old animation completed";
+ if (FeatureFlags.IS_STUDIO_BUILD) {
+ throw new IllegalArgumentException(msg);
+ } else {
+ Log.e("TaskAnimationManager", msg, new Exception());
+ }
+ }
+ // But force-finish it anyways
+ finishRunningRecentsAnimation(false /* toHome */);
+
+ final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
+ mLastGestureState = gestureState;
+ mCallbacks = new RecentsAnimationCallbacks(activityInterface.shouldMinimizeSplitScreen());
+ mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ mController = controller;
+ mTargets = targets;
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ if (thumbnailData != null) {
+ // If a screenshot is provided, switch to the screenshot before cleaning up
+ activityInterface.switchRunningTaskViewToScreenshot(thumbnailData,
+ () -> cleanUpRecentsAnimation(thumbnailData));
+ } else {
+ cleanUpRecentsAnimation(null /* canceledThumbnail */);
+ }
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ cleanUpRecentsAnimation(null /* canceledThumbnail */);
+ }
+ });
+ mCallbacks.addListener(gestureState);
+ mCallbacks.addListener(listener);
+ UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+ .startRecentsActivity(intent, null, mCallbacks, null, null));
+ gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
+ return mCallbacks;
+ }
+
+ /**
+ * Continues the existing running recents animation for a new gesture.
+ */
+ public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) {
+ mCallbacks.removeListener(mLastGestureState);
+ mLastGestureState = gestureState;
+ mCallbacks.addListener(gestureState);
+ return mCallbacks;
+ }
+
+ /**
+ * Finishes the running recents animation.
+ */
+ public void finishRunningRecentsAnimation(boolean toHome) {
+ if (mController != null) {
+ mCallbacks.notifyAnimationCanceled();
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
+ ? mController::finishAnimationToHome
+ : mController::finishAnimationToApp);
+ cleanUpRecentsAnimation(null /* canceledThumbnail */);
+ }
+ }
+
+ /**
+ * Used to notify a listener of the current recents animation state (used if the listener was
+ * not yet added to the callbacks at the point that the listener callbacks would have been
+ * made).
+ */
+ public void notifyRecentsAnimationState(
+ RecentsAnimationCallbacks.RecentsAnimationListener listener) {
+ if (isRecentsAnimationRunning()) {
+ listener.onRecentsAnimationStart(mController, mTargets);
+ }
+ // TODO: Do we actually need to report canceled/finished?
+ }
+
+ /**
+ * @return whether there is a recents animation running.
+ */
+ public boolean isRecentsAnimationRunning() {
+ return mController != null;
+ }
+
+ /**
+ * Cleans up the recents animation entirely.
+ */
+ private void cleanUpRecentsAnimation(ThumbnailData canceledThumbnail) {
+ // Clean up the screenshot if necessary
+ if (mController != null && canceledThumbnail != null) {
+ mController.cleanupScreenshot();
+ }
+
+ // Release all the target leashes
+ if (mTargets != null) {
+ mTargets.release();
+ }
+
+ // Clean up all listeners to ensure we don't get subsequent callbacks
+ if (mCallbacks != null) {
+ mCallbacks.removeAllListeners();
+ }
+
+ mController = null;
+ mCallbacks = null;
+ mTargets = null;
+ mLastGestureState = null;
+ }
+
+ public void dump() {
+ // TODO
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 289a129..7ff799e 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,67 +15,65 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import android.content.ComponentName;
+import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.util.LruCache;
+import android.os.UserHandle;
+import android.util.SparseArray;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.recents.model.TaskKeyLruCache;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.system.TaskDescriptionCompat;
-import java.util.Map;
import java.util.function.Consumer;
/**
* Manages the caching of task icons and related data.
- * TODO(b/138944598): This class should later be merged into IconCache.
*/
public class TaskIconCache {
private final Handler mBackgroundHandler;
private final AccessibilityManager mAccessibilityManager;
- private final NormalizedIconLoader mIconLoader;
-
- private final TaskKeyLruCache<Drawable> mIconCache;
- private final TaskKeyLruCache<String> mContentDescriptionCache;
- private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
-
- private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
- new TaskKeyLruCache.EvictionCallback() {
- @Override
- public void onEntryEvicted(Task.TaskKey key) {
- if (key != null) {
- mActivityInfoCache.remove(key.getComponent());
- }
- }
- };
+ private final Context mContext;
+ private final TaskKeyLruCache<TaskCacheEntry> mIconCache;
+ private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
+ private final IconProvider mIconProvider;
public TaskIconCache(Context context, Looper backgroundLooper) {
+ mContext = context;
mBackgroundHandler = new Handler(backgroundLooper);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
Resources res = context.getResources();
int cacheSize = res.getInteger(R.integer.recentsIconCacheSize);
- mIconCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
- mContentDescriptionCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
- mActivityInfoCache = new LruCache<>(cacheSize);
- mIconLoader = new NormalizedIconLoader(context, mIconCache, mActivityInfoCache,
- true /* disableColorExtraction */);
+ mIconCache = new TaskKeyLruCache<>(cacheSize);
+ mIconProvider = new IconProvider(context);
}
/**
@@ -96,15 +94,14 @@
IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) {
@Override
public void run() {
- Drawable icon = mIconLoader.getIcon(task);
- String contentDescription = loadContentDescriptionInBackground(task);
+ TaskCacheEntry entry = getCacheEntry(task);
if (isCanceled()) {
// We don't call back to the provided callback in this case
return;
}
MAIN_EXECUTOR.execute(() -> {
- task.icon = icon;
- task.titleDescription = contentDescription;
+ task.icon = entry.icon;
+ task.titleDescription = entry.contentDescription;
callback.accept(task);
onEnd();
});
@@ -116,51 +113,99 @@
public void clear() {
mIconCache.evictAll();
- mContentDescriptionCache.evictAll();
}
- /**
- * Loads the content description for the given {@param task}.
- */
- private String loadContentDescriptionInBackground(Task task) {
- // Return the cached content description if it exists
- String label = mContentDescriptionCache.getAndInvalidateIfModified(task.key);
- if (label != null) {
- return label;
- }
-
- // Skip loading content descriptions if accessibility is disabled unless low RAM recents
- // is enabled.
- if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) {
- return "";
- }
-
- // Skip loading the content description if the activity no longer exists
- ActivityInfo activityInfo = mIconLoader.getAndUpdateActivityInfo(task.key);
- if (activityInfo == null) {
- return "";
- }
-
- // Load the label otherwise
- label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(activityInfo,
- task.key.userId, task.taskDescription);
- mContentDescriptionCache.put(task.key, label);
- return label;
- }
-
-
void onTaskRemoved(TaskKey taskKey) {
mIconCache.remove(taskKey);
}
- void invalidatePackage(String packageName) {
- // TODO(b/138944598): Merge this class into IconCache so we can do this at the base level
- Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
- for (ComponentName cn : activityInfoCache.keySet()) {
- if (cn.getPackageName().equals(packageName)) {
- mActivityInfoCache.remove(cn);
+ void invalidateCacheEntries(String pkg, UserHandle handle) {
+ Utilities.postAsyncCallback(mBackgroundHandler,
+ () -> mIconCache.removeAll(key ->
+ pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId));
+ }
+
+ @WorkerThread
+ private TaskCacheEntry getCacheEntry(Task task) {
+ TaskCacheEntry entry = mIconCache.getAndInvalidateIfModified(task.key);
+ if (entry != null) {
+ return entry;
+ }
+
+ TaskDescription desc = task.taskDescription;
+ TaskKey key = task.key;
+ ActivityInfo activityInfo = null;
+
+ // Create new cache entry
+ entry = new TaskCacheEntry();
+
+ // Load icon
+ // TODO: Load icon resource (b/143363444)
+ Bitmap icon = TaskDescriptionCompat.getIcon(desc, key.userId);
+ if (icon != null) {
+ entry.icon = new FastBitmapDrawable(getBitmapInfo(
+ new BitmapDrawable(mContext.getResources(), icon),
+ key.userId,
+ desc.getPrimaryColor(),
+ false /* isInstantApp */));
+ } else {
+ activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
+ key.getComponent(), key.userId);
+ if (activityInfo != null) {
+ BitmapInfo bitmapInfo = getBitmapInfo(
+ mIconProvider.getIcon(activityInfo, UserHandle.of(key.userId)),
+ key.userId,
+ desc.getPrimaryColor(),
+ activityInfo.applicationInfo.isInstantApp());
+ entry.icon = newIcon(mContext, bitmapInfo);
+ } else {
+ entry.icon = getDefaultIcon(key.userId);
}
}
+
+ // Loading content descriptions if accessibility or low RAM recents is enabled.
+ if (GO_LOW_RAM_RECENTS_ENABLED || mAccessibilityManager.isEnabled()) {
+ // Skip loading the content description if the activity no longer exists
+ if (activityInfo == null) {
+ activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
+ key.getComponent(), key.userId);
+ }
+ if (activityInfo != null) {
+ entry.contentDescription = ActivityManagerWrapper.getInstance()
+ .getBadgedContentDescription(activityInfo, task.key.userId,
+ task.taskDescription);
+ }
+ }
+
+ mIconCache.put(task.key, entry);
+ return entry;
+ }
+
+ @WorkerThread
+ private Drawable getDefaultIcon(int userId) {
+ synchronized (mDefaultIcons) {
+ BitmapInfo info = mDefaultIcons.get(userId);
+ if (info == null) {
+ try (LauncherIcons la = LauncherIcons.obtain(mContext)) {
+ info = la.makeDefaultIcon(UserHandle.of(userId));
+ }
+ mDefaultIcons.put(userId, info);
+ }
+ return new FastBitmapDrawable(info);
+ }
+ }
+
+ @WorkerThread
+ private BitmapInfo getBitmapInfo(Drawable drawable, int userId,
+ int primaryColor, boolean isInstantApp) {
+ try (LauncherIcons la = LauncherIcons.obtain(mContext)) {
+ la.disableColorExtraction();
+ la.setWrapperBackgroundColor(primaryColor);
+
+ // User version code O, so that the icon is always wrapped in an adaptive icon container
+ return la.createBadgedIconBitmap(drawable, UserHandle.of(userId),
+ Build.VERSION_CODES.O, isInstantApp);
+ }
}
public static abstract class IconLoadRequest extends HandlerRunnable {
@@ -168,4 +213,9 @@
super(handler, null);
}
}
+
+ private static class TaskCacheEntry {
+ public Drawable icon;
+ public String contentDescription = "";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 3b50c26..ace6743 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
@@ -27,9 +26,9 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.recents.model.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -41,11 +40,12 @@
private final Handler mBackgroundHandler;
private final int mCacheSize;
- private final ThumbnailCache mCache;
+ private final TaskKeyLruCache<ThumbnailData> mCache;
private final HighResLoadingState mHighResLoadingState;
+ private final boolean mEnableTaskSnapshotPreloading;
public static class HighResLoadingState {
- private boolean mIsLowRamDevice;
+ private boolean mForceHighResThumbnails;
private boolean mVisible;
private boolean mFlingingFast;
private boolean mHighResLoadingEnabled;
@@ -56,9 +56,9 @@
}
private HighResLoadingState(Context context) {
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mIsLowRamDevice = activityManager.isLowRamDevice();
+ // If the device does not support low-res thumbnails, only attempt to load high-res
+ // thumbnails
+ mForceHighResThumbnails = !supportsLowResThumbnails();
}
public void addCallback(HighResLoadingStateChangedCallback callback) {
@@ -85,7 +85,7 @@
private void updateState() {
boolean prevState = mHighResLoadingEnabled;
- mHighResLoadingEnabled = !mIsLowRamDevice && mVisible && !mFlingingFast;
+ mHighResLoadingEnabled = mForceHighResThumbnails || (mVisible && !mFlingingFast);
if (prevState != mHighResLoadingEnabled) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);
@@ -100,7 +100,8 @@
Resources res = context.getResources();
mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
- mCache = new ThumbnailCache(mCacheSize);
+ mEnableTaskSnapshotPreloading = res.getBoolean(R.bool.config_enableTaskSnapshotPreloading);
+ mCache = new TaskKeyLruCache<>(mCacheSize);
}
/**
@@ -110,7 +111,7 @@
Preconditions.assertUIThread();
// Fetch the thumbnail for this task and put it in the cache
if (task.thumbnail == null) {
- updateThumbnailInBackground(task.key, true /* reducedResolution */,
+ updateThumbnailInBackground(task.key, true /* lowResolution */,
t -> task.thumbnail = t);
}
}
@@ -133,8 +134,8 @@
Task task, Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
- boolean reducedResolution = !mHighResLoadingState.isEnabled();
- if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {
+ boolean lowResolution = !mHighResLoadingState.isEnabled();
+ if (task.thumbnail != null && (!task.thumbnail.reducedResolution || lowResolution)) {
// Nothing to load, the thumbnail is already high-resolution or matches what the
// request, so just callback
callback.accept(task.thumbnail);
@@ -148,23 +149,23 @@
});
}
- private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean reducedResolution,
+ private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean lowResolution,
Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
- if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {
+ if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || lowResolution)) {
// Already cached, lets use that thumbnail
callback.accept(cachedThumbnail);
return null;
}
ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
- reducedResolution) {
+ lowResolution) {
@Override
public void run() {
ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
- key.id, reducedResolution);
+ key.id, lowResolution);
if (isCanceled()) {
// We don't call back to the provided callback in this case
return;
@@ -212,32 +213,31 @@
* @return Whether to enable background preloading of task thumbnails.
*/
public boolean isPreloadingEnabled() {
- return !mHighResLoadingState.mIsLowRamDevice && mHighResLoadingState.mVisible;
+ return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible;
}
public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
- public final boolean reducedResolution;
+ public final boolean mLowResolution;
- ThumbnailLoadRequest(Handler handler, boolean reducedResolution) {
+ ThumbnailLoadRequest(Handler handler, boolean lowResolution) {
super(handler, null);
- this.reducedResolution = reducedResolution;
+ mLowResolution = lowResolution;
}
}
- private static class ThumbnailCache extends TaskKeyLruCache<ThumbnailData> {
-
- public ThumbnailCache(int cacheSize) {
- super(cacheSize);
+ /**
+ * @return Whether device supports low-res thumbnails. Low-res files are an optimization
+ * for faster load times of snapshots. Devices can optionally disable low-res files so that
+ * they only store snapshots at high-res scale. The actual scale can be configured in
+ * frameworks/base config overlay.
+ */
+ private static boolean supportsLowResThumbnails() {
+ Resources res = Resources.getSystem();
+ int resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android");
+ if (resId != 0) {
+ return 0 < res.getFloat(resId);
}
-
- /**
- * Updates the cache entry if it is already present in the cache
- */
- public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) {
- ThumbnailData oldData = getCacheEntry(taskId);
- if (oldData != null) {
- putCacheEntry(taskId, thumbnailData);
- }
- }
+ return true;
}
+
}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 5f76ca7..04b488d 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -23,9 +23,9 @@
import android.os.UserHandle;
import android.util.Log;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -44,15 +44,14 @@
* TODO: remove this once we switch to getting the icon and label from IconCache.
*/
public static CharSequence getTitle(Context context, Task task) {
- LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(context);
- PackageManager packageManager = context.getPackageManager();
UserHandle user = UserHandle.of(task.key.userId);
- ApplicationInfo applicationInfo = launcherAppsCompat.getApplicationInfo(
- task.getTopComponent().getPackageName(), 0, user);
+ ApplicationInfo applicationInfo = new PackageManagerHelper(context)
+ .getApplicationInfo(task.getTopComponent().getPackageName(), user, 0);
if (applicationInfo == null) {
Log.e(TAG, "Failed to get title for task " + task);
return "";
}
+ PackageManager packageManager = context.getPackageManager();
return packageManager.getUserBadgedLabel(
applicationInfo.loadLabel(packageManager), user);
}
@@ -79,7 +78,7 @@
if (currentUserId == UserHandle.myUserId()) {
return true;
}
- List<UserHandle> allUsers = UserManagerCompat.getInstance(context).getUserProfiles();
+ List<UserHandle> allUsers = UserCache.INSTANCE.get(context).getUserProfiles();
for (int i = allUsers.size() - 1; i >= 0; i--) {
if (currentUserId == allUsers.get(i).getIdentifier()) {
return true;
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java
new file mode 100644
index 0000000..486d676
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+
+import java.util.Optional;
+
+/**
+ * An implementation of {@link BackGestureTutorialController} that defines the behavior of the
+ * {@link TutorialStep#CONFIRM}.
+ */
+final class BackGestureTutorialConfirmController extends BackGestureTutorialController {
+
+ BackGestureTutorialConfirmController(BackGestureTutorialFragment fragment,
+ BackGestureTutorialTypeInfo tutorialTypeInfo) {
+ super(fragment, TutorialStep.CONFIRM, Optional.of(tutorialTypeInfo));
+ }
+
+ @Override
+ Optional<Integer> getTitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmTitleId());
+ }
+
+ @Override
+ Optional<Integer> getSubtitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmSubtitleId());
+ }
+
+ @Override
+ Optional<Integer> getActionButtonStringId() {
+ return Optional.of(R.string.back_gesture_tutorial_action_button_label);
+ }
+
+ @Override
+ Optional<Integer> getActionTextButtonStringId() {
+ return Optional.of(R.string.back_gesture_tutorial_action_text_button_label);
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ hideHandCoachingAnimation();
+ if (button == mActionTextButton) {
+ mFragment.startSystemNavigationSetting();
+ }
+ mFragment.closeTutorial();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
new file mode 100644
index 0000000..3fe91a3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+import java.util.Optional;
+
+/**
+ * Defines the behavior of the particular {@link TutorialStep} and implements the transition to it.
+ */
+abstract class BackGestureTutorialController {
+
+ final BackGestureTutorialFragment mFragment;
+ final TutorialStep mTutorialStep;
+ final Optional<BackGestureTutorialTypeInfo> mTutorialTypeInfo;
+ final Button mActionTextButton;
+ final Button mActionButton;
+ final TextView mSubtitleTextView;
+ final ImageButton mCloseButton;
+ final BackGestureTutorialHandAnimation mHandCoachingAnimation;
+ final LinearLayout mTitlesContainer;
+
+ private final TextView mTitleTextView;
+ private final ImageView mHandCoachingView;
+
+ BackGestureTutorialController(
+ BackGestureTutorialFragment fragment,
+ TutorialStep tutorialStep,
+ Optional<BackGestureTutorialTypeInfo> tutorialTypeInfo) {
+ mFragment = fragment;
+ mTutorialStep = tutorialStep;
+ mTutorialTypeInfo = tutorialTypeInfo;
+
+ View rootView = fragment.getRootView();
+ mActionTextButton = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_action_text_button);
+ mActionButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_action_button);
+ mSubtitleTextView = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_subtitle_view);
+ mTitleTextView = rootView.findViewById(R.id.back_gesture_tutorial_fragment_title_view);
+ mHandCoachingView = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_hand_coaching);
+ mHandCoachingAnimation = mFragment.getHandAnimation();
+ mHandCoachingView.bringToFront();
+ mCloseButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button);
+ mTitlesContainer = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_titles_container);
+ }
+
+ void transitToController() {
+ updateTitles();
+ updateActionButtons();
+ }
+
+ void hideHandCoachingAnimation() {
+ mHandCoachingAnimation.stop();
+ }
+
+ void onGestureDetected() {
+ hideHandCoachingAnimation();
+
+ if (mTutorialStep == TutorialStep.CONFIRM) {
+ mFragment.closeTutorial();
+ return;
+ }
+
+ if (mTutorialTypeInfo.get().getTutorialType() == TutorialType.RIGHT_EDGE_BACK_NAVIGATION) {
+ mFragment.changeController(TutorialStep.ENGAGED,
+ TutorialType.LEFT_EDGE_BACK_NAVIGATION);
+ return;
+ }
+
+ mFragment.changeController(TutorialStep.CONFIRM);
+ }
+
+ abstract Optional<Integer> getTitleStringId();
+
+ abstract Optional<Integer> getSubtitleStringId();
+
+ abstract Optional<Integer> getActionButtonStringId();
+
+ abstract Optional<Integer> getActionTextButtonStringId();
+
+ abstract void onActionButtonClicked(View button);
+
+ private void updateActionButtons() {
+ updateButton(mActionButton, getActionButtonStringId(), this::onActionButtonClicked);
+ updateButton(mActionTextButton, getActionTextButtonStringId(), this::onActionButtonClicked);
+ }
+
+ private static void updateButton(Button button, Optional<Integer> stringId,
+ View.OnClickListener listener) {
+ if (!stringId.isPresent()) {
+ button.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ button.setVisibility(View.VISIBLE);
+ button.setText(stringId.get());
+ button.setOnClickListener(listener);
+ }
+
+ private void updateTitles() {
+ updateTitleView(mTitleTextView, getTitleStringId(),
+ R.style.TextAppearance_BackGestureTutorial_Title);
+ updateTitleView(mSubtitleTextView, getSubtitleStringId(),
+ R.style.TextAppearance_BackGestureTutorial_Subtitle);
+ }
+
+ private static void updateTitleView(TextView textView, Optional<Integer> stringId,
+ int styleId) {
+ if (!stringId.isPresent()) {
+ textView.setVisibility(View.GONE);
+ return;
+ }
+
+ textView.setVisibility(View.VISIBLE);
+ textView.setText(stringId.get());
+ textView.setTextAppearance(styleId);
+ }
+
+ /**
+ * Constructs {@link BackGestureTutorialController} for providing {@link TutorialType} and
+ * {@link TutorialStep}.
+ */
+ static Optional<BackGestureTutorialController> getTutorialController(
+ BackGestureTutorialFragment fragment, TutorialStep tutorialStep,
+ TutorialType tutorialType) {
+ BackGestureTutorialTypeInfo tutorialTypeInfo =
+ BackGestureTutorialTypeInfoProvider.getTutorialTypeInfo(tutorialType);
+ switch (tutorialStep) {
+ case ENGAGED:
+ return Optional.of(
+ new BackGestureTutorialEngagedController(fragment, tutorialTypeInfo));
+ case CONFIRM:
+ return Optional.of(
+ new BackGestureTutorialConfirmController(fragment, tutorialTypeInfo));
+ default:
+ throw new AssertionError("Unexpected tutorial step: " + tutorialStep);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java
new file mode 100644
index 0000000..c9ee1e2
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import android.view.View;
+
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+
+import java.util.Optional;
+
+/**
+ * An implementation of {@link BackGestureTutorialController} that defines the behavior of the
+ * {@link TutorialStep#ENGAGED}.
+ */
+final class BackGestureTutorialEngagedController extends BackGestureTutorialController {
+
+ BackGestureTutorialEngagedController(
+ BackGestureTutorialFragment fragment, BackGestureTutorialTypeInfo tutorialTypeInfo) {
+ super(fragment, TutorialStep.ENGAGED, Optional.of(tutorialTypeInfo));
+ }
+
+ @Override
+ void transitToController() {
+ super.transitToController();
+ mHandCoachingAnimation.maybeStartLoopedAnimation(mTutorialTypeInfo.get().getTutorialType());
+ }
+
+ @Override
+ Optional<Integer> getTitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialPlaygroundTitleId());
+ }
+
+ @Override
+ Optional<Integer> getSubtitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialEngagedSubtitleId());
+ }
+
+ @Override
+ Optional<Integer> getActionButtonStringId() {
+ return Optional.empty();
+ }
+
+ @Override
+ Optional<Integer> getActionTextButtonStringId() {
+ return Optional.empty();
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
new file mode 100644
index 0000000..54408ce
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.launcher3.R;
+
+import java.net.URISyntaxException;
+import java.util.Optional;
+
+/** Shows the Back gesture interactive tutorial. */
+public class BackGestureTutorialFragment extends Fragment {
+
+ private static final String LOG_TAG = "TutorialFragment";
+ private static final String KEY_TUTORIAL_STEP = "tutorialStep";
+ private static final String KEY_TUTORIAL_TYPE = "tutorialType";
+ private static final String SYSTEM_NAVIGATION_SETTING_INTENT =
+ "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S"
+ + ".:settings:fragment_args_key=gesture_system_navigation_input_summary;S"
+ + ".:settings:show_fragment=com.android.settings.gestures"
+ + ".SystemNavigationGestureSettings;end";
+
+ private TutorialStep mTutorialStep;
+ private TutorialType mTutorialType;
+ private Optional<BackGestureTutorialController> mTutorialController = Optional.empty();
+ private View mRootView;
+ private BackGestureTutorialHandAnimation mHandCoachingAnimation;
+
+ public static BackGestureTutorialFragment newInstance(
+ TutorialStep tutorialStep, TutorialType tutorialType) {
+ BackGestureTutorialFragment fragment = new BackGestureTutorialFragment();
+ Bundle args = new Bundle();
+ args.putSerializable(KEY_TUTORIAL_STEP, tutorialStep);
+ args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
+ mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP);
+ mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment,
+ container, /* attachToRoot= */ false);
+ mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button)
+ .setOnClickListener(this::onCloseButtonClicked);
+ mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView);
+
+ return mRootView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ changeController(mTutorialStep, mTutorialType);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHandCoachingAnimation.stop();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep);
+ savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType);
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ View getRootView() {
+ return mRootView;
+ }
+
+ BackGestureTutorialHandAnimation getHandAnimation() {
+ return mHandCoachingAnimation;
+ }
+
+ void changeController(TutorialStep tutorialStep) {
+ changeController(tutorialStep, mTutorialType);
+ }
+
+ void changeController(TutorialStep tutorialStep, TutorialType tutorialType) {
+ Optional<BackGestureTutorialController> tutorialController =
+ BackGestureTutorialController.getTutorialController(/* fragment= */ this,
+ tutorialStep, tutorialType);
+ if (!tutorialController.isPresent()) {
+ return;
+ }
+
+ mTutorialController = tutorialController;
+ mTutorialController.get().transitToController();
+ this.mTutorialStep = mTutorialController.get().mTutorialStep;
+ this.mTutorialType = tutorialType;
+ }
+
+ void onBackPressed() {
+ if (mTutorialController.isPresent()) {
+ mTutorialController.get().onGestureDetected();
+ }
+ }
+
+ void closeTutorial() {
+ getActivity().finish();
+ }
+
+ void startSystemNavigationSetting() {
+ try {
+ startActivityForResult(
+ Intent.parseUri(SYSTEM_NAVIGATION_SETTING_INTENT, /* flags= */ 0),
+ /* requestCode= */ 0);
+ } catch (URISyntaxException e) {
+ Log.e(LOG_TAG, "The launch Intent Uri is wrong syntax: " + e);
+ } catch (ActivityNotFoundException e) {
+ Log.e(LOG_TAG, "The launch Activity not found: " + e);
+ }
+ }
+
+ private void onCloseButtonClicked(View button) {
+ closeTutorial();
+ }
+
+ /** Denotes the step of the tutorial. */
+ enum TutorialStep {
+ ENGAGED,
+ CONFIRM,
+ }
+
+ /** Denotes the type of the tutorial. */
+ enum TutorialType {
+ RIGHT_EDGE_BACK_NAVIGATION,
+ LEFT_EDGE_BACK_NAVIGATION,
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java
new file mode 100644
index 0000000..d03811d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import android.content.Context;
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+import java.time.Duration;
+
+/** Hand coaching animation. */
+final class BackGestureTutorialHandAnimation {
+
+ // A delay for waiting the Activity fully launches.
+ private static final Duration ANIMATION_START_DELAY = Duration.ofMillis(300L);
+
+ private final ImageView mHandCoachingView;
+ private final AnimatedVectorDrawable mGestureAnimation;
+
+ private boolean mIsAnimationPlayed = false;
+
+ BackGestureTutorialHandAnimation(Context context, View rootView) {
+ mHandCoachingView = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_hand_coaching);
+ mGestureAnimation = (AnimatedVectorDrawable) ContextCompat.getDrawable(context,
+ R.drawable.back_gesture);
+ }
+
+ boolean isRunning() {
+ return mGestureAnimation.isRunning();
+ }
+
+ /**
+ * Starts animation if the playground is launched for the first time.
+ */
+ void maybeStartLoopedAnimation(TutorialType tutorialType) {
+ if (isRunning() || mIsAnimationPlayed) {
+ return;
+ }
+
+ mIsAnimationPlayed = true;
+ clearAnimationCallbacks();
+ mGestureAnimation.registerAnimationCallback(
+ new Animatable2.AnimationCallback() {
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ super.onAnimationEnd(drawable);
+ mGestureAnimation.start();
+ }
+ });
+ start(tutorialType);
+ }
+
+ private void start(TutorialType tutorialType) {
+ // Because the gesture animation has only the right side form.
+ // The left side form of the gesture animation is made from flipping the View.
+ float rotationY = tutorialType == TutorialType.LEFT_EDGE_BACK_NAVIGATION ? 180f : 0f;
+ mHandCoachingView.setRotationY(rotationY);
+ mHandCoachingView.setImageDrawable(mGestureAnimation);
+ mHandCoachingView.postDelayed(() -> mGestureAnimation.start(),
+ ANIMATION_START_DELAY.toMillis());
+ }
+
+ private void clearAnimationCallbacks() {
+ mGestureAnimation.clearAnimationCallbacks();
+ }
+
+ void stop() {
+ mIsAnimationPlayed = false;
+ clearAnimationCallbacks();
+ mGestureAnimation.stop();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java
new file mode 100644
index 0000000..ac8443d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+/** Defines the UI element identifiers for the particular {@link TutorialType}. */
+final class BackGestureTutorialTypeInfo {
+
+ private final TutorialType mTutorialType;
+ private final int mTutorialPlaygroundTitleId;
+ private final int mTutorialEngagedSubtitleId;
+ private final int mTutorialConfirmTitleId;
+ private final int mTutorialConfirmSubtitleId;
+
+ TutorialType getTutorialType() {
+ return mTutorialType;
+ }
+
+ int getTutorialPlaygroundTitleId() {
+ return mTutorialPlaygroundTitleId;
+ }
+
+ int getTutorialEngagedSubtitleId() {
+ return mTutorialEngagedSubtitleId;
+ }
+
+ int getTutorialConfirmTitleId() {
+ return mTutorialConfirmTitleId;
+ }
+
+ int getTutorialConfirmSubtitleId() {
+ return mTutorialConfirmSubtitleId;
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ private BackGestureTutorialTypeInfo(
+ TutorialType tutorialType,
+ int tutorialPlaygroundTitleId,
+ int tutorialEngagedSubtitleId,
+ int tutorialConfirmTitleId,
+ int tutorialConfirmSubtitleId) {
+ mTutorialType = tutorialType;
+ mTutorialPlaygroundTitleId = tutorialPlaygroundTitleId;
+ mTutorialEngagedSubtitleId = tutorialEngagedSubtitleId;
+ mTutorialConfirmTitleId = tutorialConfirmTitleId;
+ mTutorialConfirmSubtitleId = tutorialConfirmSubtitleId;
+ }
+
+ /** Builder for producing {@link BackGestureTutorialTypeInfo} objects. */
+ static class Builder {
+
+ private TutorialType mTutorialType;
+ private Integer mTutorialPlaygroundTitleId;
+ private Integer mTutorialEngagedSubtitleId;
+ private Integer mTutorialConfirmTitleId;
+ private Integer mTutorialConfirmSubtitleId;
+
+ Builder setTutorialType(TutorialType tutorialType) {
+ mTutorialType = tutorialType;
+ return this;
+ }
+
+ Builder setTutorialPlaygroundTitleId(int stringId) {
+ mTutorialPlaygroundTitleId = stringId;
+ return this;
+ }
+
+ Builder setTutorialEngagedSubtitleId(int stringId) {
+ mTutorialEngagedSubtitleId = stringId;
+ return this;
+ }
+
+ Builder setTutorialConfirmTitleId(int stringId) {
+ mTutorialConfirmTitleId = stringId;
+ return this;
+ }
+
+ Builder setTutorialConfirmSubtitleId(int stringId) {
+ mTutorialConfirmSubtitleId = stringId;
+ return this;
+ }
+
+ BackGestureTutorialTypeInfo build() {
+ return new BackGestureTutorialTypeInfo(
+ mTutorialType,
+ mTutorialPlaygroundTitleId,
+ mTutorialEngagedSubtitleId,
+ mTutorialConfirmTitleId,
+ mTutorialConfirmSubtitleId);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java
new file mode 100644
index 0000000..9575d83
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+/** Provides instances of {@link BackGestureTutorialTypeInfo} for each {@link TutorialType}. */
+final class BackGestureTutorialTypeInfoProvider {
+
+ private static final BackGestureTutorialTypeInfo RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO =
+ BackGestureTutorialTypeInfo.builder()
+ .setTutorialType(TutorialType.RIGHT_EDGE_BACK_NAVIGATION)
+ .setTutorialPlaygroundTitleId(
+ R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge)
+ .setTutorialEngagedSubtitleId(
+ R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge)
+ .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title)
+ .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle)
+ .build();
+
+ private static final BackGestureTutorialTypeInfo LEFT_EDGE_BACK_NAV_TUTORIAL_INFO =
+ BackGestureTutorialTypeInfo.builder()
+ .setTutorialType(TutorialType.LEFT_EDGE_BACK_NAVIGATION)
+ .setTutorialPlaygroundTitleId(
+ R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge)
+ .setTutorialEngagedSubtitleId(
+ R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge)
+ .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title)
+ .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle)
+ .build();
+
+ static BackGestureTutorialTypeInfo getTutorialTypeInfo(TutorialType tutorialType) {
+ switch (tutorialType) {
+ case RIGHT_EDGE_BACK_NAVIGATION:
+ return RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO;
+ case LEFT_EDGE_BACK_NAVIGATION:
+ return LEFT_EDGE_BACK_NAV_TUTORIAL_INFO;
+ default:
+ throw new AssertionError("Unexpected tutorial type: " + tutorialType);
+ }
+ }
+
+ private BackGestureTutorialTypeInfoProvider() {
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
new file mode 100644
index 0000000..8081ad7
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.View;
+import android.view.Window;
+
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+import java.util.List;
+import java.util.Optional;
+
+/** Shows the gesture interactive sandbox in full screen mode. */
+public class GestureSandboxActivity extends FragmentActivity {
+
+ Optional<BackGestureTutorialFragment> mFragment = Optional.empty();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.back_gesture_tutorial_activity);
+
+ mFragment = Optional.of(BackGestureTutorialFragment.newInstance(TutorialStep.ENGAGED,
+ TutorialType.RIGHT_EDGE_BACK_NAVIGATION));
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.back_gesture_tutorial_fragment_container, mFragment.get())
+ .commit();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ disableSystemGestures();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus) {
+ hideSystemUI();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mFragment.isPresent()) {
+ mFragment.get().onBackPressed();
+ }
+ }
+
+ private void hideSystemUI() {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN);
+ getWindow().setNavigationBarColor(Color.TRANSPARENT);
+ }
+
+ private void disableSystemGestures() {
+ Display display = getDisplay();
+ if (display != null) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ getWindow().setSystemGestureExclusionRects(
+ List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 8e5ed1a..22fe2e1 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -17,32 +17,40 @@
package com.android.quickstep.logging;
import static android.stats.launcher.nano.Launcher.ALLAPPS;
+import static android.stats.launcher.nano.Launcher.BACKGROUND;
+import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
import static android.stats.launcher.nano.Launcher.HOME;
import static android.stats.launcher.nano.Launcher.LAUNCH_APP;
import static android.stats.launcher.nano.Launcher.LAUNCH_TASK;
-import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
-import static android.stats.launcher.nano.Launcher.BACKGROUND;
import static android.stats.launcher.nano.Launcher.OVERVIEW;
+import static com.android.launcher3.logging.UserEventDispatcher.makeTargetsList;
+
import android.content.Context;
import android.content.Intent;
+import android.os.UserHandle;
import android.stats.launcher.nano.Launcher;
import android.stats.launcher.nano.LauncherExtension;
import android.stats.launcher.nano.LauncherTarget;
import android.util.Log;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.ItemInfo;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogUtils;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
-import com.android.systemui.shared.system.StatsLogCompat;
+import com.android.systemui.shared.system.SysUiStatsLog;
+
import com.google.protobuf.nano.MessageNano;
+import java.util.ArrayList;
+
/**
* This method calls the StatsLog hidden method until they are made available public.
*
@@ -57,10 +65,11 @@
private static final String TAG = "StatsLogCompatManager";
private static final boolean DEBUG = false;
- public StatsLogCompatManager(Context context) { }
+ public StatsLogCompatManager(Context context) {
+ }
@Override
- public void logAppLaunch(View v, Intent intent) {
+ public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
LauncherExtension ext = new LauncherExtension();
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
int srcState = mStateProvider.getCurrentState();
@@ -68,8 +77,8 @@
if (ext.srcTarget[0] != null) {
ext.srcTarget[0].item = LauncherTarget.APP_ICON;
}
- StatsLogCompat.write(LAUNCH_APP, srcState, BACKGROUND /* dstState */,
- MessageNano.toByteArray(ext), true);
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_APP, srcState,
+ BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
}
@Override
@@ -78,8 +87,8 @@
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
int srcState = OVERVIEW;
fillInLauncherExtension(v, ext);
- StatsLogCompat.write(LAUNCH_TASK, srcState, BACKGROUND /* dstState */,
- MessageNano.toByteArray(ext), true);
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_TASK, srcState,
+ BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
}
@Override
@@ -88,8 +97,8 @@
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
int srcState = OVERVIEW;
fillInLauncherExtension(v, ext);
- StatsLogCompat.write(DISMISS_TASK, srcState, BACKGROUND /* dstState */,
- MessageNano.toByteArray(ext), true);
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, DISMISS_TASK, srcState,
+ BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
}
@Override
@@ -99,7 +108,7 @@
int srcState = mStateProvider.getCurrentState();
fillInLauncherExtensionWithPageId(ext, pageId);
int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT;
- StatsLogCompat.write(launcherAction, srcState, srcState,
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, launcherAction, srcState, srcState,
MessageNano.toByteArray(ext), true);
}
@@ -116,14 +125,17 @@
return false;
}
- ItemInfo itemInfo = (ItemInfo) v.getTag();
Target child = new Target();
- Target parent = new Target();
- provider.fillInLogContainerData(v, itemInfo, child, parent);
- extension.srcTarget[0] = new LauncherTarget();
- extension.srcTarget[1] = new LauncherTarget();
- copy(child, extension.srcTarget[0]);
- copy(parent, extension.srcTarget[1]);
+ ArrayList<Target> targets = makeTargetsList(child);
+ targets.add(child);
+ provider.fillInLogContainerData((ItemInfo) v.getTag(), child, targets);
+
+ int maxDepth = Math.min(SUPPORTED_TARGET_DEPTH, targets.size());
+ extension.srcTarget = new LauncherTarget[maxDepth];
+ for (int i = 0; i < maxDepth; i++) {
+ extension.srcTarget[i] = new LauncherTarget();
+ copy(targets.get(i), extension.srcTarget[i]);
+ }
return true;
}
@@ -234,10 +246,10 @@
@Override
public void verify() {
- if(!(StatsLogUtils.LAUNCHER_STATE_ALLAPPS == ALLAPPS &&
- StatsLogUtils.LAUNCHER_STATE_BACKGROUND == BACKGROUND &&
- StatsLogUtils.LAUNCHER_STATE_OVERVIEW == OVERVIEW &&
- StatsLogUtils.LAUNCHER_STATE_HOME == HOME)) {
+ if (!(StatsLogUtils.LAUNCHER_STATE_ALLAPPS == ALLAPPS
+ && StatsLogUtils.LAUNCHER_STATE_BACKGROUND == BACKGROUND
+ && StatsLogUtils.LAUNCHER_STATE_OVERVIEW == OVERVIEW
+ && StatsLogUtils.LAUNCHER_STATE_HOME == HOME)) {
throw new IllegalStateException(
"StatsLogUtil constants doesn't match enums in launcher.proto");
}
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
new file mode 100644
index 0000000..b1c72ce
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
+
+import java.util.function.BiPredicate;
+
+public class ActivityInitListener<T extends BaseActivity> implements SchedulerCallback<T> {
+
+ private final BiPredicate<T, Boolean> mOnInitListener;
+ private final ActivityTracker<T> mActivityTracker;
+
+ /**
+ * @param onInitListener a callback made when the activity is initialized. The callback should
+ * return true to continue receiving callbacks (ie. for if the activity is
+ * recreated).
+ */
+ public ActivityInitListener(BiPredicate<T, Boolean> onInitListener,
+ ActivityTracker<T> tracker) {
+ mOnInitListener = onInitListener;
+ mActivityTracker = tracker;
+ }
+
+ @Override
+ public boolean init(T activity, boolean alreadyOnHome) {
+ return mOnInitListener.test(activity, alreadyOnHome);
+ }
+
+ /**
+ * Registers the activity-created listener. If the activity is already created, then the
+ * callback provided in the constructor will be called synchronously.
+ */
+ public void register() {
+ mActivityTracker.schedule(this);
+ }
+
+ public void unregister() {
+ mActivityTracker.clearReference(this);
+ }
+
+ public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+ Context context, Handler handler, long duration) {
+ register();
+
+ Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
+ context.startActivity(addToIntent(new Intent((intent))), options);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/BinderTracker.java b/quickstep/src/com/android/quickstep/util/BinderTracker.java
index 32d0d53..cb04e5b 100644
--- a/quickstep/src/com/android/quickstep/util/BinderTracker.java
+++ b/quickstep/src/com/android/quickstep/util/BinderTracker.java
@@ -31,7 +31,7 @@
private static final String TAG = "BinderTracker";
public static void start() {
- if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (!FeatureFlags.IS_STUDIO_BUILD) {
Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
return;
}
@@ -40,7 +40,7 @@
}
public static void stop() {
- if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (!FeatureFlags.IS_STUDIO_BUILD) {
Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
return;
}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 2e118b4..ba99016 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -15,6 +15,9 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
@@ -30,6 +33,9 @@
import java.lang.annotation.Retention;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
public class LayoutUtils {
private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
@@ -57,9 +63,16 @@
} else {
Resources res = context.getResources();
- extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
- + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
- + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+ //TODO: this needs to account for the swipe gesture height and accessibility
+ // UI when shown.
+ extraSpace = 0;
+ } else {
+ extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+ + res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+ }
}
calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
}
@@ -74,6 +87,7 @@
float taskWidth, taskHeight, paddingHorz;
Resources res = context.getResources();
Rect insets = dp.getInsets();
+ final boolean overviewActionsEnabled = ENABLE_OVERVIEW_ACTIONS.get();
if (dp.isMultiWindowMode) {
if (multiWindowStrategy == MULTI_WINDOW_STRATEGY_HALF_SCREEN) {
@@ -99,13 +113,23 @@
} else {
taskWidth = dp.availableWidthPx;
taskHeight = dp.availableHeightPx;
- paddingHorz = res.getDimension(dp.isVerticalBarLayout()
- ? R.dimen.landscape_task_card_horz_space
- : R.dimen.portrait_task_card_horz_space);
+
+ final int paddingResId;
+ if (dp.isVerticalBarLayout()) {
+ paddingResId = R.dimen.landscape_task_card_horz_space;
+ } else if (overviewActionsEnabled && removeShelfFromOverview(context)) {
+ paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
+ } else {
+ paddingResId = R.dimen.portrait_task_card_horz_space;
+ }
+ paddingHorz = res.getDimension(paddingResId);
}
float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
- float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
+ float bottomMargin = thumbnailBottomMargin(context);
+
+ float paddingVert = overviewActionsEnabled && removeShelfFromOverview(context)
+ ? 0 : res.getDimension(R.dimen.task_card_vert_space);
// Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
// we override the insets ourselves.
@@ -113,7 +137,7 @@
int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
float availableHeight = launcherVisibleHeight
- - topIconMargin - extraVerticalSpace - paddingVert;
+ - topIconMargin - extraVerticalSpace - paddingVert - bottomMargin;
float availableWidth = launcherVisibleWidth - paddingHorz;
float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
@@ -123,16 +147,33 @@
// Center in the visible space
float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
float y = insets.top + Math.max(topIconMargin,
- (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
+ (launcherVisibleHeight - extraVerticalSpace - outHeight - bottomMargin) / 2);
outRect.set(Math.round(x), Math.round(y),
Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
}
public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
// Track the bottom of the window.
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+ Rect taskSize = new Rect();
+ calculateLauncherTaskSize(context, dp, taskSize);
+ return (dp.heightPx - taskSize.height()) / 2;
+ }
int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
R.dimen.task_card_vert_space);
return shelfHeight + spaceBetweenShelfAndRecents;
}
+
+ /**
+ * Get the margin that the task thumbnail view should use.
+ * @return the margin in pixels.
+ */
+ public static int thumbnailBottomMargin(Context context) {
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+ return context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height);
+ } else {
+ return 0;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 801a560..7d52571 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LSQ_VELOCITY_PROVIDER;
+
import android.content.Context;
import android.content.res.Resources;
import android.view.MotionEvent;
@@ -37,8 +39,7 @@
private static final long FORCE_PAUSE_TIMEOUT = 300;
/**
- * After {@link #makePauseHarderToTrigger()}, must
- * move slowly for this long to trigger a pause.
+ * After {@link #mMakePauseHarderToTrigger}, must move slowly for this long to trigger a pause.
*/
private static final long HARDER_TRIGGER_TIMEOUT = 400;
@@ -49,13 +50,10 @@
private final Alarm mForcePauseTimeout;
private final boolean mMakePauseHarderToTrigger;
private final Context mContext;
+ private final VelocityProvider mVelocityProvider;
- private Long mPreviousTime = null;
- private Float mPreviousPosition = null;
private Float mPreviousVelocity = null;
- private Float mFirstPosition = null;
-
private OnMotionPauseListener mOnMotionPauseListener;
private boolean mIsPaused;
// Bias more for the first pause to make it feel extra responsive.
@@ -73,6 +71,13 @@
* @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
*/
public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger) {
+ this(context, makePauseHarderToTrigger, MotionEvent.AXIS_Y);
+ }
+
+ /**
+ * @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
+ */
+ public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger, int axis) {
mContext = context;
Resources res = context.getResources();
mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
@@ -82,6 +87,8 @@
mForcePauseTimeout = new Alarm();
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
mMakePauseHarderToTrigger = makePauseHarderToTrigger;
+ mVelocityProvider = ENABLE_LSQ_VELOCITY_PROVIDER.get()
+ ? new LSqVelocityProvider(axis) : new LinearVelocityProvider(axis);
}
/**
@@ -101,28 +108,26 @@
/**
* Computes velocity and acceleration to determine whether the motion is paused.
- * @param position The x or y component of the motion being tracked.
- *
- * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
+ * @param ev The motion being tracked.
*/
- public void addPosition(float position, long time) {
- if (mFirstPosition == null) {
- mFirstPosition = position;
- }
+ public void addPosition(MotionEvent ev) {
+ addPosition(ev, 0);
+ }
+
+ /**
+ * Computes velocity and acceleration to determine whether the motion is paused.
+ * @param ev The motion being tracked.
+ * @param pointerIndex Index for the pointer being tracked in the motion event
+ */
+ public void addPosition(MotionEvent ev, int pointerIndex) {
mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
? HARDER_TRIGGER_TIMEOUT
: FORCE_PAUSE_TIMEOUT);
- if (mPreviousTime != null && mPreviousPosition != null) {
- long changeInTime = Math.max(1, time - mPreviousTime);
- float changeInPosition = position - mPreviousPosition;
- float velocity = changeInPosition / changeInTime;
- if (mPreviousVelocity != null) {
- checkMotionPaused(velocity, mPreviousVelocity, time);
- }
- mPreviousVelocity = velocity;
+ Float newVelocity = mVelocityProvider.addMotionEvent(ev, pointerIndex);
+ if (newVelocity != null && mPreviousVelocity != null) {
+ checkMotionPaused(newVelocity, mPreviousVelocity, ev.getEventTime());
}
- mPreviousTime = time;
- mPreviousPosition = position;
+ mPreviousVelocity = newVelocity;
}
private void checkMotionPaused(float velocity, float prevVelocity, long time) {
@@ -178,10 +183,8 @@
}
public void clear() {
- mPreviousTime = null;
- mPreviousPosition = null;
+ mVelocityProvider.clear();
mPreviousVelocity = null;
- mFirstPosition = null;
setOnMotionPauseListener(null);
mIsPaused = mHasEverBeenPaused = false;
mSlowStartTime = 0;
@@ -195,4 +198,188 @@
public interface OnMotionPauseListener {
void onMotionPauseChanged(boolean isPaused);
}
+
+ /**
+ * Interface to abstract out velocity calculations
+ */
+ protected interface VelocityProvider {
+
+ /**
+ * Adds a new motion events, and returns the velocity at this point, or null if
+ * the velocity is not available
+ */
+ Float addMotionEvent(MotionEvent ev, int pointer);
+
+ /**
+ * Clears all stored motion event records
+ */
+ void clear();
+ }
+
+ private static class LinearVelocityProvider implements VelocityProvider {
+
+ private Long mPreviousTime = null;
+ private Float mPreviousPosition = null;
+
+ private final int mAxis;
+
+ LinearVelocityProvider(int axis) {
+ mAxis = axis;
+ }
+
+ @Override
+ public Float addMotionEvent(MotionEvent ev, int pointer) {
+ long time = ev.getEventTime();
+ float position = ev.getAxisValue(mAxis, pointer);
+ Float velocity = null;
+
+ if (mPreviousTime != null && mPreviousPosition != null) {
+ long changeInTime = Math.max(1, time - mPreviousTime);
+ float changeInPosition = position - mPreviousPosition;
+ velocity = changeInPosition / changeInTime;
+ }
+ mPreviousTime = time;
+ mPreviousPosition = position;
+ return velocity;
+ }
+
+ @Override
+ public void clear() {
+ mPreviousTime = null;
+ mPreviousPosition = null;
+ }
+ }
+
+ /**
+ * Java implementation of {@link android.view.VelocityTracker} using the Least Square (deg 2)
+ * algorithm.
+ */
+ private static class LSqVelocityProvider implements VelocityProvider {
+
+ // Maximum age of a motion event to be considered when calculating the velocity.
+ private static final long HORIZON_MS = 100;
+ // Number of samples to keep.
+ private static final int HISTORY_SIZE = 20;
+
+ // Position history are stored in a circular array
+ private final float[] mHistoricTimes = new float[HISTORY_SIZE];
+ private final float[] mHistoricPos = new float[HISTORY_SIZE];
+ private int mHistoryCount = 0;
+ private int mHistoryStart = 0;
+
+ private final int mAxis;
+
+ LSqVelocityProvider(int axis) {
+ mAxis = axis;
+ }
+
+ @Override
+ public void clear() {
+ mHistoryCount = mHistoryStart = 0;
+ }
+
+ private void addPositionAndTime(float eventTime, float eventPosition) {
+ mHistoricTimes[mHistoryStart] = eventTime;
+ mHistoricPos[mHistoryStart] = eventPosition;
+ mHistoryStart++;
+ if (mHistoryStart >= HISTORY_SIZE) {
+ mHistoryStart = 0;
+ }
+ mHistoryCount = Math.min(HISTORY_SIZE, mHistoryCount + 1);
+ }
+
+ @Override
+ public Float addMotionEvent(MotionEvent ev, int pointer) {
+ // Add all historic points
+ int historyCount = ev.getHistorySize();
+ for (int i = 0; i < historyCount; i++) {
+ addPositionAndTime(
+ ev.getHistoricalEventTime(i), ev.getHistoricalAxisValue(mAxis, pointer, i));
+ }
+
+ // Start index for the last position (about to be added)
+ int eventStartIndex = mHistoryStart;
+ addPositionAndTime(ev.getEventTime(), ev.getAxisValue(mAxis, pointer));
+ return solveUnweightedLeastSquaresDeg2(eventStartIndex);
+ }
+
+ /**
+ * Solves the instantaneous velocity.
+ * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp
+ */
+ private Float solveUnweightedLeastSquaresDeg2(final int pointPos) {
+ final float eventTime = mHistoricTimes[pointPos];
+
+ float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
+ int count = 0;
+ for (int i = 0; i < mHistoryCount; i++) {
+ int index = pointPos - i;
+ if (index < 0) {
+ index += HISTORY_SIZE;
+ }
+
+ float time = mHistoricTimes[index];
+ float age = eventTime - time;
+ if (age > HORIZON_MS) {
+ break;
+ }
+ count++;
+ float xi = -age;
+
+ float yi = mHistoricPos[index];
+ float xi2 = xi * xi;
+ float xi3 = xi2 * xi;
+ float xi4 = xi3 * xi;
+ float xiyi = xi * yi;
+ float xi2yi = xi2 * yi;
+
+ sxi += xi;
+ sxi2 += xi2;
+ sxiyi += xiyi;
+ sxi2yi += xi2yi;
+ syi += yi;
+ sxi3 += xi3;
+ sxi4 += xi4;
+ }
+
+ if (count < 3) {
+ // Too few samples
+ if (count == 2) {
+ int endPos = pointPos - 1;
+ if (endPos < 0) {
+ endPos += HISTORY_SIZE;
+ }
+ float denominator = eventTime - mHistoricTimes[endPos];
+ if (denominator != 0) {
+ return (eventTime - mHistoricPos[endPos]) / denominator;
+
+ }
+ }
+ return null;
+ }
+
+ float Sxx = sxi2 - sxi * sxi / count;
+ float Sxy = sxiyi - sxi * syi / count;
+ float Sxx2 = sxi3 - sxi * sxi2 / count;
+ float Sx2y = sxi2yi - sxi2 * syi / count;
+ float Sx2x2 = sxi4 - sxi2 * sxi2 / count;
+
+ float denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
+ if (denominator == 0) {
+ // division by 0 when computing velocity
+ return null;
+ }
+ // Compute a
+ // float numerator = Sx2y*Sxx - Sxy*Sxx2;
+
+ // Compute b
+ float numerator = Sxy * Sx2x2 - Sx2y * Sxx2;
+ float b = numerator / denominator;
+
+ // Compute c
+ // float c = syi/count - b * sxi/count - a * sxi2/count;
+
+ return b;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
new file mode 100644
index 0000000..8dc19dc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.Surface;
+
+import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.SysUINavigationMode;
+
+/**
+ * Utility class to check nav bar position.
+ */
+public class NavBarPosition {
+
+ public static final RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
+ @Override
+ public void mapRect(int left, int top, int right, int bottom, Rect out) {
+ out.left = top;
+ out.top = right;
+ out.right = bottom;
+ out.bottom = left;
+ }
+
+ @Override
+ public void mapInsets(Context context, Rect insets, Rect out) {
+ // If there is a display cutout, the top insets in portrait would also include the
+ // cutout, which we will get as the left inset in landscape. Using the max of left and
+ // top allows us to cover both cases (with or without cutout).
+ if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
+ out.top = Math.max(insets.top, insets.left);
+ out.bottom = Math.max(insets.right, insets.bottom);
+ out.left = out.right = 0;
+ } else {
+ out.top = Math.max(insets.top, insets.left);
+ out.bottom = insets.right;
+ out.left = insets.bottom;
+ out.right = 0;
+ }
+ }
+ };
+
+ public static final RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
+ @Override
+ public void mapRect(int left, int top, int right, int bottom, Rect out) {
+ out.left = bottom;
+ out.top = left;
+ out.right = top;
+ out.bottom = right;
+ }
+
+ @Override
+ public void mapInsets(Context context, Rect insets, Rect out) {
+ if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
+ out.top = Math.max(insets.top, insets.right);
+ out.bottom = Math.max(insets.left, insets.bottom);
+ out.left = out.right = 0;
+ } else {
+ out.top = Math.max(insets.top, insets.right);
+ out.bottom = insets.left;
+ out.right = insets.bottom;
+ out.left = 0;
+ }
+ }
+
+ @Override
+ public int toNaturalGravity(int absoluteGravity) {
+ int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ if (horizontalGravity == Gravity.RIGHT) {
+ horizontalGravity = Gravity.LEFT;
+ } else if (horizontalGravity == Gravity.LEFT) {
+ horizontalGravity = Gravity.RIGHT;
+ }
+
+ if (verticalGravity == Gravity.TOP) {
+ verticalGravity = Gravity.BOTTOM;
+ } else if (verticalGravity == Gravity.BOTTOM) {
+ verticalGravity = Gravity.TOP;
+ }
+
+ return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
+ & ~Gravity.VERTICAL_GRAVITY_MASK)
+ | horizontalGravity | verticalGravity;
+ }
+ };
+
+ private final SysUINavigationMode.Mode mMode;
+ private final int mDisplayRotation;
+
+ public NavBarPosition(SysUINavigationMode.Mode mode, DefaultDisplay.Info info) {
+ mMode = mode;
+ mDisplayRotation = info.rotation;
+ }
+
+ public boolean isRightEdge() {
+ return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
+ }
+
+ public boolean isLeftEdge() {
+ return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
+ }
+
+ public RotationMode getRotationMode() {
+ return isLeftEdge() ? ROTATION_SEASCAPE
+ : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 4503a43..6520c4f 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -31,16 +31,17 @@
static final int Z_BOOST_BASE = 800570000;
- AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
+ AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets);
default ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
false /* startAtFrontOfQueue */) {
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
- AnimationResult result) {
- result.setAnimation(createWindowAnimation(targetCompats), context);
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+ result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
}
};
return ActivityOptionsCompat.makeRemoteAnimation(
@@ -67,4 +68,26 @@
? Z_BOOST_BASE + target.prefixOrderIndex
: target.prefixOrderIndex;
}
+
+ /**
+ * @return the target with the lowest opaque layer for a certain app animation, or null.
+ */
+ static RemoteAnimationTargetCompat findLowestOpaqueLayerTarget(
+ RemoteAnimationTargetCompat[] appTargets, int mode) {
+ int lowestLayer = Integer.MAX_VALUE;
+ int lowestLayerIndex = -1;
+ for (int i = appTargets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = appTargets[i];
+ if (target.mode == mode && !target.isTranslucent) {
+ int layer = getLayer(target, mode);
+ if (layer < lowestLayer) {
+ lowestLayer = layer;
+ lowestLayerIndex = i;
+ }
+ }
+ }
+ return lowestLayerIndex != -1
+ ? appTargets[lowestLayerIndex]
+ : null;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
index 40dd74b..fa2d338 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
@@ -21,6 +21,7 @@
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import com.android.quickstep.RemoteAnimationTargets;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TransactionCompat;
@@ -29,11 +30,12 @@
*/
public class RemoteFadeOutAnimationListener implements AnimatorUpdateListener {
- private final RemoteAnimationTargetSet mTarget;
+ private final RemoteAnimationTargets mTarget;
private boolean mFirstFrame = true;
- public RemoteFadeOutAnimationListener(RemoteAnimationTargetCompat[] targets) {
- mTarget = new RemoteAnimationTargetSet(targets, MODE_CLOSING);
+ public RemoteFadeOutAnimationListener(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets) {
+ mTarget = new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_CLOSING);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
new file mode 100644
index 0000000..d87feec
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.util.Log;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+import java.util.LinkedHashMap;
+import java.util.function.Predicate;
+
+/**
+ * A simple LRU cache for task key entries
+ * @param <V> The type of the value
+ */
+public class TaskKeyLruCache<V> {
+
+ private final MyLinkedHashMap<V> mMap;
+
+ public TaskKeyLruCache(int maxSize) {
+ mMap = new MyLinkedHashMap<>(maxSize);
+ }
+
+ /**
+ * Removes all entries from the cache
+ */
+ public synchronized void evictAll() {
+ mMap.clear();
+ }
+
+ /**
+ * Removes a particular entry from the cache
+ */
+ public synchronized void remove(TaskKey key) {
+ mMap.remove(key.id);
+ }
+
+ /**
+ * Removes all entries matching keyCheck
+ */
+ public synchronized void removeAll(Predicate<TaskKey> keyCheck) {
+ mMap.entrySet().removeIf(e -> keyCheck.test(e.getValue().mKey));
+ }
+
+ /**
+ * Gets the entry if it is still valid
+ */
+ public synchronized V getAndInvalidateIfModified(TaskKey key) {
+ Entry<V> entry = mMap.get(key.id);
+
+ if (entry != null && entry.mKey.windowingMode == key.windowingMode
+ && entry.mKey.lastActiveTime == key.lastActiveTime) {
+ return entry.mValue;
+ } else {
+ remove(key);
+ return null;
+ }
+ }
+
+ /**
+ * Adds an entry to the cache, optionally evicting the last accessed entry
+ */
+ public final synchronized void put(TaskKey key, V value) {
+ if (key != null && value != null) {
+ mMap.put(key.id, new Entry<>(key, value));
+ } else {
+ Log.e("TaskKeyCache", "Unexpected null key or value: " + key + ", " + value);
+ }
+ }
+
+ /**
+ * Updates the cache entry if it is already present in the cache
+ */
+ public synchronized void updateIfAlreadyInCache(int taskId, V data) {
+ Entry<V> entry = mMap.get(taskId);
+ if (entry != null) {
+ entry.mValue = data;
+ }
+ }
+
+ private static class Entry<V> {
+
+ final TaskKey mKey;
+ V mValue;
+
+ Entry(TaskKey key, V value) {
+ mKey = key;
+ mValue = value;
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey.id;
+ }
+ }
+
+ private static class MyLinkedHashMap<V> extends LinkedHashMap<Integer, Entry<V>> {
+
+ private final int mMaxSize;
+
+ MyLinkedHashMap(int maxSize) {
+ super(0, 0.75f, true /* accessOrder */);
+ mMaxSize = maxSize;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Entry<Integer, TaskKeyLruCache.Entry<V>> eldest) {
+ return size() > mMaxSize;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 3320dae..14c458e 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -35,19 +35,20 @@
import android.util.AttributeSet;
import android.view.animation.Interpolator;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.ShelfPeekAnim;
/**
* Scrim used for all-apps and shelf in Overview
@@ -56,7 +57,8 @@
* From normal state to overview state, the shelf just fades in and does not move
* From overview state to all-apps state the shelf moves up and fades in to cover the screen
*/
-public class ShelfScrimView extends ScrimView implements NavigationModeChangeListener {
+public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
+ implements NavigationModeChangeListener {
// If the progress is more than this, shelf follows the finger, otherwise it moves faster to
// cover the whole screen
@@ -155,12 +157,19 @@
mRemainingScreenPathValid = false;
mShiftRange = mLauncher.getAllAppsController().getShiftRange();
+ Context context = getContext();
if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
- mMidProgress = 1;
mDragHandleProgress = 1;
- mMidAlpha = 0;
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
+ && SysUINavigationMode.removeShelfFromOverview(context)) {
+ // Fade in all apps background quickly to distinguish from swiping from nav bar.
+ mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
+ mMidProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
+ } else {
+ mMidAlpha = 0;
+ mMidProgress = 1;
+ }
} else {
- Context context = getContext();
mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
mMidProgress = OVERVIEW.getVerticalProgress(mLauncher);
Rect hotseatPadding = dp.getHotseatLayoutPadding();
@@ -197,12 +206,10 @@
if (mProgress >= 1) {
mRemainingScreenColor = 0;
mShelfColor = 0;
- ShelfPeekAnim shelfPeekAnim = ((QuickstepAppTransitionManagerImpl)
- mLauncher.getAppTransitionManager()).getShelfPeekAnim();
LauncherState state = mLauncher.getStateManager().getState();
if (mSysUINavigationMode == Mode.NO_BUTTON
&& (state == BACKGROUND_APP || state == QUICK_SWITCH)
- && shelfPeekAnim.isPeeking()) {
+ && mLauncher.getShelfPeekAnim().isPeeking()) {
// Show the shelf background when peeking during swipe up.
mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
}
@@ -231,6 +238,13 @@
}
@Override
+ protected boolean shouldDragHandleBeVisible() {
+ boolean twoZoneSwipeModel = FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
+ && SysUINavigationMode.removeShelfFromOverview(mLauncher);
+ return twoZoneSwipeModel || super.shouldDragHandleBeVisible();
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas);
drawDragHandle(canvas);
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index 7801775..8e4762d 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -24,6 +24,7 @@
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.os.Process;
import android.view.View;
@@ -35,7 +36,6 @@
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.model.AppLaunchTracker;
import org.junit.After;
@@ -60,7 +60,7 @@
public void setUp() throws Exception {
super.setUp();
- List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(mTargetContext)
+ List<LauncherActivityInfo> activities = mTargetContext.getSystemService(LauncherApps.class)
.getActivityList(null, Process.myUserHandle());
mSampleApp1 = activities.get(0);
mSampleApp2 = activities.get(1);
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index a7c33a9..ccfa3fc 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -11,6 +11,7 @@
import android.app.PendingIntent;
import android.app.usage.UsageStatsManager;
import android.content.Intent;
+import android.os.Build;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -33,6 +34,9 @@
@Test
public void testToast() throws Exception {
+ // b/150303529
+ if (Build.MODEL.contains("Cuttlefish")) return;
+
startAppFast(CALCULATOR_PACKAGE);
final UsageStatsManager usageStatsManager =
@@ -68,7 +72,7 @@
private DigitalWellBeingToast getToast() {
executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW));
- waitForState("Launcher internal state didn't switch to Overview", OVERVIEW);
+ waitForState("Launcher internal state didn't switch to Overview", () -> OVERVIEW);
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 8c11c1c..a726052 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -22,14 +22,22 @@
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_ACTIVITY_TIMEOUT;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT;
import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.startAppFast;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.startTestActivity;
+import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
-import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -41,12 +49,19 @@
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.tapl.BaseOverview;
import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.OverviewTask;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.FailureRewriterRule;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+import com.android.quickstep.views.RecentsView;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
@@ -54,11 +69,11 @@
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
-/**
- * TODO: Fix fallback when quickstep is enabled
- */
public class FallbackRecentsTest {
private final UiDevice mDevice;
@@ -80,10 +95,16 @@
mDevice = UiDevice.getInstance(instrumentation);
mDevice.setOrientationNatural();
mLauncher = new LauncherInstrumentation();
+ mLauncher.enableCheckEventsForSuccessfulGestures();
- mOrderSensitiveRules = RuleChain.
- outerRule(new NavigationModeSwitchRule(mLauncher)).
- around(new FailureWatcher(mDevice));
+ if (TestHelpers.isInLauncherProcess()) {
+ Utilities.enableRunningInTestHarnessForTests();
+ }
+
+ mOrderSensitiveRules = RuleChain
+ .outerRule(new FailureRewriterRule())
+ .around(new NavigationModeSwitchRule(mLauncher))
+ .around(new FailureWatcher(mDevice));
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
getHomeIntentInPackage(context),
@@ -111,8 +132,9 @@
}
}
- @NavigationModeSwitch(mode = THREE_BUTTON)
+ @NavigationModeSwitch
@Test
+ @Ignore // b/143488140
public void goToOverviewFromHome() {
mDevice.pressHome();
assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
@@ -121,23 +143,97 @@
mLauncher.getBackground().switchToOverview();
}
- @NavigationModeSwitch(mode = THREE_BUTTON)
+ @NavigationModeSwitch
@Test
+ @Ignore // b/143488140
public void goToOverviewFromApp() {
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
mLauncher.getBackground().switchToOverview();
}
- private void startAppFast(String packageName) {
- final Instrumentation instrumentation = getInstrumentation();
- final Intent intent = instrumentation.getContext().getPackageManager().
- getLaunchIntentForPackage(packageName);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- instrumentation.getTargetContext().startActivity(intent);
- assertTrue(packageName + " didn't start",
- mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), WAIT_TIME_MS));
+ protected void executeOnRecents(Consumer<RecentsActivity> f) {
+ getFromRecents(r -> {
+ f.accept(r);
+ return true;
+ });
}
+ protected <T> T getFromRecents(Function<RecentsActivity, T> f) {
+ if (!TestHelpers.isInLauncherProcess()) return null;
+ Object[] result = new Object[1];
+ Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> {
+ RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
+ if (activity == null) {
+ return false;
+ }
+ result[0] = f.apply(activity);
+ return true;
+ }).get(), DEFAULT_UI_TIMEOUT, mLauncher);
+ return (T) result[0];
+ }
+
+ private BaseOverview pressHomeAndGoToOverview() {
+ mDevice.pressHome();
+ return mLauncher.getBackground().switchToOverview();
+ }
+
+ @NavigationModeSwitch
+ @Test
+ @Ignore // b/143488140
+ public void testOverview() {
+ startAppFast(getAppPackageName());
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ startTestActivity(2);
+ Wait.atMost("Expected three apps in the task list",
+ () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
+
+ BaseOverview overview = mLauncher.getBackground().switchToOverview();
+ executeOnRecents(recents ->
+ assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3));
+
+ // Test flinging forward and backward.
+ overview.flingForward();
+ final Integer currentTaskAfterFlingForward = getFromRecents(this::getCurrentOverviewPage);
+ executeOnRecents(recents -> assertTrue("Current task in Overview is still 0",
+ currentTaskAfterFlingForward > 0));
+
+ overview.flingBackward();
+ executeOnRecents(recents -> assertTrue("Flinging back in Overview did nothing",
+ getCurrentOverviewPage(recents) < currentTaskAfterFlingForward));
+
+ // Test opening a task.
+ overview = pressHomeAndGoToOverview();
+
+ OverviewTask task = overview.getCurrentTask();
+ assertNotNull("overview.getCurrentTask() returned null (1)", task);
+ assertNotNull("OverviewTask.open returned null", task.open());
+ assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
+ By.pkg(getAppPackageName()).text("TestActivity2")),
+ DEFAULT_UI_TIMEOUT));
+
+
+ // Test dismissing a task.
+ overview = pressHomeAndGoToOverview();
+ final Integer numTasks = getFromRecents(this::getTaskCount);
+ task = overview.getCurrentTask();
+ assertNotNull("overview.getCurrentTask() returned null (2)", task);
+ task.dismiss();
+ executeOnRecents(
+ recents -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+ numTasks - 1, getTaskCount(recents)));
+
+ // Test dismissing all tasks.
+ pressHomeAndGoToOverview().dismissAllTasks();
+ assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
+ mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
+ }
+
+ private int getCurrentOverviewPage(RecentsActivity recents) {
+ return recents.<RecentsView>getOverviewPanel().getCurrentPage();
+ }
+
+ private int getTaskCount(RecentsActivity recents) {
+ return recents.<RecentsView>getOverviewPanel().getTaskViewCount();
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index c2197ab..4ac815e 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -30,10 +30,13 @@
import android.content.pm.PackageManager;
import android.util.Log;
+import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.systemui.shared.system.QuickStepContract;
@@ -46,7 +49,6 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
/**
* Test rule that allows executing a test with Quickstep on and then Quickstep off.
@@ -56,6 +58,8 @@
static final String TAG = "QuickStepOnOffRule";
+ public static final int WAIT_TIME_MS = 10000;
+
public enum Mode {
THREE_BUTTON, TWO_BUTTON, ZERO_BUTTON, ALL
}
@@ -69,6 +73,9 @@
private final LauncherInstrumentation mLauncher;
+ static final SysUINavigationMode SYS_UI_NAVIGATION_MODE =
+ SysUINavigationMode.INSTANCE.get(getInstrumentation().getTargetContext());
+
public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
mLauncher = launcher;
}
@@ -79,26 +86,13 @@
description.getAnnotation(NavigationModeSwitch.class) != null) {
Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
return new Statement() {
- private void assertTrue(String message, boolean condition) {
- if(!condition) {
- final AssertionError assertionError = new AssertionError(message);
- FailureWatcher.onError(mLauncher.getDevice(), description, assertionError);
- throw assertionError;
- }
- }
-
@Override
public void evaluate() throws Throwable {
mLauncher.enableDebugTracing();
final Context context = getInstrumentation().getContext();
final int currentInteractionMode =
LauncherInstrumentation.getCurrentInteractionMode(context);
- final String prevOverlayPkg =
- QuickStepContract.isGesturalMode(currentInteractionMode)
- ? NAV_BAR_MODE_GESTURAL_OVERLAY
- : QuickStepContract.isSwipeUpMode(currentInteractionMode)
- ? NAV_BAR_MODE_2BUTTON_OVERLAY
- : NAV_BAR_MODE_3BUTTON_OVERLAY;
+ final String prevOverlayPkg = getCurrentOverlayPackage(currentInteractionMode);
final LauncherInstrumentation.NavigationModel originalMode =
mLauncher.getNavigationModel();
try {
@@ -111,114 +105,51 @@
if (mode == THREE_BUTTON || mode == ALL) {
evaluateWithThreeButtons();
}
- } catch (Exception e) {
- Log.e(TAG, "Exception", e);
+ } catch (Throwable e) {
+ Log.e(TAG, "Error", e);
throw e;
} finally {
- assertTrue("Couldn't set overlay",
- setActiveOverlay(prevOverlayPkg, originalMode));
+ Log.d(TAG, "In Finally block");
+ assertTrue(mLauncher, "Couldn't set overlay",
+ setActiveOverlay(mLauncher, prevOverlayPkg, originalMode,
+ description), description);
}
}
private void evaluateWithThreeButtons() throws Throwable {
- if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
- LauncherInstrumentation.NavigationModel.THREE_BUTTON)) {
+ if (setActiveOverlay(mLauncher, NAV_BAR_MODE_3BUTTON_OVERLAY,
+ LauncherInstrumentation.NavigationModel.THREE_BUTTON, description)) {
base.evaluate();
}
}
private void evaluateWithTwoButtons() throws Throwable {
- if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
- LauncherInstrumentation.NavigationModel.TWO_BUTTON)) {
+ if (setActiveOverlay(mLauncher, NAV_BAR_MODE_2BUTTON_OVERLAY,
+ LauncherInstrumentation.NavigationModel.TWO_BUTTON, description)) {
base.evaluate();
}
}
private void evaluateWithZeroButtons() throws Throwable {
- if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
- LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) {
+ if (setActiveOverlay(mLauncher, NAV_BAR_MODE_GESTURAL_OVERLAY,
+ LauncherInstrumentation.NavigationModel.ZERO_BUTTON, description)) {
base.evaluate();
}
}
-
- private boolean packageExists(String packageName) {
- try {
- PackageManager pm = getInstrumentation().getContext().getPackageManager();
- if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
- return false;
- }
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- return true;
- }
-
- private boolean setActiveOverlay(String overlayPackage,
- LauncherInstrumentation.NavigationModel expectedMode) throws Exception {
- if (!packageExists(overlayPackage)) {
- Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
- return false;
- }
-
- setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
- overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
- setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
- overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
- setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
- overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
-
- if (currentSysUiNavigationMode() != expectedMode) {
- final CountDownLatch latch = new CountDownLatch(1);
- final Context targetContext = getInstrumentation().getTargetContext();
- final SysUINavigationMode.NavigationModeChangeListener listener =
- newMode -> {
- if (LauncherInstrumentation.getNavigationModel(newMode.resValue)
- == expectedMode) {
- latch.countDown();
- }
- };
- final SysUINavigationMode sysUINavigationMode =
- SysUINavigationMode.INSTANCE.get(targetContext);
- targetContext.getMainExecutor().execute(() ->
- sysUINavigationMode.addModeChangeListener(listener));
- latch.await(60, TimeUnit.SECONDS);
- targetContext.getMainExecutor().execute(() ->
- sysUINavigationMode.removeModeChangeListener(listener));
- assertTrue("Navigation mode didn't change to " + expectedMode,
- currentSysUiNavigationMode() == expectedMode);
- }
-
- for (int i = 0; i != 100; ++i) {
- if (mLauncher.getNavigationModel() == expectedMode) break;
- Thread.sleep(100);
- }
- assertTrue("Couldn't switch to " + overlayPackage,
- mLauncher.getNavigationModel() == expectedMode);
-
- for (int i = 0; i != 100; ++i) {
- if (mLauncher.getNavigationModeMismatchError() == null) break;
- Thread.sleep(100);
- }
- final String error = mLauncher.getNavigationModeMismatchError();
- assertTrue("Switching nav mode: " + error, error == null);
-
- Thread.sleep(5000);
- return true;
- }
-
- private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
- throws Exception {
- Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
- final String action = enable ? "enable" : "disable";
- UiDevice.getInstance(getInstrumentation()).executeShellCommand(
- "cmd overlay " + action + " " + overlayPackage);
- }
};
} else {
return base;
}
}
+ public static String getCurrentOverlayPackage(int currentInteractionMode) {
+ return QuickStepContract.isGesturalMode(currentInteractionMode)
+ ? NAV_BAR_MODE_GESTURAL_OVERLAY
+ : QuickStepContract.isSwipeUpMode(currentInteractionMode)
+ ? NAV_BAR_MODE_2BUTTON_OVERLAY
+ : NAV_BAR_MODE_3BUTTON_OVERLAY;
+ }
+
private static LauncherInstrumentation.NavigationModel currentSysUiNavigationMode() {
return LauncherInstrumentation.getNavigationModel(
SysUINavigationMode.getMode(
@@ -226,4 +157,93 @@
getTargetContext()).
resValue);
}
+
+ public static boolean setActiveOverlay(LauncherInstrumentation launcher, String overlayPackage,
+ LauncherInstrumentation.NavigationModel expectedMode, Description description)
+ throws Exception {
+ if (!packageExists(overlayPackage)) {
+ Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
+ return false;
+ }
+
+ setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
+ setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
+ setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+
+ if (currentSysUiNavigationMode() != expectedMode) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Context targetContext = getInstrumentation().getTargetContext();
+ final SysUINavigationMode.NavigationModeChangeListener listener =
+ newMode -> {
+ if (LauncherInstrumentation.getNavigationModel(newMode.resValue)
+ == expectedMode) {
+ latch.countDown();
+ }
+ };
+ targetContext.getMainExecutor().execute(() ->
+ SYS_UI_NAVIGATION_MODE.addModeChangeListener(listener));
+ // b/139137636
+// latch.await(60, TimeUnit.SECONDS);
+ targetContext.getMainExecutor().execute(() ->
+ SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
+
+ Wait.atMost(() -> "Navigation mode didn't change to " + expectedMode,
+ () -> currentSysUiNavigationMode() == expectedMode, 60000 /* b/148422894 */,
+ launcher);
+ // b/139137636
+// assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
+// currentSysUiNavigationMode() == expectedMode, description);
+
+ }
+
+ Wait.atMost("Couldn't switch to " + overlayPackage,
+ () -> launcher.getNavigationModel() == expectedMode, WAIT_TIME_MS, launcher);
+
+ Wait.atMost(() -> "Switching nav mode: "
+ + launcher.getNavigationModeMismatchError(),
+ () -> launcher.getNavigationModeMismatchError() == null,
+ 60000 /* b/148422894 */, launcher);
+ AbstractLauncherUiTest.checkDetectedLeaks();
+ return true;
+ }
+
+ private static void setOverlayPackageEnabled(String overlayPackage, boolean enable)
+ throws Exception {
+ Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
+ final String action = enable ? "enable" : "disable";
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+ "cmd overlay " + action + " " + overlayPackage);
+ }
+
+ private static boolean packageExists(String packageName) {
+ try {
+ PackageManager pm = getInstrumentation().getContext().getPackageManager();
+ if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ private static void assertTrue(LauncherInstrumentation launcher, String message,
+ boolean condition, Description description) {
+ if (launcher.getDevice().hasObject(By.textStartsWith(""))) {
+ // The condition above is "screen is not empty". We are not treating
+ // "Screen is empty" as an anomaly here. It's an acceptable state when
+ // Launcher just starts under instrumentation.
+ launcher.checkForAnomaly();
+ }
+ if (!condition) {
+ final AssertionError assertionError = new AssertionError(message);
+ if (description != null) {
+ FailureWatcher.onError(launcher.getDevice(), description, assertionError);
+ }
+ throw assertionError;
+ }
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 2111e2c..3d048a6 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -16,8 +16,8 @@
package com.android.quickstep;
-import static com.android.launcher3.util.RaceConditionTracker.enterEvt;
-import static com.android.launcher3.util.RaceConditionTracker.exitEvt;
+import static com.android.launcher3.util.RaceConditionReproducer.enterEvt;
+import static com.android.launcher3.util.RaceConditionReproducer.exitEvt;
import android.content.Intent;
@@ -25,6 +25,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.Launcher;
+import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.util.RaceConditionReproducer;
import com.android.quickstep.NavigationModeSwitchRule.Mode;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 8cd3bb6..75fcfe2 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertTrue;
import android.content.Intent;
-import android.os.RemoteException;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -44,7 +43,6 @@
import com.android.quickstep.views.RecentsView;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -69,26 +67,21 @@
}
@Test
- @PortraitLandscape
- @Ignore // Enable after b/131115533
- public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
- mDevice.pressRecentApps();
- waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
-
- assertNotNull("getOverview() returned null", mLauncher.getOverview());
- }
-
- @Test
@NavigationModeSwitch
@PortraitLandscape
public void testWorkspaceSwitchToAllApps() {
assertNotNull("switchToAllApps() returned null",
mLauncher.getWorkspace().switchToAllApps());
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
}
@Test
public void testAllAppsFromOverview() throws Exception {
+ if (!mLauncher.hasAllAppsInOverview()) {
+ return;
+ }
+
// Test opening all apps from Overview.
assertNotNull("switchToAllApps() returned null",
mLauncher.getWorkspace().switchToOverview().switchToAllApps());
@@ -100,9 +93,10 @@
@PortraitLandscape
public void testOverview() throws Exception {
startTestApps();
+ // mLauncher.pressHome() also tests an important case of pressing home while in background.
Overview overview = mLauncher.pressHome().switchToOverview();
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
executeOnLauncher(
launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
@@ -111,14 +105,16 @@
0, getCurrentOverviewPage(launcher)));
overview.flingForward();
- assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
final Integer currentTaskAfterFlingForward = getFromLauncher(
launcher -> getCurrentOverviewPage(launcher));
executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
currentTaskAfterFlingForward > 0));
overview.flingBackward();
- assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
@@ -137,7 +133,7 @@
// Test dismissing a task.
overview = mLauncher.pressHome().switchToOverview();
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
task = overview.getCurrentTask();
assertNotNull("overview.getCurrentTask() returned null (2)", task);
@@ -146,35 +142,35 @@
launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
numTasks - 1, getTaskCount(launcher)));
- if (!TestHelpers.isInLauncherProcess() ||
- getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape)) {
+ if (mLauncher.hasAllAppsInOverview() && (!TestHelpers.isInLauncherProcess()
+ || getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape))) {
// Test switching to all apps and back.
final AllAppsFromOverview allApps = overview.switchToAllApps();
assertNotNull("overview.switchToAllApps() returned null (1)", allApps);
assertTrue("Launcher internal state is not All Apps (1)",
- isInState(LauncherState.ALL_APPS));
+ isInState(() -> LauncherState.ALL_APPS));
overview = allApps.switchBackToOverview();
assertNotNull("allApps.switchBackToOverview() returned null", overview);
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
// Test UIDevice.pressBack()
overview.switchToAllApps();
assertNotNull("overview.switchToAllApps() returned null (2)", allApps);
assertTrue("Launcher internal state is not All Apps (2)",
- isInState(LauncherState.ALL_APPS));
+ isInState(() -> LauncherState.ALL_APPS));
mDevice.pressBack();
mLauncher.getOverview();
}
// Test UIDevice.pressHome, once we are in AllApps.
mDevice.pressHome();
- waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
// Test dismissing all tasks.
mLauncher.getWorkspace().switchToOverview().dismissAllTasks();
- waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
executeOnLauncher(
launcher -> assertEquals("Still have tasks after dismissing all",
0, getTaskCount(launcher)));
@@ -190,9 +186,14 @@
@Test
public void testAppIconLaunchFromAllAppsFromOverview() throws Exception {
+ if (!mLauncher.hasAllAppsInOverview()) {
+ return;
+ }
+
final AllApps allApps =
mLauncher.getWorkspace().switchToOverview().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
TaplTestsLauncher3.runIconLaunchFromAllAppsTest(this, allApps);
}
@@ -204,7 +205,7 @@
assertNotNull("Workspace.switchToOverview() returned null",
mLauncher.pressHome().switchToOverview());
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
}
@Test
@@ -216,7 +217,7 @@
assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
}
private Background getAndAssertBackground() {
@@ -239,9 +240,11 @@
TaplTestsLauncher3.runAllAppsTest(this, mLauncher.getAllApps());
// Testing pressHome.
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
assertNotNull("pressHome returned null", mLauncher.pressHome());
- assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+ assertTrue("Launcher internal state is not Home",
+ isInState(() -> LauncherState.NORMAL));
assertNotNull("getHome returned null", mLauncher.getWorkspace());
}
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
new file mode 100644
index 0000000..f8f22a1
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
+import static com.android.launcher3.ui.widget.BindWidgetTest.createWidgetInfo;
+import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.Suppress;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.tapl.Background;
+import com.android.launcher3.testcomponent.ListViewService;
+import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
+import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.TestViewHelpers;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.function.IntConsumer;
+
+/**
+ * Test to verify view inflation does not happen during swipe up.
+ * To verify view inflation, we setup a dummy ViewConfiguration and check if any call to that class
+ * does from a View.init method or not.
+ *
+ * Alternative approaches considered:
+ * Overriding LayoutInflater: This does not cover views initialized
+ * directly (ex: new LinearLayout)
+ * Using ExtendedMockito: Mocking static methods from platform classes (loaded in zygote) makes
+ * the main thread extremely slow and untestable
+ *
+ * Suppressed until b/141579810 is resolved
+ */
+@Suppress
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
+
+ private ContentResolver mResolver;
+ private SparseArray<ViewConfiguration> mConfigMap;
+ private InitTracker mInitTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // Workaround for b/142351228, when there are no activities, the system may not destroy the
+ // activity correctly for activities under instrumentation, which can leave two concurrent
+ // activities, which changes the order in which the activities are cleaned up (overlapping
+ // stop and start) leading to all sort of issues. To workaround this, ensure that the test
+ // is started only after starting another app.
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+
+ TaplTestsLauncher3.initialize(this);
+
+ mResolver = mTargetContext.getContentResolver();
+ LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+ // Get static configuration map
+ Field field = ViewConfiguration.class.getDeclaredField("sConfigurations");
+ field.setAccessible(true);
+ mConfigMap = (SparseArray<ViewConfiguration>) field.get(null);
+
+ mInitTracker = new InitTracker();
+ }
+
+ @Test
+ @NavigationModeSwitch(mode = ZERO_BUTTON)
+ public void testSwipeUpFromApp() throws Exception {
+ try {
+ // Go to overview once so that all views are initialized and cached
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ mLauncher.getBackground().switchToOverview().dismissAllTasks();
+
+ // Track view creations
+ mInitTracker.startTracking();
+
+ startTestActivity(2);
+ mLauncher.getBackground().switchToOverview();
+
+ assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
+ } finally {
+ mConfigMap.clear();
+ }
+ }
+
+ @Test
+ @NavigationModeSwitch(mode = ZERO_BUTTON)
+ public void testSwipeUpFromApp_widget_update() {
+ String dummyText = "Some random dummy text";
+
+ executeSwipeUpTestWithWidget(
+ widgetId -> { },
+ widgetId -> AppWidgetManager.getInstance(getContext())
+ .updateAppWidget(widgetId, createMainWidgetViews(dummyText)),
+ dummyText);
+ }
+
+ @Test
+ @NavigationModeSwitch(mode = ZERO_BUTTON)
+ public void testSwipeUp_with_list_widgets() {
+ SimpleViewsFactory viewFactory = new SimpleViewsFactory();
+ viewFactory.viewCount = 1;
+ Bundle args = new Bundle();
+ args.putBinder(EXTRA_VALUE, viewFactory.toBinder());
+ TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, args);
+
+ try {
+ executeSwipeUpTestWithWidget(
+ widgetId -> {
+ // Initialize widget
+ RemoteViews views = createMainWidgetViews("List widget title");
+ views.setRemoteAdapter(android.R.id.list,
+ new Intent(getContext(), ListViewService.class));
+ AppWidgetManager.getInstance(getContext()).updateAppWidget(widgetId, views);
+ verifyWidget(viewFactory.getLabel(0));
+ },
+ widgetId -> {
+ // Update widget
+ viewFactory.viewCount = 2;
+ AppWidgetManager.getInstance(getContext())
+ .notifyAppWidgetViewDataChanged(widgetId, android.R.id.list);
+ },
+ viewFactory.getLabel(1)
+ );
+ } finally {
+ TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, new Bundle());
+ }
+ }
+
+ private void executeSwipeUpTestWithWidget(IntConsumer widgetIdCreationCallback,
+ IntConsumer updateBeforeSwipeUp, String finalWidgetText) {
+ try {
+ // Clear all existing data
+ LauncherSettings.Settings.call(mResolver,
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+ LauncherSettings.Settings.call(mResolver,
+ LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+ LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+
+ addItemToScreen(item);
+ assertTrue("Widget is not present",
+ mLauncher.pressHome().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
+ int widgetId = item.appWidgetId;
+
+ // Verify widget id
+ widgetIdCreationCallback.accept(widgetId);
+
+ // Go to overview once so that all views are initialized and cached
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ mLauncher.getBackground().switchToOverview().dismissAllTasks();
+
+ // Track view creations
+ mInitTracker.startTracking();
+
+ startTestActivity(2);
+ Background background = mLauncher.getBackground();
+
+ // Update widget
+ updateBeforeSwipeUp.accept(widgetId);
+
+ background.switchToOverview();
+ assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
+
+ // Widget is updated when going home
+ mInitTracker.disableLog();
+ mLauncher.pressHome();
+ verifyWidget(finalWidgetText);
+ assertNotEquals(1, mInitTracker.viewInitCount);
+ } finally {
+ mConfigMap.clear();
+ }
+ }
+
+ private void verifyWidget(String text) {
+ assertNotNull("Widget not updated",
+ UiDevice.getInstance(getInstrumentation())
+ .wait(Until.findObject(By.text(text)), DEFAULT_UI_TIMEOUT));
+ }
+
+ private RemoteViews createMainWidgetViews(String title) {
+ Context c = getContext();
+ int layoutId = c.getResources().getIdentifier(
+ "test_layout_widget_list", "layout", c.getPackageName());
+ RemoteViews views = new RemoteViews(c.getPackageName(), layoutId);
+ views.setTextViewText(android.R.id.text1, title);
+ return views;
+ }
+
+ private class InitTracker implements Answer {
+
+ public int viewInitCount = 0;
+
+ public boolean log = true;
+
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ Exception ex = new Exception();
+
+ boolean found = false;
+ for (StackTraceElement ste : ex.getStackTrace()) {
+ if ("<init>".equals(ste.getMethodName())
+ && View.class.getName().equals(ste.getClassName())) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ viewInitCount++;
+ if (log) {
+ Log.d("InitTracker", "New view inflated", ex);
+ }
+
+ }
+ return invocation.callRealMethod();
+ }
+
+ public void disableLog() {
+ log = false;
+ }
+
+ public void startTracking() {
+ ViewConfiguration vc = ViewConfiguration.get(mTargetContext);
+ ViewConfiguration spyVC = spy(vc);
+ mConfigMap.put(mConfigMap.keyAt(mConfigMap.indexOfValue(vc)), spyVC);
+ doAnswer(this).when(spyVC).getScaledTouchSlop();
+ }
+ }
+}
diff --git a/res/drawable-hdpi/work_tab_user_education.png b/res/drawable-hdpi/work_tab_user_education.png
deleted file mode 100644
index 1879dfb..0000000
--- a/res/drawable-hdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/work_tab_user_education.png b/res/drawable-mdpi/work_tab_user_education.png
deleted file mode 100644
index 65c7e63..0000000
--- a/res/drawable-mdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/work_tab_user_education.png b/res/drawable-xhdpi/work_tab_user_education.png
deleted file mode 100644
index 59df7a8..0000000
--- a/res/drawable-xhdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/work_tab_user_education.png b/res/drawable-xxhdpi/work_tab_user_education.png
deleted file mode 100644
index 3c6aa20..0000000
--- a/res/drawable-xxhdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/bg_all_apps_button.xml b/res/drawable/bg_all_apps_button.xml
new file mode 100644
index 0000000..169a468
--- /dev/null
+++ b/res/drawable/bg_all_apps_button.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="oval" >
+ <solid android:color="#ffffffff" />
+ </shape>
+ </item>
+
+ <item>
+ <shape android:shape="oval" >
+ <solid android:color="?android:attr/colorAccent" />
+ </shape>
+ </item>
+</ripple>
diff --git a/go/quickstep/res/drawable/empty_content_box.xml b/res/drawable/bottom_sheet_top_border.xml
similarity index 61%
rename from go/quickstep/res/drawable/empty_content_box.xml
rename to res/drawable/bottom_sheet_top_border.xml
index a488388..23f4e51 100644
--- a/go/quickstep/res/drawable/empty_content_box.xml
+++ b/res/drawable/bottom_sheet_top_border.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 The Android Open Source Project
+<!-- Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,10 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@android:color/transparent"/>
- <stroke android:color="@android:color/white" android:width="4px"/>
- <corners android:radius="2dp"/>
-</shape>
\ No newline at end of file
+<vector android:height="15.53398dp" android:viewportHeight="32"
+ android:viewportWidth="412" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M412,32v-2.64C349.26,10.51 279.5,0 206,0S62.74,10.51 0,29.36V32H412z"/>
+</vector>
diff --git a/SecondaryDisplayLauncher/res/drawable/ic_apps.xml b/res/drawable/ic_apps.xml
similarity index 100%
rename from SecondaryDisplayLauncher/res/drawable/ic_apps.xml
rename to res/drawable/ic_apps.xml
diff --git a/res/drawable/ic_block.xml b/res/drawable/ic_block.xml
new file mode 100644
index 0000000..edeb4c6
--- /dev/null
+++ b/res/drawable/ic_block.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:tint="?android:attr/textColorPrimary">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M4,12c0-4.42,3.58-8,8-8c1.85,0,3.55,0.63,4.9,1.69
+ L5.69,16.9C4.63,15.55,4,13.85,4,12z M12,20c-1.85,0-3.55-0.63-4.9-1.69L18.31,7.1C19.37,8.45,20,10.15,20,12
+ C20,16.42,16.42,20,12,20z" />
+</vector>
diff --git a/res/drawable/ic_corp.xml b/res/drawable/ic_corp.xml
index b59113d..76dccd3 100644
--- a/res/drawable/ic_corp.xml
+++ b/res/drawable/ic_corp.xml
@@ -19,9 +19,5 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?android:attr/textColorHint" >
- <path
- android:pathData="M20 6h-4V4c0-1.11-0.89-2-2-2h-4c-1.11 0-2 0.89-2 2v2H4c-1.11 0-1.99 0.89 -1.99
-2L2 19c0 1.11 0.89 2 2 2h16c1.11 0 2-0.89 2-2V8c0-1.11-0.89-2-2-2zM10
-4h4v2h-4V4zm10 15H4V8h16v11z"
- android:fillColor="@android:color/white"/>
+ <path android:fillColor="@android:color/white" android:pathData="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-8 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm2-9h-4V4h4v2z"/>
</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_corp_off.xml b/res/drawable/ic_corp_off.xml
new file mode 100644
index 0000000..62a9787
--- /dev/null
+++ b/res/drawable/ic_corp_off.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<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/textColorHint" >
+ <path
+ android:pathData="M22 7.95c.05-1.11-.84-2-1.95-1.95H16V3.95c0-1.11-.84-2-1.95-1.95h-4C8.94 1.95 8 2.84 8 3.95v.32l14 14V7.95zM14 6h-4V4h4v2zm7.54 14.28l-7.56-7.56v.01l-1.7-1.7h.01L7.21 5.95 3.25 1.99 1.99 3.27 4.69 6h-.64c-1.11 0-1.99.86-1.99 1.97l-.01 11.02c0 1.11.89 2.01 2 2.01h15.64l2.05 2.02L23 21.75l-1.46-1.47z"
+ android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_pin.xml b/res/drawable/ic_pin.xml
similarity index 100%
rename from quickstep/res/drawable/ic_pin.xml
rename to res/drawable/ic_pin.xml
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 9427ae0..a41fb9a 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -16,7 +16,7 @@
<!-- The top and bottom paddings are defined in this container, but since we want
the list view to span the full width (for touch interception purposes), we
will bake the left/right padding into that view's background itself. -->
-<com.android.launcher3.allapps.AllAppsContainerView
+<com.android.launcher3.allapps.LauncherAllAppsContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view"
android:layout_width="match_parent"
@@ -57,7 +57,6 @@
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:text="@string/all_apps_personal_tab"
- android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
@@ -68,7 +67,6 @@
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:text="@string/all_apps_work_tab"
- android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
</com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
@@ -79,4 +77,4 @@
layout="@layout/search_container_all_apps"/>
<include layout="@layout/all_apps_fast_scroller" />
-</com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
+</com.android.launcher3.allapps.LauncherAllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index cca899b..ab6c960 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -48,6 +48,10 @@
layout="@layout/overview_panel"
android:visibility="gone" />
+ <include
+ android:id="@+id/overview_actions_view"
+ layout="@layout/overview_actions_container" />
+
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
<com.android.launcher3.pageindicators.WorkspacePageIndicator
diff --git a/go/quickstep/res/drawable/clear_all_button.xml b/res/layout/overview_actions_container.xml
similarity index 77%
rename from go/quickstep/res/drawable/clear_all_button.xml
rename to res/layout/overview_actions_container.xml
index acac32d..5946bf6 100644
--- a/go/quickstep/res/drawable/clear_all_button.xml
+++ b/res/layout/overview_actions_container.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2019 The Android Open Source Project
+ Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,9 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
+<Space
xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/clear_all_button_bg"/>
- <corners android:radius="4dp"/>
-</shape>
+ android:layout_width="0dp"
+ android:layout_height="0dp" />
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index bdd5d23..2637f03 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -15,6 +15,6 @@
limitations under the License.
-->
<Space
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="0dp" />
\ No newline at end of file
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dp"
+ android:layout_height="0dp" />
\ No newline at end of file
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
new file mode 100644
index 0000000..98cfc34
--- /dev/null
+++ b/res/layout/secondary_launcher.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.secondarydisplay.SecondaryDragLayer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/drag_layer" >
+
+ <GridView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="100dp"
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_gravity="center_horizontal|top"
+ android:layout_margin="@dimen/dynamic_grid_edge_margin"
+ android:id="@+id/workspace_grid" />
+
+ <ImageButton
+ android:id="@+id/all_apps_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="40dp"
+ android:padding="16dp"
+ android:src="@drawable/ic_apps"
+ android:background="@drawable/bg_all_apps_button"
+ android:contentDescription="@string/all_apps_button_label"
+ android:onClick="onAppsButtonClicked" />
+
+ <com.android.launcher3.allapps.AllAppsContainerView
+ android:id="@+id/apps_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="true"
+ android:clipToPadding="false"
+ android:focusable="false"
+ android:saveEnabled="false"
+ android:layout_gravity="bottom|end"
+ android:background="@drawable/round_rect_primary"
+ android:elevation="2dp"
+ android:visibility="invisible" >
+
+ <include
+ layout="@layout/all_apps_rv_layout"
+ android:visibility="gone" />
+
+ <com.android.launcher3.allapps.FloatingHeaderView
+ android:id="@+id/all_apps_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/search_container_all_apps"
+ android:clipToPadding="false"
+ android:paddingTop="@dimen/all_apps_header_top_padding"
+ android:orientation="vertical" >
+
+ <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_header_tab_height"
+ android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+ android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
+ android:orientation="horizontal"
+ style="@style/TextHeadline">
+
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/all_apps_personal_tab"
+ android:textAllCaps="true"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/all_apps_work_tab"
+ android:textAllCaps="true"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+ </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+ </com.android.launcher3.allapps.FloatingHeaderView>
+
+ <com.android.launcher3.allapps.search.AppsSearchContainerLayout
+ android:id="@id/search_container_all_apps"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_search_bar_field_height"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="top|center_horizontal"
+ android:background="@drawable/bg_all_apps_searchbox"
+ android:elevation="1dp"
+ android:focusableInTouchMode="true"
+ android:gravity="center"
+ android:hint="@string/all_apps_search_bar_hint"
+ android:imeOptions="actionSearch|flagNoExtractUi"
+ android:inputType="text|textNoSuggestions|textCapWords"
+ android:maxLines="1"
+ android:padding="8dp"
+ android:saveEnabled="false"
+ android:scrollHorizontally="true"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textColorHint="@drawable/all_apps_search_hint"
+ android:textSize="16sp"
+ android:translationY="24dp" />
+
+ <include layout="@layout/all_apps_fast_scroller" />
+ </com.android.launcher3.allapps.AllAppsContainerView>
+</com.android.launcher3.secondarydisplay.SecondaryDragLayer>
\ No newline at end of file
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index a1033f0..923352e 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -41,14 +41,14 @@
android:paddingLeft="12dp"
android:paddingRight="12dp" >
- <com.android.launcher3.ExtendedEditText
+ <com.android.launcher3.folder.FolderNameEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
+ style="@style/TextHeadline"
android:layout_weight="1"
android:background="@android:color/transparent"
- android:fontFamily="sans-serif-condensed"
android:textStyle="bold"
android:gravity="center_horizontal"
android:hint="@string/folder_hint_text"
@@ -58,7 +58,7 @@
android:singleLine="true"
android:textColor="?attr/folderTextColor"
android:textColorHighlight="?android:attr/colorControlHighlight"
- android:textColorHint="?attr/folderTextColor"
+ android:textColorHint="?attr/folderHintColor"
android:textSize="@dimen/folder_label_text_size" />
<com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
new file mode 100644
index 0000000..cf1e835
--- /dev/null
+++ b/res/layout/work_apps_paused.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="48dp"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <TextView
+ style="@style/TextHeadline"
+ android:textColor="?attr/workProfileOverlayTextColor"
+ android:id="@+id/work_apps_paused_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:text="@string/work_apps_paused_title"
+ android:textAlignment="center"
+ android:textSize="20sp" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?attr/workProfileOverlayTextColor"
+ android:text="@string/work_apps_paused_body"
+ android:textAlignment="center"
+ android:textSize="16sp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
new file mode 100644
index 0000000..5506b94
--- /dev/null
+++ b/res/layout/work_profile_edu.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.views.WorkEduView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:gravity="bottom"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:background="@drawable/bottom_sheet_top_border"
+ android:backgroundTint="?android:attr/colorAccent" />
+
+ <LinearLayout
+ android:id="@+id/view_wrapper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/colorAccent"
+ android:orientation="vertical"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+ android:paddingRight="@dimen/bottom_sheet_edu_padding">
+
+ <TextView
+ android:id="@+id/content_text"
+ style="@style/TextHeadline"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="48dp"
+ android:layout_marginBottom="48dp"
+ android:gravity="center"
+ android:text="@string/work_profile_edu_personal_apps"
+ android:textAlignment="center"
+ android:textColor="@android:color/white"
+ android:textSize="20sp" />
+
+ <Button
+ android:id="@+id/proceed"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_gravity="end"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center"
+ android:text="@string/work_profile_edu_next"
+ android:textAlignment="center"
+ android:textColor="@android:color/white" />
+ </LinearLayout>
+</com.android.launcher3.views.WorkEduView>
\ No newline at end of file
diff --git a/res/layout/work_tab_bottom_user_education_view.xml b/res/layout/work_tab_bottom_user_education_view.xml
deleted file mode 100644
index ac2deeb..0000000
--- a/res/layout/work_tab_bottom_user_education_view.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.views.BottomUserEducationView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:background="?android:attr/colorAccent"
- android:elevation="2dp"
- android:focusable="true"
- android:orientation="horizontal">
-
- <ImageView
- android:layout_width="134dp"
- android:layout_height="134dp"
- android:layout_marginTop="28dp"
- android:layout_marginLeft="20dp"
- android:src="@drawable/work_tab_user_education"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="24dp"
- android:orientation="vertical">
-
- <ImageView
- android:id="@+id/close_bottom_user_tip"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginTop="12dp"
- android:layout_marginEnd="12dp"
- android:layout_gravity="right"
- android:contentDescription="@string/bottom_work_tab_user_education_close_button"
- android:src="@drawable/ic_remove_no_shadow"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:layout_marginEnd="24dp"
- android:fontFamily="roboto-medium"
- android:text="@string/bottom_work_tab_user_education_title"
- android:textColor="@android:color/white"
- android:textSize="20sp"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="24dp"
- android:text="@string/bottom_work_tab_user_education_body"
- android:textColor="@android:color/white"
- android:textSize="14sp"/>
-
- </LinearLayout>
-
-</com.android.launcher3.views.BottomUserEducationView>
\ No newline at end of file
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 379e9d0..dbcdbdb 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -17,63 +17,34 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:id="@+id/work_toggle_container"
android:focusable="true"
- android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_bottom_padding"
- android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
- android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
- android:paddingTop="@dimen/all_apps_work_profile_tab_footer_top_padding">
-
- <ImageView
- android:id="@+id/work_footer_divider"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:focusable="false"
- android:importantForAccessibility="no"
- android:paddingBottom="@dimen/all_apps_divider_margin_vertical"
- android:paddingTop="@dimen/all_apps_divider_margin_vertical"
- android:scaleType="fitXY"
- android:src="@drawable/all_apps_divider"/>
-
- <com.android.launcher3.allapps.WorkModeSwitch
- android:id="@+id/work_mode_toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/work_footer_divider"/>
+ android:orientation="horizontal"
+ android:background="?attr/allAppsScrimColor"
+ android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
+ android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
+ android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
+ android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding">
<TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
+ style="@style/PrimaryMediumText"
+ android:id="@+id/work_mode_label"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:drawableStart="@drawable/ic_corp"
+ android:drawablePadding="16dp"
+ android:drawableTint="?attr/workProfileOverlayTextColor"
+ android:textColor="?attr/workProfileOverlayTextColor"
android:layout_height="wrap_content"
- android:layout_alignBaseline="@id/work_mode_toggle"
- android:layout_alignParentStart="true"
- android:ellipsize="end"
- android:lines="1"
- android:text="@string/work_profile_toggle_label"
- android:textColor="?android:attr/textColorTertiary"
- android:textSize="16sp"/>
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_below="@android:id/title"
- android:layout_marginTop="8dp"
- android:src="@drawable/ic_corp"/>
-
- <TextView
- android:id="@+id/managed_by_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_marginTop="8dp"
- android:layout_toEndOf="@android:id/icon"
android:ellipsize="end"
android:gravity="center_vertical"
android:lines="1"
android:minHeight="24dp"
- android:paddingStart="12dp"
- android:textColor="?android:attr/textColorHint"
- android:textSize="13sp"/>
+ android:paddingEnd="12dp"
+ android:textSize="16sp"/>
+ <com.android.launcher3.allapps.WorkModeSwitch
+ android:id="@+id/work_mode_toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
</com.android.launcher3.views.WorkFooterContainer>
\ No newline at end of file
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index ba16dd3..ca59afa 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Werk"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Program is nie geïnstalleer nie."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Program is nie beskikbaar nie"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index aae55af..a80ecb0 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"العمل"</string>
<string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string>
<string name="activity_not_available" msgid="7456344436509528827">"التطبيق ليس متاحًا"</string>
@@ -91,8 +92,8 @@
<string name="notification_dots_title" msgid="9062440428204120317">"نقاط الإشعارات"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"مفعّل"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"غير مفعّل"</string>
- <string name="title_missing_notification_access" msgid="7503287056163941064">"يلزم تمكين الوصول إلى الإشعارات"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"لعرض نقاط الإشعارات، يجب تفعيل إشعارات التطبيق في <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="title_missing_notification_access" msgid="7503287056163941064">"يلزم تفعيل الوصول إلى الإشعارات"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"لعرض نقاط الإشعارات، يجب تشغيل إشعارات التطبيق في <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"تغيير الإعدادات"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"عرض نقاط الإشعارات"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"إضافة رمز إلى الشاشة الرئيسية"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 221921f..2984603 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"কৰ্মস্থান"</string>
<string name="activity_not_found" msgid="8071924732094499514">"এপটো ইনষ্টল কৰা নহ\'ল।"</string>
<string name="activity_not_available" msgid="7456344436509528827">"এপটো নাই"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index e6fe3bd..883003c 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 2ccd34f..b4cf913 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Працоўная"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Праграма не ўсталявана."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Праграма недаступная"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index f7d1d0f..408f205 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Работа"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Приложението не е инсталирано."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Приложението не е налично"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 0a72632..09608b5 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -92,7 +92,7 @@
<string name="title_change_settings" msgid="1376365968844349552">"সেটিংস পরিবর্তন করুন"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"বিজ্ঞপ্তির ডট দেখুন"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"হোম স্ক্রিনে আইকন যোগ করুন"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপের জন্য"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপ্লিকেশানগুলির জন্যে"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"অজানা"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"সরান"</string>
<string name="abandoned_search" msgid="891119232568284442">"সার্চ"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index abd0405..4ef9ec3 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -46,7 +46,7 @@
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No hi ha més espai a la safata Preferits."</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Llista d\'aplicacions"</string>
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"Llista d\'aplicacions personals"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"Llista d\'aplicacions de treball"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"Llista d\'aplicacions per a la feina"</string>
<string name="all_apps_home_button_label" msgid="252062713717058851">"Inici"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Suprimeix"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Desinstal·la"</string>
@@ -131,9 +131,9 @@
<string name="notification_dismissed" msgid="6002233469409822874">"S\'ha ignorat la notificació"</string>
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Feina"</string>
- <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de treball"</string>
- <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Cerca aplicacions de treball aquí"</string>
- <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Totes les aplicacions de treball tenen una insígnia que indica que estan protegides per la teva organització. Mou les aplicacions a la pantalla d\'inici per poder-hi accedir més fàcilment."</string>
+ <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil professional"</string>
+ <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Cerca aplicacions per a la feina aquí"</string>
+ <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Totes les aplicacions per a la feina tenen una insígnia que indica que estan protegides per la teva organització. Mou les aplicacions a la pantalla d\'inici per poder-hi accedir més fàcilment."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Gestionat per la teva organització"</string>
<string name="work_mode_off_label" msgid="3194894777601421047">"Les notificacions i les aplicacions estan desactivades"</string>
<string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Tanca"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 57c5072..692b57d 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Práce"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikace není nainstalována."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikace není k dispozici."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 9097ffd..dc17516 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Arbejde"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installeret."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgængelig"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 1602a53..a345bab 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Arbeit"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App ist nicht installiert."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App nicht verfügbar"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
deleted file mode 100644
index 7adc218..0000000
--- a/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,142 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/*
-* Copyright (C) 2008 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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
- <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
- <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
- <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
- <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
- <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
- <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
- <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
- <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
- <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
- <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
- <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
- <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch & hold to place manually"</string>
- <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
- <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
- <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
- <string name="label_application" msgid="8531721983832654978">"App"</string>
- <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
- <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Touch & hold to pick up a shortcut."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Double-tap & hold to pick up a shortcut or use custom actions."</string>
- <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
- <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
- <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
- <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
- <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
- <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
- <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
- <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
- <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
- <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
- <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
- <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not allowed to make phone calls"</string>
- <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
- <string name="gadget_setup_text" msgid="8274003207686040488">"Setup"</string>
- <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
- <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
- <string name="disabled_app_label" msgid="6673129024321402780">"Disabled <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <plurals name="dotted_app_label" formatted="false" msgid="5194538107138265416">
- <item quantity="other"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> notifications</item>
- <item quantity="one"><xliff:g id="APP_NAME_0">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g> notification</item>
- </plurals>
- <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
- <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
- <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
- <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
- <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
- <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
- <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
- <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
- <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Styles & wallpapers"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
- <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
- <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Home screen rotation"</string>
- <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
- <string name="notification_dots_title" msgid="9062440428204120317">"Notification dots"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"On"</string>
- <string name="notification_dots_desc_off" msgid="1760796511504341095">"Off"</string>
- <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
- <string name="notification_dots_service_title" msgid="4284221181793592871">"Show notification dots"</string>
- <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
- <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
- <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
- <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
- <string name="abandoned_promises_title" msgid="7096178467971716750">"This app is not installed"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
- <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
- <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
- <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
- <string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
- <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
- <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
- <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
- <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
- <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
- <string name="undo" msgid="4151576204245173321">"Undo"</string>
- <string name="action_move" msgid="4339390619886385032">"Move item"</string>
- <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
- <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
- <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
- <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
- <string name="add_to_folder" msgid="9040534766770853243">"Add to folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="add_to_folder_with_app" msgid="4534929978967147231">"Add to folder with <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="added_to_folder" msgid="4793259502305558003">"Item added to folder"</string>
- <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
- <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
- <string name="action_resize" msgid="1802976324781771067">"Re-size"</string>
- <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
- <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
- <string name="action_decrease_width" msgid="1374549771083094654">"Decrease width"</string>
- <string name="action_decrease_height" msgid="282377193880900022">"Decrease height"</string>
- <string name="widget_resized" msgid="9130327887929620">"Widget re-sized to width <xliff:g id="NUMBER_0">%1$s</xliff:g> height <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
- <string name="action_deep_shortcut" msgid="2864038805849372848">"Short cuts"</string>
- <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Shortcuts and notifications"</string>
- <string name="action_dismiss_notification" msgid="5909461085055959187">"Dismiss"</string>
- <string name="notification_dismissed" msgid="6002233469409822874">"Notification dismissed"</string>
- <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
- <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
- <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
- <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
- <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
- <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
- <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
- <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Close"</string>
- <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Closed"</string>
- <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
-</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
deleted file mode 100644
index c741482..0000000
--- a/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,142 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/*
-* Copyright (C) 2008 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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
- <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
- <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
- <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
- <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
- <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
- <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
- <string name="custom_actions" msgid="3747508247759093328">"Custom actions"</string>
- <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
- <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use custom actions."</string>
- <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
- <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
- <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch & hold to place manually"</string>
- <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
- <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
- <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
- <string name="label_application" msgid="8531721983832654978">"App"</string>
- <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
- <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Touch & hold to pick up a shortcut."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Double-tap & hold to pick up a shortcut or use custom actions."</string>
- <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
- <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favorites tray"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
- <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
- <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
- <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
- <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
- <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
- <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
- <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
- <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
- <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
- <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not allowed to make phone calls"</string>
- <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
- <string name="gadget_setup_text" msgid="8274003207686040488">"Setup"</string>
- <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
- <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
- <string name="disabled_app_label" msgid="6673129024321402780">"Disabled <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <plurals name="dotted_app_label" formatted="false" msgid="5194538107138265416">
- <item quantity="other"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> notifications</item>
- <item quantity="one"><xliff:g id="APP_NAME_0">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g> notification</item>
- </plurals>
- <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
- <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
- <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
- <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
- <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
- <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
- <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
- <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
- <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Styles & wallpapers"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
- <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
- <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Home screen rotation"</string>
- <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
- <string name="notification_dots_title" msgid="9062440428204120317">"Notification dots"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"On"</string>
- <string name="notification_dots_desc_off" msgid="1760796511504341095">"Off"</string>
- <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
- <string name="notification_dots_service_title" msgid="4284221181793592871">"Show notification dots"</string>
- <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
- <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
- <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
- <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
- <string name="abandoned_promises_title" msgid="7096178467971716750">"This app is not installed"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
- <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
- <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
- <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
- <string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
- <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
- <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
- <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
- <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
- <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
- <string name="undo" msgid="4151576204245173321">"Undo"</string>
- <string name="action_move" msgid="4339390619886385032">"Move item"</string>
- <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
- <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
- <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favorites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
- <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
- <string name="add_to_folder" msgid="9040534766770853243">"Add to folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="add_to_folder_with_app" msgid="4534929978967147231">"Add to folder with <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="added_to_folder" msgid="4793259502305558003">"Item added to folder"</string>
- <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
- <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
- <string name="action_resize" msgid="1802976324781771067">"Resize"</string>
- <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
- <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
- <string name="action_decrease_width" msgid="1374549771083094654">"Decrease width"</string>
- <string name="action_decrease_height" msgid="282377193880900022">"Decrease height"</string>
- <string name="widget_resized" msgid="9130327887929620">"Widget resized to width <xliff:g id="NUMBER_0">%1$s</xliff:g> height <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
- <string name="action_deep_shortcut" msgid="2864038805849372848">"Shortcuts"</string>
- <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Shortcuts and notifications"</string>
- <string name="action_dismiss_notification" msgid="5909461085055959187">"Dismiss"</string>
- <string name="notification_dismissed" msgid="6002233469409822874">"Notification dismissed"</string>
- <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
- <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
- <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
- <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
- <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organization. Move apps to your Home screen for easier access."</string>
- <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organization"</string>
- <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
- <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Close"</string>
- <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Closed"</string>
- <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
-</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 960ef93..d10c84b 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string>
<string name="activity_not_found" msgid="8071924732094499514">"No se instaló la aplicación."</string>
<string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 01f60e4..09b1239 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string>
<string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string>
<string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index ff121fa..1e470e1 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Töö"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Rakendus pole installitud."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Rakendus ei ole saadaval"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 913ff48..926cdb9 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"کاری"</string>
<string name="activity_not_found" msgid="8071924732094499514">"برنامه نصب نشده است."</string>
<string name="activity_not_available" msgid="7456344436509528827">"برنامه در دسترس نیست"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index e0930b6..f87441f 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Työ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Sovellusta ei ole asennettu."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Sovellus ei ole käytettävissä"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 50d6a06..5ac514d 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Lanceur3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Travail"</string>
<string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index da9da38..3228dea 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -80,7 +80,7 @@
<string name="widget_button_text" msgid="2880537293434387943">"વિજેટ્સ"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"વૉલપેપર્સ"</string>
<string name="styles_wallpaper_button_text" msgid="4342122323125579619">"શૈલીઓ અને વૉલપેપર"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"હોમ સેટિંગ"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"હોમ સેટિંગ્સ"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"તમારા વ્યવસ્થાપક દ્વારા અક્ષમ કરેલ"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"હોમ સ્ક્રીનને ફેરવવાની મંજૂરી આપો"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"જ્યારે ફોન ફેરવવામાં આવે ત્યારે"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 7e26ca4..3327590 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Munka"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Az alkalmazás nincs telepítve."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Az alkalmazás nem érhető el"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index fc04ca3..93404e6 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -50,7 +50,7 @@
<string name="all_apps_home_button_label" msgid="252062713717058851">"Հիմնական"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Ապատեղադրել"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Հեռացնել"</string>
- <string name="app_info_drop_target_label" msgid="692894985365717661">"Հավելվածի մասին"</string>
+ <string name="app_info_drop_target_label" msgid="692894985365717661">"Հավելվածի տվյալներ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Տեղադրել"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"տեղադրել դյուրանցումներ"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Ծրագրին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 9d26c8c..d66ca32 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Vinna"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Forritið er ekki uppsett."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Forritið er ekki í boði"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 1807817..8c4e3c5 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Lavoro"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App non installata."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App non disponibile"</string>
@@ -86,7 +87,7 @@
<string name="allow_rotation_desc" msgid="8662546029078692509">"Con il telefono ruotato"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Indicatori di notifica"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"On"</string>
- <string name="notification_dots_desc_off" msgid="1760796511504341095">"OFF"</string>
+ <string name="notification_dots_desc_off" msgid="1760796511504341095">"Off"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Accesso alle notifiche necessario"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Per mostrare gli indicatori di notifica, attiva le notifiche per l\'app <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Modifica impostazioni"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 3525699..c25e879 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"עבודה"</string>
<string name="activity_not_found" msgid="8071924732094499514">"האפליקציה לא מותקנת."</string>
<string name="activity_not_available" msgid="7456344436509528827">"האפליקציה אינה זמינה"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index dad4879..354f020 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"仕事用"</string>
<string name="activity_not_found" msgid="8071924732094499514">"このアプリはインストールされていません。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"このアプリは使用できません"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index b348afb..e9afef9 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"სამუშაო"</string>
<string name="activity_not_found" msgid="8071924732094499514">"აპი არ არის დაყენებული."</string>
<string name="activity_not_available" msgid="7456344436509528827">"აპი მიუწვდომელია"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index bd25ac9..1cd9045 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Жұмыс"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Қолданба орнатылмаған."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Қолданба қол жетімді емес"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index e2767f9..58b65da 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"업무"</string>
<string name="activity_not_found" msgid="8071924732094499514">"앱이 설치되지 않았습니다."</string>
<string name="activity_not_available" msgid="7456344436509528827">"앱을 사용할 수 없음"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index b266e8e..d52446c 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -88,7 +88,7 @@
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Күйүк"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Өчүк"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Эскертмелерге уруксат берилиши керек"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"Эскертме белгилерин көрсөтүү максатында, <xliff:g id="NAME">%1$s</xliff:g> үчүн колдонмонун билдирмелерин күйгүзүү керек"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"Эскертме белгилерин көрсөтүү максатында, <xliff:g id="NAME">%1$s</xliff:g> үчүн колдонмонун эскертмелерин күйгүзүү керек"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Жөндөөлөрдү өзгөртүү"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Билдирмелер белгилерин көрсөтүү"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Башкы экранга сүрөтчө кошуу"</string>
@@ -111,7 +111,7 @@
<string name="action_move" msgid="4339390619886385032">"Муну жылдыруу"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> катарга <xliff:g id="NUMBER_1">%2$s</xliff:g> тилкеге жылдыруу"</string>
<string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> орунга жылдыруу"</string>
- <string name="move_to_hotseat_position" msgid="6295412897075147808">"Тандалмаларга <xliff:g id="NUMBER">%1$s</xliff:g> жылдыруу"</string>
+ <string name="move_to_hotseat_position" msgid="6295412897075147808">"Сүйүктүүлөргө <xliff:g id="NUMBER">%1$s</xliff:g> жылдыруу"</string>
<string name="item_moved" msgid="4606538322571412879">"Нерсе жылдырылды"</string>
<string name="add_to_folder" msgid="9040534766770853243">"Куржунга кошуу: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> куржунуна кошуу"</string>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index bc658e4..662b86e 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -15,9 +15,6 @@
-->
<resources>
- <!-- Container -->
- <item name="container_margin" format="fraction" type="fraction">12%</item>
-
<!-- Fast scroll -->
<dimen name="fastscroll_popup_width">58dp</dimen>
<dimen name="fastscroll_popup_height">48dp</dimen>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index e764347..afe7664 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ວຽກ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ແອັບຯບໍ່ໄດ້ຖືກຕິດຕັ້ງ."</string>
<string name="activity_not_available" msgid="7456344436509528827">"ແອັບຯໃຊ້ບໍ່ໄດ້"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 7f93ac8..6571582 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Darbas"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Programa neįdiegta."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Programa nepasiekiama"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 13d88fe..3da0fbb 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Darbs"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Lietotne nav instalēta."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Lietotne nav pieejama."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 458fb73..ab14e89 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -80,9 +80,9 @@
<string name="widget_button_text" msgid="2880537293434387943">"Виџети"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
<string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Стилови и тапети"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Поставки за почетен екран"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Поставки за Home"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Оневозможено од администраторот"</string>
- <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на почетниот екран"</string>
+ <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на Почетниот екран"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Кога телефонот се ротира"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Точки за известување"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Вклучено"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index a118368..4362e7c 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"ലോഞ്ചർ3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ഔദ്യോഗികം"</string>
<string name="activity_not_found" msgid="8071924732094499514">"അപ്ലിക്കേഷൻ ഇൻസ്റ്റാളുചെയ്തിട്ടില്ല."</string>
<string name="activity_not_available" msgid="7456344436509528827">"അപ്ലിക്കേഷൻ ലഭ്യമല്ല"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 1b0e753..ab02ac6 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Ажил"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Апп суугаагүй байна."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Апп-г ашиглах боломжгүй"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 69eb5ee..49e3898 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"कार्य"</string>
<string name="activity_not_found" msgid="8071924732094499514">"अॅप इंस्टॉल केलेला नाही."</string>
<string name="activity_not_available" msgid="7456344436509528827">"अॅप उपलब्ध नाही"</string>
@@ -46,7 +47,7 @@
<string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"अॅप्स सूची"</string>
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"वैयक्तिक अॅप्स सूची"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"कामाच्या ठिकाणी वापरली जाणाऱ्या ॲप्सची सूची"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"कामाच्या ठिकाणी वापरली जाणाऱ्या अॅप्सची सूची"</string>
<string name="all_apps_home_button_label" msgid="252062713717058851">"होम"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"काढा"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइंस्टॉल करा"</string>
@@ -82,7 +83,7 @@
<string name="styles_wallpaper_button_text" msgid="4342122323125579619">"शैली आणि वॉलपेपर"</string>
<string name="settings_button_text" msgid="8873672322605444408">"होम सेटिंग्ज"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"आपल्या प्रशासकाने अक्षम केले"</string>
- <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्य स्क्रीन फिरविण्यास अनुमती द्या"</string>
+ <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्यस्क्रीन फिरविण्यास अनुमती द्या"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"फोन फिरविला जातो तेव्हा"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"सूचना बिंदू"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"सुरू"</string>
@@ -92,7 +93,7 @@
<string name="title_change_settings" msgid="1376365968844349552">"सेटिंग्ज बदला"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"सूचना बिंदू दाखवा"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"होम स्क्रीनवर आयकन जोडा"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन ॲप्ससाठी"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन अॅप्ससाठी"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
<string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 492e6b6..7d05412 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Kerja"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Apl tidak dipasang."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Apl tidak tersedia"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 356ed03..2257367 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Jobb"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installert."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgjengelig"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 58d61f9..1e7aee9 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"कार्य"</string>
<string name="activity_not_found" msgid="8071924732094499514">"अनुप्रयोग स्थापित छैन।"</string>
<string name="activity_not_available" msgid="7456344436509528827">"अनुप्रयोग उपलब्ध छैन"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index c2dfb71..ec30d8c 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Werk"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App is niet geïnstalleerd."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App is niet beschikbaar"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 2479d1b..4ddc903 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"ଲଞ୍ଚର୍3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"କାମ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇନାହିଁ"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
@@ -84,8 +85,8 @@
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"ହୋମ୍ ସ୍କ୍ରୀନ୍ ବୁଲାଇବା ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ଯେତେବେଳେ ଫୋନକୁ ବୁଲାଯାଇଥାଏ"</string>
- <string name="notification_dots_title" msgid="9062440428204120317">"ବିଜ୍ଞପ୍ତି ଡଟ୍ସ"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"ଚାଲୁ"</string>
+ <string name="notification_dots_title" msgid="9062440428204120317">"ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁଗୁଡ଼ିକ"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"ଚାଲୁ କରନ୍ତୁ"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"ବିଜ୍ଞପ୍ତି ଆକ୍ସେସ୍ ଆବଶ୍ୟକ ଅଟେ"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁ ଦେଖାଇବାକୁ, <xliff:g id="NAME">%1$s</xliff:g> ପାଇଁ ଆପ୍ ବିଜ୍ଞପ୍ତି ଅନ୍ କରନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 24686c4..d4cd5be 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ਦਫ਼ਤਰ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 48c8baf..6885960 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Praca"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacja nie jest zainstalowana."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacja niedostępna"</string>
@@ -129,7 +130,7 @@
<string name="widget_resized" msgid="9130327887929620">"Szerokość i wysokość widżetu zmieniła się na <xliff:g id="NUMBER_0">%1$s</xliff:g> x <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
<string name="action_deep_shortcut" msgid="2864038805849372848">"Skróty"</string>
<string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Skróty i powiadomienia"</string>
- <string name="action_dismiss_notification" msgid="5909461085055959187">"Zamknij"</string>
+ <string name="action_dismiss_notification" msgid="5909461085055959187">"Odrzuć"</string>
<string name="notification_dismissed" msgid="6002233469409822874">"Powiadomienie odrzucone"</string>
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobiste"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Praca"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 1cc699f..23b00d0 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Работа"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Приложение удалено"</string>
<string name="activity_not_available" msgid="7456344436509528827">"Приложение недоступно"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 2f9dc42..ef99a59 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"කාර්යාලය"</string>
<string name="activity_not_found" msgid="8071924732094499514">"යෙදුම ස්ථාපනය කර නැත."</string>
<string name="activity_not_available" msgid="7456344436509528827">"යෙදුම නොතිබේ"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 417151b..044a4b4 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Služba"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacija ni nameščena."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacija ni na voljo"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 5e2c954..4e52592 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Апликација није инсталирана."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Апликација није доступна"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 7df507b..e7400a6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Arbete"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Appen är inte installerad."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Appen är inte tillgänglig"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 019fa1d..6e933a1 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,21 +27,21 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ஷார்ட்கட் இல்லை"</string>
<string name="home_screen" msgid="806512411299847073">"முகப்புத் திரை"</string>
- <string name="custom_actions" msgid="3747508247759093328">"பிரத்தியேக செயல்கள்"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"தனிப்பயன் செயல்கள்"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"விட்ஜெட்டைத் தேர்வுசெய்ய தொட்டுப் பிடிக்கவும்."</string>
- <string name="long_accessible_way_to_add" msgid="4289502106628154155">"விட்ஜெட்டைத் தேர்ந்தெடுக்க இருமுறை தட்டிப் பிடிக்கவும் அல்லது பிரத்தியேக செயல்களைப் பயன்படுத்தவும்."</string>
+ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"விட்ஜெட்டைத் தேர்ந்தெடுக்க இருமுறை தட்டிப் பிடிக்கவும் அல்லது தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d அகலத்திற்கு %2$d உயரம்"</string>
<string name="add_item_request_drag_hint" msgid="5899764264480397019">"நீங்களே சேர்க்க, தொட்டுப் பிடித்திருக்கவும்"</string>
<string name="place_automatically" msgid="8064208734425456485">"தானாகவே சேர்"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"பயன்பாடுகளில் தேடுக"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"ஆப்ஸை ஏற்றுகிறது…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் ஆப்ஸ் இல்லை"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"கூடுதல் பயன்பாடுகளைத் தேடு"</string>
<string name="label_application" msgid="8531721983832654978">"ஆப்ஸ்"</string>
<string name="notifications_header" msgid="1404149926117359025">"அறிவிப்புகள்"</string>
<string name="long_press_shortcut_to_add" msgid="4524750017792716791">"ஷார்ட்கட்டைச் சேர்க்க, தொட்டு பிடித்திருக்கவும்."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"ஷார்ட்கட்டைச் சேர்க்க, இருமுறை தட்டிப் பிடித்திருக்கவும் (அ) பிரத்தியேக செயல்களைப் பயன்படுத்தவும்."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"ஷார்ட்கட்டைச் சேர்க்க, இருமுறை தட்டிப் பிடித்திருக்கவும் (அ) தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string>
<string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"ஆப்ஸின் பட்டியல்"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index de4863a..0df94c7 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabaho"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Hindi naka-install ang app."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Hindi available ang app"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 1b2b3d1..56f8447 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"İş"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Uygulama yüklü değil."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Uygulama kullanılamıyor"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 622cdcc..13ba701 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Робоча папка"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Додаток видалено."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Додаток недоступний"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 74b5eaa..4f77670 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"دفتری"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ایپ انسٹال نہیں ہے۔"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ایپ دستیاب نہیں ہے"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index dac1ac9..69084d7 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Ishga oid"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Ilova o‘rnatilmadi."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Ilova mavjud emas"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index a0a122a..71decfc 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -20,15 +20,16 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Trình chạy 3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Ứng dụng chưa được cài đặt."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Ứng dụng không có sẵn"</string>
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Ứng dụng đã tải xuống bị tắt ở chế độ An toàn"</string>
- <string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích bị vô hiệu hóa ở chế độ an toàn"</string>
+ <string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích con bị vô hiệu hóa ở chế độ an toàn"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
<string name="home_screen" msgid="806512411299847073">"Màn hình chính"</string>
<string name="custom_actions" msgid="3747508247759093328">"Tác vụ tùy chỉnh"</string>
- <string name="long_press_widget_to_add" msgid="7699152356777458215">"Chạm và giữ để chọn tiện ích."</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Chạm và giữ để chọn tiện ích con."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Nhấn đúp và giữ để chọn tiện ích hoặc sử dụng tác vụ tùy chỉnh."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Rộng %1$d x cao %2$d"</string>
@@ -59,7 +60,7 @@
<string name="permlab_write_settings" msgid="3574213698004620587">"ghi cài đặt và lối tắt trên Màn hình chính"</string>
<string name="permdesc_write_settings" msgid="5440712911516509985">"Cho phép ứng dụng thay đổi cài đặt và lối tắt trên Màn hình chính."</string>
<string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> không được phép thực hiện cuộc gọi điện thoại"</string>
- <string name="gadget_error_text" msgid="6081085226050792095">"Sự cố khi tải tiện ích"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Sự cố khi tải tiện ích con"</string>
<string name="gadget_setup_text" msgid="8274003207686040488">"Thiết lập"</string>
<string name="uninstall_system_app_text" msgid="4172046090762920660">"Đây là ứng dụng hệ thống và không thể gỡ cài đặt."</string>
<string name="folder_hint_text" msgid="6617836969016293992">"Thư mục chưa đặt tên"</string>
@@ -85,7 +86,7 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Cho phép xoay Màn hình chính"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Khi xoay điện thoại"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Dấu chấm thông báo"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"Đang bật"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"Bật"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Tắt"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Cần quyền truy cập thông báo"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Để hiển thị Dấu chấm thông báo, hãy bật thông báo ứng dụng cho <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 0478627..9804af1 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"未安装该应用。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"应用不可用"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ed53c32..e737744 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"工作"</string>
<string name="activity_not_found" msgid="8071924732094499514">"尚未安裝應用程式。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"目前無法使用這個應用程式"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 14f2e06..e971b69 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -20,6 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"公司"</string>
<string name="activity_not_found" msgid="8071924732094499514">"應用程式未安裝。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"應用程式目前無法使用"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index de17eb7..5a15ec6 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -40,6 +40,8 @@
<attr name="folderIconRadius" format="float" />
<attr name="folderIconBorderColor" format="color" />
<attr name="folderTextColor" format="color" />
+ <attr name="folderHintColor" format="color" />
+ <attr name="workProfileOverlayTextColor" format="color" />
<!-- BubbleTextView specific attributes. -->
<declare-styleable name="BubbleTextView">
@@ -115,6 +117,7 @@
<attr name="numFolderColumns" format="integer" />
<!-- numHotseatIcons defaults to numColumns, if not specified -->
<attr name="numHotseatIcons" format="integer" />
+ <attr name="dbFile" format="string" />
<attr name="defaultLayoutId" format="reference" />
<attr name="demoModeLayoutId" format="reference" />
</declare-styleable>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 3c8fe1e..194ef2c 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -37,4 +37,12 @@
<color name="all_apps_bg_hand_fill">#E5E5E5</color>
<color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
+
+ <color name="back_gesture_tutorial_background_color">#FFFFFFFF</color>
+ <color name="back_gesture_tutorial_subtitle_color">#99000000</color> <!-- 60% black -->
+ <color name="back_gesture_tutorial_title_color">#FF000000</color>
+ <color name="back_gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
+ <color name="back_gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
+
+
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 0387184..df0f233 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -1,5 +1,5 @@
<resources>
-<!-- Miscellaneous -->
+ <!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
<bool name="is_tablet">false</bool>
<bool name="is_large_tablet">false</bool>
@@ -21,10 +21,10 @@
<!-- String representing the fragment class for settings activity.-->
<string name="settings_fragment_name" translatable="false">com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment</string>
-<!-- DragController -->
+ <!-- DragController -->
<item type="id" name="drag_event_parity" />
-<!-- AllApps & Launcher transitions -->
+ <!-- AllApps & Launcher transitions -->
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
<integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
@@ -34,7 +34,7 @@
<!-- View tag key used to store SpringAnimation data. -->
<item type="id" name="spring_animation_tag" />
-<!-- Workspace -->
+ <!-- Workspace -->
<!-- The duration (in ms) of the fade animation on the object outlines, used when
we are dragging objects around on the home screen. -->
<integer name="config_dragOutlineFadeTime">900</integer>
@@ -57,29 +57,21 @@
<!-- The duration of the caret animation -->
<integer name="config_caretAnimationDuration">200</integer>
-<!-- Hotseat -->
+ <!-- Hotseat -->
<bool name="hotseat_transpose_layout_with_orientation">true</bool>
<!-- Various classes overriden by projects/build flavors. -->
<string name="app_filter_class" translatable="false"></string>
- <string name="icon_provider_class" translatable="false"></string>
- <string name="drawable_factory_class" translatable="false"></string>
<string name="user_event_dispatcher_class" translatable="false"></string>
+ <string name="folder_name_provider_class" translatable="false"></string>
<string name="stats_log_manager_class" translatable="false"></string>
<string name="app_transition_manager_class" translatable="false"></string>
<string name="instant_app_resolver_class" translatable="false"></string>
<string name="main_process_initializer_class" translatable="false"></string>
- <string name="system_shortcut_factory_class" translatable="false"></string>
<string name="app_launch_tracker_class" translatable="false"></string>
<string name="test_information_handler_class" translatable="false"></string>
<string name="launcher_activity_logic_class" translatable="false"></string>
- <!-- Package name of the default wallpaper picker. -->
- <string name="wallpaper_picker_package" translatable="false"></string>
-
- <!-- Whitelisted package to retrieve packagename for badge. Can be empty. -->
- <string name="shortcutinfo_badgepkg_whitelist" translatable="false"></string>
-
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />
@@ -97,7 +89,12 @@
<integer name="config_popupArrowOpenCloseDuration">40</integer>
<integer name="config_removeNotificationViewDuration">300</integer>
-<!-- Accessibility actions -->
+ <!-- Default packages -->
+ <string name="wallpaper_picker_package" translatable="false"></string>
+ <string name="calendar_component_name" translatable="false"></string>
+ <string name="clock_component_name" translatable="false"></string>
+
+ <!-- Accessibility actions -->
<item type="id" name="action_remove" />
<item type="id" name="action_uninstall" />
<item type="id" name="action_reconfigure" />
@@ -111,11 +108,70 @@
<item type="id" name="action_shortcuts_and_notifications"/>
<item type="id" name="action_dismiss_notification" />
<item type="id" name="action_remote_action_shortcut" />
+ <item type="id" name="action_dismiss_prediction" />
-<!-- QSB IDs. DO not change -->
+ <!-- QSB IDs. DO not change -->
<item type="id" name="search_container_workspace" />
<item type="id" name="search_container_all_apps" />
-<!-- Recents -->
+ <!-- Recents -->
<item type="id" name="overview_panel"/>
+
+ <!-- Whether to enable background preloading of task thumbnails. -->
+ <bool name="config_enableTaskSnapshotPreloading">true</bool>
+
+ <!-- Configuration resources -->
+ <item name="all_apps_spring_damping_ratio" type="dimen" format="float">0.75</item>
+ <item name="all_apps_spring_stiffness" type="dimen" format="float">600</item>
+
+ <item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.5</item>
+ <item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">1500</item>
+
+ <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.5</item>
+ <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">1500</item>
+
+ <item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.75</item>
+ <item name="horizontal_spring_stiffness" type="dimen" format="float">200</item>
+
+ <item name="swipe_up_rect_scale_damping_ratio" type="dimen" format="float">0.75</item>
+ <item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
+
+ <item name="swipe_up_rect_xy_fling_friction" type="dimen" format="float">1.5</item>
+ <item name="swipe_up_rect_xy_damping_ratio" type="dimen" format="float">0.8</item>
+ <item name="swipe_up_rect_xy_stiffness" type="dimen" format="float">200</item>
+
+ <item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
+ <item name="staggered_stiffness" type="dimen" format="float">150</item>
+
+ <!-- Swipe up to home related -->
+ <dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
+ <dimen name="swipe_up_y_overshoot">10dp</dimen>
+ <dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
+
+ <array name="dynamic_resources">
+ <item>@dimen/all_apps_spring_damping_ratio</item>
+ <item>@dimen/all_apps_spring_stiffness</item>
+
+ <item>@dimen/dismiss_task_trans_y_damping_ratio</item>
+ <item>@dimen/dismiss_task_trans_y_stiffness</item>
+
+ <item>@dimen/dismiss_task_trans_x_damping_ratio</item>
+ <item>@dimen/dismiss_task_trans_x_stiffness</item>
+
+ <item>@dimen/horizontal_spring_damping_ratio</item>
+ <item>@dimen/horizontal_spring_stiffness</item>
+
+ <item>@dimen/swipe_up_rect_scale_damping_ratio</item>
+ <item>@dimen/swipe_up_rect_scale_stiffness</item>
+
+ <item>@dimen/swipe_up_rect_xy_fling_friction</item>
+ <item>@dimen/swipe_up_rect_xy_damping_ratio</item>
+ <item>@dimen/swipe_up_rect_xy_stiffness</item>
+
+ <item>@dimen/staggered_damping_ratio</item>
+ <item>@dimen/staggered_stiffness</item>
+
+ <item>@dimen/swipe_up_fling_min_visible_change</item>
+ <item>@dimen/swipe_up_y_overshoot</item>
+ </array>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4bcb8a7..871651d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,6 +85,8 @@
<dimen name="all_apps_tabs_side_padding">12dp</dimen>
<dimen name="all_apps_divider_height">1dp</dimen>
+ <dimen name="all_apps_work_profile_tab_footer_padding">20dp</dimen>
+
<!-- Search bar in All Apps -->
<dimen name="all_apps_header_max_elevation">3dp</dimen>
<dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
@@ -235,4 +237,7 @@
<!-- Theming related -->
<dimen name="default_dialog_corner_radius">8dp</dimen>
+ <!-- Onboarding bottomsheet related -->
+ <dimen name="bottom_sheet_edu_padding">24dp</dimen>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9d9c2e8..9d0fb56 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -102,9 +102,11 @@
<string name="app_info_drop_target_label">App info</string>
<!-- Label for install drop target. [CHAR_LIMIT=20] -->
<string name="install_drop_target_label">Install</string>
-
<!-- Label for install dismiss prediction. -->
- <string translatable="false" name="dismiss_prediction_label">Dismiss prediction</string>
+ <string translatable="false" name="dismiss_prediction_label">Don\'t suggest app</string>
+ <!-- Label for pinning predicted app. -->
+ <string name="pin_prediction" translatable="false">Pin Prediction</string>
+
<!-- Permissions: -->
<skip />
@@ -140,7 +142,7 @@
<string name="uninstall_system_app_text">This is a system app and can\'t be uninstalled.</string>
<!-- Default folder title -->
- <string name="folder_hint_text">Unnamed Folder</string>
+ <string name="folder_hint_text">Edit Name</string>
<!-- Accessibility -->
<!-- The format string for when an app is temporarily disabled. -->
@@ -325,17 +327,23 @@
<!-- This string is in the work profile tab when a user has All Apps open on their phone. This is a label for a toggle to turn the work profile on and off. "Work profile" means a separate profile on a user's phone that's specifically for their work apps and managed by their company. "Work" is used as an adjective.-->
<string name="work_profile_toggle_label">Work profile</string>
- <!-- Title of an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer. "Work apps" are apps in a user's work profile.-->
- <string name="bottom_work_tab_user_education_title">Find work apps here</string>
- <!-- Text in an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer.-->
- <string name="bottom_work_tab_user_education_body">Each work app has a badge and is kept secure by your organization. Move apps to your Home screen for easier access.</string>
+ <!--- User onboarding title for personal apps -->
+ <string name="work_profile_edu_personal_apps">Personal apps are private & can\'t be seen by IT</string>
+ <!--- User onboarding title for work profile apps -->
+ <string name="work_profile_edu_work_apps">Work apps are badged & visible to IT</string>
+ <!-- Action label to proceed to the next work profile edu section-->
+ <string name="work_profile_edu_next">Next</string>
+ <!-- Action label to finish work profile edu-->
+ <string name="work_profile_edu_accept">Got it</string>
+
<!-- This string is in the work profile tab when a user has All Apps open on their phone. It describes the label of a toggle, "Work profile," as being managed by the user's employer.
"Organization" is used to represent a variety of businesses, non-profits, and educational institutions).-->
- <string name="work_mode_on_label">Managed by your organization</string>
+ <string name="work_mode_on_label">Work apps: On</string>
<!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
- <string name="work_mode_off_label">Notifications and apps are off</string>
- <string name="bottom_work_tab_user_education_close_button">Close</string>
- <string name="bottom_work_tab_user_education_closed">Closed</string>
+ <string name="work_mode_off_label">Work apps: Paused</string>
+
+ <string name="work_apps_paused_title">Work apps are paused</string>
+ <string name="work_apps_paused_body">You won\'t get any work notifications, and your IT admin can\'t see your location</string>
<!-- Failed action error message: e.g. Failed: Pause -->
<string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 80c791c..bc6ab45 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -48,13 +48,17 @@
<item name="folderFillColor">#CDFFFFFF</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="folderTextColor">#FF212121</item>
+ <item name="folderHintColor">#FF616161</item>
<item name="loadingIconColor">#CCFFFFFF</item>
+ <item name="workProfileOverlayTextColor">#FF212121</item>
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">#00000000</item>
<item name="android:navigationBarColor">#00000000</item>
+
+
</style>
<style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
@@ -74,6 +78,7 @@
<item name="folderFillColor">#CDFFFFFF</item>
<item name="folderIconBorderColor">#FF80868B</item>
<item name="folderTextColor">?attr/workspaceTextColor</item>
+
</style>
<style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
@@ -94,8 +99,10 @@
<item name="folderFillColor">#DD3C4043</item> <!-- 87% GM2 800 -->
<item name="folderIconBorderColor">#FF80868B</item>
<item name="folderTextColor">@android:color/white</item>
+ <item name="folderHintColor">#FFCCCCCC</item>
<item name="isMainColorDark">true</item>
<item name="loadingIconColor">#99FFFFFF</item>
+ <item name="workProfileOverlayTextColor">@android:color/white</item>
</style>
<style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
@@ -218,6 +225,7 @@
<style name="DropTargetButton" parent="DropTargetButtonBase" />
<style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
+ <style name="PrimaryMediumText" parent="@android:style/TextAppearance.DeviceDefault.Medium"/>
<style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 82547d5..1c99dfc 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -24,6 +24,7 @@
launcher:numFolderRows="2"
launcher:numFolderColumns="3"
launcher:numHotseatIcons="3"
+ launcher:dbFile="launcher_3_by_3.db"
launcher:defaultLayoutId="@xml/default_workspace_3x3" >
<display-option
@@ -51,6 +52,7 @@
launcher:numFolderRows="3"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="4"
+ launcher:dbFile="launcher_4_by_4.db"
launcher:defaultLayoutId="@xml/default_workspace_4x4" >
<display-option
@@ -102,6 +104,7 @@
launcher:numFolderRows="4"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="5"
+ launcher:dbFile="launcher.db"
launcher:defaultLayoutId="@xml/default_workspace_5x5" >
<display-option
diff --git a/res/xml/dynamic_resources.xml b/res/xml/dynamic_resources.xml
new file mode 100644
index 0000000..f5d2628
--- /dev/null
+++ b/res/xml/dynamic_resources.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<DynamicResources>
+ <entry id="@color/delete_target_hover_tint" />
+ <entry id="@color/delete_target_hover_tint" />
+ <entry id="@color/delete_target_hover_tint" />
+ <entry id="@color/delete_target_hover_tint" />
+
+
+</DynamicResources>
\ No newline at end of file
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 62915f2..7c7e73c 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -19,6 +19,8 @@
include $(CLEAR_VARS)
LOCAL_MODULE := LauncherRoboTests
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
@@ -27,13 +29,16 @@
mockito-robolectric-prebuilt \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
- platform-robolectric-3.6.1-prebuilt
+ platform-robolectric-4.3.1-prebuilt
LOCAL_JAVA_RESOURCE_DIRS := resources config
LOCAL_INSTRUMENTATION_FOR := Launcher3
LOCAL_MODULE_TAGS := optional
+# Generate test_config.properties
+include external/robolectric-shadows/gen_test_config.mk
+
include $(BUILD_STATIC_JAVA_LIBRARY)
############################################
@@ -43,15 +48,12 @@
LOCAL_MODULE := RunLauncherRoboTests
LOCAL_SDK_VERSION := current
-LOCAL_JAVA_LIBRARIES := \
- LauncherRoboTests
+LOCAL_JAVA_LIBRARIES := LauncherRoboTests
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
LOCAL_TEST_PACKAGE := Launcher3
-
-LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \
+LOCAL_INSTRUMENT_SOURCE_DIRS := packages/apps/Launcher3/src
LOCAL_ROBOTEST_TIMEOUT := 36000
-include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
+include prebuilts/misc/common/robolectric/4.3.1/run_robotests.mk
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index e0d6e53..3d78689 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -1,2 +1 @@
-manifest=packages/apps/Launcher3/AndroidManifest.xml
-sdk=26
+sdk=29
diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
deleted file mode 100644
index a3d1216..0000000
--- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.android.launcher3.config;
-
-
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
-import com.android.launcher3.uioverrides.TogglableFlag;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.robolectric.RuntimeEnvironment;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Test rule that makes overriding flags in Robolectric tests easier. This rule clears all flags
- * before and after your test, avoiding one test method affecting subsequent methods.
- *
- * <p>Usage:
- * <pre>
- * {@literal @}Rule public final FlagOverrideRule flags = new FlagOverrideRule();
- *
- * {@literal @}FlagOverride(flag = "FOO", value=true)
- * {@literal @}Test public void myTest() {
- * ...
- * }
- * </pre>
- */
-public final class FlagOverrideRule implements TestRule {
-
- /**
- * Container annotation for handling multiple {@link FlagOverride} annotations.
- * <p>
- * <p>Don't use this directly, use repeated {@link FlagOverride} annotations instead.
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD})
- public @interface FlagOverrides {
- FlagOverride[] value();
- }
-
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD})
- @Repeatable(FlagOverrides.class)
- public @interface FlagOverride {
- String key();
-
- boolean value();
- }
-
- private boolean ruleInProgress;
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- FeatureFlags.initialize(RuntimeEnvironment.application.getApplicationContext());
- ruleInProgress = true;
- try {
- clearOverrides();
- applyAnnotationOverrides(description);
- base.evaluate();
- } finally {
- ruleInProgress = false;
- clearOverrides();
- }
- }
- };
- }
-
- private void override(BaseTogglableFlag flag, boolean newValue) {
- if (!ruleInProgress) {
- throw new IllegalStateException(
- "Rule isn't in progress. Did you remember to mark it with @Rule?");
- }
- flag.setForTests(newValue);
- }
-
- private void applyAnnotationOverrides(Description description) {
- for (Annotation annotation : description.getAnnotations()) {
- if (annotation.annotationType() == FlagOverride.class) {
- applyAnnotation((FlagOverride) annotation);
- } else if (annotation.annotationType() == FlagOverrides.class) {
- // Note: this branch is hit if the annotation is repeated
- for (FlagOverride flagOverride : ((FlagOverrides) annotation).value()) {
- applyAnnotation(flagOverride);
- }
- }
- }
- }
-
- private void applyAnnotation(FlagOverride flagOverride) {
- boolean found = false;
- for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
- if (flag.getKey().equals(flagOverride.key())) {
- override(flag, flagOverride.value());
- found = true;
- break;
- }
- }
- if (!found) {
- throw new IllegalStateException("Flag " + flagOverride.key() + " not found");
- }
- }
-
- /**
- * Resets all flags to their default values.
- */
- private void clearOverrides() {
- for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
- flag.setForTests(flag.getDefaultValue());
- }
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
deleted file mode 100644
index 1351348..0000000
--- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.launcher3.config;
-
-import com.android.launcher3.config.FlagOverrideRule.FlagOverride;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Sample Robolectric test that demonstrates flag-overriding.
- */
-@RunWith(RobolectricTestRunner.class)
-public class FlagOverrideSampleTest {
-
- // Check out https://junit.org/junit4/javadoc/4.12/org/junit/Rule.html for more information
- // on @Rules.
- @Rule
- public final FlagOverrideRule flags = new FlagOverrideRule();
-
- @FlagOverride(key = "EXAMPLE_FLAG", value = true)
- @Test
- public void withFlagOn() {
- assertTrue(FeatureFlags.EXAMPLE_FLAG.get());
- }
-
-
- @FlagOverride(key = "EXAMPLE_FLAG", value = false)
- @Test
- public void withFlagOff() {
- assertFalse(FeatureFlags.EXAMPLE_FLAG.get());
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
new file mode 100644
index 0000000..74ef55e
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.folder;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+@RunWith(LauncherRoboTestRunner.class)
+public final class FolderNameProviderTest {
+ private Context mContext;
+ private WorkspaceItemInfo mItem1;
+ private WorkspaceItemInfo mItem2;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mItem1 = new WorkspaceItemInfo(new AppInfo(
+ new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
+ "title1",
+ LShadowUserManager.newUserHandle(10),
+ new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
+ ));
+ mItem2 = new WorkspaceItemInfo(new AppInfo(
+ new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
+ "title2",
+ LShadowUserManager.newUserHandle(10),
+ new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
+ ));
+ }
+
+ @Test
+ public void getSuggestedFolderName_workAssignedToEnd() {
+ ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
+ list.add(mItem1);
+ list.add(mItem2);
+ FolderNameInfo[] nameInfos =
+ new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+ assertEquals("Work", nameInfos[0].getLabel());
+
+ nameInfos[0] = new FolderNameInfo("candidate1", 0.9);
+ nameInfos[1] = new FolderNameInfo("candidate2", 0.8);
+ nameInfos[2] = new FolderNameInfo("candidate3", 0.7);
+ new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+ assertEquals("Work", nameInfos[3].getLabel());
+
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index 096db57..95a4146 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -1,30 +1,34 @@
package com.android.launcher3.logging;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.launcher3.util.LauncherRoboTestRunner;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.util.Scheduler;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Calendar;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
/**
* Tests for {@link FileLog}
*/
-@RunWith(RobolectricTestRunner.class)
+@RunWith(LauncherRoboTestRunner.class)
public class FileLogTest {
private File mTempDir;
+ private boolean mTestActive;
@Before
- public void setUp() throws Exception {
+ public void setUp() {
int count = 0;
do {
mTempDir = new File(RuntimeEnvironment.application.getCacheDir(),
@@ -32,14 +36,25 @@
} while (!mTempDir.mkdir());
FileLog.setDir(mTempDir);
+
+ mTestActive = true;
+ Scheduler scheduler = Shadows.shadowOf(FileLog.getHandler().getLooper()).getScheduler();
+ new Thread(() -> {
+ while (mTestActive) {
+ scheduler.advanceToLastPostedRunnable();
+ }
+ }).start();
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
// Clear existing logs
- new File(mTempDir, "log-0").delete();
- new File(mTempDir, "log-1").delete();
+ for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+ new File(mTempDir, "log-" + i).delete();
+ }
mTempDir.delete();
+
+ mTestActive = false;
}
@Test
@@ -49,12 +64,12 @@
}
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
- FileLog.flushAll(new PrintWriter(writer));
+ assertTrue(FileLog.flushAll(new PrintWriter(writer)));
assertTrue(writer.toString().contains("hoolalala"));
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
- FileLog.flushAll(new PrintWriter(writer));
+ assertTrue(FileLog.flushAll(new PrintWriter(writer)));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
@@ -70,17 +85,18 @@
}
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
- FileLog.flushAll(new PrintWriter(writer));
+ assertTrue(FileLog.flushAll(new PrintWriter(writer)));
assertTrue(writer.toString().contains("hoolalala"));
Calendar threeDaysAgo = Calendar.getInstance();
threeDaysAgo.add(Calendar.HOUR, -72);
- new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis());
- new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis());
+ for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+ new File(mTempDir, "log-" + i).setLastModified(threeDaysAgo.getTimeInMillis());
+ }
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
- FileLog.flushAll(new PrintWriter(writer));
+ assertTrue(FileLog.flushAll(new PrintWriter(writer)));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
diff --git a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index d7a2278..b7f2243 100644
--- a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -4,54 +4,70 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.util.Pair;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
/**
* Tests for {@link AddWorkspaceItemsTask}
*/
-@RunWith(RobolectricTestRunner.class)
-public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class AddWorkspaceItemsTaskTest {
private final ComponentName mComponent1 = new ComponentName("a", "b");
private final ComponentName mComponent2 = new ComponentName("b", "b");
- private IntArray existingScreens;
- private IntArray newScreens;
- private IntSparseArrayMap<GridOccupancy> screenOccupancy;
+ private Context mTargetContext;
+ private InvariantDeviceProfile mIdp;
+ private LauncherAppState mAppState;
+ private LauncherModelHelper mModelHelper;
+
+ private IntArray mExistingScreens;
+ private IntArray mNewScreens;
+ private IntSparseArrayMap<GridOccupancy> mScreenOccupancy;
@Before
- public void initData() throws Exception {
- existingScreens = new IntArray();
- screenOccupancy = new IntSparseArrayMap<>();
- newScreens = new IntArray();
+ public void setup() {
+ mModelHelper = new LauncherModelHelper();
+ mTargetContext = RuntimeEnvironment.application;
+ mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
+ mIdp.numColumns = mIdp.numRows = 5;
+ mAppState = LauncherAppState.getInstance(mTargetContext);
- idp.numColumns = 5;
- idp.numRows = 5;
+ mExistingScreens = new IntArray();
+ mScreenOccupancy = new IntSparseArrayMap<>();
+ mNewScreens = new IntArray();
}
private AddWorkspaceItemsTask newTask(ItemInfo... items) {
@@ -70,17 +86,17 @@
// Second screen has 2 holes of sizes 3x2 and 2x3
setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
- int[] spaceFound = newTask()
- .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
+ int[] spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
assertEquals(2, spaceFound[0]);
- assertTrue(screenOccupancy.get(spaceFound[0])
+ assertTrue(mScreenOccupancy.get(spaceFound[0])
.isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
// Find a larger space
- spaceFound = newTask()
- .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
+ spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3);
assertEquals(2, spaceFound[0]);
- assertTrue(screenOccupancy.get(spaceFound[0])
+ assertTrue(mScreenOccupancy.get(spaceFound[0])
.isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
}
@@ -89,11 +105,11 @@
// First screen has 2 holes of sizes 3x2 and 2x3
setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
- IntArray oldScreens = existingScreens.clone();
- int[] spaceFound = newTask()
- .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
+ IntArray oldScreens = mExistingScreens.clone();
+ int[] spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3);
assertFalse(oldScreens.contains(spaceFound[0]));
- assertTrue(newScreens.contains(spaceFound[0]));
+ assertTrue(mNewScreens.contains(spaceFound[0]));
}
@Test
@@ -105,11 +121,14 @@
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
// Nothing was added
- assertTrue(executeTaskForTest(newTask(info)).isEmpty());
+ assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty());
}
@Test
public void testAddItem_some_items_added() throws Exception {
+ Callbacks callbacks = mock(Callbacks.class);
+ mModelHelper.getModel().addCallbacks(callbacks);
+
WorkspaceItemInfo info = new WorkspaceItemInfo();
info.intent = new Intent().setComponent(mComponent1);
@@ -119,7 +138,7 @@
// Setup a screen with a hole
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
- executeTaskForTest(newTask(info, info2)).get(0).run();
+ mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run();
ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
@@ -134,18 +153,23 @@
}
private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
- GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
- occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
+ return mModelHelper.executeSimpleTask(
+ model -> writeWorkspaceWithHoles(model, startId, screenId, holes));
+ }
+
+ private int writeWorkspaceWithHoles(
+ BgDataModel bgDataModel, int startId, int screenId, Rect... holes) {
+ GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows);
+ occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true);
for (Rect r : holes) {
occupancy.markCells(r, false);
}
- existingScreens.add(screenId);
- screenOccupancy.append(screenId, occupancy);
+ mExistingScreens.add(screenId);
+ mScreenOccupancy.append(screenId, occupancy);
- ExecutorService executor = Executors.newSingleThreadExecutor();
- for (int x = 0; x < idp.numColumns; x++) {
- for (int y = 0; y < idp.numRows; y++) {
+ for (int x = 0; x < mIdp.numColumns; x++) {
+ for (int y = 0; y < mIdp.numRows; y++) {
if (!occupancy.cells[x][y]) {
continue;
}
@@ -157,20 +181,15 @@
info.cellX = x;
info.cellY = y;
info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- bgDataModel.addItem(targetContext, info, false);
+ bgDataModel.addItem(mTargetContext, info, false);
- executor.execute(() -> {
- ContentWriter writer = new ContentWriter(targetContext);
- info.writeToValues(writer);
- writer.put(Favorites._ID, info.id);
- targetContext.getContentResolver().insert(Favorites.CONTENT_URI,
- writer.getValues(targetContext));
- });
+ ContentWriter writer = new ContentWriter(mTargetContext);
+ info.writeToValues(writer);
+ writer.put(Favorites._ID, info.id);
+ mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI,
+ writer.getValues(mTargetContext));
}
}
-
- executor.submit(() -> null).get();
- executor.shutdown();
return startId;
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
new file mode 100644
index 0000000..7072adf
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+
+import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
+import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
+import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.robolectric.util.ReflectionHelpers.setField;
+
+import android.app.backup.BackupManager;
+import android.content.pm.PackageInstaller;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.shadows.LShadowBackupManager;
+import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.shadow.api.Shadow;
+
+/**
+ * Tests to verify backup and restore flow.
+ */
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(LooperMode.Mode.PAUSED)
+public class BackupRestoreTest {
+
+ private static final long MY_OLD_PROFILE_ID = 1;
+ private static final long MY_PROFILE_ID = 0;
+ private static final long OLD_WORK_PROFILE_ID = 11;
+ private static final int WORK_PROFILE_ID = 10;
+
+ private static final int SYSTEM_USER = 0;
+ private static final int FLAG_SYSTEM = 0x00000800;
+ private static final int FLAG_PROFILE = 0x00001000;
+
+ private LShadowUserManager mUserManager;
+ private BackupManager mBackupManager;
+ private LauncherModelHelper mModelHelper;
+ private SQLiteDatabase mDb;
+ private InvariantDeviceProfile mIdp;
+ private UserHandle mMainProfileUser;
+ private UserHandle mWorkProfileUser;
+
+ @Before
+ public void setUp() {
+ setupUserManager();
+ setupBackupManager();
+ mModelHelper = new LauncherModelHelper();
+ RestoreDbTask.setPending(RuntimeEnvironment.application, true);
+ mDb = mModelHelper.provider.getDb();
+ mIdp = InvariantDeviceProfile.INSTANCE.get(RuntimeEnvironment.application);
+ }
+
+ private void setupUserManager() {
+ final UserManager userManager = RuntimeEnvironment.application.getSystemService(
+ UserManager.class);
+ mUserManager = Shadow.extract(userManager);
+ // sign in to primary user
+ mMainProfileUser = mUserManager.addUser(SYSTEM_USER, "me", FLAG_SYSTEM);
+ // sign in to work profile
+ mWorkProfileUser = mUserManager.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE);
+ }
+
+ private void setupBackupManager() {
+ mBackupManager = new BackupManager(RuntimeEnvironment.application);
+ final LShadowBackupManager bm = Shadow.extract(mBackupManager);
+ bm.addProfile(MY_OLD_PROFILE_ID, mMainProfileUser);
+ bm.addProfile(OLD_WORK_PROFILE_ID, mWorkProfileUser);
+ }
+
+ @Test
+ public void testOnCreateDbIfNotExists_CreatesBackup() {
+ assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
+ }
+
+ @Test
+ public void testOnRestoreSessionWithValidCondition_PerformsRestore() throws Exception {
+ setupBackup();
+ verifyTableIsFilled(BACKUP_TABLE_NAME, false);
+ verifyTableIsEmpty(TABLE_NAME);
+ createRestoreSession();
+ verifyTableIsFilled(TABLE_NAME, true);
+ }
+
+ private void setupBackup() {
+ createTableUsingOldProfileId();
+ // setup grid for main user on first screen
+ mModelHelper.createGrid(new int[][][]{{
+ { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
+ { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+ { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+ { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
+ }}, 1, MY_OLD_PROFILE_ID);
+ // setup grid for work profile on second screen
+ mModelHelper.createGrid(new int[][][]{{
+ { NO__ICON, APP_ICON, SHORTCUT, SHORTCUT},
+ { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+ { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+ { APP_ICON, SHORTCUT, SHORTCUT, NO__ICON},
+ }}, 2, OLD_WORK_PROFILE_ID);
+ // simulates the creation of backup upon restore
+ new GridBackupTable(RuntimeEnvironment.application, mDb, mIdp.numHotseatIcons,
+ mIdp.numColumns, mIdp.numRows).doBackup(
+ MY_OLD_PROFILE_ID, GridBackupTable.OPTION_REQUIRES_SANITIZATION);
+ // reset favorites table
+ createTableUsingOldProfileId();
+ }
+
+ private void verifyTableIsEmpty(String tableName) {
+ assertEquals(0, getCount(mDb, "SELECT * FROM " + tableName));
+ }
+
+ private void verifyTableIsFilled(String tableName, boolean sanitized) {
+ assertEquals(sanitized ? 12 : 13, getCount(mDb,
+ "SELECT * FROM " + tableName + " WHERE profileId = "
+ + (sanitized ? MY_PROFILE_ID : MY_OLD_PROFILE_ID)));
+ assertEquals(10, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = "
+ + (sanitized ? WORK_PROFILE_ID : OLD_WORK_PROFILE_ID)));
+ }
+
+ private void createTableUsingOldProfileId() {
+ // simulates the creation of favorites table on old device
+ dropTable(mDb, TABLE_NAME);
+ addTableToDb(mDb, MY_OLD_PROFILE_ID, false);
+ }
+
+ private void createRestoreSession() throws Exception {
+ final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ final PackageInstaller installer = RuntimeEnvironment.application.getPackageManager()
+ .getPackageInstaller();
+ final int sessionId = installer.createSession(params);
+ final PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
+ setField(info, "installReason", INSTALL_REASON_DEVICE_RESTORE);
+ // TODO: (b/148410677) we should verify the following call instead
+ // InstallSessionHelper.INSTANCE.get(getContext()).restoreDbIfApplicable(info);
+ RestoreDbTask.restoreIfPossible(RuntimeEnvironment.application,
+ mModelHelper.provider.getHelper(), mBackupManager);
+ }
+
+ private static int getCount(SQLiteDatabase db, String sql) {
+ try (Cursor c = db.rawQuery(sql, null)) {
+ return c.getCount();
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java
deleted file mode 100644
index 07834fc..0000000
--- a/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.android.launcher3.model;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.sqlite.SQLiteDatabase;
-
-import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.util.TestLauncherProvider;
-
-import org.junit.Before;
-import org.robolectric.Robolectric;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowContentResolver;
-import org.robolectric.shadows.ShadowLog;
-
-public abstract class BaseGridChangesTestCase {
-
-
- public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
- public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
- public static final int NO__ICON = -1;
-
- public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
-
- public Context mContext;
- public TestLauncherProvider mProvider;
- public SQLiteDatabase mDb;
-
- @Before
- public void setUpBaseCase() {
- ShadowLog.stream = System.out;
-
- mContext = RuntimeEnvironment.application;
- mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
- ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
- mDb = mProvider.getDb();
- }
-
- /**
- * Adds a dummy item in the DB.
- * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
- * folder (where the type represents the number of items in the folder).
- */
- public int addItem(int type, int screen, int container, int x, int y) {
- int id = LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
- ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites._ID, id);
- values.put(LauncherSettings.Favorites.CONTAINER, container);
- values.put(LauncherSettings.Favorites.SCREEN, screen);
- values.put(LauncherSettings.Favorites.CELLX, x);
- values.put(LauncherSettings.Favorites.CELLY, y);
- values.put(LauncherSettings.Favorites.SPANX, 1);
- values.put(LauncherSettings.Favorites.SPANY, 1);
-
- if (type == APP_ICON || type == SHORTCUT) {
- values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
- values.put(LauncherSettings.Favorites.INTENT,
- new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
- } else {
- values.put(LauncherSettings.Favorites.ITEM_TYPE,
- LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
- // Add folder items.
- for (int i = 0; i < type; i++) {
- addItem(APP_ICON, 0, id, 0, 0);
- }
- }
-
- mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
- return id;
- }
-
- public int[][][] createGrid(int[][][] typeArray) {
- return createGrid(typeArray, 1);
- }
-
- /**
- * Initializes the DB with dummy elements to represent the provided grid structure.
- * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
- * type definitions. The first dimension represents the screens and the next
- * two represent the workspace grid.
- * @param startScreen First screen id from where the icons will be added.
- * @return the same grid representation where each entry is the corresponding item id.
- */
- public int[][][] createGrid(int[][][] typeArray, int startScreen) {
- LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- int[][][] ids = new int[typeArray.length][][];
-
- for (int i = 0; i < typeArray.length; i++) {
- // Add screen to DB
- int screenId = startScreen + i;
-
- // Keep the screen id counter up to date
- LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
- ids[i] = new int[typeArray[i].length][];
- for (int y = 0; y < typeArray[i].length; y++) {
- ids[i][y] = new int[typeArray[i][y].length];
- for (int x = 0; x < typeArray[i][y].length; x++) {
- if (typeArray[i][y][x] < 0) {
- // Empty cell
- ids[i][y][x] = -1;
- } else {
- ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
- }
- }
- }
- }
-
- return ids;
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
deleted file mode 100644
index bc936b7..0000000
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package com.android.launcher3.model;
-
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Color;
-import android.os.Process;
-import android.os.UserHandle;
-
-import com.android.launcher3.AppFilter;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.TestLauncherProvider;
-
-import org.junit.Before;
-import org.mockito.ArgumentCaptor;
-import org.robolectric.Robolectric;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowContentResolver;
-import org.robolectric.shadows.ShadowLog;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.function.Supplier;
-
-import androidx.annotation.NonNull;
-
-/**
- * Base class for writing tests for Model update tasks.
- */
-public class BaseModelUpdateTaskTestCase {
-
- public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
- private TestLauncherProvider mProvider;
-
- public Context targetContext;
- public UserHandle myUser;
-
- public InvariantDeviceProfile idp;
- public LauncherAppState appState;
- public LauncherModel model;
- public ModelWriter modelWriter;
- public MyIconCache iconCache;
-
- public BgDataModel bgDataModel;
- public AllAppsList allAppsList;
- public Callbacks callbacks;
-
- @Before
- public void setUp() throws Exception {
- ShadowLog.stream = System.out;
-
- mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
- ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
-
- callbacks = mock(Callbacks.class);
- appState = mock(LauncherAppState.class);
- model = mock(LauncherModel.class);
- modelWriter = mock(ModelWriter.class);
-
- when(appState.getModel()).thenReturn(model);
- when(model.getWriter(anyBoolean(), anyBoolean())).thenReturn(modelWriter);
- when(model.getCallback()).thenReturn(callbacks);
-
- myUser = Process.myUserHandle();
-
- bgDataModel = new BgDataModel();
- targetContext = RuntimeEnvironment.application;
-
- idp = new InvariantDeviceProfile();
- iconCache = new MyIconCache(targetContext, idp);
-
- allAppsList = new AllAppsList(iconCache, new AppFilter());
-
- when(appState.getIconCache()).thenReturn(iconCache);
- when(appState.getInvariantDeviceProfile()).thenReturn(idp);
- when(appState.getContext()).thenReturn(targetContext);
- }
-
- /**
- * Synchronously executes the task and returns all the UI callbacks posted.
- */
- public List<Runnable> executeTaskForTest(ModelUpdateTask task) throws Exception {
- when(model.isModelLoaded()).thenReturn(true);
-
- Executor mockExecutor = mock(Executor.class);
-
- task.init(appState, model, bgDataModel, allAppsList, mockExecutor);
- task.run();
- ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
- verify(mockExecutor, atLeast(0)).execute(captor.capture());
-
- return captor.getAllValues();
- }
-
- /**
- * Initializes mock data for the test.
- */
- public void initializeData(String resourceName) throws Exception {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(
- this.getClass().getResourceAsStream(resourceName)))) {
- String line;
- HashMap<String, Class> classMap = new HashMap<>();
- while((line = reader.readLine()) != null) {
- line = line.trim();
- if (line.startsWith("#") || line.isEmpty()) {
- continue;
- }
- String[] commands = line.split(" ");
- switch (commands[0]) {
- case "classMap":
- classMap.put(commands[1], Class.forName(commands[2]));
- break;
- case "bgItem":
- bgDataModel.addItem(targetContext,
- (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
- break;
- case "allApps":
- allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
- break;
- }
- }
- }
- }
-
- private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
- HashMap<String, Field> cache = fieldCache.get(clazz);
- if (cache == null) {
- cache = new HashMap<>();
- Class c = clazz;
- while (c != null) {
- for (Field f : c.getDeclaredFields()) {
- f.setAccessible(true);
- cache.put(f.getName(), f);
- }
- c = c.getSuperclass();
- }
- fieldCache.put(clazz, cache);
- }
-
- Object item = clazz.newInstance();
- for (int i = startIndex; i < fieldDef.length; i++) {
- String[] fieldData = fieldDef[i].split("=", 2);
- Field f = cache.get(fieldData[0]);
- Class type = f.getType();
- if (type == int.class || type == long.class) {
- f.set(item, Integer.parseInt(fieldData[1]));
- } else if (type == CharSequence.class || type == String.class) {
- f.set(item, fieldData[1]);
- } else if (type == Intent.class) {
- if (!fieldData[1].startsWith("#Intent")) {
- fieldData[1] = "#Intent;" + fieldData[1] + ";end";
- }
- f.set(item, Intent.parseUri(fieldData[1], 0));
- } else if (type == ComponentName.class) {
- f.set(item, ComponentName.unflattenFromString(fieldData[1]));
- } else {
- throw new Exception("Added parsing logic for "
- + f.getName() + " of type " + f.getType());
- }
- }
- return item;
- }
-
- public static class MyIconCache extends IconCache {
-
- private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
-
- public MyIconCache(Context context, InvariantDeviceProfile idp) {
- super(context, idp);
- }
-
- @Override
- protected <T> CacheEntry cacheLocked(
- @NonNull ComponentName componentName,
- UserHandle user, @NonNull Supplier<T> infoProvider,
- @NonNull CachingLogic<T> cachingLogic,
- boolean usePackageIcon, boolean useLowResIcon) {
- CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
- if (entry == null) {
- entry = new CacheEntry();
- getDefaultIcon(user).applyTo(entry);
- }
- return entry;
- }
-
- public void addCache(ComponentName key, String title) {
- CacheEntry entry = new CacheEntry();
- entry.icon = newIcon();
- entry.color = Color.RED;
- entry.title = title;
- mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
- }
-
- public Bitmap newIcon() {
- return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
- }
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 42848f4..f128e24 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -2,19 +2,37 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.annotation.NonNull;
import com.android.launcher3.AppInfo;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
import java.util.Arrays;
import java.util.HashSet;
@@ -22,62 +40,92 @@
/**
* Tests for {@link CacheDataUpdatedTask}
*/
-@RunWith(RobolectricTestRunner.class)
-public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class CacheDataUpdatedTaskTest {
private static final String NEW_LABEL_PREFIX = "new-label-";
+ private LauncherModelHelper mModelHelper;
+
@Before
- public void initData() throws Exception {
- initializeData("/cache_data_updated_task_data.txt");
+ public void setup() throws Exception {
+ mModelHelper = new LauncherModelHelper();
+ mModelHelper.initializeData("/cache_data_updated_task_data.txt");
+
// Add dummy entries in the cache to simulate update
- for (ItemInfo info : bgDataModel.itemsIdMap) {
- iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id);
+ Context context = RuntimeEnvironment.application;
+ IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
+ CachingLogic<ItemInfo> dummyLogic = new CachingLogic<ItemInfo>() {
+ @Override
+ public ComponentName getComponent(ItemInfo info) {
+ return info.getTargetComponent();
+ }
+
+ @Override
+ public UserHandle getUser(ItemInfo info) {
+ return info.user;
+ }
+
+ @Override
+ public CharSequence getLabel(ItemInfo info) {
+ return NEW_LABEL_PREFIX + info.id;
+ }
+
+ @NonNull
+ @Override
+ public BitmapInfo loadIcon(Context context, ItemInfo info) {
+ return BitmapInfo.of(Bitmap.createBitmap(1, 1, Config.ARGB_8888), Color.RED);
+ }
+ };
+
+ UserManager um = context.getSystemService(UserManager.class);
+ for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
+ iconCache.addIconToDBAndMemCache(info, dummyLogic, new PackageInfo(),
+ um.getSerialNumberForUser(info.user), true);
}
}
private CacheDataUpdatedTask newTask(int op, String... pkg) {
- return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
+ return new CacheDataUpdatedTask(op, Process.myUserHandle(),
+ new HashSet<>(Arrays.asList(pkg)));
}
@Test
- @Ignore("This test fails with resource errors") // b/131115553
public void testCacheUpdate_update_apps() throws Exception {
// Clear all icons from apps list so that its easy to check what was updated
- for (AppInfo info : allAppsList.data) {
- info.iconBitmap = null;
+ for (AppInfo info : mModelHelper.getAllAppsList().data) {
+ info.bitmap = BitmapInfo.LOW_RES_INFO;
}
- executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
+ mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
// Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
// is not updated
verifyUpdate(1, 2);
// Verify that only app1 var updated in allAppsList
- assertFalse(allAppsList.data.isEmpty());
- for (AppInfo info : allAppsList.data) {
+ assertFalse(mModelHelper.getAllAppsList().data.isEmpty());
+ for (AppInfo info : mModelHelper.getAllAppsList().data) {
if (info.componentName.getPackageName().equals("app1")) {
- assertNotNull(info.iconBitmap);
+ assertFalse(info.bitmap.isNullOrLowRes());
} else {
- assertNull(info.iconBitmap);
+ assertTrue(info.bitmap.isNullOrLowRes());
}
}
}
@Test
- @Ignore("This test fails with resource errors") // b/131115553
public void testSessionUpdate_ignores_normal_apps() throws Exception {
- executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
+ mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
// app1 has no restored shortcuts. Verify that nothing was updated.
verifyUpdate();
}
@Test
- @Ignore("This test fails with resource errors") // b/131115553
public void testSessionUpdate_updates_pending_apps() throws Exception {
- executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
+ mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
// app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
// were updated
@@ -86,13 +134,13 @@
private void verifyUpdate(Integer... idsUpdated) {
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
- for (ItemInfo info : bgDataModel.itemsIdMap) {
+ for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
if (updates.contains(info.id)) {
assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
- assertNotNull(((WorkspaceItemInfo) info).iconBitmap);
+ assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
} else {
assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
- assertNull(((WorkspaceItemInfo) info).iconBitmap);
+ assertTrue(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
}
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index a46617e..1442c55 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -36,11 +36,11 @@
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.io.File;
@@ -48,7 +48,7 @@
/**
* Tests for {@link DbDowngradeHelper}
*/
-@RunWith(RobolectricTestRunner.class)
+@RunWith(LauncherRoboTestRunner.class)
public class DbDowngradeHelperTest {
private static final String SCHEMA_FILE = "test_schema.json";
@@ -130,7 +130,7 @@
}
helper.close();
- helper = new DatabaseHelper(mContext, null, DB_FILE) {
+ helper = new DatabaseHelper(mContext, DB_FILE) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
@@ -161,7 +161,7 @@
DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
- DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, DB_FILE) {
+ DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
new file mode 100644
index 0000000..f8ac010
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+
+import static org.junit.Assert.assertEquals;
+import static org.robolectric.Shadows.shadowOf;
+import static org.robolectric.util.ReflectionHelpers.setField;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for layout parser for remote layout
+ */
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class DefaultLayoutProviderTest {
+
+ private LauncherModelHelper mModelHelper;
+ private Context mTargetContext;
+
+ @Before
+ public void setUp() {
+ mModelHelper = new LauncherModelHelper();
+ mTargetContext = RuntimeEnvironment.application;
+
+ shadowOf(mTargetContext.getPackageManager())
+ .addActivityIfNotPresent(new ComponentName(TEST_PACKAGE, TEST_PACKAGE));
+ }
+
+ @Test
+ public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
+ writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
+ .putApp(TEST_PACKAGE, TEST_PACKAGE));
+
+ // Verify one item in hotseat
+ assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
+ ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
+ assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType);
+ }
+
+ @Test
+ public void testCustomProfileLoaded_with_folder() throws Exception {
+ writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
+ .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .build());
+
+ // Verify folder
+ assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
+ ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
+ assertEquals(3, ((FolderInfo) info).contents.size());
+ }
+
+ @Test
+ public void testCustomProfileLoaded_with_widget() throws Exception {
+ String pendingAppPkg = "com.test.pending";
+
+ // Add a dummy session info so that the widget exists
+ SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(pendingAppPkg);
+
+ PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
+ int sessionId = installer.createSession(params);
+ SessionInfo sessionInfo = installer.getSessionInfo(sessionId);
+ setField(sessionInfo, "installerPackageName", "com.test");
+ setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON);
+
+ writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
+ .putWidget(pendingAppPkg, "DummyWidget", 2, 2));
+
+ // Verify widget
+ assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size());
+ ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0);
+ assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType);
+ assertEquals(2, info.spanX);
+ assertEquals(2, info.spanY);
+ }
+
+ private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception {
+ mModelHelper.setupDefaultLayoutProvider(builder);
+
+ LoaderResults results = new LoaderResults(
+ LauncherAppState.getInstance(mTargetContext),
+ mModelHelper.getBgDataModel(),
+ mModelHelper.getAllAppsList(),
+ new Callbacks[0]);
+ LoaderTask task = new LoaderTask(
+ LauncherAppState.getInstance(mTargetContext),
+ mModelHelper.getAllAppsList(),
+ mModelHelper.getBgDataModel(),
+ results);
+ Executors.MODEL_EXECUTOR.submit(() -> task.loadWorkspace(new ArrayList<>())).get();
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
index 53287a9..f46b849 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
@@ -6,33 +6,53 @@
import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
+import static com.android.launcher3.util.LauncherModelHelper.DESKTOP;
+import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
+import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link GridBackupTable}
*/
-@RunWith(RobolectricTestRunner.class)
-public class GridBackupTableTest extends BaseGridChangesTestCase {
+@RunWith(LauncherRoboTestRunner.class)
+public class GridBackupTableTest {
private static final int BACKUP_ITEM_COUNT = 12;
+ private LauncherModelHelper mModelHelper;
+ private Context mContext;
+ private SQLiteDatabase mDb;
+
@Before
- public void setupGridData() {
- createGrid(new int[][][]{{
+ public void setUp() {
+ mModelHelper = new LauncherModelHelper();
+ mContext = RuntimeEnvironment.application;
+ mDb = mModelHelper.provider.getDb();
+
+ setupGridData();
+ }
+
+ private void setupGridData() {
+ mModelHelper.createGrid(new int[][][]{{
{ APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
{ SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
{ NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
@@ -81,7 +101,7 @@
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
- addItem(1, 2, DESKTOP, 1, 1);
+ mModelHelper.addItem(1, 2, DESKTOP, 1, 1);
assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index 53f6a06..c426dc5 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -1,25 +1,31 @@
package com.android.launcher3.model;
import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScreenIds;
+import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
+import static com.android.launcher3.util.LauncherModelHelper.HOTSEAT;
+import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.FlagOverrideRule;
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
import java.util.HashSet;
import java.util.LinkedList;
@@ -27,34 +33,39 @@
/**
* Unit tests for {@link GridSizeMigrationTask}
*/
-@RunWith(RobolectricTestRunner.class)
-public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase {
+@RunWith(LauncherRoboTestRunner.class)
+public class GridSizeMigrationTaskTest {
- @Rule
- public final FlagOverrideRule flags = new FlagOverrideRule();
+ private LauncherModelHelper mModelHelper;
+ private Context mContext;
+ private SQLiteDatabase mDb;
private HashSet<String> mValidPackages;
private InvariantDeviceProfile mIdp;
@Before
public void setUp() {
+ mModelHelper = new LauncherModelHelper();
+ mContext = RuntimeEnvironment.application;
+ mDb = mModelHelper.provider.getDb();
+
mValidPackages = new HashSet<>();
mValidPackages.add(TEST_PACKAGE);
- mIdp = new InvariantDeviceProfile();
+ mIdp = InvariantDeviceProfile.INSTANCE.get(mContext);
}
@Test
public void testHotseatMigration_apps_dropped() throws Exception {
int[] hotseatItems = {
- addItem(APP_ICON, 0, HOTSEAT, 0, 0),
- addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
+ mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0),
+ mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
-1,
- addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
- addItem(APP_ICON, 4, HOTSEAT, 0, 0),
+ mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
+ mModelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0),
};
mIdp.numHotseatIcons = 3;
- new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
.migrateHotseat();
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -63,15 +74,15 @@
@Test
public void testHotseatMigration_shortcuts_dropped() throws Exception {
int[] hotseatItems = {
- addItem(APP_ICON, 0, HOTSEAT, 0, 0),
- addItem(30, 1, HOTSEAT, 0, 0),
+ mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0),
+ mModelHelper.addItem(30, 1, HOTSEAT, 0, 0),
-1,
- addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
- addItem(10, 4, HOTSEAT, 0, 0),
+ mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
+ mModelHelper.addItem(10, 4, HOTSEAT, 0, 0),
};
mIdp.numHotseatIcons = 3;
- new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
.migrateHotseat();
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -109,14 +120,14 @@
@Test
public void testWorkspace_empty_row_column_removed() throws Exception {
- int[][][] ids = createGrid(new int[][][]{{
+ int[][][] ids = mModelHelper.createGrid(new int[][][]{{
{ 0, 0, -1, 1},
{ 3, 1, -1, 4},
{ -1, -1, -1, -1},
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Column 2 and row 2 got removed.
@@ -129,14 +140,14 @@
@Test
public void testWorkspace_new_screen_created() throws Exception {
- int[][][] ids = createGrid(new int[][][]{{
+ int[][][] ids = mModelHelper.createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column get moved to new screen
@@ -151,7 +162,7 @@
@Test
public void testWorkspace_items_merged_in_next_screen() throws Exception {
- int[][][] ids = createGrid(new int[][][]{{
+ int[][][] ids = mModelHelper.createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
@@ -161,7 +172,7 @@
{ 3, 1, -1, 4},
}});
- new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on the 3rd
@@ -179,9 +190,9 @@
@Test
public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
- // First screen has 2 items that need to be moved, but second screen has only one
+ // First screen has 2 mItems that need to be moved, but second screen has only one
// empty space after migration (top-left corner)
- int[][][] ids = createGrid(new int[][][]{{
+ int[][][] ids = mModelHelper.createGrid(new int[][][]{{
{ 0, 0, 0, 1},
{ 3, 1, 0, 4},
{ -1, -1, -1, -1},
@@ -193,7 +204,7 @@
{ 5, 2, -1, 6},
}});
- new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@@ -217,14 +228,14 @@
}
// The first screen has one item on the 4th column which needs moving, as the first row
// will be kept empty.
- int[][][] ids = createGrid(new int[][][]{{
+ int[][][] ids = mModelHelper.createGrid(new int[][][]{{
{ -1, -1, -1, -1},
{ 3, 1, 7, 0},
{ 8, 7, 7, -1},
{ 5, 2, 7, -1},
}}, 0);
- new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 4)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@@ -244,14 +255,14 @@
return;
}
// Items will get moved to the next screen to keep the first screen empty.
- int[][][] ids = createGrid(new int[][][]{{
+ int[][][] ids = mModelHelper.createGrid(new int[][][]{{
{ -1, -1, -1, -1},
{ 0, 1, 0, 0},
{ 8, 7, 7, -1},
{ 5, 6, 7, -1},
}}, 0);
- new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+ new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@@ -266,12 +277,12 @@
}
/**
- * Verifies that the workspace items are arranged in the provided order.
+ * Verifies that the workspace mItems are arranged in the provided order.
* @param ids A 3d array where the first dimension represents the screen, and the rest two
* represent the workspace grid.
*/
private void verifyWorkspace(int[][][] ids) {
- IntArray allScreens = getWorkspaceScreenIds(mDb);
+ IntArray allScreens = getWorkspaceScreenIds(mDb, LauncherSettings.Favorites.TABLE_NAME);
assertEquals(ids.length, allScreens.size());
int total = 0;
@@ -340,7 +351,7 @@
private final LinkedList<Point> mPoints;
public MultiStepMigrationTaskVerifier(int... points) {
- super(null, null, null);
+ super(null, null, null, false);
mPoints = new LinkedList<>();
for (int i = 0; i < points.length; i += 2) {
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
similarity index 74%
rename from tests/src/com/android/launcher3/model/LoaderCursorTest.java
rename to robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 64df8e0..6e41a4f 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -1,29 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.launcher3.model;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.database.MatrixCursor;
-import android.graphics.Bitmap;
-import android.os.Process;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.util.PackageManagerHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
import static com.android.launcher3.LauncherSettings.Favorites.CELLX;
import static com.android.launcher3.LauncherSettings.Favorites.CELLY;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
@@ -32,6 +24,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.ICON;
import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE;
import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE;
+import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
@@ -40,50 +33,66 @@
import static com.android.launcher3.LauncherSettings.Favorites.SCREEN;
import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
import static com.android.launcher3.LauncherSettings.Favorites._ID;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.os.Process;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
/**
* Tests for {@link LoaderCursor}
*/
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(Mode.PAUSED)
public class LoaderCursorTest {
- private LauncherAppState mMockApp;
- private IconCache mMockIconCache;
+ private LauncherAppState mApp;
private MatrixCursor mCursor;
private InvariantDeviceProfile mIDP;
private Context mContext;
- private LauncherAppsCompat mLauncherApps;
private LoaderCursor mLoaderCursor;
@Before
public void setup() {
- mIDP = new InvariantDeviceProfile();
+ mContext = RuntimeEnvironment.application;
+ mIDP = InvariantDeviceProfile.INSTANCE.get(mContext);
+ mApp = LauncherAppState.getInstance(mContext);
+
mCursor = new MatrixCursor(new String[] {
ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE,
_ID, CONTAINER, ITEM_TYPE, PROFILE_ID,
SCREEN, CELLX, CELLY, RESTORED, INTENT
});
- mContext = InstrumentationRegistry.getTargetContext();
- mMockApp = mock(LauncherAppState.class);
- mMockIconCache = mock(IconCache.class);
- when(mMockApp.getIconCache()).thenReturn(mMockIconCache);
- when(mMockApp.getInvariantDeviceProfile()).thenReturn(mIDP);
- when(mMockApp.getContext()).thenReturn(mContext);
- mLauncherApps = LauncherAppsCompat.getInstance(mContext);
-
- mLoaderCursor = new LoaderCursor(mCursor, mMockApp);
+ mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp);
mLoaderCursor.allUsers.put(0, Process.myUserHandle());
}
@@ -106,26 +115,31 @@
}
@Test
- public void getAppShortcutInfo_dontAllowMissing_validComponent() {
+ public void getAppShortcutInfo_dontAllowMissing_validComponent() throws Exception {
+ ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_PACKAGE);
+ shadowOf(mContext.getPackageManager()).addActivityIfNotPresent(cn);
+
initCursor(ITEM_TYPE_APPLICATION, "");
assertTrue(mLoaderCursor.moveToNext());
- ComponentName cn = mLauncherApps.getActivityList(null, mLoaderCursor.user)
- .get(0).getComponentName();
- WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo(
- new Intent().setComponent(cn), false /* allowMissingTarget */, true);
+ WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() ->
+ mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), false /* allowMissingTarget */, true))
+ .get();
assertNotNull(info);
assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
}
@Test
- public void getAppShortcutInfo_allowMissing_invalidComponent() {
+ public void getAppShortcutInfo_allowMissing_invalidComponent() throws Exception {
initCursor(ITEM_TYPE_APPLICATION, "");
assertTrue(mLoaderCursor.moveToNext());
ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
- WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo(
- new Intent().setComponent(cn), true /* allowMissingTarget */, true);
+ WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() ->
+ mLoaderCursor.getAppShortcutInfo(
+ new Intent().setComponent(cn), true /* allowMissingTarget */, true))
+ .get();
assertNotNull(info);
assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
}
@@ -135,11 +149,8 @@
initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut");
assertTrue(mLoaderCursor.moveToNext());
- Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
- when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user)))
- .thenReturn(BitmapInfo.fromBitmap(icon));
WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
- assertEquals(icon, info.iconBitmap);
+ assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user));
assertEquals("my-shortcut", info.title);
assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
}
@@ -161,7 +172,7 @@
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
- // Overlapping items are not placed
+ // Overlapping mItems are not placed
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
assertFalse(mLoaderCursor.checkItemPlacement(
@@ -187,7 +198,7 @@
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
- // Hotseat items are only placed based on screenId
+ // Hotseat mItems are only placed based on screenId
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1)));
assertTrue(mLoaderCursor.checkItemPlacement(
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
new file mode 100644
index 0000000..c7979b2
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import static com.android.launcher3.util.Executors.createAndStartNewForegroundLooper;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.spy;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.os.Process;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Tests to verify multiple callbacks in Loader
+ */
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class ModelMultiCallbacksTest {
+
+ private LauncherModelHelper mModelHelper;
+
+ private ShadowPackageManager mSpm;
+ private LooperExecutor mTempMainExecutor;
+
+ @Before
+ public void setUp() throws Exception {
+ mModelHelper = new LauncherModelHelper();
+ mModelHelper.installApp(TEST_PACKAGE);
+
+ mSpm = shadowOf(RuntimeEnvironment.application.getPackageManager());
+
+ // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
+ // so that we can wait appropriately for the loader to complete.
+ mTempMainExecutor = new LooperExecutor(createAndStartNewForegroundLooper("tempMain"));
+ ReflectionHelpers.setField(mModelHelper.getModel(), "mMainExecutor", mTempMainExecutor);
+ }
+
+ @Test
+ public void testTwoCallbacks_loadedTogether() throws Exception {
+ setupWorkspacePages(3);
+
+ MyCallbacks cb1 = spy(MyCallbacks.class);
+ mModelHelper.getModel().addCallbacksAndLoad(cb1);
+
+ waitForLoaderAndTempMainThread();
+ cb1.verifySynchronouslyBound(3);
+
+ // Add a new callback
+ cb1.reset();
+ MyCallbacks cb2 = spy(MyCallbacks.class);
+ cb2.mPageToBindSync = 2;
+ mModelHelper.getModel().addCallbacksAndLoad(cb2);
+
+ waitForLoaderAndTempMainThread();
+ cb1.verifySynchronouslyBound(3);
+ cb2.verifySynchronouslyBound(3);
+
+ // Remove callbacks
+ cb1.reset();
+ cb2.reset();
+
+ // No effect on callbacks when removing an callback
+ mModelHelper.getModel().removeCallbacks(cb2);
+ waitForLoaderAndTempMainThread();
+ assertNull(cb1.mDeferredExecutor);
+ assertNull(cb2.mDeferredExecutor);
+
+ // Reloading only loads registered callbacks
+ mModelHelper.getModel().startLoader();
+ waitForLoaderAndTempMainThread();
+ cb1.verifySynchronouslyBound(3);
+ assertNull(cb2.mDeferredExecutor);
+ }
+
+ @Test
+ public void testTwoCallbacks_receiveUpdates() throws Exception {
+ setupWorkspacePages(1);
+
+ MyCallbacks cb1 = spy(MyCallbacks.class);
+ MyCallbacks cb2 = spy(MyCallbacks.class);
+ mModelHelper.getModel().addCallbacksAndLoad(cb1);
+ mModelHelper.getModel().addCallbacksAndLoad(cb2);
+ waitForLoaderAndTempMainThread();
+
+ cb1.verifyApps(TEST_PACKAGE);
+ cb2.verifyApps(TEST_PACKAGE);
+
+ // Install package 1
+ String pkg1 = "com.test.pkg1";
+ mModelHelper.installApp(pkg1);
+ mModelHelper.getModel().onPackageAdded(pkg1, Process.myUserHandle());
+ waitForLoaderAndTempMainThread();
+ cb1.verifyApps(TEST_PACKAGE, pkg1);
+ cb2.verifyApps(TEST_PACKAGE, pkg1);
+
+ // Install package 2
+ String pkg2 = "com.test.pkg2";
+ mModelHelper.installApp(pkg2);
+ mModelHelper.getModel().onPackageAdded(pkg2, Process.myUserHandle());
+ waitForLoaderAndTempMainThread();
+ cb1.verifyApps(TEST_PACKAGE, pkg1, pkg2);
+ cb2.verifyApps(TEST_PACKAGE, pkg1, pkg2);
+
+ // Uninstall package 2
+ mSpm.removePackage(pkg1);
+ mModelHelper.getModel().onPackageRemoved(pkg1, Process.myUserHandle());
+ waitForLoaderAndTempMainThread();
+ cb1.verifyApps(TEST_PACKAGE, pkg2);
+ cb2.verifyApps(TEST_PACKAGE, pkg2);
+
+ // Unregister a callback and verify updates no longer received
+ mModelHelper.getModel().removeCallbacks(cb2);
+ mSpm.removePackage(pkg2);
+ mModelHelper.getModel().onPackageRemoved(pkg2, Process.myUserHandle());
+ waitForLoaderAndTempMainThread();
+ cb1.verifyApps(TEST_PACKAGE);
+ cb2.verifyApps(TEST_PACKAGE, pkg2);
+ }
+
+ private void waitForLoaderAndTempMainThread() throws Exception {
+ Executors.MODEL_EXECUTOR.submit(() -> { }).get();
+ mTempMainExecutor.submit(() -> { }).get();
+ }
+
+ private void setupWorkspacePages(int pageCount) throws Exception {
+ // Create a layout with 3 pages
+ LauncherLayoutBuilder builder = new LauncherLayoutBuilder();
+ for (int i = 0; i < pageCount; i++) {
+ builder.atWorkspace(1, 1, i).putApp(TEST_PACKAGE, TEST_PACKAGE);
+ }
+ mModelHelper.setupDefaultLayoutProvider(builder);
+ }
+
+ private abstract static class MyCallbacks implements Callbacks {
+
+ final List<ItemInfo> mItems = new ArrayList<>();
+ int mPageToBindSync = 0;
+ int mPageBoundSync = PagedView.INVALID_PAGE;
+ ViewOnDrawExecutor mDeferredExecutor;
+ AppInfo[] mAppInfos;
+
+ MyCallbacks() { }
+
+ @Override
+ public void onPageBoundSynchronously(int page) {
+ mPageBoundSync = page;
+ }
+
+ @Override
+ public void executeOnNextDraw(ViewOnDrawExecutor executor) {
+ mDeferredExecutor = executor;
+ }
+
+ @Override
+ public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) {
+ mItems.addAll(shortcuts);
+ }
+
+ @Override
+ public void bindAllApplications(AppInfo[] apps) {
+ mAppInfos = apps;
+ }
+
+ @Override
+ public int getPageToBindSynchronously() {
+ return mPageToBindSync;
+ }
+
+ public void reset() {
+ mItems.clear();
+ mPageBoundSync = PagedView.INVALID_PAGE;
+ mDeferredExecutor = null;
+ mAppInfos = null;
+ }
+
+ public void verifySynchronouslyBound(int totalItems) {
+ // Verify that the requested page is bound synchronously
+ assertEquals(mPageBoundSync, mPageToBindSync);
+ assertEquals(mItems.size(), 1);
+ assertEquals(mItems.get(0).screenId, mPageBoundSync);
+ assertNotNull(mDeferredExecutor);
+
+ // Verify that all other pages are bound properly
+ mDeferredExecutor.runAllTasks();
+ assertEquals(mItems.size(), totalItems);
+ }
+
+ public void verifyApps(String... apps) {
+ assertEquals(apps.length, mAppInfos.length);
+ assertEquals(Arrays.stream(mAppInfos)
+ .map(ai -> ai.getTargetComponent().getPackageName())
+ .collect(Collectors.toSet()),
+ new HashSet<>(Arrays.asList(apps)));
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index 42a4f5c..bd71f01 100644
--- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -5,13 +5,15 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
import java.util.Arrays;
import java.util.HashSet;
@@ -19,16 +21,20 @@
/**
* Tests for {@link PackageInstallStateChangedTask}
*/
-@RunWith(RobolectricTestRunner.class)
-public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
+@RunWith(LauncherRoboTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class PackageInstallStateChangedTaskTest {
+
+ private LauncherModelHelper mModelHelper;
@Before
- public void initData() throws Exception {
- initializeData("/package_install_state_change_task_data.txt");
+ public void setup() throws Exception {
+ mModelHelper = new LauncherModelHelper();
+ mModelHelper.initializeData("/package_install_state_change_task_data.txt");
}
private PackageInstallStateChangedTask newTask(String pkg, int progress) {
- int state = PackageInstallerCompat.STATUS_INSTALLING;
+ int state = PackageInstallInfo.STATUS_INSTALLING;
PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress,
android.os.Process.myUserHandle());
return new PackageInstallStateChangedTask(installInfo);
@@ -36,7 +42,7 @@
@Test
public void testSessionUpdate_ignore_installed() throws Exception {
- executeTaskForTest(newTask("app1", 30));
+ mModelHelper.executeTaskForTest(newTask("app1", 30));
// No shortcuts were updated
verifyProgressUpdate(0);
@@ -44,21 +50,21 @@
@Test
public void testSessionUpdate_shortcuts_updated() throws Exception {
- executeTaskForTest(newTask("app3", 30));
+ mModelHelper.executeTaskForTest(newTask("app3", 30));
verifyProgressUpdate(30, 5, 6, 7);
}
@Test
public void testSessionUpdate_widgets_updated() throws Exception {
- executeTaskForTest(newTask("app4", 30));
+ mModelHelper.executeTaskForTest(newTask("app4", 30));
verifyProgressUpdate(30, 8, 9);
}
private void verifyProgressUpdate(int progress, Integer... idsUpdated) {
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
- for (ItemInfo info : bgDataModel.itemsIdMap) {
+ for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
if (info instanceof WorkspaceItemInfo) {
assertEquals(updates.contains(info.id) ? progress: 0,
((WorkspaceItemInfo) info).getInstallProgress());
diff --git a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java b/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
index 83bf7da..7612ae1 100644
--- a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
@@ -27,9 +27,10 @@
import android.content.pm.ShortcutInfo;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
@@ -39,7 +40,7 @@
/**
* Tests the sorting and filtering of shortcuts in {@link PopupPopulator}.
*/
-@RunWith(RobolectricTestRunner.class)
+@RunWith(LauncherRoboTestRunner.class)
public class PopupPopulatorTest {
@Test
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
similarity index 78%
rename from tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
rename to robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 6fa8d62..7ef670c 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -1,25 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.launcher3.provider;
+import static org.junit.Assert.assertEquals;
+
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
-
-import static org.junit.Assert.assertEquals;
+import org.robolectric.RuntimeEnvironment;
/**
* Tests for {@link RestoreDbTask}
*/
-@MediumTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(LauncherRoboTestRunner.class)
public class RestoreDbTaskTest {
@Test
@@ -82,7 +95,7 @@
private final long mProfileId;
MyDatabaseHelper(long profileId) {
- super(InstrumentationRegistry.getContext(), null, null);
+ super(RuntimeEnvironment.application, null);
mProfileId = profileId;
}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
new file mode 100644
index 0000000..696ffd0
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shadows;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.os.Process;
+import android.os.UserHandle;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowAppWidgetManager;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Extension of {@link ShadowAppWidgetManager} with missing shadow methods
+ */
+@Implements(value = AppWidgetManager.class)
+public class LShadowAppWidgetManager extends ShadowAppWidgetManager {
+
+ @Override
+ protected List<AppWidgetProviderInfo> getInstalledProviders() {
+ return getInstalledProvidersForProfile(null);
+ }
+
+ @Implementation
+ public List<AppWidgetProviderInfo> getInstalledProvidersForProfile(UserHandle profile) {
+ UserHandle user = profile == null ? Process.myUserHandle() : profile;
+ return super.getInstalledProviders().stream().filter(
+ info -> user.equals(info.getProfile())).collect(Collectors.toList());
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
new file mode 100644
index 0000000..eae0101
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.shadows;
+
+import android.app.backup.BackupManager;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+
+import androidx.annotation.Nullable;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBackupManager;
+
+/**
+ * Extension of {@link ShadowBackupManager} with missing shadow methods
+ */
+@Implements(value = BackupManager.class)
+public class LShadowBackupManager extends ShadowBackupManager {
+
+ private LongSparseArray<UserHandle> mProfileMapping = new LongSparseArray<>();
+
+ public void addProfile(long userSerial, UserHandle userHandle) {
+ mProfileMapping.put(userSerial, userHandle);
+ }
+
+ @Implementation
+ @Nullable
+ public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
+ return mProfileMapping.get(ancestralSerialNumber);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBitmap.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBitmap.java
new file mode 100644
index 0000000..abd90bb
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBitmap.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shadows;
+
+import android.graphics.Bitmap;
+import android.graphics.Paint;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBitmap;
+
+/**
+ * Extension of {@link ShadowBitmap} with missing shadow methods
+ */
+@Implements(value = Bitmap.class)
+public class LShadowBitmap extends ShadowBitmap {
+
+ @Implementation
+ protected Bitmap extractAlpha(Paint paint, int[] offsetXY) {
+ return extractAlpha();
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
new file mode 100644
index 0000000..f16ed33
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shadows;
+
+import static org.robolectric.util.ReflectionHelpers.ClassParameter;
+import static org.robolectric.util.ReflectionHelpers.callConstructor;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowLauncherApps;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+/**
+ * Extension of {@link ShadowLauncherApps} with missing shadow methods
+ */
+@Implements(value = LauncherApps.class)
+public class LShadowLauncherApps extends ShadowLauncherApps {
+
+ public final ArraySet<PackageUserKey> disabledApps = new ArraySet<>();
+ public final ArraySet<ComponentKey> disabledActivities = new ArraySet<>();
+
+ @Implementation
+ @Override
+ protected List<ShortcutInfo> getShortcuts(LauncherApps.ShortcutQuery query, UserHandle user) {
+ try {
+ return super.getShortcuts(query, user);
+ } catch (UnsupportedOperationException e) {
+ return Collections.emptyList();
+ }
+ }
+
+ @Implementation
+ protected boolean isPackageEnabled(String packageName, UserHandle user) {
+ return !disabledApps.contains(new PackageUserKey(packageName, user));
+ }
+
+ @Implementation
+ protected boolean isActivityEnabled(ComponentName component, UserHandle user) {
+ return !disabledActivities.contains(new ComponentKey(component, user));
+ }
+
+ @Implementation
+ protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+ ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
+ .resolveActivity(intent, 0);
+ return ri == null ? null : getLauncherActivityInfo(ri.activityInfo);
+ }
+
+ public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) {
+ return callConstructor(LauncherActivityInfo.class,
+ ClassParameter.from(Context.class, RuntimeEnvironment.application),
+ ClassParameter.from(ActivityInfo.class, activityInfo),
+ ClassParameter.from(UserHandle.class, Process.myUserHandle()));
+ }
+
+ @Implementation
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ return RuntimeEnvironment.application.getPackageManager()
+ .getApplicationInfo(packageName, flags);
+ }
+
+ @Implementation
+ public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(packageName);
+ return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
+ .stream()
+ .map(ri -> getLauncherActivityInfo(ri.activityInfo))
+ .collect(Collectors.toList());
+ }
+
+ @Implementation
+ public boolean hasShortcutHostPermission() {
+ return true;
+ }
+
+ @Implementation
+ public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
+ return RuntimeEnvironment.application.getPackageManager().getPackageInstaller()
+ .getAllSessions();
+ }
+
+ @Implementation
+ public void registerPackageInstallerSessionCallback(
+ Executor executor, PackageInstaller.SessionCallback callback) {
+ }
+
+ @Override
+ protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
+ UserHandle user) {
+ return Collections.emptyList();
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
new file mode 100644
index 0000000..576ddbd
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shadows;
+
+import android.os.Parcel;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.SparseBooleanArray;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowUserManager;
+
+/**
+ * Extension of {@link ShadowUserManager} with missing shadow methods
+ */
+@Implements(value = UserManager.class)
+public class LShadowUserManager extends ShadowUserManager {
+
+ private final SparseBooleanArray mQuietUsers = new SparseBooleanArray();
+ private final SparseBooleanArray mLockedUsers = new SparseBooleanArray();
+
+ @Implementation
+ protected boolean isQuietModeEnabled(UserHandle userHandle) {
+ return mQuietUsers.get(userHandle.hashCode());
+ }
+
+ public void setQuietModeEnabled(UserHandle userHandle, boolean enabled) {
+ mQuietUsers.put(userHandle.hashCode(), enabled);
+ }
+
+ @Implementation
+ protected boolean isUserUnlocked(UserHandle userHandle) {
+ return !mLockedUsers.get(userHandle.hashCode());
+ }
+
+ public void setUserLocked(UserHandle userHandle, boolean enabled) {
+ mLockedUsers.put(userHandle.hashCode(), enabled);
+ }
+
+ // Create user handle from parcel since UserHandle.of() was only added in later APIs.
+ public static UserHandle newUserHandle(int uid) {
+ Parcel userParcel = Parcel.obtain();
+ userParcel.writeInt(uid);
+ userParcel.setDataPosition(0);
+ return new UserHandle(userParcel);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
new file mode 100644
index 0000000..344f532
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shadows;
+
+import android.content.Context;
+
+import com.android.launcher3.uioverrides.DeviceFlag;
+import com.android.launcher3.util.LooperExecutor;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
+ */
+@Implements(value = DeviceFlag.class, isInAndroidSdk = false)
+public class ShadowDeviceFlag {
+
+ /**
+ * Mock change listener as it uses internal system classes not available to robolectric
+ */
+ @Implementation
+ protected void addChangeListener(Context context, Runnable r) { }
+
+ @Implementation
+ protected static boolean getDeviceValue(String key, boolean defaultValue) {
+ return defaultValue;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
new file mode 100644
index 0000000..a3b7dc7
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shadows;
+
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
+import static org.robolectric.shadow.api.Shadow.directlyOn;
+import static org.robolectric.util.ReflectionHelpers.setField;
+
+import android.os.Handler;
+
+import com.android.launcher3.util.LooperExecutor;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+
+/**
+ * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
+ */
+@Implements(value = LooperExecutor.class, isInAndroidSdk = false)
+public class ShadowLooperExecutor {
+
+ @RealObject private LooperExecutor mRealExecutor;
+
+ @Implementation
+ protected Handler getHandler() {
+ Handler handler = directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
+ Thread thread = handler.getLooper().getThread();
+ if (!thread.isAlive()) {
+ // Robolectric destroys all loopers at the end of every test. Since Launcher maintains
+ // some static threads, they need to be reinitialized in case they were destroyed.
+ setField(mRealExecutor, "mHandler",
+ new Handler(createAndStartNewLooper(thread.getName())));
+ }
+ return directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
new file mode 100644
index 0000000..6e2ccf8
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shadows;
+
+import static org.robolectric.shadow.api.Shadow.invokeConstructor;
+import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * Shadow for {@link MainThreadInitializedObject} to provide reset functionality for static sObjects
+ */
+@Implements(value = MainThreadInitializedObject.class, isInAndroidSdk = false)
+public class ShadowMainThreadInitializedObject {
+
+ // Keep reference to all created MainThreadInitializedObject so they can be cleared after test
+ private static Set<MainThreadInitializedObject> sObjects =
+ Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
+
+ @RealObject private MainThreadInitializedObject mRealObject;
+
+ @Implementation
+ protected void __constructor__(ObjectProvider provider) {
+ invokeConstructor(MainThreadInitializedObject.class, mRealObject,
+ from(ObjectProvider.class, provider));
+ sObjects.add(mRealObject);
+ }
+
+ /**
+ * Resets all the initialized sObjects to be null
+ */
+ public static void resetInitializedObjects() {
+ for (MainThreadInitializedObject object : new ArrayList<>(sObjects)) {
+ object.initializeForTesting(null);
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
index aa51ad2..e453e31 100644
--- a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
@@ -2,7 +2,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -11,7 +10,7 @@
/**
* Unit tests for {@link GridOccupancy}
*/
-@RunWith(RobolectricTestRunner.class)
+@RunWith(LauncherRoboTestRunner.class)
public class GridOccupancyTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
index c08e198..5974ea5 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
@@ -19,12 +19,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
/**
* Robolectric unit tests for {@link IntArray}
*/
-@RunWith(RobolectricTestRunner.class)
+@RunWith(LauncherRoboTestRunner.class)
public class IntArrayTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
index 8513353..aedf71e 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
@@ -20,8 +20,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -29,7 +27,7 @@
/**
* Robolectric unit tests for {@link IntSet}
*/
-@RunWith(RobolectricTestRunner.class)
+@RunWith(LauncherRoboTestRunner.class)
public class IntSetTest {
@Test
diff --git a/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
similarity index 100%
rename from tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
rename to robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
new file mode 100644
index 0000000..e133cf2
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Process;
+import android.provider.Settings;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.pm.UserCache;
+
+import org.mockito.ArgumentCaptor;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+
+/**
+ * Utility class to help manage Launcher Model and related objects for test.
+ */
+public class LauncherModelHelper {
+
+ public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+
+ public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ public static final int NO__ICON = -1;
+ public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
+
+ // Authority for providing a dummy default-workspace-layout data.
+ private static final String TEST_PROVIDER_AUTHORITY =
+ LauncherModelHelper.class.getName().toLowerCase();
+ private static final int DEFAULT_BITMAP_SIZE = 10;
+ private static final int DEFAULT_GRID_SIZE = 4;
+
+ private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>();
+ public final TestLauncherProvider provider;
+ private final long mDefaultProfileId;
+
+ private BgDataModel mDataModel;
+ private AllAppsList mAllAppsList;
+
+ public LauncherModelHelper() {
+ provider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+ mDefaultProfileId = UserCache.INSTANCE.get(RuntimeEnvironment.application)
+ .getSerialNumberForUser(Process.myUserHandle());
+ ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider);
+ }
+
+ public LauncherModel getModel() {
+ return LauncherAppState.getInstance(RuntimeEnvironment.application).getModel();
+ }
+
+ public synchronized BgDataModel getBgDataModel() {
+ if (mDataModel == null) {
+ mDataModel = ReflectionHelpers.getField(getModel(), "mBgDataModel");
+ }
+ return mDataModel;
+ }
+
+ public synchronized AllAppsList getAllAppsList() {
+ if (mAllAppsList == null) {
+ mAllAppsList = ReflectionHelpers.getField(getModel(), "mBgAllAppsList");
+ }
+ return mAllAppsList;
+ }
+
+ /**
+ * Synchronously executes the task and returns all the UI callbacks posted.
+ */
+ public List<Runnable> executeTaskForTest(ModelUpdateTask task) throws Exception {
+ LauncherModel model = getModel();
+ if (!model.isModelLoaded()) {
+ ReflectionHelpers.setField(model, "mModelLoaded", true);
+ }
+ Executor mockExecutor = mock(Executor.class);
+ model.enqueueModelUpdateTask(new ModelUpdateTask() {
+ @Override
+ public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
+ AllAppsList allAppsList, Executor uiExecutor) {
+ task.init(app, model, dataModel, allAppsList, mockExecutor);
+ }
+
+ @Override
+ public void run() {
+ task.run();
+ }
+ });
+ MODEL_EXECUTOR.submit(() -> null).get();
+
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mockExecutor, atLeast(0)).execute(captor.capture());
+ return captor.getAllValues();
+ }
+
+ /**
+ * Synchronously executes a task on the model
+ */
+ public <T> T executeSimpleTask(Function<BgDataModel, T> task) throws Exception {
+ BgDataModel dataModel = getBgDataModel();
+ return MODEL_EXECUTOR.submit(() -> task.apply(dataModel)).get();
+ }
+
+ /**
+ * Initializes mock data for the test.
+ */
+ public void initializeData(String resourceName) throws Exception {
+ Context targetContext = RuntimeEnvironment.application;
+ BgDataModel bgDataModel = getBgDataModel();
+ AllAppsList allAppsList = getAllAppsList();
+
+ MODEL_EXECUTOR.submit(() -> {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ this.getClass().getResourceAsStream(resourceName)))) {
+ String line;
+ HashMap<String, Class> classMap = new HashMap<>();
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("#") || line.isEmpty()) {
+ continue;
+ }
+ String[] commands = line.split(" ");
+ switch (commands[0]) {
+ case "classMap":
+ classMap.put(commands[1], Class.forName(commands[2]));
+ break;
+ case "bgItem":
+ bgDataModel.addItem(targetContext,
+ (ItemInfo) initItem(classMap.get(commands[1]), commands, 2),
+ false);
+ break;
+ case "allApps":
+ allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
+ break;
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }).get();
+ }
+
+ private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
+ HashMap<String, Field> cache = mFieldCache.get(clazz);
+ if (cache == null) {
+ cache = new HashMap<>();
+ Class c = clazz;
+ while (c != null) {
+ for (Field f : c.getDeclaredFields()) {
+ f.setAccessible(true);
+ cache.put(f.getName(), f);
+ }
+ c = c.getSuperclass();
+ }
+ mFieldCache.put(clazz, cache);
+ }
+
+ Object item = clazz.newInstance();
+ for (int i = startIndex; i < fieldDef.length; i++) {
+ String[] fieldData = fieldDef[i].split("=", 2);
+ Field f = cache.get(fieldData[0]);
+ Class type = f.getType();
+ if (type == int.class || type == long.class) {
+ f.set(item, Integer.parseInt(fieldData[1]));
+ } else if (type == CharSequence.class || type == String.class) {
+ f.set(item, fieldData[1]);
+ } else if (type == Intent.class) {
+ if (!fieldData[1].startsWith("#Intent")) {
+ fieldData[1] = "#Intent;" + fieldData[1] + ";end";
+ }
+ f.set(item, Intent.parseUri(fieldData[1], 0));
+ } else if (type == ComponentName.class) {
+ f.set(item, ComponentName.unflattenFromString(fieldData[1]));
+ } else {
+ throw new Exception("Added parsing logic for "
+ + f.getName() + " of type " + f.getType());
+ }
+ }
+ return item;
+ }
+
+ public int addItem(int type, int screen, int container, int x, int y) {
+ return addItem(type, screen, container, x, y, mDefaultProfileId);
+ }
+
+ /**
+ * Adds a dummy item in the DB.
+ * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
+ * folder (where the type represents the number of items in the folder).
+ */
+ public int addItem(int type, int screen, int container, int x, int y, long profileId) {
+ Context context = RuntimeEnvironment.application;
+ int id = LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+
+ ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites._ID, id);
+ values.put(LauncherSettings.Favorites.CONTAINER, container);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ values.put(LauncherSettings.Favorites.CELLX, x);
+ values.put(LauncherSettings.Favorites.CELLY, y);
+ values.put(LauncherSettings.Favorites.SPANX, 1);
+ values.put(LauncherSettings.Favorites.SPANY, 1);
+ values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
+
+ if (type == APP_ICON || type == SHORTCUT) {
+ values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
+ values.put(LauncherSettings.Favorites.INTENT,
+ new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
+ } else {
+ values.put(LauncherSettings.Favorites.ITEM_TYPE,
+ LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
+ // Add folder items.
+ for (int i = 0; i < type; i++) {
+ addItem(APP_ICON, 0, id, 0, 0, profileId);
+ }
+ }
+
+ context.getContentResolver().insert(CONTENT_URI, values);
+ return id;
+ }
+
+ public int[][][] createGrid(int[][][] typeArray) {
+ return createGrid(typeArray, 1);
+ }
+
+ public int[][][] createGrid(int[][][] typeArray, int startScreen) {
+ final Context context = RuntimeEnvironment.application;
+ LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+ LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+ return createGrid(typeArray, startScreen, mDefaultProfileId);
+ }
+
+ /**
+ * Initializes the DB with dummy elements to represent the provided grid structure.
+ * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
+ * type definitions. The first dimension represents the screens and the next
+ * two represent the workspace grid.
+ * @param startScreen First screen id from where the icons will be added.
+ * @return the same grid representation where each entry is the corresponding item id.
+ */
+ public int[][][] createGrid(int[][][] typeArray, int startScreen, long profileId) {
+ Context context = RuntimeEnvironment.application;
+ int[][][] ids = new int[typeArray.length][][];
+ for (int i = 0; i < typeArray.length; i++) {
+ // Add screen to DB
+ int screenId = startScreen + i;
+
+ // Keep the screen id counter up to date
+ LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+ ids[i] = new int[typeArray[i].length][];
+ for (int y = 0; y < typeArray[i].length; y++) {
+ ids[i][y] = new int[typeArray[i][y].length];
+ for (int x = 0; x < typeArray[i][y].length; x++) {
+ if (typeArray[i][y][x] < 0) {
+ // Empty cell
+ ids[i][y][x] = -1;
+ } else {
+ ids[i][y][x] = addItem(
+ typeArray[i][y][x], screenId, DESKTOP, x, y, profileId);
+ }
+ }
+ }
+ }
+
+ return ids;
+ }
+
+ /**
+ * Sets up a dummy provider to load the provided layout by default, next time the layout loads
+ */
+ public void setupDefaultLayoutProvider(LauncherLayoutBuilder builder) throws Exception {
+ Context context = RuntimeEnvironment.application;
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
+ idp.numRows = idp.numColumns = idp.numHotseatIcons = DEFAULT_GRID_SIZE;
+ idp.iconBitmapSize = DEFAULT_BITMAP_SIZE;
+
+ Settings.Secure.putString(context.getContentResolver(),
+ "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
+
+ shadowOf(context.getPackageManager())
+ .addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority =
+ TEST_PROVIDER_AUTHORITY;
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ builder.build(new OutputStreamWriter(bos));
+ Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, context);
+ shadowOf(context.getContentResolver()).registerInputStream(layoutUri,
+ new ByteArrayInputStream(bos.toByteArray()));
+ }
+
+ /**
+ * Simulates an apk install with a default main activity with same class and package name
+ */
+ public void installApp(String component) throws NameNotFoundException {
+ ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager());
+ ComponentName cn = new ComponentName(component, component);
+ spm.addActivityIfNotPresent(cn);
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
+ filter.addCategory(Intent.CATEGORY_LAUNCHER);
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+ spm.addIntentFilterForActivity(cn, filter);
+ }
+
+ /**
+ * An extension of LauncherProvider backed up by in-memory database.
+ */
+ public static class TestLauncherProvider extends LauncherProvider {
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ public SQLiteDatabase getDb() {
+ createDbIfNotExists();
+ return mOpenHelper.getWritableDatabase();
+ }
+
+ public DatabaseHelper getHelper() {
+ return mOpenHelper;
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
new file mode 100644
index 0000000..6277c66
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static org.mockito.Mockito.mock;
+
+import com.android.launcher3.shadows.LShadowAppWidgetManager;
+import com.android.launcher3.shadows.LShadowBackupManager;
+import com.android.launcher3.shadows.LShadowBitmap;
+import com.android.launcher3.shadows.LShadowLauncherApps;
+import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.shadows.ShadowDeviceFlag;
+import com.android.launcher3.shadows.ShadowLooperExecutor;
+import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+
+import org.junit.runners.model.InitializationError;
+import org.robolectric.DefaultTestLifecycle;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.TestLifecycle;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
+
+import java.lang.reflect.Method;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Test runner with Launcher specific configurations
+ */
+public class LauncherRoboTestRunner extends RobolectricTestRunner {
+
+ private static final Class<?>[] SHADOWS = new Class<?>[] {
+ LShadowAppWidgetManager.class,
+ LShadowUserManager.class,
+ LShadowLauncherApps.class,
+ LShadowBitmap.class,
+ LShadowBackupManager.class,
+ ShadowLooperExecutor.class,
+ ShadowMainThreadInitializedObject.class,
+ ShadowDeviceFlag.class,
+ };
+
+ public LauncherRoboTestRunner(Class<?> testClass) throws InitializationError {
+ super(testClass);
+ }
+
+ @Override
+ protected Config buildGlobalConfig() {
+ return new Config.Builder().setShadows(SHADOWS).build();
+ }
+
+ @Nonnull
+ @Override
+ protected Class<? extends TestLifecycle> getTestLifecycleClass() {
+ return LauncherTestLifecycle.class;
+ }
+
+ public static class LauncherTestLifecycle extends DefaultTestLifecycle {
+
+ @Override
+ public void beforeTest(Method method) {
+ super.beforeTest(method);
+ ShadowLog.stream = System.out;
+
+ // Disable plugins
+ PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class));
+ }
+
+ @Override
+ public void afterTest(Method method) {
+ super.afterTest(method);
+
+ ShadowLog.stream = null;
+ ShadowMainThreadInitializedObject.resetInitializedObjects();
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
deleted file mode 100644
index 31e303e..0000000
--- a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.android.launcher3.util;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
-import com.android.launcher3.LauncherProvider;
-
-/**
- * An extension of LauncherProvider backed up by in-memory database.
- */
-public class TestLauncherProvider extends LauncherProvider {
-
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- protected synchronized void createDbIfNotExists() {
- if (mOpenHelper == null) {
- mOpenHelper = new MyDatabaseHelper(getContext());
- }
- }
-
- public SQLiteDatabase getDb() {
- createDbIfNotExists();
- return mOpenHelper.getWritableDatabase();
- }
-
- @Override
- protected void notifyListeners() { }
-
- private static class MyDatabaseHelper extends DatabaseHelper {
- public MyDatabaseHelper(Context context) {
- super(context, null, null);
- initIds();
- }
-
- @Override
- public long getDefaultUserSerial() {
- return 0;
- }
-
- @Override
- protected void onEmptyDbCreated() { }
-
- @Override
- protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
- }
-}
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
similarity index 81%
rename from tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
rename to robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index a31d8a6..daae818 100644
--- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -19,37 +19,38 @@
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import android.view.LayoutInflater;
-import com.android.launcher3.icons.IconCache;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.LauncherRoboTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
-import java.util.Map;
+import java.util.Collections;
-import androidx.recyclerview.widget.RecyclerView;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(LauncherRoboTestRunner.class)
public class WidgetsListAdapterTest {
@Mock private LayoutInflater mMockLayoutInflater;
@@ -64,7 +65,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mContext = InstrumentationRegistry.getTargetContext();
+ mContext = RuntimeEnvironment.application;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
@@ -121,29 +122,28 @@
/**
* Helper method to generate the sample widget model map that can be used for the tests
* @param num the number of WidgetItem the map should contain
- * @return
*/
private ArrayList<WidgetListRowEntry> generateSampleMap(int num) {
ArrayList<WidgetListRowEntry> result = new ArrayList<>();
if (num <= 0) return result;
+ ShadowPackageManager spm = shadowOf(mContext.getPackageManager());
- MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap();
- AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext);
- for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) {
+ for (int i = 0; i < num; i++) {
+ ComponentName cn = new ComponentName("com.dummy.apk" + i, "DummyWidet");
+
+ AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+ widgetInfo.provider = cn;
+ ReflectionHelpers.setField(widgetInfo, "providerInfo", spm.addReceiverIfNotPresent(cn));
+
WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
.fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache);
PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
pInfo.title = pInfo.packageName;
pInfo.user = wi.user;
- pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8);
- newMap.addToList(pInfo, wi);
- if (newMap.size() == num) {
- break;
- }
- }
- for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : newMap.entrySet()) {
- result.add(new WidgetListRowEntry(entry.getKey(), entry.getValue()));
+ pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+
+ result.add(new WidgetListRowEntry(pInfo, new ArrayList<>(Collections.singleton(wi))));
}
return result;
diff --git a/settings.gradle b/settings.gradle
index b52bd4f..ce13bfb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,5 @@
include ':IconLoader'
project(':IconLoader').projectDir = new File(rootDir, 'iconloaderlib')
+
+include ':SharedLibWrapper'
+project(':SharedLibWrapper').projectDir = new File(rootDir, 'SharedLibWrapper')
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index c8e7619..f76ca50 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -24,8 +24,10 @@
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
-import com.android.launcher3.compat.UserManagerCompat;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
@@ -36,8 +38,8 @@
*/
public class AppInfo extends ItemInfoWithIcon {
- public static AppInfo[] EMPTY_ARRAY = new AppInfo[0];
- public static Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
+ public static final AppInfo[] EMPTY_ARRAY = new AppInfo[0];
+ public static final Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
int uc = a.user.hashCode() - b.user.hashCode();
return uc != 0 ? uc : a.componentName.compareTo(b.componentName);
};
@@ -65,7 +67,7 @@
* Must not hold the Context.
*/
public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
- this(info, user, UserManagerCompat.getInstance(context).isQuietModeEnabled(user));
+ this(info, user, context.getSystemService(UserManager.class).isQuietModeEnabled(user));
}
public AppInfo(LauncherActivityInfo info, UserHandle user, boolean quietModeEnabled) {
@@ -89,6 +91,15 @@
runtimeStatusFlags = info.runtimeStatusFlags;
}
+ @VisibleForTesting
+ public AppInfo(ComponentName componentName, CharSequence title,
+ UserHandle user, Intent intent) {
+ this.componentName = componentName;
+ this.title = title;
+ this.user = user;
+ this.intent = intent;
+ }
+
@Override
protected String dumpProperties() {
return super.dumpProperties() + " componentName=" + componentName;
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index f9a8d1b..73d8a88 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -22,6 +22,7 @@
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.FocusLogic;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.util.ArrayList;
@@ -35,7 +36,12 @@
private static final Rect sTmpRect = new Rect();
// Represents the cell size on the grid in the two orientations.
- private static Point[] sCellSize;
+ private static final MainThreadInitializedObject<Point[]> CELL_SIZE =
+ new MainThreadInitializedObject<>(c -> {
+ InvariantDeviceProfile inv = LauncherAppState.getIDP(c);
+ return new Point[] {inv.landscapeProfile.getCellSize(),
+ inv.portraitProfile.getCellSize()};
+ });
private static final int HANDLE_COUNT = 4;
private static final int INDEX_LEFT = 0;
@@ -352,27 +358,19 @@
}
public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
- if (sCellSize == null) {
- InvariantDeviceProfile inv = LauncherAppState.getIDP(context);
-
- // Initiate cell sizes.
- sCellSize = new Point[2];
- sCellSize[0] = inv.landscapeProfile.getCellSize();
- sCellSize[1] = inv.portraitProfile.getCellSize();
- }
-
if (rect == null) {
rect = new Rect();
}
final float density = context.getResources().getDisplayMetrics().density;
+ final Point[] cellSize = CELL_SIZE.get(context);
// Compute landscape size
- int landWidth = (int) ((spanX * sCellSize[0].x) / density);
- int landHeight = (int) ((spanY * sCellSize[0].y) / density);
+ int landWidth = (int) ((spanX * cellSize[0].x) / density);
+ int landHeight = (int) ((spanY * cellSize[0].y) / density);
// Compute portrait size
- int portWidth = (int) ((spanX * sCellSize[1].x) / density);
- int portHeight = (int) ((spanY * sCellSize[1].y) / density);
+ int portWidth = (int) ((spanX * cellSize[1].x) / density);
+ int portHeight = (int) ((spanY * cellSize[1].y) / density);
rect.set(portWidth, landHeight, landWidth, portHeight);
return rect;
}
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index e3ef5d6..71b7206 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -1,5 +1,7 @@
package com.android.launcher3;
+import static android.os.Process.myUserHandle;
+
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -10,16 +12,14 @@
import android.database.Cursor;
import android.util.Log;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.LoaderTask;
-import com.android.launcher3.provider.RestoreDbTask;
-import com.android.launcher3.util.ContentWriter;
-
import androidx.annotation.WorkerThread;
-import static android.os.Process.myUserHandle;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.util.ContentWriter;
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
@@ -50,7 +50,7 @@
@WorkerThread
public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
AppWidgetHost appWidgetHost = new LauncherAppWidgetHost(context);
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
Log.e(TAG, "Skipping widget ID remap as widgets not supported");
appWidgetHost.deleteHost();
return;
@@ -82,7 +82,7 @@
// b/135926478: Work profile widget restore is broken in platform. This forces us to
// recreate the widget during loading with the correct host provider.
- long mainProfileId = UserManagerCompat.getInstance(context)
+ long mainProfileId = UserCache.INSTANCE.get(context)
.getSerialNumberForUser(myUserHandle());
String oldWidgetId = Integer.toString(oldWidgetIds[i]);
int result = new ContentWriter(context, new ContentWriter.CommitParams(
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index b28077f..814b728 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -24,7 +25,12 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
import android.view.ContextThemeWrapper;
import androidx.annotation.IntDef;
@@ -34,20 +40,23 @@
import com.android.launcher3.logging.StatsLogUtils;
import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
-import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.util.ArrayList;
-public abstract class BaseActivity extends Activity
- implements UserEventDelegate, LogStateProvider, ActivityContext {
+/**
+ * Launcher BaseActivity
+ */
+public abstract class BaseActivity extends Activity implements LogStateProvider, ActivityContext {
+
+ private static final String TAG = "BaseActivity";
public static final int INVISIBLE_BY_STATE_HANDLER = 1 << 0;
public static final int INVISIBLE_BY_APP_TRANSITIONS = 1 << 1;
@@ -81,19 +90,39 @@
protected StatsLogManager mStatsLogManager;
protected SystemUiController mSystemUiController;
- private static final int ACTIVITY_STATE_STARTED = 1 << 0;
- private static final int ACTIVITY_STATE_RESUMED = 1 << 1;
+
+ public static final int ACTIVITY_STATE_STARTED = 1 << 0;
+ public static final int ACTIVITY_STATE_RESUMED = 1 << 1;
+
/**
- * State flag indicating if the user is active or the actitvity when to background as a result
+ * State flags indicating that the activity has received one frame after resume, and was
+ * not immediately paused.
+ */
+ public static final int ACTIVITY_STATE_DEFERRED_RESUMED = 1 << 2;
+
+ public static final int ACTIVITY_STATE_WINDOW_FOCUSED = 1 << 3;
+
+ /**
+ * State flag indicating if the user is active or the activity when to background as a result
* of user action.
* @see #isUserActive()
*/
- private static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 2;
+ public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4;
+
+ /**
+ * State flag indicating that a state transition is in progress
+ */
+ public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 5;
@Retention(SOURCE)
@IntDef(
flag = true,
- value = {ACTIVITY_STATE_STARTED, ACTIVITY_STATE_RESUMED, ACTIVITY_STATE_USER_ACTIVE})
+ value = {ACTIVITY_STATE_STARTED,
+ ACTIVITY_STATE_RESUMED,
+ ACTIVITY_STATE_DEFERRED_RESUMED,
+ ACTIVITY_STATE_WINDOW_FOCUSED,
+ ACTIVITY_STATE_USER_ACTIVE,
+ ACTIVITY_STATE_TRANSITION_ACTIVE})
public @interface ActivityFlags{}
@ActivityFlags
@@ -127,7 +156,7 @@
public final UserEventDispatcher getUserEventDispatcher() {
if (mUserEventDispatcher == null) {
- mUserEventDispatcher = UserEventDispatcher.newInstance(this, this);
+ mUserEventDispatcher = UserEventDispatcher.newInstance(this);
}
return mUserEventDispatcher;
}
@@ -146,19 +175,19 @@
@Override
protected void onStart() {
- mActivityFlags |= ACTIVITY_STATE_STARTED;
+ addActivityFlags(ACTIVITY_STATE_STARTED);
super.onStart();
}
@Override
protected void onResume() {
- mActivityFlags |= ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE;
+ addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
super.onResume();
}
@Override
protected void onUserLeaveHint() {
- mActivityFlags &= ~ACTIVITY_STATE_USER_ACTIVE;
+ removeActivityFlags(ACTIVITY_STATE_USER_ACTIVE);
super.onUserLeaveHint();
}
@@ -172,7 +201,7 @@
@Override
protected void onStop() {
- mActivityFlags &= ~ACTIVITY_STATE_STARTED & ~ACTIVITY_STATE_USER_ACTIVE;
+ removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE);
mForceInvisible = 0;
super.onStop();
@@ -183,7 +212,7 @@
@Override
protected void onPause() {
- mActivityFlags &= ~ACTIVITY_STATE_RESUMED;
+ removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED);
super.onPause();
// Reset the overridden sysui flags used for the task-swipe launch animation, we do this
@@ -193,6 +222,17 @@
getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
}
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus) {
+ addActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
+ } else {
+ removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
+ }
+
+ }
+
public boolean isStarted() {
return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0;
}
@@ -208,6 +248,22 @@
return (mActivityFlags & ACTIVITY_STATE_USER_ACTIVE) != 0;
}
+ public int getActivityFlags() {
+ return mActivityFlags;
+ }
+
+ protected void addActivityFlags(int flags) {
+ mActivityFlags |= flags;
+ onActivityFlagsChanged(flags);
+ }
+
+ protected void removeActivityFlags(int flags) {
+ mActivityFlags &= ~flags;
+ onActivityFlagsChanged(flags);
+ }
+
+ protected void onActivityFlagsChanged(int changeBits) { }
+
public void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
mDPChangeListeners.add(listener);
}
@@ -233,7 +289,7 @@
/**
* Used to set the override visibility state, used only to handle the transition home with the
* recents animation.
- * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner()
+ * @see QuickstepAppTransitionManagerImpl#createWallpaperOpenRunner
*/
public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
mForceInvisible |= flag;
@@ -258,13 +314,6 @@
void onMultiWindowModeChanged(boolean isInMultiWindowMode);
}
- @Override
- public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
- if (!UiFactory.dumpActivity(this, writer)) {
- super.dump(prefix, fd, writer, args);
- }
- }
-
protected void dumpMisc(String prefix, PrintWriter writer) {
writer.println(prefix + "deviceProfile isTransposed="
+ getDeviceProfile().isVerticalBarLayout());
@@ -274,6 +323,23 @@
writer.println(prefix + "mForceInvisible: " + mForceInvisible);
}
+ /**
+ * A wrapper around the platform method with Launcher specific checks
+ */
+ public void startShortcut(String packageName, String id, Rect sourceBounds,
+ Bundle startActivityOptions, UserHandle user) {
+ if (GO_DISABLE_WIDGETS) {
+ return;
+ }
+ try {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: shortcut", packageName);
+ getSystemService(LauncherApps.class).startShortcut(packageName, id, sourceBounds,
+ startActivityOptions, user);
+ } catch (SecurityException | IllegalStateException e) {
+ Log.e(TAG, "Failed to start shortcut", e);
+ }
+ }
+
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 994ba65..9f3b48f 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -16,9 +16,12 @@
package com.android.launcher3;
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
+
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -28,24 +31,29 @@
import android.util.Log;
import android.view.ActionMode;
import android.view.View;
+import android.view.View.OnClickListener;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.uioverrides.DisplayRotationListener;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DefaultDisplay.DisplayInfoChangeListener;
+import com.android.launcher3.util.DefaultDisplay.Info;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TraceHelper;
/**
* Extension of BaseActivity allowing support for drag-n-drop
*/
public abstract class BaseDraggingActivity extends BaseActivity
- implements WallpaperColorInfo.OnChangeListener {
+ implements WallpaperColorInfo.OnChangeListener, DisplayInfoChangeListener {
private static final String TAG = "BaseDraggingActivity";
@@ -56,21 +64,21 @@
private ActionMode mCurrentActionMode;
protected boolean mIsSafeModeEnabled;
- private OnStartCallback mOnStartCallback;
+ private Runnable mOnStartCallback;
private int mThemeRes = R.style.AppTheme;
- private DisplayRotationListener mRotationListener;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mIsSafeModeEnabled = getPackageManager().isSafeMode();
- mRotationListener = new DisplayRotationListener(this, this::onDeviceRotationChanged);
+
+
+ mIsSafeModeEnabled = TraceHelper.whitelistIpcs("isSafeMode",
+ () -> getPackageManager().isSafeMode());
+ DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
// Update theme
- WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
- wallpaperColorInfo.addOnChangeListener(this);
+ WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this);
int themeRes = Themes.getActivityThemeRes(this);
if (themeRes != mThemeRes) {
mThemeRes = themeRes;
@@ -162,17 +170,18 @@
startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: activity", intent);
startActivity(intent, optsBundle);
AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
Process.myUserHandle(), sourceContainer);
} else {
- LauncherAppsCompat.getInstance(this).startActivityForProfile(
+ getSystemService(LauncherApps.class).startMainActivity(
intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
sourceContainer);
}
- getUserEventDispatcher().logAppLaunch(v, intent);
- getStatsLogManager().logAppLaunch(v, intent);
+ getUserEventDispatcher().logAppLaunch(v, intent, user);
+ getStatsLogManager().logAppLaunch(v, intent, user);
return true;
} catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
@@ -195,8 +204,7 @@
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
String packageName = intent.getPackage();
- DeepShortcutManager.getInstance(this).startShortcut(
- packageName, id, intent.getSourceBounds(), optsBundle, info.user);
+ startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
AppLaunchTracker.INSTANCE.get(this).onStartShortcut(packageName, id, info.user,
sourceContainer);
} else {
@@ -222,7 +230,7 @@
super.onStart();
if (mOnStartCallback != null) {
- mOnStartCallback.onActivityStart(this);
+ mOnStartCallback.run();
mOnStartCallback = null;
}
}
@@ -230,36 +238,34 @@
@Override
protected void onDestroy() {
super.onDestroy();
- WallpaperColorInfo.getInstance(this).removeOnChangeListener(this);
- mRotationListener.disable();
+ WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this);
+ DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
}
- public <T extends BaseDraggingActivity> void setOnStartCallback(OnStartCallback<T> callback) {
- mOnStartCallback = callback;
+ public void runOnceOnStart(Runnable action) {
+ mOnStartCallback = action;
+ }
+
+ public void clearRunOnceOnStartCallback() {
+ mOnStartCallback = null;
}
protected void onDeviceProfileInitiated() {
if (mDeviceProfile.isVerticalBarLayout()) {
- mRotationListener.enable();
mDeviceProfile.updateIsSeascape(this);
- } else {
- mRotationListener.disable();
}
}
- private void onDeviceRotationChanged() {
- if (mDeviceProfile.updateIsSeascape(this)) {
+ @Override
+ public void onDisplayInfoChanged(Info info, int flags) {
+ if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.updateIsSeascape(this)) {
reapplyUi();
}
}
- protected abstract void reapplyUi();
-
- /**
- * Callback for listening for onStart
- */
- public interface OnStartCallback<T extends BaseDraggingActivity> {
-
- void onActivityStart(T activity);
+ public OnClickListener getItemOnClickListener() {
+ return ItemClickHandler.INSTANCE;
}
+
+ protected abstract void reapplyUi();
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 864fa6e..38e1201 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,11 +22,11 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.views.RecyclerViewFastScroller;
-import androidx.recyclerview.widget.RecyclerView;
-
/**
* A base {@link RecyclerView}, which does the following:
@@ -138,7 +138,7 @@
if (getCurrentScrollY() == 0) {
return true;
}
- return false;
+ return getAdapter() == null || getAdapter().getItemCount() == 0;
}
/**
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 7adb6a4..e6f8a85 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
@@ -45,7 +47,6 @@
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.PreloadIconDrawable;
@@ -215,6 +216,7 @@
cancelDotScaleAnim();
mDotParams.scale = 0f;
mForceHideDot = false;
+ setBackground(null);
}
private void cancelDotScaleAnim() {
@@ -287,9 +289,8 @@
}
private void applyIconAndLabel(ItemInfoWithIcon info) {
- FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext())
- .newIcon(getContext(), info);
- mDotParams.color = IconPalette.getMutedColor(info.iconColor, 0.54f);
+ FastBitmapDrawable iconDrawable = newIcon(getContext(), info);
+ mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
setIcon(iconDrawable);
setText(info.title);
@@ -496,7 +497,8 @@
// Text should be visible everywhere but the hotseat.
Object tag = getParent() instanceof FolderIcon ? ((View) getParent()).getTag() : getTag();
ItemInfo info = tag instanceof ItemInfo ? (ItemInfo) tag : null;
- return info == null || info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ return info == null || (info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ && info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
}
public void setTextVisibility(boolean visible) {
@@ -567,8 +569,7 @@
preloadDrawable = (PreloadIconDrawable) mIcon;
preloadDrawable.setLevel(progressLevel);
} else {
- preloadDrawable = DrawableFactory.INSTANCE.get(getContext())
- .newPendingIcon(getContext(), info);
+ preloadDrawable = newPendingIcon(getContext(), info);
preloadDrawable.setLevel(progressLevel);
setIcon(preloadDrawable);
}
@@ -665,7 +666,7 @@
mDisableRelayout = true;
// Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
- info.iconBitmap.prepareToDraw();
+ info.bitmap.icon.prepareToDraw();
if (info instanceof AppInfo) {
applyFromApplicationInfo((AppInfo) info);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 976ccd5..e3eb387 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -110,7 +110,7 @@
private OnTouchListener mInterceptTouchListener;
- private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>();
+ private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
@@ -179,7 +179,7 @@
private final Rect mTempRect = new Rect();
- private final static Paint sPaint = new Paint();
+ private static final Paint sPaint = new Paint();
// Related to accessible drag and drop
private DragAndDropAccessibilityDelegate mTouchHelper;
@@ -219,8 +219,8 @@
mPreviousReorderDirection[0] = INVALID_DIRECTION;
mPreviousReorderDirection[1] = INVALID_DIRECTION;
- mFolderLeaveBehind.delegateCellX = -1;
- mFolderLeaveBehind.delegateCellY = -1;
+ mFolderLeaveBehind.mDelegateCellX = -1;
+ mFolderLeaveBehind.mDelegateCellY = -1;
setAlwaysDrawnWithCacheEnabled(false);
final Resources res = getResources();
@@ -466,21 +466,18 @@
}
}
- for (int i = 0; i < mFolderBackgrounds.size(); i++) {
- PreviewBackground bg = mFolderBackgrounds.get(i);
- cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
+ for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
+ DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
+ cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackground(canvas);
- if (!bg.isClipping) {
- bg.drawBackgroundStroke(canvas);
- }
+ cellDrawing.drawUnderItem(canvas);
canvas.restore();
}
- if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) {
- cellToPoint(mFolderLeaveBehind.delegateCellX,
- mFolderLeaveBehind.delegateCellY, mTempLocation);
+ if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
+ cellToPoint(mFolderLeaveBehind.mDelegateCellX,
+ mFolderLeaveBehind.mDelegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
mFolderLeaveBehind.drawLeaveBehind(canvas);
@@ -492,23 +489,28 @@
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- for (int i = 0; i < mFolderBackgrounds.size(); i++) {
- PreviewBackground bg = mFolderBackgrounds.get(i);
- if (bg.isClipping) {
- cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
- canvas.save();
- canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackgroundStroke(canvas);
- canvas.restore();
- }
+ for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
+ DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
+ cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
+ canvas.save();
+ canvas.translate(mTempLocation[0], mTempLocation[1]);
+ bg.drawOverItem(canvas);
+ canvas.restore();
}
}
- public void addFolderBackground(PreviewBackground bg) {
- mFolderBackgrounds.add(bg);
+ /**
+ * Add Delegated cell drawing
+ */
+ public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
+ mDelegatedCellDrawings.add(bg);
}
- public void removeFolderBackground(PreviewBackground bg) {
- mFolderBackgrounds.remove(bg);
+
+ /**
+ * Remove item from DelegatedCellDrawings
+ */
+ public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
+ mDelegatedCellDrawings.remove(bg);
}
public void setFolderLeaveBehindCell(int x, int y) {
@@ -516,14 +518,14 @@
mFolderLeaveBehind.setup(getContext(), mActivity, null,
child.getMeasuredWidth(), child.getPaddingTop());
- mFolderLeaveBehind.delegateCellX = x;
- mFolderLeaveBehind.delegateCellY = y;
+ mFolderLeaveBehind.mDelegateCellX = x;
+ mFolderLeaveBehind.mDelegateCellY = y;
invalidate();
}
public void clearFolderLeaveBehind() {
- mFolderLeaveBehind.delegateCellX = -1;
- mFolderLeaveBehind.delegateCellY = -1;
+ mFolderLeaveBehind.mDelegateCellX = -1;
+ mFolderLeaveBehind.mDelegateCellY = -1;
invalidate();
}
@@ -536,7 +538,7 @@
try {
dispatchRestoreInstanceState(states);
} catch (IllegalArgumentException ex) {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
throw ex;
}
// Mismatched viewId / viewType preventing restore. Skip restore on production builds.
@@ -2744,6 +2746,24 @@
}
/**
+ * A Delegated cell Drawing for drawing on CellLayout
+ */
+ public abstract static class DelegatedCellDrawing {
+ public int mDelegateCellX;
+ public int mDelegateCellY;
+
+ /**
+ * Draw under CellLayout
+ */
+ public abstract void drawUnderItem(Canvas canvas);
+
+ /**
+ * Draw over CellLayout
+ */
+ public abstract void drawOverItem(Canvas canvas);
+ }
+
+ /**
* Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
* if necessary).
*/
@@ -2763,6 +2783,26 @@
return false;
}
+ /**
+ * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
+ */
+ public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
+ if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) return false;
+ int[] cellPoint = new int[2];
+ int[] directionVector = new int[]{0, -1};
+ cellToPoint(0, mCountY, cellPoint);
+ ItemConfiguration configuration = new ItemConfiguration();
+ if (findReorderSolution(cellPoint[0], cellPoint[1], mCountX, 1, mCountX, 1,
+ directionVector, null, false, configuration).isSolution) {
+ if (commitConfig) {
+ copySolutionToTempState(configuration, null);
+ commitTempPlacement();
+ }
+ return true;
+ }
+ return false;
+ }
+
public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
return mOccupied.isRegionVacant(x, y, spanX, spanY);
}
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 3347b2a..423f2bb 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -113,6 +113,7 @@
public void onDrop(DragObject d, DragOptions options) {
if (canRemove(d.dragInfo)) {
mLauncher.getModelWriter().prepareToUndoDelete();
+ d.dragInfo.container = NO_ID;
}
super.onDrop(d, options);
}
@@ -125,7 +126,8 @@
onAccessibilityDrop(null, item);
ModelWriter modelWriter = mLauncher.getModelWriter();
Runnable onUndoClicked = () -> {
- modelWriter.abortDelete(itemPage);
+ mLauncher.setPageToBindSynchronously(itemPage);
+ modelWriter.abortDelete();
mLauncher.getUserEventDispatcher().logActionOnControl(TAP, UNDO);
};
Snackbar.show(mLauncher, R.string.item_removed, R.string.undo,
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index bc6fa6e..c049069 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -245,7 +245,7 @@
allAppsIconTextSizePx = originalProfile.iconTextSizePx;
allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
- allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
}
updateWorkspacePadding();
@@ -271,7 +271,7 @@
// In multi-window mode, we can have widthPx = availableWidthPx
// and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
// widthPx and heightPx values where it's needed.
- DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
+ DeviceProfile profile = new DeviceProfile(context, inv, null, mwSize, mwSize,
mwSize.x, mwSize.y, isLandscape, true);
// If there isn't enough vertical cell padding with the labels displayed, hide the labels.
@@ -360,7 +360,7 @@
allAppsIconTextSizePx = iconTextSizePx;
allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
allAppsCellHeightPx = getCellSize().y;
- allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
if (isVerticalBarLayout()) {
// Always hide the Workspace text with vertical bar layout.
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 763432d..a32fd12 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,11 +16,14 @@
package com.android.launcher3;
+import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.FolderNameProvider;
/**
* Interface defining an object that can receive a drag.
@@ -67,7 +70,12 @@
public DragViewStateAnnouncer stateAnnouncer;
- public DragObject() {
+ public FolderNameProvider folderNameProvider;
+
+ public DragObject(Context context) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ folderNameProvider = FolderNameProvider.newInstance(context);
+ }
}
/**
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 4e0f2e7..d64967b 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -29,6 +29,7 @@
/**
* The edit text that reports back when the back key has been pressed.
+ * Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion
*/
public class ExtendedEditText extends EditText {
@@ -39,7 +40,7 @@
* Implemented by listeners of the back key.
*/
public interface OnBackKeyListener {
- public boolean onBackKey();
+ boolean onBackKey();
}
private OnBackKeyListener mBackKeyListener;
@@ -85,12 +86,9 @@
super.onLayout(changed, left, top, right, bottom);
if (mShowImeAfterFirstLayout) {
// soft input only shows one frame after the layout of the EditText happens,
- post(new Runnable() {
- @Override
- public void run() {
- showSoftInput();
- mShowImeAfterFirstLayout = false;
- }
+ post(() -> {
+ showSoftInput();
+ mShowImeAfterFirstLayout = false;
});
}
}
@@ -105,7 +103,7 @@
private boolean showSoftInput() {
return requestFocus() &&
- ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
+ getContext().getSystemService(InputMethodManager.class)
.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index a90025e..a78159f 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import android.animation.ObjectAnimator;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -28,35 +29,25 @@
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Property;
-import android.util.SparseArray;
+import com.android.launcher3.graphics.PlaceHolderIconDrawable;
import com.android.launcher3.icons.BitmapInfo;
+
public class FastBitmapDrawable extends Drawable {
private static final float PRESSED_SCALE = 1.1f;
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
+ private static final float DISABLED_ALPHA = 0.54f;
public static final int CLICK_FEEDBACK_DURATION = 200;
- // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
- // reduce the value space to a smaller value V, which reduces the number of cached
- // ColorMatrixColorFilters that we need to keep to V^2
- private static final int REDUCED_FILTER_VALUE_SPACE = 48;
-
- // A cache of ColorFilters for optimizing brightness and saturation animations
- private static final SparseArray<ColorFilter> sCachedFilter = new SparseArray<>();
-
- // Temporary matrices used for calculation
- private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
- private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
+ private static ColorFilter sDisabledFColorFilter;
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
protected Bitmap mBitmap;
@@ -82,13 +73,7 @@
private ObjectAnimator mScaleAnimation;
private float mScale = 1;
-
- // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
- // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
- private int mDesaturation = 0;
- private int mBrightness = 0;
private int mAlpha = 255;
- private int mPrevUpdateKey = Integer.MAX_VALUE;
public FastBitmapDrawable(Bitmap b) {
this(b, Color.TRANSPARENT);
@@ -98,10 +83,6 @@
this(info.icon, info.color);
}
- public FastBitmapDrawable(ItemInfoWithIcon info) {
- this(info.iconBitmap, info.iconColor);
- }
-
protected FastBitmapDrawable(Bitmap b, int iconColor) {
this(b, iconColor, false);
}
@@ -245,15 +226,10 @@
return false;
}
- private void invalidateDesaturationAndBrightness() {
- setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
- setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS : 0);
- }
-
public void setIsDisabled(boolean isDisabled) {
if (mIsDisabled != isDisabled) {
mIsDisabled = isDisabled;
- invalidateDesaturationAndBrightness();
+ updateFilter();
}
}
@@ -261,90 +237,33 @@
return mIsDisabled;
}
- /**
- * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
- */
- private void setDesaturation(float desaturation) {
- int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
- if (mDesaturation != newDesaturation) {
- mDesaturation = newDesaturation;
- updateFilter();
+ private ColorFilter getDisabledColorFilter() {
+ if (sDisabledFColorFilter == null) {
+ ColorMatrix tempBrightnessMatrix = new ColorMatrix();
+ ColorMatrix tempFilterMatrix = new ColorMatrix();
+
+ tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
+ float scale = 1 - DISABLED_BRIGHTNESS;
+ int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
+ float[] mat = tempBrightnessMatrix.getArray();
+ mat[0] = scale;
+ mat[6] = scale;
+ mat[12] = scale;
+ mat[4] = brightnessI;
+ mat[9] = brightnessI;
+ mat[14] = brightnessI;
+ mat[18] = DISABLED_ALPHA;
+ tempFilterMatrix.preConcat(tempBrightnessMatrix);
+ sDisabledFColorFilter = new ColorMatrixColorFilter(tempFilterMatrix);
}
- }
-
- public float getDesaturation() {
- return (float) mDesaturation / REDUCED_FILTER_VALUE_SPACE;
- }
-
- /**
- * Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
- */
- private void setBrightness(float brightness) {
- int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
- if (mBrightness != newBrightness) {
- mBrightness = newBrightness;
- updateFilter();
- }
- }
-
- private float getBrightness() {
- return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
+ return sDisabledFColorFilter;
}
/**
* Updates the paint to reflect the current brightness and saturation.
*/
protected void updateFilter() {
- boolean usePorterDuffFilter = false;
- int key = -1;
- if (mDesaturation > 0) {
- key = (mDesaturation << 16) | mBrightness;
- } else if (mBrightness > 0) {
- // Compose a key with a fully saturated icon if we are just animating brightness
- key = (1 << 16) | mBrightness;
-
- // We found that in L, ColorFilters cause drawing artifacts with shadows baked into
- // icons, so just use a PorterDuff filter when we aren't animating saturation
- usePorterDuffFilter = true;
- }
-
- // Debounce multiple updates on the same frame
- if (key == mPrevUpdateKey) {
- return;
- }
- mPrevUpdateKey = key;
-
- if (key != -1) {
- ColorFilter filter = sCachedFilter.get(key);
- if (filter == null) {
- float brightnessF = getBrightness();
- int brightnessI = (int) (255 * brightnessF);
- if (usePorterDuffFilter) {
- filter = new PorterDuffColorFilter(Color.argb(brightnessI, 255, 255, 255),
- PorterDuff.Mode.SRC_ATOP);
- } else {
- float saturationF = 1f - getDesaturation();
- sTempFilterMatrix.setSaturation(saturationF);
- if (mBrightness > 0) {
- // Brightness: C-new = C-old*(1-amount) + amount
- float scale = 1f - brightnessF;
- float[] mat = sTempBrightnessMatrix.getArray();
- mat[0] = scale;
- mat[6] = scale;
- mat[12] = scale;
- mat[4] = brightnessI;
- mat[9] = brightnessI;
- mat[14] = brightnessI;
- sTempFilterMatrix.preConcat(sTempBrightnessMatrix);
- }
- filter = new ColorMatrixColorFilter(sTempFilterMatrix);
- }
- sCachedFilter.append(key, filter);
- }
- mPaint.setColorFilter(filter);
- } else {
- mPaint.setColorFilter(null);
- }
+ mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : null);
invalidateSelf();
}
@@ -365,7 +284,7 @@
}
@Override
- public Drawable newDrawable() {
+ public FastBitmapDrawable newDrawable() {
return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
}
@@ -374,4 +293,37 @@
return 0;
}
}
+
+ /**
+ * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction
+ */
+ public interface Factory {
+
+ /**
+ * Called to create a new drawable
+ */
+ FastBitmapDrawable newDrawable();
+ }
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
+ FastBitmapDrawable drawable = newIcon(context, info.bitmap);
+ drawable.setIsDisabled(info.isDisabled());
+ return drawable;
+ }
+
+ /**
+ * Creates a drawable for the provided BitmapInfo
+ */
+ public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) {
+ if (info instanceof Factory) {
+ return ((Factory) info).newDrawable();
+ } else if (info.isLowRes()) {
+ return new PlaceHolderIconDrawable(info, context);
+ } else {
+ return new FastBitmapDrawable(info);
+ }
+ }
}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 466b7b2..f07040d 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -96,7 +96,7 @@
}
if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
throw new IllegalStateException("Parent of the focused item is not supported.");
} else {
return false;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index e2b7b68..336e423 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.content.Intent;
import android.os.Process;
import com.android.launcher3.model.ModelWriter;
@@ -45,8 +46,14 @@
*/
public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
+ public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
+
+ public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
+
public int options;
+ public Intent suggestedFolderNames;
+
/**
* The apps and shortcuts
*/
@@ -140,4 +147,10 @@
writer.updateItemInDatabase(this);
}
}
+
+ @Override
+ protected String dumpProperties() {
+ return super.dumpProperties()
+ + " manuallyTypedTitle=" + hasOption(FLAG_MANUAL_FOLDER_NAME);
+ }
}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 03ee707..76cfe1c 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,12 +16,13 @@
package com.android.launcher3;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
-import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -32,6 +33,8 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.views.Transposable;
+import java.util.ArrayList;
+
public class Hotseat extends CellLayout implements LogContainerProvider, Insettable, Transposable {
@ViewDebug.ExportedProperty(category = "launcher")
@@ -49,12 +52,17 @@
super(context, attrs, defStyle);
}
- /* Get the orientation specific coordinates given an invariant order in the hotseat. */
- int getCellXFromOrder(int rank) {
+ /**
+ * Returns orientation specific cell X given invariant order in the hotseat
+ */
+ public int getCellXFromOrder(int rank) {
return mHasVerticalHotseat ? 0 : rank;
}
- int getCellYFromOrder(int rank) {
+ /**
+ * Returns orientation specific cell Y given invariant order in the hotseat
+ */
+ public int getCellYFromOrder(int rank) {
return mHasVerticalHotseat ? (getCountY() - (rank + 1)) : 0;
}
@@ -70,10 +78,12 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- target.gridX = info.cellX;
- target.gridY = info.cellY;
- targetParent.containerType = LauncherLogProto.ContainerType.HOTSEAT;
+ public void fillInLogContainerData(ItemInfo childInfo, Target child,
+ ArrayList<Target> parents) {
+ child.rank = childInfo.rank;
+ child.gridX = childInfo.cellX;
+ child.gridY = childInfo.cellY;
+ parents.add(newContainerTarget(LauncherLogProto.ContainerType.HOTSEAT));
}
@Override
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
deleted file mode 100644
index 0f006f7..0000000
--- a/src/com/android/launcher3/IconProvider.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.android.launcher3;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import android.content.pm.LauncherActivityInfo;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-public class IconProvider implements ResourceBasedOverride {
-
- public static MainThreadInitializedObject<IconProvider> INSTANCE =
- forOverride(IconProvider.class, R.string.icon_provider_class);
-
- public IconProvider() { }
-
- public String getSystemStateForPackage(String systemState, String packageName) {
- return systemState;
- }
-
- /**
- * @param flattenDrawable true if the caller does not care about the specification of the
- * original icon as long as the flattened version looks the same.
- */
- public Drawable getIcon(LauncherActivityInfo info, int iconDpi, boolean flattenDrawable) {
- return info.getIcon(iconDpi);
- }
-}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index fe91602..a5142d8 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -27,6 +27,7 @@
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
@@ -42,13 +43,12 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
@@ -59,7 +59,6 @@
import java.net.URISyntaxException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@@ -71,7 +70,6 @@
public static final int FLAG_ACTIVITY_PAUSED = 1;
public static final int FLAG_LOADER_RUNNING = 2;
public static final int FLAG_DRAG_AND_DROP = 4;
- public static final int FLAG_BULK_ADD = 4;
// Determines whether to defer installing shortcuts immediately until
// processAllPendingInstalls() is called.
@@ -112,8 +110,7 @@
@WorkerThread
private static void flushQueueInBackground(Context context) {
- LauncherModel model = LauncherAppState.getInstance(context).getModel();
- if (model.getCallback() == null) {
+ if (Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null) {
// Launcher not loaded
return;
}
@@ -126,7 +123,7 @@
return;
}
- LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
for (String encoded : strings) {
PendingInstallShortcutInfo info = decode(encoded, context);
if (info == null) {
@@ -135,7 +132,7 @@
String pkg = getIntentPackage(info.launchIntent);
if (!TextUtils.isEmpty(pkg)
- && !launcherApps.isPackageEnabledForProfile(pkg, info.user)
+ && !launcherApps.isPackageEnabled(pkg, info.user)
&& !info.isActivity) {
if (DBG) {
Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
@@ -148,7 +145,8 @@
}
prefs.edit().remove(APPS_PENDING_INSTALL).apply();
if (!installQueue.isEmpty()) {
- model.addAndBindAddedWorkspaceItems(installQueue);
+ LauncherAppState.getInstance(context).getModel()
+ .addAndBindAddedWorkspaceItems(installQueue);
}
}
@@ -421,7 +419,7 @@
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(DEEPSHORTCUT_TYPE_KEY).value(true)
- .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+ .key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
.getSerialNumberForUser(user))
.endObject().toString();
} else if (providerInfo != null) {
@@ -431,7 +429,7 @@
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(APP_WIDGET_TYPE_KEY).value(true)
- .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+ .key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
.getSerialNumberForUser(user))
.endObject().toString();
}
@@ -459,7 +457,7 @@
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(NAME_KEY).value(name)
.key(USER_HANDLE_KEY).value(
- UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user))
+ UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user))
.key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
if (icon != null) {
byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
@@ -490,9 +488,8 @@
return Pair.create(si, null);
} else if (shortcutInfo != null) {
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
- LauncherIcons li = LauncherIcons.obtain(mContext);
- itemInfo.applyFrom(li.createShortcutIcon(shortcutInfo));
- li.recycle();
+ LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(
+ itemInfo, shortcutInfo);
return Pair.create(itemInfo, shortcutInfo);
} else if (providerInfo != null) {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
@@ -527,18 +524,15 @@
try {
Decoder decoder = new Decoder(encoded, context);
if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
- LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+ LauncherActivityInfo info = context.getSystemService(LauncherApps.class)
.resolveActivity(decoder.launcherIntent, decoder.user);
if (info != null) {
return new PendingInstallShortcutInfo(info, context);
}
} else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
- DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
- List<ShortcutInfo> si = sm.queryForFullDetails(
- decoder.launcherIntent.getPackage(),
- Arrays.asList(decoder.launcherIntent.getStringExtra(
- ShortcutKey.EXTRA_SHORTCUT_ID)),
- decoder.user);
+ List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user)
+ .buildRequest(context)
+ .query(ShortcutRequest.ALL);
if (si.isEmpty()) {
return null;
} else {
@@ -593,7 +587,7 @@
private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
super(encoded);
launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0);
- user = has(USER_HANDLE_KEY) ? UserManagerCompat.getInstance(context)
+ user = has(USER_HANDLE_KEY) ? UserCache.INSTANCE.get(context)
.getUserForSerialNumber(getLong(USER_HANDLE_KEY))
: Process.myUserHandle();
if (user == null) {
@@ -617,7 +611,7 @@
return original;
}
- LauncherActivityInfo info = LauncherAppsCompat.getInstance(original.mContext)
+ LauncherActivityInfo info = original.mContext.getSystemService(LauncherApps.class)
.resolveActivity(original.launchIntent, original.user);
if (info == null) {
return original;
@@ -662,7 +656,7 @@
if (iconInfo == null) {
iconInfo = app.getIconCache().getDefaultIcon(info.user);
}
- info.applyFrom(iconInfo);
+ info.bitmap = iconInfo;
info.title = Utilities.trim(name);
info.contentDescription = app.getContext().getPackageManager()
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index d66ba73..2ad84b9 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,8 +18,8 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import android.annotation.TargetApi;
@@ -41,13 +41,16 @@
import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
+import android.view.Display;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DefaultDisplay.Info;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Themes;
@@ -104,8 +107,6 @@
public int iconBitmapSize;
public int fillResIconDpi;
public float iconTextSize;
- public float allAppsIconSize;
- public float allAppsIconTextSize;
private SparseArray<TypedValue> mExtraAttrs;
@@ -119,6 +120,7 @@
*/
public int numAllAppsColumns;
+ public String dbFile;
public int defaultLayoutId;
int demoModeLayoutId;
@@ -146,8 +148,7 @@
iconTextSize = p.iconTextSize;
numHotseatIcons = p.numHotseatIcons;
numAllAppsColumns = p.numAllAppsColumns;
- allAppsIconSize = p.allAppsIconSize;
- allAppsIconTextSize = p.allAppsIconTextSize;
+ dbFile = p.dbFile;
defaultLayoutId = p.defaultLayoutId;
demoModeLayoutId = p.demoModeLayoutId;
mExtraAttrs = p.mExtraAttrs;
@@ -156,6 +157,11 @@
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
+ if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+ throw new IllegalArgumentException(
+ "PreviewContext is passed into this IDP constructor");
+ }
+
String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
: null;
@@ -176,6 +182,13 @@
}
/**
+ * This constructor should NOT have any monitors by design.
+ */
+ public InvariantDeviceProfile(Context context, Display display) {
+ initGrid(context, null, new Info(display));
+ }
+
+ /**
* Retrieve system defined or RRO overriden icon shape.
*/
private static String getIconShapePath(Context context) {
@@ -187,8 +200,10 @@
}
private String initGrid(Context context, String gridName) {
- DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+ return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo());
+ }
+ private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) {
Point smallestSize = new Point(displayInfo.smallestSize);
Point largestSize = new Point(displayInfo.largestSize);
@@ -285,6 +300,7 @@
numRows = closestProfile.numRows;
numColumns = closestProfile.numColumns;
numHotseatIcons = closestProfile.numHotseatIcons;
+ dbFile = closestProfile.dbFile;
defaultLayoutId = closestProfile.defaultLayoutId;
demoModeLayoutId = closestProfile.demoModeLayoutId;
numFolderRows = closestProfile.numFolderRows;
@@ -552,6 +568,7 @@
private final int numHotseatIcons;
+ private final String dbFile;
private final int defaultLayoutId;
private final int demoModeLayoutId;
@@ -564,6 +581,7 @@
numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
+ dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
defaultLayoutId = a.getResourceId(
R.styleable.GridDisplayOption_defaultLayoutId, 0);
demoModeLayoutId = a.getResourceId(
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 3f723d1..c99465c 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -214,4 +214,11 @@
return id;
}
+ /**
+ * Returns if an Item is a predicted item
+ */
+ public boolean isPredictedItem() {
+ return container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
+ || container == LauncherSettings.Favorites.CONTAINER_PREDICTION;
+ }
}
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 1550bb0..1941455 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -16,10 +16,6 @@
package com.android.launcher3;
-import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
-
-import android.graphics.Bitmap;
-
import com.android.launcher3.icons.BitmapInfo;
/**
@@ -30,14 +26,9 @@
public static final String TAG = "ItemInfoDebug";
/**
- * A bitmap version of the application icon.
+ * The bitmap for the application icon
*/
- public Bitmap iconBitmap;
-
- /**
- * Dominant color in the {@link #iconBitmap}.
- */
- public int iconColor;
+ public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
/**
* Indicates that the icon is disabled due to safe mode restrictions.
@@ -106,8 +97,7 @@
protected ItemInfoWithIcon(ItemInfoWithIcon info) {
super(info);
- iconBitmap = info.iconBitmap;
- iconColor = info.iconColor;
+ bitmap = info.bitmap;
runtimeStatusFlags = info.runtimeStatusFlags;
}
@@ -120,12 +110,7 @@
* Indicates whether we're using a low res icon
*/
public boolean usingLowResIcon() {
- return iconBitmap == LOW_RES_ICON;
- }
-
- public void applyFrom(BitmapInfo info) {
- iconBitmap = info.icon;
- iconColor = info.color;
+ return bitmap.isLowRes();
}
/**
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1338b30..1413a5c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -27,12 +27,13 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
+import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
-import static com.android.launcher3.util.RaceConditionTracker.ENTER;
-import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -58,6 +59,7 @@
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Parcelable;
import android.os.Process;
@@ -72,32 +74,35 @@
import android.view.KeyboardShortcutInfo;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.OvershootInterpolator;
import android.widget.Toast;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompatVO;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -105,23 +110,27 @@
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.StatsLogUtils;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.qsb.QsbContainerView;
-import com.android.launcher3.states.InternalStateHandler;
import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.UiFactory;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ActivityResultInfo;
+import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -130,11 +139,11 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
-import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -148,8 +157,15 @@
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.WidgetsFullSheet;
import com.android.launcher3.widget.custom.CustomWidgetManager;
+import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.shared.LauncherExterns;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -159,14 +175,18 @@
import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
/**
* Default launcher application.
*/
public class Launcher extends BaseDraggingActivity implements LauncherExterns,
- Callbacks, LauncherProviderChangeListener, UserEventDelegate,
- InvariantDeviceProfile.OnIDPChangeListener {
+ Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
public static final String TAG = "Launcher";
+
+ public static final ActivityTracker<Launcher> ACTIVITY_TRACKER = new ActivityTracker<>();
+
static final boolean LOGD = false;
static final boolean DEBUG_STRICT_MODE = false;
@@ -202,9 +222,11 @@
private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
// Type: SparseArray<Parcelable>
private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
+
public static final String ON_CREATE_EVT = "Launcher.onCreate";
- private static final String ON_START_EVT = "Launcher.onStart";
- private static final String ON_RESUME_EVT = "Launcher.onResume";
+ public static final String ON_START_EVT = "Launcher.onStart";
+ public static final String ON_RESUME_EVT = "Launcher.onResume";
+ public static final String ON_NEW_INTENT_EVT = "Launcher.onNewIntent";
private LauncherStateManager mStateManager;
@@ -228,7 +250,7 @@
DragLayer mDragLayer;
private DragController mDragController;
- private AppWidgetManagerCompat mAppWidgetManager;
+ private WidgetManagerHelper mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
private final int[] mTmpAddItemCellCoordinates = new int[2];
@@ -249,12 +271,17 @@
// UI and state for the overview panel
private View mOverviewPanel;
+ private View mActionsView;
@Thunk
boolean mWorkspaceLoading = true;
private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
+ // Used to notify when an activity launch has been deferred because launcher is not yet resumed
+ // TODO: See if we can remove this later
+ private Runnable mOnDeferredActivityLaunchCallback;
+
private ViewOnDrawExecutor mPendingExecutor;
private LauncherModel mModel;
@@ -265,6 +292,7 @@
private PopupDataProvider mPopupDataProvider;
private int mSynchronouslyBoundPage = PagedView.INVALID_PAGE;
+ private int mPageToBindSynchronously = PagedView.INVALID_PAGE;
// We only want to get the SharedPreferences once since it does an FS stat each time we get
// it from the context.
@@ -278,12 +306,11 @@
*/
private PendingRequestArgs mPendingRequestArgs;
// Request id for any pending activity result
- private int mPendingActivityRequestCode = -1;
+ protected int mPendingActivityRequestCode = -1;
- public ViewGroupFocusHelper mFocusHandler;
+ private ViewGroupFocusHelper mFocusHandler;
private RotationHelper mRotationHelper;
- private Runnable mCancelTouchController;
final Handler mHandler = new Handler();
private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -294,9 +321,28 @@
private DeviceProfile mStableDeviceProfile;
private RotationMode mRotationMode = RotationMode.NORMAL;
+ protected LauncherOverlayManager mOverlayManager;
+ // If true, overlay callbacks are deferred
+ private boolean mDeferOverlayCallbacks;
+ private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
+
+ private BackgroundBlurController mBackgroundBlurController =
+ new BackgroundBlurController(this);
+
+ private final ViewTreeObserver.OnDrawListener mOnDrawListener =
+ new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ getBackgroundBlurController().setSurfaceToLauncher(mDragLayer);
+ mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(
+ this));
+ }
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
- RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
+ Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
+ TraceHelper.FLAG_UI_EVENT);
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
@@ -311,14 +357,12 @@
.penaltyDeath()
.build());
}
- TraceHelper.beginSection("Launcher-onCreate");
super.onCreate(savedInstanceState);
- TraceHelper.partitionSection("Launcher-onCreate", "super call");
LauncherAppState app = LauncherAppState.getInstance(this);
mOldConfig = new Configuration(getResources().getConfiguration());
- mModel = app.setLauncher(this);
+ mModel = app.getModel();
mRotationHelper = new RotationHelper(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
initDeviceProfile(idp);
@@ -330,9 +374,8 @@
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new LauncherStateManager(this);
- UiFactory.onCreate(this);
- mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
+ mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHost = new LauncherAppWidgetHost(this,
appWidgetId -> getWorkspace().removeWidget(appWidgetId));
mAppWidgetHost.startListening();
@@ -340,11 +383,12 @@
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
setupViews();
- mPopupDataProvider = new PopupDataProvider(this);
+ mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
+ mAppTransitionManager.registerRemoteAnimations();
- boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
+ boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
if (internalStateHandled) {
if (savedInstanceState != null) {
// InternalStateHandler has already set the appropriate state.
@@ -357,22 +401,18 @@
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
- int currentScreen = PagedView.INVALID_RESTORE_PAGE;
+ int currentScreen = PagedView.INVALID_PAGE;
if (savedInstanceState != null) {
currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
}
+ mPageToBindSynchronously = currentScreen;
- if (!mModel.startLoader(currentScreen)) {
+ if (!mModel.addCallbacksAndLoad(this)) {
if (!internalStateHandled) {
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
}
- } else {
- // Pages bound synchronously.
- mWorkspace.setCurrentPage(currentScreen);
-
- setWorkspaceLoading(true);
}
// For handling default keys
@@ -390,10 +430,12 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}
+ mOverlayManager = getDefaultOverlay();
+ PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
+ OverlayPlugin.class, false /* allowedMultiple */);
+
mRotationHelper.initialize();
- TraceHelper.endSection("Launcher-onCreate");
- RaceConditionTracker.onEvent(ON_CREATE_EVT, EXIT);
mStateManager.addStateListener(new LauncherStateManager.StateListener() {
@Override
public void onStateTransitionStart(LauncherState toState) {
@@ -413,13 +455,45 @@
}
}
});
+
+ TraceHelper.INSTANCE.endSection(traceToken);
+ }
+
+ protected LauncherOverlayManager getDefaultOverlay() {
+ return new LauncherOverlayManager() { };
+ }
+
+ @Override
+ public void onPluginConnected(OverlayPlugin overlayManager, Context context) {
+ switchOverlay(() -> overlayManager.createOverlayManager(this, this));
+ }
+
+ @Override
+ public void onPluginDisconnected(OverlayPlugin plugin) {
+ switchOverlay(this::getDefaultOverlay);
+ }
+
+ private void switchOverlay(Supplier<LauncherOverlayManager> overlaySupplier) {
+ if (mOverlayManager != null) {
+ mOverlayManager.onActivityDestroyed(this);
+ }
+ mOverlayManager = overlaySupplier.get();
+ if (getRootView().isAttachedToWindow()) {
+ mOverlayManager.onAttachedToWindow();
+ }
+ mDeferOverlayCallbacks = true;
+ checkIfOverlayStillDeferred();
+ }
+
+ @Override
+ protected void dispatchDeviceProfileChanged() {
+ super.dispatchDeviceProfileChanged();
+ mOverlayManager.onDeviceProvideChanged();
}
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
- UiFactory.onEnterAnimationComplete(this);
- mAllAppsController.highlightWorkTabIfNecessary();
mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
}
@@ -432,7 +506,6 @@
}
mOldConfig.setTo(newConfig);
- UiFactory.onLauncherStateOrResumeChanged(this);
super.onConfigurationChanged(newConfig);
}
@@ -452,22 +525,13 @@
public void reapplyUi(boolean cancelCurrentAnimation) {
if (supportsFakeLandscapeUI()) {
mRotationMode = mStableDeviceProfile == null
- ? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile);
+ ? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile);
}
getRootView().dispatchInsets();
getStateManager().reapplyState(cancelCurrentAnimation);
}
@Override
- public void rebindModel() {
- int currentPage = mWorkspace.getNextPage();
- if (mModel.startLoader(currentPage)) {
- mWorkspace.setCurrentPage(currentPage);
- setWorkspaceLoading(true);
- }
- }
-
- @Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
onIdpChanged(idp);
}
@@ -485,7 +549,7 @@
// initialized properly.
onSaveInstanceState(new Bundle());
if (oldWallpaperProfile != getWallpaperDeviceProfile()) {
- rebindModel();
+ mModel.rebindCallbacks();
}
}
@@ -515,7 +579,7 @@
if (supportsFakeLandscapeUI() && mDeviceProfile.isVerticalBarLayout()) {
mStableDeviceProfile = mDeviceProfile.inv.portraitProfile;
- mRotationMode = UiFactory.getRotationMode(mDeviceProfile);
+ mRotationMode = getFakeRotationMode(mDeviceProfile);
} else {
mStableDeviceProfile = null;
mRotationMode = RotationMode.NORMAL;
@@ -554,12 +618,12 @@
return mRotationHelper;
}
- public LauncherStateManager getStateManager() {
- return mStateManager;
+ public ViewGroupFocusHelper getFocusHandler() {
+ return mFocusHandler;
}
- public FolderNameProvider getFolderNameProvider() {
- return new FolderNameProvider();
+ public LauncherStateManager getStateManager() {
+ return mStateManager;
}
@Override
@@ -567,18 +631,12 @@
return mLauncherView.findViewById(id);
}
- @Override
- public void onAppWidgetHostReset() {
- if (mAppWidgetHost != null) {
- mAppWidgetHost.startListening();
- }
- }
-
private LauncherCallbacks mLauncherCallbacks;
/**
* Call this after onCreate to set or clear overlay.
*/
+ @Override
public void setLauncherOverlay(LauncherOverlay overlay) {
if (overlay != null) {
overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl());
@@ -586,18 +644,16 @@
mWorkspace.setLauncherOverlay(overlay);
}
+ @Override
+ public void runOnOverlayHidden(Runnable runnable) {
+ getWorkspace().runOnOverlayHidden(runnable);
+ }
+
public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
mLauncherCallbacks = callbacks;
return true;
}
- @Override
- public void onLauncherProviderChanged() {
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onLauncherProviderChange();
- }
- }
-
public boolean isDraggingEnabled() {
// We prevent dragging when we are loading the workspace as it is possible to pick up a view
// that is subsequently removed from the workspace in startBinding().
@@ -643,6 +699,7 @@
switch (requestCode) {
case REQUEST_CREATE_SHORTCUT:
completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info);
+ announceForAccessibility(R.string.item_added_to_workspace);
break;
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(appWidgetId, info, null, null);
@@ -667,7 +724,6 @@
break;
}
}
-
return screenId;
}
@@ -788,6 +844,7 @@
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
}
}
+
mDragLayer.clearAnimatedView();
}
@@ -796,9 +853,6 @@
final int requestCode, final int resultCode, final Intent data) {
mPendingActivityRequestCode = -1;
handleActivityResult(requestCode, resultCode, data);
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
- }
}
@Override
@@ -825,10 +879,6 @@
getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show();
}
}
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions,
- grantResults);
- }
}
/**
@@ -885,33 +935,57 @@
@Override
protected void onStop() {
+ final boolean wasActive = isUserActive();
+ final LauncherState origState = getStateManager().getState();
+ final int origDragLayerChildCount = mDragLayer.getChildCount();
super.onStop();
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onStop();
+ mDragLayer.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+
+ if (mDeferOverlayCallbacks) {
+ checkIfOverlayStillDeferred();
+ } else {
+ mOverlayManager.onActivityStopped(this);
}
+
logStopAndResume(Action.Command.STOP);
mAppWidgetHost.setListenIfResumed(false);
NotificationListener.removeNotificationsChangedListener();
getStateManager().moveToRestState();
-
- UiFactory.onLauncherStateOrResumeChanged(this);
+ getBackgroundBlurController().setSurfaceToLauncher(null);
// Workaround for b/78520668, explicitly trim memory once UI is hidden
onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
+
+ if (wasActive) {
+ // The expected condition is that this activity is stopped because the device goes to
+ // sleep and the UI may have noticeable changes.
+ mDragLayer.post(() -> {
+ if ((!getStateManager().isInStableState(origState)
+ // The drag layer may be animating (e.g. dismissing QSB).
+ || mDragLayer.getAlpha() < 1
+ // Maybe an ArrowPopup is closed.
+ || mDragLayer.getChildCount() != origDragLayerChildCount)) {
+ onUiChangedWhileSleeping();
+ }
+ });
+ }
}
@Override
protected void onStart() {
- RaceConditionTracker.onEvent(ON_START_EVT, ENTER);
+ Object traceToken = TraceHelper.INSTANCE.beginSection(ON_START_EVT,
+ TraceHelper.FLAG_UI_EVENT);
super.onStart();
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onStart();
+ if (!mDeferOverlayCallbacks) {
+ mOverlayManager.onActivityStarted(this);
}
+ mDragLayer.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
+
mAppWidgetHost.setListenIfResumed(true);
- RaceConditionTracker.onEvent(ON_START_EVT, EXIT);
+ TraceHelper.INSTANCE.endSection(traceToken);
}
private void handleDeferredResume() {
@@ -919,7 +993,6 @@
logStopAndResume(Action.Command.RESUME);
getUserEventDispatcher().startSession();
- UiFactory.onLauncherStateOrResumeChanged(this);
AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
// Process any items that were added while Launcher was away.
@@ -934,15 +1007,17 @@
DiscoveryBounce.showForHomeIfNeeded(this);
- if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
- UiFactory.resetPendingActivityResults(this, mPendingActivityRequestCode);
- }
+ onDeferredResumed();
+ addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+
mDeferredResumePending = false;
} else {
mDeferredResumePending = true;
}
}
+ protected void onDeferredResumed() { }
+
private void logStopAndResume(int command) {
int containerType = mStateManager.getState().containerType;
if (containerType == ContainerType.WORKSPACE && mWorkspace != null) {
@@ -951,30 +1026,66 @@
} else {
getUserEventDispatcher().logActionCommand(command, containerType, -1);
}
+ }
+ private void scheduleDeferredCheck() {
+ mHandler.removeCallbacks(mDeferredOverlayCallbacks);
+ postAsyncCallback(mHandler, mDeferredOverlayCallbacks);
+ }
+
+ private void checkIfOverlayStillDeferred() {
+ if (!mDeferOverlayCallbacks) {
+ return;
+ }
+ if (isStarted() && (!hasBeenResumed() || mStateManager.getState().disableInteraction)) {
+ return;
+ }
+ mDeferOverlayCallbacks = false;
+
+ // Move the client to the correct state. Calling the same method twice is no-op.
+ if (isStarted()) {
+ mOverlayManager.onActivityStarted(this);
+ }
+ if (hasBeenResumed()) {
+ mOverlayManager.onActivityResumed(this);
+ } else {
+ mOverlayManager.onActivityPaused(this);
+ }
+ if (!isStarted()) {
+ mOverlayManager.onActivityStopped(this);
+ }
+ }
+
+ public void deferOverlayCallbacksUntilNextResumeOrStop() {
+ mDeferOverlayCallbacks = true;
+ }
+
+ public LauncherOverlayManager getOverlayManager() {
+ return mOverlayManager;
}
public void onStateSetStart(LauncherState state) {
if (mDeferredResumePending) {
handleDeferredResume();
}
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onStateChanged();
+ if (mDeferOverlayCallbacks) {
+ scheduleDeferredCheck();
}
+ addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
}
public void onStateSetEnd(LauncherState state) {
getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
getWorkspace().setClipChildren(!state.disablePageClipping);
finishAutoCancelActionMode();
+ removeActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
}
@Override
protected void onResume() {
- RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER);
- TraceHelper.beginSection("ON_RESUME");
+ Object traceToken = TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT,
+ TraceHelper.FLAG_UI_EVENT);
super.onResume();
- TraceHelper.partitionSection("ON_RESUME", "superCall");
mHandler.removeCallbacks(mHandleDeferredResume);
Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
@@ -988,12 +1099,13 @@
resumeCallbacks.clear();
}
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onResume();
+ if (mDeferOverlayCallbacks) {
+ scheduleDeferredCheck();
+ } else {
+ mOverlayManager.onActivityResumed(this);
}
- TraceHelper.endSection("ON_RESUME");
- RaceConditionTracker.onEvent(ON_RESUME_EVT, EXIT);
+ TraceHelper.INSTANCE.endSection(traceToken);
}
@Override
@@ -1005,52 +1117,12 @@
mDragController.cancelDrag();
mDragController.resetLastGestureUpTime();
mDropTargetBar.animateToVisibility(false);
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onPause();
+
+ if (!mDeferOverlayCallbacks) {
+ mOverlayManager.onActivityPaused(this);
}
}
- @Override
- protected void onUserLeaveHint() {
- super.onUserLeaveHint();
- UiFactory.onLauncherStateOrResumeChanged(this);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- mStateManager.onWindowFocusChanged();
- }
-
- public interface LauncherOverlay {
-
- /**
- * Touch interaction leading to overscroll has begun
- */
- void onScrollInteractionBegin();
-
- /**
- * Touch interaction related to overscroll has ended
- */
- void onScrollInteractionEnd();
-
- /**
- * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
- * screen (or in the case of RTL, the rightmost screen).
- */
- void onScrollChange(float progress, boolean rtl);
-
- /**
- * Called when the launcher is ready to use the overlay
- * @param callbacks A set of callbacks provided by Launcher in relation to the overlay
- */
- void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
- }
-
- public interface LauncherOverlayCallbacks {
- void onScrollChanged(float progress);
- }
-
class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
public void onScrollChanged(float progress) {
@@ -1100,12 +1172,13 @@
/**
* Finds all the views we need and configure them properly.
*/
- private void setupViews() {
+ protected void setupViews() {
mDragLayer = findViewById(R.id.drag_layer);
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = mDragLayer.findViewById(R.id.workspace);
mWorkspace.initParentViews(mDragLayer);
mOverviewPanel = findViewById(R.id.overview_panel);
+ mActionsView = findViewById(R.id.overview_actions_view);
mHotseat = findViewById(R.id.hotseat);
mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1114,7 +1187,6 @@
// Setup the drag layer
mDragLayer.setup(mDragController, mWorkspace);
- mCancelTouchController = UiFactory.enableLiveUIChanges(this);
mWorkspace.setup(mDragController);
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
@@ -1181,8 +1253,8 @@
WorkspaceItemInfo info = null;
if (Utilities.ATLEAST_OREO) {
- info = LauncherAppsCompatVO.createWorkspaceItemFromPinItemRequest(
- this, LauncherAppsCompatVO.getPinItemRequest(data), 0);
+ info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
+ this, PinRequestHelper.getPinItemRequest(data), 0);
}
if (info == null) {
@@ -1211,13 +1283,13 @@
cellXY[1] = cellY;
foundCellSpan = true;
+ DragObject dragObject = new DragObject(getApplicationContext());
+ dragObject.dragInfo = info;
// If appropriate, either create a folder or add to an existing folder
if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
- true, null)) {
+ true, dragObject)) {
return;
}
- DragObject dragObject = new DragObject();
- dragObject.dragInfo = info;
if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
true)) {
return;
@@ -1280,6 +1352,7 @@
hostView.setVisibility(View.VISIBLE);
prepareAppWidget(hostView, launcherInfo);
mWorkspace.addInScreen(hostView, launcherInfo);
+ announceForAccessibility(R.string.item_added_to_workspace);
}
private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) {
@@ -1295,12 +1368,17 @@
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
if (mPendingRequestArgs == null) {
+ if (!isInState(NORMAL)) {
+ onUiChangedWhileSleeping();
+ }
mStateManager.goToState(NORMAL);
}
}
};
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
+ protected void onUiChangedWhileSleeping() { }
+
+ private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
mWorkspace.updateNotificationDots(updatedDots);
mAppsView.getAppsStore().updateNotificationDots(updatedDots);
}
@@ -1308,19 +1386,14 @@
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
-
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onAttachedToWindow();
- }
+ mOverlayManager.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
-
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onDetachedFromWindow();
- }
+ mOverlayManager.onDetachedFromWindow();
+ closeContextMenu();
}
public AllAppsTransitionController getAllAppsController() {
@@ -1353,6 +1426,10 @@
return (T) mOverviewPanel;
}
+ public View getActionsView() {
+ return mActionsView;
+ }
+
public DropTargetBar getDropTargetBar() {
return mDropTargetBar;
}
@@ -1369,17 +1446,26 @@
return mModelWriter;
}
+ @Override
public SharedPreferences getSharedPrefs() {
return mSharedPrefs;
}
+ @Override
+ public SharedPreferences getDevicePrefs() {
+ return Utilities.getDevicePrefs(this);
+ }
+
public int getOrientation() {
return mOldConfig.orientation;
}
@Override
protected void onNewIntent(Intent intent) {
- TraceHelper.beginSection("NEW_INTENT");
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
+ }
+ Object traceToken = TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
super.onNewIntent(intent);
boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() &
@@ -1390,14 +1476,12 @@
boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
&& AbstractFloatingView.getTopOpenView(this) == null;
boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
- boolean internalStateHandled = InternalStateHandler
- .handleNewIntent(this, intent, isStarted());
+ boolean internalStateHandled = ACTIVITY_TRACKER.handleNewIntent(this, intent);
if (isActionMain) {
if (!internalStateHandled) {
// In all these cases, only animate if we're already on home
- AbstractFloatingView.closeAllOpenViews(this, isStarted());
- UiFactory.closeSystemWindows();
+ closeOpenViews(isStarted());
if (!isInState(NORMAL)) {
// Only change state, if not already the same. This prevents cancelling any
@@ -1430,9 +1514,10 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent(internalStateHandled);
}
+ mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
}
- TraceHelper.endSection("NEW_INTENT");
+ TraceHelper.INSTANCE.endSection(traceToken);
}
@Override
@@ -1475,31 +1560,19 @@
}
super.onSaveInstanceState(outState);
-
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onSaveInstanceState(outState);
- }
+ mOverlayManager.onActivitySaveInstanceState(this, outState);
}
@Override
public void onDestroy() {
super.onDestroy();
+ ACTIVITY_TRACKER.onActivityDestroyed(this);
unregisterReceiver(mScreenOffReceiver);
mWorkspace.removeFolderListeners();
+ PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
- if (mCancelTouchController != null) {
- mCancelTouchController.run();
- mCancelTouchController = null;
- }
-
- // Stop callbacks from LauncherModel
- // It's possible to receive onDestroy after a new Launcher activity has
- // been created. In this case, don't interfere with the new Launcher.
- if (mModel.isCurrentCallbacks(this)) {
- mModel.stopLoader();
- LauncherAppState.getInstance(this).setLauncher(null);
- }
+ mModel.removeCallbacks(this);
mRotationHelper.destroy();
try {
@@ -1511,9 +1584,9 @@
TextKeyListener.getInstance().release();
clearPendingBinds();
LauncherAppState.getIDP(this).removeOnChangeListener(this);
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onDestroy();
- }
+
+ mOverlayManager.onActivityDestroyed(this);
+ mAppTransitionManager.unregisterRemoteAnimations();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1529,10 +1602,7 @@
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
}
- if (requestCode == -1
- || !UiFactory.startActivityForResult(this, intent, requestCode, options)) {
- super.startActivityForResult(intent, requestCode, options);
- }
+ super.startActivityForResult(intent, requestCode, options);
}
@Override
@@ -1541,14 +1611,11 @@
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
}
- if (requestCode == -1 || !UiFactory.startIntentSenderForResult(this, intent, requestCode,
- fillInIntent, flagsMask, flagsValues, extraFlags, options)) {
- try {
- super.startIntentSenderForResult(intent, requestCode,
- fillInIntent, flagsMask, flagsValues, extraFlags, options);
- } catch (IntentSender.SendIntentException e) {
- throw new ActivityNotFoundException();
- }
+ try {
+ super.startIntentSenderForResult(intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags, options);
+ } catch (IntentSender.SendIntentException e) {
+ throw new ActivityNotFoundException();
}
}
@@ -1692,7 +1759,10 @@
}
}
- FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
+ /**
+ * Creates and adds new folder to CellLayout
+ */
+ public FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
int cellY) {
final FolderInfo folderInfo = new FolderInfo();
folderInfo.title = "";
@@ -1701,7 +1771,8 @@
getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
// Create the view
- FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo);
+ FolderIcon newFolder = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this, layout,
+ folderInfo);
mWorkspace.addInScreen(newFolder, folderInfo);
// Force measure the new folder icon
CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
@@ -1710,6 +1781,16 @@
}
/**
+ * Called when a workspace item is converted into a folder
+ */
+ public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo){}
+
+ /**
+ * Called when a folder is converted into a workspace item
+ */
+ public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {}
+
+ /**
* Unbinds the view for the specified item, and removes the item and all its children.
*
* @param v the view being removed.
@@ -1751,17 +1832,21 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Key event", event);
return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
public void onBackPressed() {
if (finishAutoCancelActionMode()) {
return;
}
- if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
- return;
- }
if (mDragController.isDragging()) {
mDragController.cancelDrag();
@@ -1770,7 +1855,6 @@
// Note: There should be at most one log per method call. This is enforced implicitly
// by using if-else statements.
- UserEventDispatcher ued = getUserEventDispatcher();
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
if (topView != null && topView.onBackPressed()) {
// Handled by the floating view.
@@ -1821,23 +1905,6 @@
}
@Override
- public void modifyUserEvent(LauncherLogProto.LauncherEvent event) {
- if (event.srcTarget != null && event.srcTarget.length > 0 &&
- event.srcTarget[1].containerType == ContainerType.PREDICTION) {
- Target[] targets = new Target[3];
- targets[0] = event.srcTarget[0];
- targets[1] = event.srcTarget[1];
- targets[2] = newTarget(Target.Type.CONTAINER);
- event.srcTarget = targets;
- LauncherState state = mStateManager.getState();
- if (state == LauncherState.ALL_APPS) {
- event.srcTarget[2].containerType = ContainerType.ALLAPPS;
- } else if (state == OVERVIEW) {
- event.srcTarget[2].containerType = ContainerType.TASKSWITCHER;
- }
- }
- }
-
public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
@Nullable String sourceContainer) {
if (!hasBeenResumed()) {
@@ -1845,7 +1912,10 @@
// recents animation into launcher. Defer launching the activity until Launcher is
// next resumed.
addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
- UiFactory.clearSwipeSharedState(true /* finishAnimation */);
+ if (mOnDeferredActivityLaunchCallback != null) {
+ mOnDeferredActivityLaunchCallback.run();
+ mOnDeferredActivityLaunchCallback = null;
+ }
return true;
}
@@ -1886,10 +1956,6 @@
// This clears all widget bitmaps from the widget tray
// TODO(hyunyoungs)
}
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onTrimMemory(level);
- }
- UiFactory.onTrimMemory(this, level);
}
@Override
@@ -1910,11 +1976,29 @@
}
/**
+ * Persistant callback which notifies when an activity launch is deferred because the activity
+ * was not yet resumed.
+ */
+ public void setOnDeferredActivityLaunchCallback(Runnable callback) {
+ mOnDeferredActivityLaunchCallback = callback;
+ }
+
+ /**
+ * Sets the next page to bind synchronously on next bind.
+ * @param page
+ */
+ public void setPageToBindSynchronously(int page) {
+ mPageToBindSynchronously = page;
+ }
+
+ /**
* Implementation of the method from LauncherModel.Callbacks.
*/
@Override
- public int getCurrentWorkspaceScreen() {
- if (mWorkspace != null) {
+ public int getPageToBindSynchronously() {
+ if (mPageToBindSynchronously != PagedView.INVALID_PAGE) {
+ return mPageToBindSynchronously;
+ } else if (mWorkspace != null) {
return mWorkspace.getCurrentPage();
} else {
return 0;
@@ -1943,7 +2027,7 @@
* Implementation of the method from LauncherModel.Callbacks.
*/
public void startBinding() {
- TraceHelper.beginSection("startBinding");
+ Object traceToken = TraceHelper.INSTANCE.beginSection("startBinding");
// Floating panels (except the full widget sheet) are associated with individual icons. If
// we are starting a fresh bind, close all such panels as all the icons are about
// to go away.
@@ -1961,7 +2045,7 @@
if (mHotseat != null) {
mHotseat.resetLayout(getWallpaperDeviceProfile().isVerticalBarLayout());
}
- TraceHelper.endSection("startBinding");
+ TraceHelper.INSTANCE.endSection(traceToken);
}
@Override
@@ -2058,7 +2142,7 @@
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
- view = FolderIcon.fromXml(R.layout.folder_icon, this,
+ view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item);
break;
@@ -2085,7 +2169,7 @@
Object tag = v.getTag();
String desc = "Collision while binding workspace item: " + item
+ ". Collides with " + tag;
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
throw (new RuntimeException(desc));
} else {
Log.d(TAG, desc);
@@ -2120,7 +2204,7 @@
mWorkspace.postDelayed(new Runnable() {
public void run() {
if (mWorkspace != null) {
- AbstractFloatingView.closeAllOpenViews(Launcher.this, false);
+ closeOpenViews(false);
mWorkspace.snapToPage(newScreenIndex);
mWorkspace.postDelayed(startBounceAnimRunnable,
@@ -2154,112 +2238,125 @@
return null;
}
}
-
+ final AppWidgetHostView view;
if (mIsSafeModeEnabled) {
- PendingAppWidgetHostView view =
- new PendingAppWidgetHostView(this, item, mIconCache, true);
+ view = new PendingAppWidgetHostView(this, item, mIconCache, true);
prepareAppWidget(view, item);
return view;
}
- TraceHelper.beginSection("BIND_WIDGET");
+ Object traceToken = TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
- final LauncherAppWidgetProviderInfo appWidgetInfo;
+ try {
+ final LauncherAppWidgetProviderInfo appWidgetInfo;
- if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
- // If the provider is not ready, bind as a pending widget.
- appWidgetInfo = null;
- } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
- // The widget id is not valid. Try to find the widget based on the provider info.
- appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
- } else {
- appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
- }
-
- // If the provider is ready, but the width is not yet restored, try to restore it.
- if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) &&
- (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
- if (appWidgetInfo == null) {
- Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
- + " belongs to component " + item.providerName
- + ", as the provider is null");
- getModelWriter().deleteItemFromDatabase(item);
- return null;
+ if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
+ // If the provider is not ready, bind as a pending widget.
+ appWidgetInfo = null;
+ } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+ // The widget id is not valid. Try to find the widget based on the provider info.
+ appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
+ } else {
+ appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
}
- // If we do not have a valid id, try to bind an id.
- if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
- if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
- // Id has not been allocated yet. Allocate a new id.
- item.appWidgetId = mAppWidgetHost.allocateAppWidgetId();
- item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED;
+ // If the provider is ready, but the width is not yet restored, try to restore it.
+ if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+ && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
+ if (appWidgetInfo == null) {
+ Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+ + " belongs to component " + item.providerName
+ + ", as the provider is null");
+ getModelWriter().deleteItemFromDatabase(item);
+ return null;
+ }
- // Also try to bind the widget. If the bind fails, the user will be shown
- // a click to setup UI, which will ask for the bind permission.
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo);
- pendingInfo.spanX = item.spanX;
- pendingInfo.spanY = item.spanY;
- pendingInfo.minSpanX = item.minSpanX;
- pendingInfo.minSpanY = item.minSpanY;
- Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this,
- pendingInfo);
+ // If we do not have a valid id, try to bind an id.
+ if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+ if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
+ // Id has not been allocated yet. Allocate a new id.
+ item.appWidgetId = mAppWidgetHost.allocateAppWidgetId();
+ item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED;
- boolean isDirectConfig =
- item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
- if (isDirectConfig && item.bindOptions != null) {
- Bundle newOptions = item.bindOptions.getExtras();
- if (options != null) {
- newOptions.putAll(options);
+ // Also try to bind the widget. If the bind fails, the user will be shown
+ // a click to setup UI, which will ask for the bind permission.
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo);
+ pendingInfo.spanX = item.spanX;
+ pendingInfo.spanY = item.spanY;
+ pendingInfo.minSpanX = item.minSpanX;
+ pendingInfo.minSpanY = item.minSpanY;
+ Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this,
+ pendingInfo);
+
+ boolean isDirectConfig =
+ item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
+ if (isDirectConfig && item.bindOptions != null) {
+ Bundle newOptions = item.bindOptions.getExtras();
+ if (options != null) {
+ newOptions.putAll(options);
+ }
+ options = newOptions;
}
- options = newOptions;
+ boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+ item.appWidgetId, appWidgetInfo, options);
+
+ // We tried to bind once. If we were not able to bind, we would need to
+ // go through the permission dialog, which means we cannot skip the config
+ // activity.
+ item.bindOptions = null;
+ item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG;
+
+ // Bind succeeded
+ if (success) {
+ // If the widget has a configure activity, it is still needs to set it
+ // up, otherwise the widget is ready to go.
+ item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig
+ ? LauncherAppWidgetInfo.RESTORE_COMPLETED
+ : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+ }
+
+ getModelWriter().updateItemInDatabase(item);
}
- boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
- item.appWidgetId, appWidgetInfo, options);
-
- // We tried to bind once. If we were not able to bind, we would need to
- // go through the permission dialog, which means we cannot skip the config
- // activity.
- item.bindOptions = null;
- item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG;
-
- // Bind succeeded
- if (success) {
- // If the widget has a configure activity, it is still needs to set it up,
- // otherwise the widget is ready to go.
- item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig
- ? LauncherAppWidgetInfo.RESTORE_COMPLETED
- : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
- }
-
+ } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
+ && (appWidgetInfo.configure == null)) {
+ // The widget was marked as UI not ready, but there is no configure activity to
+ // update the UI.
+ item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
getModelWriter().updateItemInDatabase(item);
}
- } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
- && (appWidgetInfo.configure == null)) {
- // The widget was marked as UI not ready, but there is no configure activity to
- // update the UI.
- item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
- getModelWriter().updateItemInDatabase(item);
- }
- }
-
- final AppWidgetHostView view;
- if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
- // Verify that we own the widget
- if (appWidgetInfo == null) {
- FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
- getModelWriter().deleteWidgetInfo(item, getAppWidgetHost());
- return null;
+ else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
+ && appWidgetInfo.configure != null) {
+ if (mAppWidgetManager.isAppWidgetRestored(item.appWidgetId)) {
+ item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+ getModelWriter().updateItemInDatabase(item);
+ }
+ }
}
- item.minSpanX = appWidgetInfo.minSpanX;
- item.minSpanY = appWidgetInfo.minSpanY;
- view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
- } else {
- view = new PendingAppWidgetHostView(this, item, mIconCache, false);
- }
- prepareAppWidget(view, item);
+ if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
+ // Verify that we own the widget
+ if (appWidgetInfo == null) {
+ FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
+ getModelWriter().deleteWidgetInfo(item, getAppWidgetHost());
+ return null;
+ }
- TraceHelper.endSection("BIND_WIDGET", "id=" + item.appWidgetId);
+ item.minSpanX = appWidgetInfo.minSpanX;
+ item.minSpanY = appWidgetInfo.minSpanY;
+ view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
+ } else if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
+ && appWidgetInfo != null) {
+ mAppWidgetHost.addPendingView(item.appWidgetId,
+ new PendingAppWidgetHostView(this, item, mIconCache, false));
+ view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
+ } else {
+ view = new PendingAppWidgetHostView(this, item, mIconCache, false);
+ }
+ prepareAppWidget(view, item);
+ } finally {
+ TraceHelper.INSTANCE.endSection(traceToken);
+ }
+
return view;
}
@@ -2291,6 +2388,8 @@
public void onPageBoundSynchronously(int page) {
mSynchronouslyBoundPage = page;
+ mWorkspace.setCurrentPage(page);
+ mPageToBindSynchronously = PagedView.INVALID_PAGE;
}
@Override
@@ -2337,7 +2436,7 @@
* Implementation of the method from LauncherModel.Callbacks.
*/
public void finishBindingItems(int pageBoundFirst) {
- TraceHelper.beginSection("finishBindingItems");
+ Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
mWorkspace.restoreInstanceStateForRemainingPages();
setWorkspaceLoading(false);
@@ -2355,13 +2454,14 @@
// Since we are just resetting the current page without user interaction,
// override the previous page so we don't log the page switch.
mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
+ mPageToBindSynchronously = PagedView.INVALID_PAGE;
// Cache one page worth of icons
getViewCache().setCacheSize(R.layout.folder_application,
mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
getViewCache().setCacheSize(R.layout.folder_page, 2);
- TraceHelper.endSection("finishBindingItems");
+ TraceHelper.INSTANCE.endSection(traceToken);
}
private boolean canRunNewAppsAnimation() {
@@ -2377,6 +2477,10 @@
return bounceAnim;
}
+ private void announceForAccessibility(@StringRes int stringResId) {
+ getDragLayer().announceForAccessibility(getString(stringResId));
+ }
+
/**
* Add the icons for all apps.
*
@@ -2508,6 +2612,7 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.dump(prefix, fd, writer, args);
}
+ mOverlayManager.dump(prefix, writer);
}
@Override
@@ -2587,10 +2692,13 @@
if (!mDragController.isDragging() && !mWorkspace.isSwitchingState() &&
isInState(NORMAL)) {
// Close any open floating views.
- AbstractFloatingView.closeAllOpenViews(this);
+ closeOpenViews();
// Setting the touch point to (-1, -1) will show the options popup in the center of
// the screen.
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
+ }
OptionsPopupView.showDefaultOptions(this, -1, -1);
}
return true;
@@ -2598,16 +2706,53 @@
return super.onKeyUp(keyCode, event);
}
- public static Launcher getLauncher(Context context) {
- return (Launcher) fromContext(context);
+ protected StateHandler[] createStateHandlers() {
+ return new StateHandler[] { getAllAppsController(), getWorkspace(),
+ getBackgroundBlurController() };
}
+ public TouchController[] createTouchControllers() {
+ return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
+ }
+
+ protected RotationMode getFakeRotationMode(DeviceProfile deviceProfile) {
+ return RotationMode.NORMAL;
+ }
+
+ protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
+ return new ScaleAndTranslation(1.1f, 0f, 0f);
+ }
+
+ public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
+
+ public void onDragLayerHierarchyChanged() { }
+
@Override
public void returnToHomescreen() {
super.returnToHomescreen();
getStateManager().goToState(LauncherState.NORMAL);
}
+ private void closeOpenViews() {
+ closeOpenViews(true);
+ }
+
+ protected void closeOpenViews(boolean animate) {
+ AbstractFloatingView.closeAllOpenViews(this, animate);
+ }
+
+ public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
+ return Stream.of(APP_INFO, WIDGETS, INSTALL);
+ }
+
+ public BackgroundBlurController getBackgroundBlurController() {
+ return mBackgroundBlurController;
+ }
+
+ public static Launcher getLauncher(Context context) {
+ return fromContext(context);
+ }
+
/**
* Just a wrapper around the type cast to allow easier tracking of calls.
*/
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 74362ed..d9cf7f1 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -18,7 +18,7 @@
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
-import android.util.Property;
+import android.util.IntProperty;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
@@ -27,23 +27,20 @@
* Durations for various state animations. These are not defined in resources to allow
* easier access from static classes and enums
*/
- public static final int ALL_APPS_TRANSITION_MS = 320;
- public static final int OVERVIEW_TRANSITION_MS = 250;
- public static final int SPRING_LOADED_TRANSITION_MS = 150;
public static final int SPRING_LOADED_EXIT_DELAY = 500;
// The progress of an animation to all apps must be at least this far along to snap to all apps.
public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
- public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
- new Property<Drawable, Integer>(Integer.TYPE, "drawableAlpha") {
+ public static final IntProperty<Drawable> DRAWABLE_ALPHA =
+ new IntProperty<Drawable>("drawableAlpha") {
@Override
public Integer get(Drawable drawable) {
return drawable.getAlpha();
}
@Override
- public void set(Drawable drawable, Integer alpha) {
+ public void setValue(Drawable drawable, int alpha) {
drawable.setAlpha(alpha);
}
};
@@ -67,28 +64,28 @@
return (int) Utilities.boundToRange(Math.abs(velocity) / 2, 2f, 6f);
}
- public static final Property<LayoutParams, Integer> LAYOUT_WIDTH =
- new Property<LayoutParams, Integer>(Integer.TYPE, "width") {
+ public static final IntProperty<LayoutParams> LAYOUT_WIDTH =
+ new IntProperty<LayoutParams>("width") {
@Override
public Integer get(LayoutParams lp) {
return lp.width;
}
@Override
- public void set(LayoutParams lp, Integer width) {
+ public void setValue(LayoutParams lp, int width) {
lp.width = width;
}
};
- public static final Property<LayoutParams, Integer> LAYOUT_HEIGHT =
- new Property<LayoutParams, Integer>(Integer.TYPE, "height") {
+ public static final IntProperty<LayoutParams> LAYOUT_HEIGHT =
+ new IntProperty<LayoutParams>("height") {
@Override
public Integer get(LayoutParams lp) {
return lp.height;
}
@Override
- public void set(LayoutParams lp, Integer height) {
+ public void setValue(LayoutParams lp, int height) {
lp.height = height;
}
};
@@ -120,4 +117,18 @@
return view.getTranslationY();
}
};
+
+ public static final FloatProperty<View> VIEW_ALPHA =
+ View.ALPHA instanceof FloatProperty ? (FloatProperty) View.ALPHA
+ : new FloatProperty<View>("alpha") {
+ @Override
+ public void setValue(View view, float v) {
+ view.setAlpha(v);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getAlpha();
+ }
+ };
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d70abc2..2f38037 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,26 +17,31 @@
package com.android.launcher3;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.content.ComponentName;
-import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.pm.LauncherApps;
import android.os.Handler;
import android.util.Log;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.pm.InstallSessionTracker;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.widget.custom.CustomWidgetManager;
public class LauncherAppState {
@@ -44,7 +49,7 @@
public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
// We do not need any synchronization for this variable as its only written on UI thread.
- private static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
+ public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
new MainThreadInitializedObject<>(LauncherAppState::new);
private final Context mContext;
@@ -52,7 +57,12 @@
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
- private final SecureSettingsObserver mNotificationDotsObserver;
+
+ private SecureSettingsObserver mNotificationDotsObserver;
+ private InstallSessionTracker mInstallSessionTracker;
+ private SimpleBroadcastReceiver mModelChangeReceiver;
+ private SafeCloseable mCalendarChangeTracker;
+ private SafeCloseable mUserChangeListener;
public static LauncherAppState getInstance(final Context context) {
return INSTANCE.get(context);
@@ -66,42 +76,37 @@
return mContext;
}
- private LauncherAppState(Context context) {
- if (getLocalProvider(context) == null) {
- throw new RuntimeException(
- "Initializing LauncherAppState in the absence of LauncherProvider");
+ public LauncherAppState(Context context) {
+ this(context, LauncherFiles.APP_ICONS_DB);
+
+ mModelChangeReceiver = new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
+
+ mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
+ mModelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
+ if (FeatureFlags.IS_STUDIO_BUILD) {
+ mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
}
- Log.v(Launcher.TAG, "LauncherAppState initiated");
- Preconditions.assertUIThread();
- mContext = context;
- mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext);
- mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
- mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
- mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+ mCalendarChangeTracker = IconProvider.registerIconChangeListener(mContext,
+ mModel::onAppIconChanged, MODEL_EXECUTOR.getHandler());
- LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
-
- // Register intent receivers
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- // For handling managed profiles
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
- filter.addAction(ACTION_FORCE_ROLOAD);
- }
+ // TODO: remove listener on terminate
FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
+ CustomWidgetManager.INSTANCE.get(mContext)
+ .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
- mContext.registerReceiver(mModel, filter);
- UserManagerCompat.getInstance(mContext).enableAndResetCache();
+ mUserChangeListener = UserCache.INSTANCE.get(mContext)
+ .addUserChangeListener(mModel::forceReload);
+
mInvariantDeviceProfile.addOnChangeListener(this::onIdpChanged);
new Handler().post( () -> mInvariantDeviceProfile.verifyConfigChangedInBackground(context));
+ mInstallSessionTracker = InstallSessionHelper.INSTANCE.get(context)
+ .registerInstallTracker(mModel, MODEL_EXECUTOR);
+
if (!mContext.getResources().getBoolean(R.bool.notification_dots_enabled)) {
mNotificationDotsObserver = null;
} else {
@@ -113,6 +118,17 @@
}
}
+ public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
+ Log.v(Launcher.TAG, "LauncherAppState initiated");
+ Preconditions.assertUIThread();
+ mContext = context;
+
+ mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
+ mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
+ mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
+ mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+ }
+
protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
if (areNotificationDotsEnabled) {
NotificationListener.requestRebind(new ComponentName(
@@ -138,23 +154,26 @@
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
public void onTerminate() {
- mContext.unregisterReceiver(mModel);
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
- launcherApps.removeOnAppsChangedCallback(mModel);
- PackageInstallerCompat.getInstance(mContext).onStop();
+ if (mModelChangeReceiver != null) {
+ mContext.unregisterReceiver(mModelChangeReceiver);
+ }
+ mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
+ if (mInstallSessionTracker != null) {
+ mInstallSessionTracker.unregister();
+ }
+ if (mCalendarChangeTracker != null) {
+ mCalendarChangeTracker.close();
+ }
+ if (mUserChangeListener != null) {
+ mUserChangeListener.close();
+ }
+ CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
+
if (mNotificationDotsObserver != null) {
mNotificationDotsObserver.unregister();
}
}
- LauncherModel setLauncher(Launcher launcher) {
- getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
- mModel.initialize(launcher);
- CustomWidgetManager.INSTANCE.get(launcher)
- .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
- return mModel;
- }
-
public IconCache getIconCache() {
return mIconCache;
}
@@ -177,11 +196,4 @@
public static InvariantDeviceProfile getIDP(Context context) {
return InvariantDeviceProfile.INSTANCE.get(context);
}
-
- private static LauncherProvider getLocalProvider(Context context) {
- try (ContentProviderClient cl = context.getContentResolver()
- .acquireContentProviderClient(LauncherProvider.AUTHORITY)) {
- return (LauncherProvider) cl.getLocalContentProvider();
- }
- }
}
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index c55c120..9148c2f 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -67,4 +67,18 @@
public Animator createStateElementAnimation(int index, float... values) {
throw new RuntimeException("Unknown gesture animation " + index);
}
+
+ /**
+ * Registers remote animations for certain system transitions.
+ */
+ public void registerRemoteAnimations() {
+ // Do nothing
+ }
+
+ /**
+ * Unregisters all remote animations.
+ */
+ public void unregisterRemoteAnimations() {
+ // Do nothing
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 1215d43..9921f76 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -29,9 +29,10 @@
import android.util.SparseArray;
import android.widget.Toast;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.widget.DeferredAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
@@ -53,12 +54,14 @@
private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
+ private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
private final Context mContext;
private int mFlags = FLAG_RESUMED;
private IntConsumer mAppWidgetRemovedCallback = null;
+
public LauncherAppWidgetHost(Context context) {
this(context, null);
}
@@ -73,14 +76,20 @@
@Override
protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+ final LauncherAppWidgetHostView view;
+ if (mPendingViews.get(appWidgetId) != null) {
+ view = mPendingViews.get(appWidgetId);
+ mPendingViews.remove(appWidgetId);
+ } else {
+ view = new LauncherAppWidgetHostView(context);
+ }
mViews.put(appWidgetId, view);
return view;
}
@Override
public void startListening() {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
return;
}
mFlags |= FLAG_LISTENING;
@@ -107,7 +116,7 @@
@Override
public void stopListening() {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
return;
}
mFlags &= ~FLAG_LISTENING;
@@ -166,7 +175,7 @@
@Override
public int allocateAppWidgetId() {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
return AppWidgetManager.INVALID_APPWIDGET_ID;
}
@@ -189,6 +198,10 @@
}
}
+ void addPendingView(int appWidgetId, PendingAppWidgetHostView view) {
+ mPendingViews.put(appWidgetId, view);
+ }
+
public AppWidgetHostView createView(Context context, int appWidgetId,
LauncherAppWidgetProviderInfo appWidget) {
if (appWidget.isCustomWidget()) {
@@ -238,8 +251,8 @@
/**
* Called on an appWidget is removed for a widgetId
- * @param appWidgetId
- * TODO: make this override when SDK is updated
+ *
+ * @param appWidgetId TODO: make this override when SDK is updated
*/
public void onAppWidgetRemoved(int appWidgetId) {
if (mAppWidgetRemovedCallback == null) {
@@ -263,7 +276,7 @@
public void startBindFlow(BaseActivity activity,
int appWidgetId, AppWidgetProviderInfo info, int requestCode) {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
sendActionCancelled(activity, requestCode);
return;
}
@@ -279,7 +292,7 @@
public void startConfigActivity(BaseActivity activity, int widgetId, int requestCode) {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
sendActionCancelled(activity, requestCode);
return;
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 228c07e..56cce78 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -7,10 +7,12 @@
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.UserHandle;
-import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.IconCache;
/**
* This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
@@ -19,7 +21,7 @@
* as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo}
*/
public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
- implements ComponentWithLabel {
+ implements ComponentWithLabelAndIcon {
public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
@@ -111,4 +113,9 @@
public final UserHandle getUser() {
return getProfile();
}
+
+ @Override
+ public Drawable getFullResIcon(IconCache cache) {
+ return cache.getFullResIcon(provider.getPackageName(), icon);
+ }
}
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index dfe75ec..0e529bd 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import android.content.Intent;
import android.os.Bundle;
import java.io.FileDescriptor;
@@ -36,31 +35,8 @@
* the code in the corresponding Launcher method is executed.
*/
void onCreate(Bundle savedInstanceState);
- void onResume();
- void onStart();
- void onStop();
- void onPause();
- void onDestroy();
- void onSaveInstanceState(Bundle outState);
- void onActivityResult(int requestCode, int resultCode, Intent data);
- void onRequestPermissionsResult(int requestCode, String[] permissions,
- int[] grantResults);
- void onAttachedToWindow();
- void onDetachedFromWindow();
void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args);
void onHomeIntent(boolean internalStateHandled);
- boolean handleBackPressed();
- void onTrimMemory(int level);
-
- /**
- * Called when the launcher state changed
- */
- default void onStateChanged() { }
-
- /*
- * Extension points for providing custom behavior on certain user interactions.
- */
- void onLauncherProviderChange();
/**
* Starts a search with {@param initialQuery}. Return false if search was not started.
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 9c4646b..25afb55 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -15,8 +15,13 @@
private static final String XML = ".xml";
public static final String LAUNCHER_DB = "launcher.db";
+ public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db";
+ public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db";
+ public static final String LAUNCHER_2_BY_2_DB = "launcher_2_by_2.db";
+ public static final String BACKUP_DB = "backup.db";
public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
- public static final String MANAGED_USER_PREFERENCES_KEY = "com.android.launcher3.managedusers.prefs";
+ public static final String MANAGED_USER_PREFERENCES_KEY =
+ "com.android.launcher3.managedusers.prefs";
// This preference file is not backed up to cloud.
public static final String DEVICE_PREFERENCES_KEY = "com.android.launcher3.device.prefs";
@@ -25,6 +30,10 @@
public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
LAUNCHER_DB,
+ LAUNCHER_4_BY_4_DB,
+ LAUNCHER_3_BY_3_DB,
+ LAUNCHER_2_BY_2_DB,
+ BACKUP_DB,
SHARED_PREFERENCES_KEY + XML,
WIDGET_PREVIEWS_DB,
MANAGED_USER_PREFERENCES_KEY + XML,
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index a012412..e61b7a8 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,27 +17,26 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
-import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
+import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
import android.content.pm.ShortcutInfo;
-import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.AddWorkspaceItemsTask;
import com.android.launcher3.model.AllAppsList;
@@ -52,16 +51,18 @@
import com.android.launcher3.model.PackageUpdatedTask;
import com.android.launcher3.model.ShortcutsChangedTask;
import com.android.launcher3.model.UserLockStateChangedTask;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.pm.InstallSessionTracker;
+import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Thunk;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -74,17 +75,17 @@
* LauncherModel object held in a static. Also provide APIs for updating the database state
* for the Launcher.
*/
-public class LauncherModel extends BroadcastReceiver
- implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
+public class LauncherModel extends LauncherApps.Callback implements InstallSessionTracker.Callback {
private static final boolean DEBUG_RECEIVER = false;
static final String TAG = "Launcher.Model";
- @Thunk final LauncherAppState mApp;
- @Thunk final Object mLock = new Object();
- @Thunk
- LoaderTask mLoaderTask;
- @Thunk boolean mIsLoaderTaskRunning;
+ private final LauncherAppState mApp;
+ private final Object mLock = new Object();
+ private final LooperExecutor mMainExecutor = MAIN_EXECUTOR;
+
+ private LoaderTask mLoaderTask;
+ private boolean mIsLoaderTaskRunning;
// Indicates whether the current model data is valid or not.
// We start off with everything not loaded. After that, we assume that
@@ -97,7 +98,7 @@
}
}
- @Thunk WeakReference<Callbacks> mCallbacks;
+ private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1);
// < only access in worker thread >
private final AllAppsList mBgAllAppsList;
@@ -106,18 +107,15 @@
* All the static data should be accessed on the background thread, A lock should be acquired
* on this object when accessing any data from this model.
*/
- static final BgDataModel sBgDataModel = new BgDataModel();
+ private final BgDataModel mBgDataModel = new BgDataModel();
// Runnable to check if the shortcuts permission has changed.
private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
@Override
public void run() {
- if (mModelLoaded) {
- boolean hasShortcutHostPermission =
- DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
- if (hasShortcutHostPermission != sBgDataModel.hasShortcutHostPermission) {
- forceReload();
- }
+ if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
+ != mBgDataModel.hasShortcutHostPermission) {
+ forceReload();
}
}
};
@@ -127,76 +125,27 @@
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
}
- public void setPackageState(PackageInstallInfo installInfo) {
- enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
- }
-
- /**
- * Updates the icons and label of all pending icons for the provided package name.
- */
- public void updateSessionDisplayInfo(final String packageName, final UserHandle user) {
- HashSet<String> packages = new HashSet<>();
- packages.add(packageName);
- enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_SESSION_UPDATE, user, packages));
- }
-
/**
* Adds the provided items to the workspace.
*/
public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
- Callbacks callbacks = getCallback();
- if (callbacks != null) {
- callbacks.preAddApps();
+ for (Callbacks cb : getCallbacks()) {
+ cb.preAddApps();
}
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}
public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
- return new ModelWriter(mApp.getContext(), this, sBgDataModel,
+ return new ModelWriter(mApp.getContext(), this, mBgDataModel,
hasVerticalHotseat, verifyChanges);
}
- /**
- * Set this as the current Launcher activity object for the loader.
- */
- public void initialize(Callbacks callbacks) {
- synchronized (mLock) {
- Preconditions.assertUIThread();
- mCallbacks = new WeakReference<>(callbacks);
- }
- }
-
@Override
public void onPackageChanged(String packageName, UserHandle user) {
int op = PackageUpdatedTask.OP_UPDATE;
enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
- public void onSessionFailure(String packageName, UserHandle user) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
- final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
- synchronized (dataModel) {
- for (ItemInfo info : dataModel.itemsIdMap) {
- if (info instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) info).hasPromiseIconUi()
- && user.equals(info.user)
- && info.getIntent() != null
- && TextUtils.equals(packageName, info.getIntent().getPackage())) {
- removedIds.put(info.id, true /* remove */);
- }
- }
- }
-
- if (!removedIds.isEmpty()) {
- deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
- }
- }
- });
- }
-
@Override
public void onPackageRemoved(String packageName, UserHandle user) {
onPackagesRemoved(user, packageName);
@@ -204,7 +153,7 @@
public void onPackagesRemoved(UserHandle user, String... packages) {
int op = PackageUpdatedTask.OP_REMOVE;
- FileLog.d(TAG, "package removed received " + String.join("," + packages));
+ FileLog.d(TAG, "package removed received " + TextUtils.join(",", packages));
enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
}
@@ -248,26 +197,29 @@
enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
}
- public void updatePinnedShortcuts(String packageName, List<ShortcutInfo> shortcuts,
- UserHandle user) {
- enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
+ /**
+ * Called when the icon for an app changes, outside of package event
+ */
+ @WorkerThread
+ public void onAppIconChanged(String packageName, UserHandle user) {
+ // Update the icon for the calendar package
+ Context context = mApp.getContext();
+ onPackageChanged(packageName, user);
+
+ List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user)
+ .forPackage(packageName).query(ShortcutRequest.PINNED);
+ if (!pinnedShortcuts.isEmpty()) {
+ enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user,
+ false));
+ }
}
- /**
- * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
- * ACTION_PACKAGE_CHANGED.
- */
- @Override
- public void onReceive(Context context, Intent intent) {
+ public void onBroadcastIntent(Intent intent) {
if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
final String action = intent.getAction();
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
- } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
- UserManagerCompat.getInstance(context).enableAndResetCache();
- forceReload();
} else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
@@ -286,22 +238,20 @@
enqueueModelUpdateTask(new UserLockStateChangedTask(user));
}
}
- } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
- Launcher l = (Launcher) getCallback();
- l.reload();
+ } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
+ for (Callbacks cb : getCallbacks()) {
+ if (cb instanceof Launcher) {
+ ((Launcher) cb).recreate();
+ }
+ }
}
}
- public void forceReload() {
- forceReload(-1);
- }
-
/**
* Reloads the workspace items from the DB and re-binds the workspace. This should generally
* not be called as DB updates are automatically followed by UI update
- * @param synchronousBindPage The page to bind first. Can pass -1 to use the current page.
*/
- public void forceReload(int synchronousBindPage) {
+ public void forceReload() {
synchronized (mLock) {
// Stop any existing loaders first, so they don't set mModelLoaded to true later
stopLoader();
@@ -310,37 +260,77 @@
// Start the loader if launcher is already running, otherwise the loader will run,
// the next time launcher starts
- Callbacks callbacks = getCallback();
- if (callbacks != null) {
- if (synchronousBindPage < 0) {
- synchronousBindPage = callbacks.getCurrentWorkspaceScreen();
- }
- startLoader(synchronousBindPage);
+ if (hasCallbacks()) {
+ startLoader();
}
}
- public boolean isCurrentCallbacks(Callbacks callbacks) {
- return (mCallbacks != null && mCallbacks.get() == callbacks);
+ /**
+ * Rebinds all existing callbacks with already loaded model
+ */
+ public void rebindCallbacks() {
+ if (hasCallbacks()) {
+ startLoader();
+ }
+ }
+
+ /**
+ * Removes an existing callback
+ */
+ public void removeCallbacks(Callbacks callbacks) {
+ synchronized (mCallbacksList) {
+ Preconditions.assertUIThread();
+ if (mCallbacksList.remove(callbacks)) {
+ if (stopLoader()) {
+ // Rebind existing callbacks
+ startLoader();
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a callbacks to receive model updates
+ * @return true if workspace load was performed synchronously
+ */
+ public boolean addCallbacksAndLoad(Callbacks callbacks) {
+ synchronized (mLock) {
+ addCallbacks(callbacks);
+ return startLoader();
+
+ }
+ }
+
+ /**
+ * Adds a callbacks to receive model updates
+ */
+ public void addCallbacks(Callbacks callbacks) {
+ Preconditions.assertUIThread();
+ synchronized (mCallbacksList) {
+ mCallbacksList.add(callbacks);
+ }
}
/**
* Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
* @return true if the page could be bound synchronously.
*/
- public boolean startLoader(int synchronousBindPage) {
+ public boolean startLoader() {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// Don't bother to start the thread if we know it's not going to do anything
- if (mCallbacks != null && mCallbacks.get() != null) {
- final Callbacks oldCallbacks = mCallbacks.get();
+ final Callbacks[] callbacksList = getCallbacks();
+ if (callbacksList.length > 0) {
// Clear any pending bind-runnables from the synchronized load process.
- MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds);
+ for (Callbacks cb : callbacksList) {
+ mMainExecutor.execute(cb::clearPendingBinds);
+ }
// If there is already one running, tell it to stop.
stopLoader();
- LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
- mBgAllAppsList, synchronousBindPage, mCallbacks);
+ LoaderResults loaderResults = new LoaderResults(
+ mApp, mBgDataModel, mBgAllAppsList, callbacksList, mMainExecutor);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
@@ -361,21 +351,24 @@
/**
* If there is already a loader task running, tell it to stop.
+ * @return true if an existing loader was stopped.
*/
- public void stopLoader() {
+ public boolean stopLoader() {
synchronized (mLock) {
LoaderTask oldTask = mLoaderTask;
mLoaderTask = null;
if (oldTask != null) {
oldTask.stopLocked();
+ return true;
}
+ return false;
}
}
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
- mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
+ mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
// Always post the loader task, instead of running directly (even on same thread) so
// that we exit any nested synchronized blocks
@@ -392,16 +385,65 @@
}
}
+ @Override
public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) {
+ if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
+ enqueueModelUpdateTask(new BaseModelUpdateTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ apps.addPromiseApp(app.getContext(), sessionInfo);
+ bindApplicationsIfNeeded();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onSessionFailure(String packageName, UserHandle user) {
+ if (!FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()) {
+ return;
+ }
enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
- apps.addPromiseApp(app.getContext(), sessionInfo);
- bindApplicationsIfNeeded();
+ final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) info).hasPromiseIconUi()
+ && user.equals(info.user)
+ && info.getIntent() != null
+ && TextUtils.equals(packageName, info.getIntent().getPackage())) {
+ removedIds.put(info.id, true /* remove */);
+ }
+ }
+ }
+
+ if (!removedIds.isEmpty()) {
+ deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
+ }
}
});
}
+ @Override
+ public void onPackageStateChanged(PackageInstallInfo installInfo) {
+ enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
+ }
+
+ /**
+ * Updates the icons and label of all pending icons for the provided package name.
+ */
+ @Override
+ public void onUpdateSessionDisplay(PackageUserKey key, PackageInstaller.SessionInfo info) {
+ mApp.getIconCache().updateSessionCache(key, info);
+
+ HashSet<String> packages = new HashSet<>();
+ packages.add(key.mPackageName);
+ enqueueModelUpdateTask(new CacheDataUpdatedTask(
+ CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages));
+ }
+
public class LoaderTransaction implements AutoCloseable {
private final LoaderTask mTask;
@@ -443,7 +485,7 @@
/**
* Refreshes the cached shortcuts if the shortcut permission has changed.
* Current implementation simply reloads the workspace, but it can be optimized to
- * use partial updates similar to {@link UserManagerCompat}
+ * use partial updates similar to {@link UserCache}
*/
public void refreshShortcutsIfRequired() {
MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
@@ -474,7 +516,7 @@
}
public void enqueueModelUpdateTask(ModelUpdateTask task) {
- task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
+ task.init(mApp, this, mBgDataModel, mBgAllAppsList, mMainExecutor);
MODEL_EXECUTOR.execute(task);
}
@@ -503,9 +545,7 @@
public void updateAndBindWorkspaceItem(WorkspaceItemInfo si, ShortcutInfo info) {
updateAndBindWorkspaceItem(() -> {
si.updateFromDeepShortcutInfo(info, mApp.getContext());
- LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
- si.applyFrom(li.createShortcutIcon(info));
- li.recycle();
+ mApp.getIconCache().getShortcutIcon(si, info);
return si;
});
}
@@ -540,14 +580,29 @@
if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
for (AppInfo info : mBgAllAppsList.data) {
- writer.println(prefix + " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
+ writer.println(prefix + " title=\"" + info.title
+ + "\" bitmapIcon=" + info.bitmap.icon
+ " componentName=" + info.componentName.getPackageName());
}
}
- sBgDataModel.dump(prefix, fd, writer, args);
+ mBgDataModel.dump(prefix, fd, writer, args);
}
- public Callbacks getCallback() {
- return mCallbacks != null ? mCallbacks.get() : null;
+ /**
+ * Returns true if there are any callbacks attached to the model
+ */
+ public boolean hasCallbacks() {
+ synchronized (mCallbacksList) {
+ return !mCallbacksList.isEmpty();
+ }
+ }
+
+ /**
+ * Returns an array of currently attached callbacks
+ */
+ public Callbacks[] getCallbacks() {
+ synchronized (mCallbacksList) {
+ return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
+ }
}
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 6081300..697048a 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,8 +16,10 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.backup.BackupManager;
@@ -46,9 +48,9 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.BaseColumns;
import android.provider.Settings;
import android.text.TextUtils;
@@ -57,10 +59,10 @@
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DbDowngradeHelper;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.provider.RestoreDbTask;
@@ -69,7 +71,6 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NoLocaleSQLiteHelper;
import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
@@ -89,6 +90,8 @@
private static final boolean LOGD = false;
private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
+ private static final String TOKEN_RESTORE_BACKUP_TABLE = "restore_backup_table";
+ private static final long RESTORE_BACKUP_TABLE_DELAY = 60000;
/**
* Represents the schema of the database. Changes in scheme need not be backwards compatible.
@@ -100,9 +103,6 @@
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
- private final ChangeListenerWrapper mListenerWrapper = new ChangeListenerWrapper();
- private Handler mListenerHandler;
-
protected DatabaseHelper mOpenHelper;
/**
@@ -119,10 +119,9 @@
@Override
public boolean onCreate() {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
Log.d(TAG, "Launcher process started");
}
- mListenerHandler = new Handler(mListenerWrapper);
// The content provider exists for the entire duration of the launcher main process and
// is the first component to get created.
@@ -130,14 +129,6 @@
return true;
}
- /**
- * Sets a provider listener.
- */
- public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
- Preconditions.assertUIThread();
- mListenerWrapper.mListener = listener;
- }
-
@Override
public String getType(Uri uri) {
SqlArguments args = new SqlArguments(uri, null, null);
@@ -153,7 +144,7 @@
*/
protected synchronized void createDbIfNotExists() {
if (mOpenHelper == null) {
- mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
+ mOpenHelper = new DatabaseHelper(getContext());
if (RestoreDbTask.isPending(getContext())) {
if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -167,6 +158,17 @@
}
}
+ private synchronized boolean updateCurrentOpenHelper() {
+ final InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
+ if (TextUtils.equals(idp.dbFile, mOpenHelper.getDatabaseName())) {
+ return false;
+ }
+
+ mOpenHelper.close();
+ mOpenHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
@@ -220,10 +222,9 @@
addModifiedTime(initialValues);
final int rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
if (rowId < 0) return null;
- mOpenHelper.onAddOrDeleteOp(db);
+ onAddOrDeleteOp(db);
uri = ContentUris.withAppendedId(uri, rowId);
- notifyListeners();
reloadLauncherIfExternal();
return uri;
}
@@ -279,11 +280,10 @@
return 0;
}
}
- mOpenHelper.onAddOrDeleteOp(db);
+ onAddOrDeleteOp(db);
t.commit();
}
- notifyListeners();
reloadLauncherIfExternal();
return values.length;
}
@@ -306,7 +306,7 @@
results[i].count != null && results[i].count > 0;
}
if (isAddOrDelete) {
- mOpenHelper.onAddOrDeleteOp(t.getDb());
+ onAddOrDeleteOp(t.getDb());
}
t.commit();
@@ -328,8 +328,7 @@
}
int count = db.delete(args.table, args.where, args.args);
if (count > 0) {
- mOpenHelper.onAddOrDeleteOp(db);
- notifyListeners();
+ onAddOrDeleteOp(db);
reloadLauncherIfExternal();
}
return count;
@@ -343,8 +342,6 @@
addModifiedTime(values);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count = db.update(args.table, values, args.where, args.args);
- if (count > 0) notifyListeners();
-
reloadLauncherIfExternal();
return count;
}
@@ -375,12 +372,14 @@
}
case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: {
Bundle result = new Bundle();
- result.putInt(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewItemId());
+ result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
+ mOpenHelper.generateNewItemId());
return result;
}
case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
Bundle result = new Bundle();
- result.putInt(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewScreenId());
+ result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
+ mOpenHelper.generateNewScreenId());
return result;
}
case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
@@ -402,14 +401,42 @@
return result;
}
case LauncherSettings.Settings.METHOD_REFRESH_BACKUP_TABLE: {
- mOpenHelper.mBackupTableExists =
- tableExists(mOpenHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
+ // TODO(pinyaoting): Update the behavior here.
+ if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
+ mOpenHelper.mBackupTableExists =
+ tableExists(mOpenHelper.getReadableDatabase(),
+ Favorites.BACKUP_TABLE_NAME);
+ }
return null;
}
+ case LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE: {
+ final Handler handler = MODEL_EXECUTOR.getHandler();
+ handler.removeCallbacksAndMessages(TOKEN_RESTORE_BACKUP_TABLE);
+ handler.postDelayed(() -> RestoreDbTask.restoreIfPossible(
+ getContext(), mOpenHelper, new BackupManager(getContext())),
+ TOKEN_RESTORE_BACKUP_TABLE, RESTORE_BACKUP_TABLE_DELAY);
+ return null;
+ }
+ case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
+ if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
+ Bundle result = new Bundle();
+ result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
+ updateCurrentOpenHelper());
+ return result;
+ }
+ }
}
return null;
}
+ private void onAddOrDeleteOp(SQLiteDatabase db) {
+ if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
+ // TODO(pingyaoting): Implement the behavior here.
+ } else {
+ mOpenHelper.onAddOrDeleteOp(db);
+ }
+ }
+
/**
* Deletes any empty folder from the DB.
* @return Ids of deleted folders.
@@ -438,13 +465,6 @@
}
}
- /**
- * Overridden in tests
- */
- protected void notifyListeners() {
- mListenerHandler.sendEmptyMessage(ChangeListenerWrapper.MSG_LAUNCHER_PROVIDER_CHANGED);
- }
-
@Thunk static void addModifiedTime(ContentValues values) {
values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis());
}
@@ -511,8 +531,6 @@
*/
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
Context ctx = getContext();
- InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
-
String authority = Settings.Secure.getString(ctx.getContentResolver(),
"launcher3.layout.provider");
if (TextUtils.isEmpty(authority)) {
@@ -524,13 +542,7 @@
Log.e(TAG, "No provider found for authority " + authority);
return null;
}
- Uri uri = new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
- .appendQueryParameter("version", "1")
- .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
- .appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
- .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
- .build();
-
+ Uri uri = getLayoutUri(authority, ctx);
try (InputStream in = ctx.getContentResolver().openInputStream(uri)) {
// Read the full xml so that we fail early in case of any IO error.
String layout = new String(IOUtils.toByteArray(in));
@@ -547,12 +559,22 @@
}
}
+ public static Uri getLayoutUri(String authority, Context ctx) {
+ InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
+ return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
+ .appendQueryParameter("version", "1")
+ .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
+ .appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
+ .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
+ .build();
+ }
+
private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
int defaultLayout = idp.defaultLayoutId;
- UserManagerCompat um = UserManagerCompat.getInstance(getContext());
- if (um.isDemoUser() && idp.demoModeLayoutId != 0) {
+ if (getContext().getSystemService(UserManager.class).isDemoUser()
+ && idp.demoModeLayoutId != 0) {
defaultLayout = idp.demoModeLayoutId;
}
@@ -563,16 +585,16 @@
/**
* The class is subclassed in tests to create an in-memory db.
*/
- public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
- private final BackupManager mBackupManager;
- private final Handler mWidgetHostResetHandler;
+ public static class DatabaseHelper extends NoLocaleSQLiteHelper implements
+ LayoutParserCallback {
private final Context mContext;
private int mMaxItemId = -1;
private int mMaxScreenId = -1;
private boolean mBackupTableExists;
- DatabaseHelper(Context context, Handler widgetHostResetHandler) {
- this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
+ DatabaseHelper(Context context) {
+ this(context, MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
+ context).dbFile : LauncherFiles.LAUNCHER_DB);
// Table creation sometimes fails silently, which leads to a crash loop.
// This way, we will try to create a table every time after crash, so the device
// would eventually be able to recover.
@@ -581,7 +603,10 @@
// This operation is a no-op if the table already exists.
addFavoritesTable(getWritableDatabase(), true);
}
- mBackupTableExists = tableExists(getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
+ if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
+ mBackupTableExists = tableExists(getReadableDatabase(),
+ Favorites.BACKUP_TABLE_NAME);
+ }
initIds();
}
@@ -589,12 +614,9 @@
/**
* Constructor used in tests and for restore.
*/
- public DatabaseHelper(
- Context context, Handler widgetHostResetHandler, String tableName) {
- super(context, tableName, SCHEMA_VERSION);
+ public DatabaseHelper(Context context, String dbName) {
+ super(context, dbName, SCHEMA_VERSION);
mContext = context;
- mWidgetHostResetHandler = widgetHostResetHandler;
- mBackupManager = new BackupManager(mContext);
}
protected void initIds() {
@@ -623,7 +645,7 @@
}
protected void onAddOrDeleteOp(SQLiteDatabase db) {
- if (mBackupTableExists) {
+ if (!MULTI_DB_GRID_MIRATION_ALGO.get() && mBackupTableExists) {
dropTable(db, Favorites.BACKUP_TABLE_NAME);
mBackupTableExists = false;
}
@@ -633,19 +655,12 @@
* Overriden in tests.
*/
protected void onEmptyDbCreated() {
- // Database was just created, so wipe any previous widgets
- if (mWidgetHostResetHandler != null) {
- newLauncherWidgetHost().deleteHost();
- mWidgetHostResetHandler.sendEmptyMessage(
- ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET);
- }
-
// Set the flag for empty DB
Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
}
public long getSerialNumberForUser(UserHandle user) {
- return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user);
+ return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
}
public long getDefaultUserSerial() {
@@ -676,7 +691,7 @@
*/
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) {
// Remove "profile extra"
- UserManagerCompat um = UserManagerCompat.getInstance(mContext);
+ UserCache um = UserCache.INSTANCE.get(mContext);
for (UserHandle user : um.getUserProfiles()) {
long serial = um.getSerialNumberForUser(user);
String sql = "update favorites set intent = replace(intent, "
@@ -1039,27 +1054,4 @@
}
}
}
-
- private static class ChangeListenerWrapper implements Handler.Callback {
-
- private static final int MSG_LAUNCHER_PROVIDER_CHANGED = 1;
- private static final int MSG_APP_WIDGET_HOST_RESET = 2;
-
- private LauncherProviderChangeListener mListener;
-
- @Override
- public boolean handleMessage(Message msg) {
- if (mListener != null) {
- switch (msg.what) {
- case MSG_LAUNCHER_PROVIDER_CHANGED:
- mListener.onLauncherProviderChanged();
- break;
- case MSG_APP_WIDGET_HOST_RESET:
- mListener.onAppWidgetHostReset();
- break;
- }
- }
- return true;
- }
- }
}
diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java
deleted file mode 100644
index 0243088..0000000
--- a/src/com/android/launcher3/LauncherProviderChangeListener.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.android.launcher3;
-
-/**
- * This class is a listener for {@link LauncherProvider} changes. It gets notified in the
- * sendNotify method. This listener is needed because by default the Launcher suppresses
- * standard data change callbacks.
- */
-public interface LauncherProviderChangeListener {
-
- void onLauncherProviderChanged();
-
- void onAppWidgetHostReset();
-}
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index f964b8d..2b2224a 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -8,7 +8,6 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
@@ -39,6 +38,8 @@
private WindowStateListener mWindowStateListener;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mDisallowBackGesture;
+ @ViewDebug.ExportedProperty(category = "launcher")
+ private boolean mForceHideBackArrow;
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -176,12 +177,18 @@
}
@TargetApi(Build.VERSION_CODES.Q)
+ public void setForceHideBackArrow(boolean forceHideBackArrow) {
+ this.mForceHideBackArrow = forceHideBackArrow;
+ setDisallowBackGesture(mDisallowBackGesture);
+ }
+
+ @TargetApi(Build.VERSION_CODES.Q)
public void setDisallowBackGesture(boolean disallowBackGesture) {
if (!Utilities.ATLEAST_Q) {
return;
}
mDisallowBackGesture = disallowBackGesture;
- setSystemGestureExclusionRects(mDisallowBackGesture
+ setSystemGestureExclusionRects((mForceHideBackArrow || mDisallowBackGesture)
? SYSTEM_GESTURE_EXCLUSION_RECT
: Collections.emptyList());
}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index c509680..216c221 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -93,15 +93,26 @@
public static final String TABLE_NAME = "favorites";
/**
- * Backup table created when when the favorites table is modified during grid migration
+ * Backup table created when the favorites table is modified during grid migration
*/
public static final String BACKUP_TABLE_NAME = "favorites_bakup";
/**
- * The content:// style URL for this table
+ * Temporary table used specifically for grid migrations during wallpaper preview
*/
- public static final Uri CONTENT_URI = Uri.parse("content://" +
- LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
+ public static final String PREVIEW_TABLE_NAME = "favorites_preview";
+
+ /**
+ * The content:// style URL for "favorites" table
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://"
+ + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
+
+ /**
+ * The content:// style URL for "favorites_preview" table
+ */
+ public static final Uri PREVIEW_CONTENT_URI = Uri.parse("content://"
+ + LauncherProvider.AUTHORITY + "/" + PREVIEW_TABLE_NAME);
/**
* The content:// style URL for a given row, identified by its id.
@@ -111,8 +122,8 @@
* @return The unique content URL for the specified row.
*/
public static Uri getContentUri(int id) {
- return Uri.parse("content://" + LauncherProvider.AUTHORITY +
- "/" + TABLE_NAME + "/" + id);
+ return Uri.parse("content://" + LauncherProvider.AUTHORITY
+ + "/" + TABLE_NAME + "/" + id);
}
/**
@@ -127,6 +138,7 @@
public static final int CONTAINER_DESKTOP = -100;
public static final int CONTAINER_HOTSEAT = -101;
public static final int CONTAINER_PREDICTION = -102;
+ public static final int CONTAINER_HOTSEAT_PREDICTION = -103;
static final String containerToString(int container) {
switch (container) {
@@ -299,6 +311,10 @@
public static final String METHOD_REFRESH_BACKUP_TABLE = "refresh_backup_table";
+ public static final String METHOD_RESTORE_BACKUP_TABLE = "restore_backup_table";
+
+ public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
+
public static final String EXTRA_VALUE = "value";
public static Bundle call(ContentResolver cr, String method) {
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 6e2626b..62b8927 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -26,24 +26,29 @@
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
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.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import android.content.Context;
+import android.view.View;
import android.view.animation.Interpolator;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.states.HintState;
import com.android.launcher3.states.SpringLoadedState;
-import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.uioverrides.states.AllAppsState;
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -54,7 +59,7 @@
/**
* Base state for various states used for the Launcher
*/
-public class LauncherState {
+public abstract class LauncherState {
/**
@@ -70,6 +75,10 @@
public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
public static final int RECENTS_CLEAR_ALL_BUTTON = 1 << 6;
+ /** Mask of all the items that are contained in the apps view. */
+ public static final int APPS_VIEW_ITEM_MASK =
+ HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+
protected static final int FLAG_MULTI_PAGE = 1 << 0;
protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 1;
protected static final int FLAG_DISABLE_RESTORE = 1 << 2;
@@ -89,15 +98,21 @@
}
};
- private static final LauncherState[] sAllStates = new LauncherState[7];
+ private static final LauncherState[] sAllStates = new LauncherState[8];
/**
* TODO: Create a separate class for NORMAL state.
*/
public static final LauncherState NORMAL = new LauncherState(NORMAL_STATE_ORDINAL,
- ContainerType.WORKSPACE, 0,
+ ContainerType.WORKSPACE,
FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HIDE_BACK_BUTTON |
- FLAG_HAS_SYS_UI_SCRIM);
+ FLAG_HAS_SYS_UI_SCRIM) {
+ @Override
+ public int getTransitionDuration(Launcher launcher) {
+ // Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
+ return 0;
+ }
+ };
/**
* Various Launcher states arranged in the increasing order of UI layers
@@ -105,6 +120,7 @@
public static final LauncherState SPRING_LOADED = new SpringLoadedState(
SPRING_LOADED_STATE_ORDINAL);
public static final LauncherState ALL_APPS = new AllAppsState(ALL_APPS_STATE_ORDINAL);
+ public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL);
public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
public static final LauncherState OVERVIEW_PEEK =
@@ -144,8 +160,6 @@
*/
public final boolean hasWorkspacePageBackground;
- public final int transitionDuration;
-
/**
* True if the state allows workspace icons to be dragged.
*/
@@ -175,9 +189,8 @@
public final boolean hasSysUiScrim;
- public LauncherState(int id, int containerType, int transitionDuration, int flags) {
+ public LauncherState(int id, int containerType, int flags) {
this.containerType = containerType;
- this.transitionDuration = transitionDuration;
this.hasWorkspacePageBackground = (flags & FLAG_PAGE_BACKGROUNDS) != 0;
this.hasMultipleVisiblePages = (flags & FLAG_MULTI_PAGE) != 0;
@@ -200,6 +213,12 @@
return Arrays.copyOf(sAllStates, sAllStates.length);
}
+ /**
+ * @return How long the animation to this state should take (or from this state to NORMAL).
+ * @param launcher
+ */
+ public abstract int getTransitionDuration(Launcher launcher);
+
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
return new ScaleAndTranslation(1, 0, 0);
}
@@ -210,7 +229,11 @@
}
public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- return UiFactory.getOverviewScaleAndTranslationForNormalState(launcher);
+ return launcher.getOverviewScaleAndTranslationForNormalState();
+ }
+
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ return new ScaleAndTranslation(1, 0, 0);
}
public float getOverviewFullscreenProgress() {
@@ -247,6 +270,14 @@
return 0;
}
+ /**
+ * The amount of blur to apply to the background of either the app or Launcher surface in this
+ * state.
+ */
+ public int getBackgroundBlurRadius(Context context) {
+ return 0;
+ }
+
public String getDescription(Launcher launcher) {
return launcher.getWorkspace().getCurrentPageDescription();
}
@@ -320,6 +351,15 @@
if (!isHotseatVisible) {
hotseat.setScaleX(0.92f);
hotseat.setScaleY(0.92f);
+ if (ENABLE_OVERVIEW_ACTIONS.get()) {
+ AllAppsContainerView qsbContainer = launcher.getAppsView();
+ View qsb = qsbContainer.getSearchView();
+ boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
+ if (!qsbVisible) {
+ qsbContainer.setScaleX(0.92f);
+ qsbContainer.setScaleY(0.92f);
+ }
+ }
}
} else if (this == NORMAL && fromState == OVERVIEW_PEEK) {
// Keep fully visible until the very end (when overview is offscreen) to make invisible.
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index f673508..04134f2 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -33,7 +33,6 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.uioverrides.UiFactory;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -89,20 +88,22 @@
// components may be run atomically - that is, all at once, instead of user-controlled. However,
// atomic components are not restricted to this purpose; they can be user-controlled alongside
// non atomic components as well. Note that each gesture model has exactly one atomic component,
- // ATOMIC_OVERVIEW_SCALE_COMPONENT *or* ATOMIC_OVERVIEW_PEEK_COMPONENT.
+ // PLAY_ATOMIC_OVERVIEW_SCALE *or* PLAY_ATOMIC_OVERVIEW_PEEK.
@IntDef(flag = true, value = {
- NON_ATOMIC_COMPONENT,
- ATOMIC_OVERVIEW_SCALE_COMPONENT,
- ATOMIC_OVERVIEW_PEEK_COMPONENT,
+ PLAY_NON_ATOMIC,
+ PLAY_ATOMIC_OVERVIEW_SCALE,
+ PLAY_ATOMIC_OVERVIEW_PEEK,
+ SKIP_OVERVIEW,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface AnimationComponents {}
- public static final int NON_ATOMIC_COMPONENT = 1 << 0;
- public static final int ATOMIC_OVERVIEW_SCALE_COMPONENT = 1 << 1;
- public static final int ATOMIC_OVERVIEW_PEEK_COMPONENT = 1 << 2;
+ public @interface AnimationFlags {}
+ public static final int PLAY_NON_ATOMIC = 1 << 0;
+ public static final int PLAY_ATOMIC_OVERVIEW_SCALE = 1 << 1;
+ public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
+ public static final int SKIP_OVERVIEW = 1 << 3;
- public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_OVERVIEW_SCALE_COMPONENT
- | ATOMIC_OVERVIEW_PEEK_COMPONENT;
+ public static final int ANIM_ALL_COMPONENTS = PLAY_NON_ATOMIC | PLAY_ATOMIC_OVERVIEW_SCALE
+ | PLAY_ATOMIC_OVERVIEW_PEEK;
private final AnimationConfig mConfig = new AnimationConfig();
private final Handler mUiHandler;
@@ -144,7 +145,7 @@
public StateHandler[] getStateHandlers() {
if (mStateHandlers == null) {
- mStateHandlers = UiFactory.getStateHandler(mLauncher);
+ mStateHandlers = mLauncher.createStateHandlers();
}
return mStateHandlers;
}
@@ -165,6 +166,15 @@
}
/**
+ * @return {@code true} if the state matches the current state and there is no active
+ * transition to different state.
+ */
+ public boolean isInStableState(LauncherState state) {
+ return mState == state && mCurrentStableState == state
+ && (mConfig.mTargetState == null || mConfig.mTargetState == state);
+ }
+
+ /**
* @see #goToState(LauncherState, boolean, Runnable)
*/
public void goToState(LauncherState state) {
@@ -236,12 +246,8 @@
} else if (!mConfig.userControlled && animated && mConfig.mTargetState == state) {
// We are running the same animation as requested
if (onCompleteRunnable != null) {
- mConfig.mCurrentAnimation.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- onCompleteRunnable.run();
- }
- });
+ mConfig.mCurrentAnimation.addListener(
+ AnimationSuccessListener.forRunnable(onCompleteRunnable));
}
return;
}
@@ -285,7 +291,9 @@
Runnable onCompleteRunnable) {
// Since state NORMAL can be reached from multiple states, just assume that the
// transition plays in reverse and use the same duration as previous state.
- mConfig.duration = state == NORMAL ? fromState.transitionDuration : state.transitionDuration;
+ mConfig.duration = state == NORMAL
+ ? fromState.getTransitionDuration(mLauncher)
+ : state.getTransitionDuration(mLauncher);
AnimatorSetBuilder builder = new AnimatorSetBuilder();
prepareForAtomicAnimation(fromState, state, builder);
@@ -305,10 +313,10 @@
}
public AnimatorSet createAtomicAnimation(LauncherState fromState, LauncherState toState,
- AnimatorSetBuilder builder, @AnimationComponents int atomicComponent, long duration) {
+ AnimatorSetBuilder builder, @AnimationFlags int animFlags, long duration) {
prepareForAtomicAnimation(fromState, toState, builder);
AnimationConfig config = new AnimationConfig();
- config.animComponents = atomicComponent;
+ config.mAnimFlags = animFlags;
config.duration = duration;
for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
handler.setStateWithAnimation(toState, builder, config);
@@ -349,25 +357,25 @@
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
LauncherState state, long duration) {
- return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
+ return createAnimationToNewWorkspace(state, duration, ANIM_ALL_COMPONENTS);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(
- LauncherState state, long duration, @AnimationComponents int animComponents) {
+ LauncherState state, long duration, @AnimationFlags int animComponents) {
return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
animComponents);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
- @AnimationComponents int animComponents) {
+ @AnimationFlags int animComponents) {
mConfig.reset();
mConfig.userControlled = true;
- mConfig.animComponents = animComponents;
+ mConfig.mAnimFlags = animComponents;
mConfig.duration = duration;
mConfig.playbackController = AnimatorPlaybackController.wrap(
- createAnimationToNewWorkspaceInternal(state, builder, null), duration,
- onCancelRunnable);
+ createAnimationToNewWorkspaceInternal(state, builder, null), duration)
+ .setOnCancelRunnable(onCancelRunnable);
return mConfig.playbackController;
}
@@ -412,7 +420,6 @@
// Only disable clipping if needed, otherwise leave it as previous value.
mLauncher.getWorkspace().setClipChildren(false);
}
- UiFactory.onLauncherStateOrResumeChanged(mLauncher);
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onStateTransitionStart(state);
@@ -433,8 +440,6 @@
setRestState(null);
}
- UiFactory.onLauncherStateOrResumeChanged(mLauncher);
-
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onStateTransitionComplete(state);
}
@@ -442,10 +447,6 @@
AccessibilityManagerCompat.sendStateEventToTest(mLauncher, state.ordinal);
}
- public void onWindowFocusChanged() {
- UiFactory.onLauncherStateOrFocusChanged(mLauncher);
- }
-
public LauncherState getLastState() {
return mLastStableState;
}
@@ -584,7 +585,7 @@
public long duration;
public boolean userControlled;
public AnimatorPlaybackController playbackController;
- public @AnimationComponents int animComponents = ANIM_ALL;
+ private @AnimationFlags int mAnimFlags = ANIM_ALL_COMPONENTS;
private PropertySetter mPropertySetter;
private AnimatorSet mCurrentAnimation;
@@ -598,7 +599,7 @@
public void reset() {
duration = 0;
userControlled = false;
- animComponents = ANIM_ALL;
+ mAnimFlags = ANIM_ALL_COMPONENTS;
mPropertySetter = null;
mTargetState = null;
@@ -639,16 +640,39 @@
mCurrentAnimation.addListener(this);
}
+ /**
+ * @return Whether Overview is scaling as part of this animation. If this is the only
+ * component (i.e. NON_ATOMIC_COMPONENT isn't included), then this scaling is happening
+ * atomically, rather than being part of a normal state animation. StateHandlers can use
+ * this to designate part of their animation that should scale with Overview.
+ */
public boolean playAtomicOverviewScaleComponent() {
- return (animComponents & ATOMIC_OVERVIEW_SCALE_COMPONENT) != 0;
+ return hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_SCALE);
}
- public boolean playAtomicOverviewPeekComponent() {
- return (animComponents & ATOMIC_OVERVIEW_PEEK_COMPONENT) != 0;
+ /**
+ * @return Whether this animation will play atomically at the same time as a different,
+ * user-controlled state transition. StateHandlers, which contribute to both animations, can
+ * use this to avoid animating the same properties in both animations, since they'd conflict
+ * with one another.
+ */
+ public boolean onlyPlayAtomicComponent() {
+ return getAnimComponents() == PLAY_ATOMIC_OVERVIEW_SCALE
+ || getAnimComponents() == PLAY_ATOMIC_OVERVIEW_PEEK;
}
- public boolean playNonAtomicComponent() {
- return (animComponents & NON_ATOMIC_COMPONENT) != 0;
+ /**
+ * Returns true if the config and any of the provided component flags
+ */
+ public boolean hasAnimationFlag(@AnimationFlags int a) {
+ return (mAnimFlags & a) != 0;
+ }
+
+ /**
+ * @return Only the flags that determine which animation components to play.
+ */
+ public @AnimationFlags int getAnimComponents() {
+ return mAnimFlags & ANIM_ALL_COMPONENTS;
}
}
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 95ee687..5f6ecb5 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,6 +19,7 @@
import android.content.Context;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.BitmapCreationCheck;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -39,5 +40,9 @@
FeatureFlags.initialize(context);
SessionCommitReceiver.applyDefaultUserPrefs(context);
IconShape.init(context);
+
+ if (BitmapCreationCheck.ENABLED) {
+ BitmapCreationCheck.startTracking(context);
+ }
}
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index ff2b400..e38631d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,11 +16,6 @@
package com.android.launcher3;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
-
import android.animation.LayoutTransition;
import android.animation.TimeInterpolator;
import android.annotation.SuppressLint;
@@ -35,6 +30,7 @@
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
@@ -46,11 +42,23 @@
import android.view.animation.Interpolator;
import android.widget.ScrollView;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
+import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
+import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
+import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
+
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.PagedViewOrientedState;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
import com.android.launcher3.util.OverScroller;
import com.android.launcher3.util.Thunk;
@@ -64,7 +72,7 @@
private static final String TAG = "PagedView";
private static final boolean DEBUG = false;
- protected static final int INVALID_PAGE = -1;
+ public static final int INVALID_PAGE = -1;
protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
@@ -84,8 +92,6 @@
private static final int MIN_SNAP_VELOCITY = 1500;
private static final int MIN_FLING_VELOCITY = 250;
- public static final int INVALID_RESTORE_PAGE = -1001;
-
private boolean mFreeScroll = false;
protected int mFlingThresholdVelocity;
@@ -99,8 +105,8 @@
@ViewDebug.ExportedProperty(category = "launcher")
protected int mNextPage = INVALID_PAGE;
- protected int mMinScrollX;
- protected int mMaxScrollX;
+ protected int mMaxScroll;
+ protected int mMinScroll;
protected OverScroller mScroller;
private Interpolator mDefaultInterpolator;
private VelocityTracker mVelocityTracker;
@@ -108,9 +114,12 @@
private float mDownMotionX;
private float mDownMotionY;
- private float mLastMotionX;
- private float mLastMotionXRemainder;
- private float mTotalMotionX;
+ private float mDownMotionPrimary;
+ private float mLastMotion;
+ private float mLastMotionRemainder;
+ private float mTotalMotion;
+ protected PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
+ protected final PagedViewOrientedState mOrientationState = new PagedViewOrientedState();
protected int[] mPageScrolls;
private boolean mIsBeingDragged;
@@ -125,11 +134,14 @@
protected boolean mIsPageInTransition = false;
- protected float mSpringOverScrollX;
+ protected float mSpringOverScroll;
protected boolean mWasInOverscroll = false;
- protected int mUnboundedScrollX;
+ protected int mUnboundedScroll;
+
+ protected int mLayoutRotation = Surface.ROTATION_0;
+ protected int mDisplayRotation = Surface.ROTATION_0;
// Page Indicator
@Thunk int mPageIndicatorViewId;
@@ -168,11 +180,12 @@
* Initializes various states for this workspace.
*/
protected void init() {
- mScroller = new OverScroller(getContext());
+ Context context = getContext();
+ mScroller = new OverScroller(context);
setDefaultInterpolator(Interpolators.SCROLL);
mCurrentPage = 0;
- final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledPagingTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
@@ -238,15 +251,22 @@
*/
protected void updateCurrentPageScroll() {
// If the current page is invalid, just reset the scroll position to zero
- int newX = 0;
+ int newPosition = 0;
if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
- newX = getScrollForPage(mCurrentPage);
+ newPosition = getScrollForPage(mCurrentPage);
}
- scrollTo(newX, 0);
- mScroller.startScroll(mScroller.getCurrPos(), newX - mScroller.getCurrPos());
+ mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition);
+ mOrientationHandler.scrollerStartScroll(mScroller, newPosition);
forceFinishScroller(true);
}
+ /**
+ * Returns left offset of a page. This is the gap between pages and prevents overlap.
+ */
+ public int scrollOffsetLeft() {
+ return mInsets.left + getPaddingLeft();
+ }
+
private void abortScrollerAnimation(boolean resetNextPage) {
mScroller.abortAnimation();
// We need to clean up the next page here to avoid computeScrollHelper from
@@ -280,7 +300,7 @@
int dir = !mIsRtl ? 1 : - 1;
int currScroll = getScrollForPage(page);
int prevScroll;
- while (currScroll < mMinScrollX) {
+ while (currScroll < mMinScroll) {
page += dir;
prevScroll = currScroll;
currScroll = getScrollForPage(page);
@@ -289,7 +309,7 @@
break;
}
}
- while (currScroll > mMaxScrollX) {
+ while (currScroll > mMaxScroll) {
page -= dir;
prevScroll = currScroll;
currScroll = getScrollForPage(page);
@@ -369,47 +389,77 @@
protected void onPageEndTransition() {
mWasInOverscroll = false;
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+ AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
+ AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
}
- protected int getUnboundedScrollX() {
- return mUnboundedScrollX;
+ protected int getUnboundedScroll() {
+ return mUnboundedScroll;
+ }
+
+ protected void updateLayoutRotation(int touchRotation) {
+ setLayoutRotation(touchRotation, mDisplayRotation);
+ }
+
+ /** @param touchRotation Must be one of {@link android.view.Surface.ROTATION_0/90/180/270} */
+ public void setLayoutRotation(int touchRotation, int displayRotation) {
+ if (mLayoutRotation == touchRotation && mDisplayRotation == displayRotation) {
+ return;
+ }
+
+ mOrientationState.update(touchRotation, displayRotation);
+ mOrientationHandler = mOrientationState.getOrientationHandler();
+ mLayoutRotation = touchRotation;
+ mDisplayRotation = displayRotation;
+ requestLayout();
+ }
+
+ public PagedViewOrientedState getPagedViewOrientedState() {
+ return mOrientationState;
+ }
+
+ public PagedOrientationHandler getPagedOrientationHandler() {
+ return getPagedViewOrientedState().getOrientationHandler();
+ }
+
+ public void disableMultipleLayoutRotations(boolean disable) {
+ mOrientationState.disableMultipleOrientations(disable);
+ mOrientationHandler = mOrientationState.getOrientationHandler();
+ requestLayout();
}
@Override
public void scrollBy(int x, int y) {
- scrollTo(getUnboundedScrollX() + x, getScrollY() + y);
+ mOrientationHandler.delegateScrollBy(this, getUnboundedScroll(), x, y);
}
@Override
public void scrollTo(int x, int y) {
- mUnboundedScrollX = x;
+ int primaryScroll = mOrientationHandler.getPrimaryValue(x, y);
+ int secondaryScroll = mOrientationHandler.getSecondaryValue(x, y);
+ mUnboundedScroll = primaryScroll;
- boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < mMinScrollX);
- boolean isXAfterLastPage = mIsRtl ? (x < mMinScrollX) : (x > mMaxScrollX);
-
- if (!isXBeforeFirstPage && !isXAfterLastPage) {
- mSpringOverScrollX = 0;
+ boolean isBeforeFirstPage = mIsRtl ?
+ (primaryScroll > mMaxScroll) : (primaryScroll < mMinScroll);
+ boolean isAfterLastPage = mIsRtl ?
+ (primaryScroll < mMinScroll) : (primaryScroll > mMaxScroll);
+ if (!isBeforeFirstPage && !isAfterLastPage) {
+ mSpringOverScroll = 0;
}
- if (isXBeforeFirstPage) {
- super.scrollTo(mIsRtl ? mMaxScrollX : mMinScrollX, y);
+ if (isBeforeFirstPage) {
+ mOrientationHandler.delegateScrollTo(this,
+ secondaryScroll, mIsRtl ? mMaxScroll : mMinScroll);
if (mAllowOverScroll) {
mWasInOverscroll = true;
- if (mIsRtl) {
- overScroll(x - mMaxScrollX);
- } else {
- overScroll(x - mMinScrollX);
- }
+ overScroll(primaryScroll - (mIsRtl ? mMaxScroll : mMinScroll));
}
- } else if (isXAfterLastPage) {
- super.scrollTo(mIsRtl ? mMinScrollX : mMaxScrollX, y);
+ } else if (isAfterLastPage) {
+ mOrientationHandler.delegateScrollTo(this,
+ secondaryScroll, mIsRtl ? mMinScroll : mMaxScroll);
if (mAllowOverScroll) {
mWasInOverscroll = true;
- if (mIsRtl) {
- overScroll(x - mMinScrollX);
- } else {
- overScroll(x - mMaxScrollX);
- }
+ overScroll(primaryScroll - (mIsRtl ? mMinScroll : mMaxScroll));
}
} else {
if (mWasInOverscroll) {
@@ -418,7 +468,13 @@
}
super.scrollTo(x, y);
}
+ }
+ /**
+ * Helper for {@link PagedOrientationHandler} to be able to call parent's scrollTo method
+ */
+ public void superScrollTo(int x, int y) {
+ super.scrollTo(x, y);
}
private void sendScrollAccessibilityEvent() {
@@ -429,9 +485,7 @@
ev.setScrollable(true);
ev.setScrollX(getScrollX());
ev.setScrollY(getScrollY());
- ev.setMaxScrollX(mMaxScrollX);
- ev.setMaxScrollY(0);
-
+ mOrientationHandler.setMaxScroll(ev, mMaxScroll);
sendAccessibilityEventUnchecked(ev);
}
}
@@ -452,9 +506,10 @@
protected boolean computeScrollHelper(boolean shouldInvalidate) {
if (mScroller.computeScrollOffset()) {
// Don't bother scrolling if the page does not need to be moved
- if (getUnboundedScrollX() != mScroller.getCurrPos()
- || getScrollX() != mScroller.getCurrPos()) {
- scrollTo(mScroller.getCurrPos(), 0);
+ int currentScroll = mOrientationHandler.getPrimaryScroll(this);
+ if (mUnboundedScroll != mScroller.getCurrPos()
+ || currentScroll != mScroller.getCurrPos()) {
+ mOrientationHandler.set(this, VIEW_SCROLL_TO, mScroller.getCurrPos());
}
if (shouldInvalidate) {
invalidate();
@@ -573,7 +628,8 @@
if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
- if (getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC)) {
+ boolean isScrollChanged = getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC);
+ if (isScrollChanged) {
pageScrollChanged = true;
}
@@ -614,7 +670,6 @@
/**
* Initializes {@code outPageScrolls} with scroll positions for view at that index. The length
* of {@code outPageScrolls} should be same as the the childCount
- *
*/
protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
ComputePageScrollsLogic scrollLogic) {
@@ -624,36 +679,30 @@
final int endIndex = mIsRtl ? -1 : childCount;
final int delta = mIsRtl ? -1 : 1;
- final int verticalCenter = (getPaddingTop() + getMeasuredHeight() + mInsets.top
- - mInsets.bottom - getPaddingBottom()) / 2;
+ final int pageCenter = mOrientationHandler.getCenterForPage(this, mInsets);
- final int scrollOffsetLeft = mInsets.left + getPaddingLeft();
- final int scrollOffsetRight = getWidth() - getPaddingRight() - mInsets.right;
+ final int scrollOffsetStart = mOrientationHandler.getScrollOffsetStart(this, mInsets);
+ final int scrollOffsetEnd = mOrientationHandler.getScrollOffsetEnd(this, mInsets);
boolean pageScrollChanged = false;
- for (int i = startIndex, childLeft = scrollOffsetLeft; i != endIndex; i += delta) {
+ for (int i = startIndex, childStart = scrollOffsetStart; i != endIndex; i += delta) {
final View child = getPageAt(i);
if (scrollLogic.shouldIncludeView(child)) {
- final int childWidth = child.getMeasuredWidth();
- final int childRight = childLeft + childWidth;
-
- if (layoutChildren) {
- final int childHeight = child.getMeasuredHeight();
- final int childTop = verticalCenter - childHeight / 2;
- child.layout(childLeft, childTop, childRight, childTop + childHeight);
- }
+ ChildBounds bounds = mOrientationHandler.getChildBounds(child, childStart,
+ pageCenter, layoutChildren);
+ final int primaryDimension = bounds.primaryDimension;
+ final int childPrimaryEnd = bounds.childPrimaryEnd;
// 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);
+ ? (childStart - scrollOffsetStart)
+ : Math.max(0, childPrimaryEnd - scrollOffsetEnd);
if (outPageScrolls[i] != pageScroll) {
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
}
-
- childLeft += childWidth + mPageSpacing + getChildGap();
+ childStart += primaryDimension + mPageSpacing + getChildGap();
}
}
return pageScrollChanged;
@@ -664,15 +713,15 @@
}
protected void updateMinAndMaxScrollX() {
- mMinScrollX = computeMinScrollX();
- mMaxScrollX = computeMaxScrollX();
+ mMinScroll = computeMinScroll();
+ mMaxScroll = computeMaxScroll();
}
- protected int computeMinScrollX() {
+ protected int computeMinScroll() {
return 0;
}
- protected int computeMaxScrollX() {
+ protected int computeMaxScroll() {
int childCount = getChildCount();
if (childCount > 0) {
final int index = mIsRtl ? 0 : childCount - 1;
@@ -715,7 +764,8 @@
protected int getChildOffset(int index) {
if (index < 0 || index > getChildCount() - 1) return 0;
- return getPageAt(index).getLeft();
+ View pageAtIndex = getPageAt(index);
+ return mOrientationHandler.getChildStart(pageAtIndex);
}
@Override
@@ -866,13 +916,13 @@
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
- * whether the user has moved far enough from his original down touch.
+ * whether the user has moved far enough from their original down touch.
*/
if (mActivePointerId != INVALID_POINTER) {
determineScrollingStart(ev);
}
// if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
- // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
+ // event. in that case, treat the first occurrence of a move event as a ACTION_DOWN
// i.e. fall through to the next case (don't break)
// (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
// while it's small- this was causing a crash before we checked for INVALID_POINTER)
@@ -885,9 +935,9 @@
// Remember location of down touch
mDownMotionX = x;
mDownMotionY = y;
- mLastMotionX = x;
- mLastMotionXRemainder = 0;
- mTotalMotionX = 0;
+ mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
+ mLastMotionRemainder = 0;
+ mTotalMotion = 0;
mActivePointerId = ev.getPointerId(0);
updateIsBeingDraggedOnTouchDown();
@@ -949,17 +999,17 @@
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) return;
- final float x = ev.getX(pointerIndex);
- final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev, pointerIndex);
+ final int diff = (int) Math.abs(primaryDirection - mLastMotion);
final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
- boolean xMoved = xDiff > touchSlop;
+ boolean moved = diff > touchSlop;
- if (xMoved) {
+ if (moved) {
// Scroll if the user moved far enough along the X axis
mIsBeingDragged = true;
- mTotalMotionX += Math.abs(mLastMotionX - x);
- mLastMotionX = x;
- mLastMotionXRemainder = 0;
+ mTotalMotion += Math.abs(mLastMotion - primaryDirection);
+ mLastMotion = primaryDirection;
+ mLastMotionRemainder = 0;
onScrollInteractionBegin();
pageBeginTransition();
// Stop listening for things like pinches.
@@ -1026,10 +1076,9 @@
@Override
protected void dispatchDraw(Canvas canvas) {
- if (mScroller.isSpringing() && mSpringOverScrollX != 0) {
+ if (mScroller.isSpringing() && mSpringOverScroll != 0) {
int saveCount = canvas.save();
-
- canvas.translate(-mSpringOverScrollX, 0);
+ mOrientationHandler.set(canvas, CANVAS_TRANSLATE, -mSpringOverScroll);
super.dispatchDraw(canvas);
canvas.restoreToCount(saveCount);
@@ -1039,25 +1088,27 @@
}
protected void dampedOverScroll(int amount) {
- mSpringOverScrollX = amount;
+ mSpringOverScroll = amount;
if (amount == 0) {
return;
}
- int overScrollAmount = OverScroll.dampedScroll(amount, getMeasuredWidth());
- mSpringOverScrollX = overScrollAmount;
+ int size = mOrientationHandler.getMeasuredSize(this);
+ int overScrollAmount = OverScroll.dampedScroll(amount, size);
+ mSpringOverScroll = overScrollAmount;
if (mScroller.isSpringing()) {
invalidate();
return;
}
- int x = Utilities.boundToRange(getScrollX(), mMinScrollX, mMaxScrollX);
- super.scrollTo(x + overScrollAmount, getScrollY());
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int boundedScroll = Utilities.boundToRange(primaryScroll, mMinScroll, mMaxScroll);
+ mOrientationHandler.delegateScrollTo(this, boundedScroll + overScrollAmount);
invalidate();
}
protected void overScroll(int amount) {
- mSpringOverScrollX = amount;
+ mSpringOverScroll = amount;
if (mScroller.isSpringing()) {
invalidate();
return;
@@ -1066,11 +1117,8 @@
if (amount == 0) return;
if (mFreeScroll && !mScroller.isFinished()) {
- if (amount < 0) {
- super.scrollTo(mMinScrollX + amount, getScrollY());
- } else {
- super.scrollTo(mMaxScrollX + amount, getScrollY());
- }
+ int scrollAmount = amount < 0 ? mMinScroll + amount : mMaxScroll + amount;
+ mOrientationHandler.delegateScrollTo(this, scrollAmount);
} else {
dampedOverScroll(amount);
}
@@ -1120,37 +1168,37 @@
}
// Remember where the motion event started
- mDownMotionX = mLastMotionX = ev.getX();
+ mDownMotionX = ev.getX();
mDownMotionY = ev.getY();
- mLastMotionXRemainder = 0;
- mTotalMotionX = 0;
+ mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
+ mLastMotionRemainder = 0;
+ mTotalMotion = 0;
mActivePointerId = ev.getPointerId(0);
-
if (mIsBeingDragged) {
onScrollInteractionBegin();
pageBeginTransition();
}
break;
- case MotionEvent.ACTION_MOVE:
- if (mIsBeingDragged) {
+ case MotionEvent.ACTION_MOVE:
+ if (mIsBeingDragged) {
// Scroll to follow the motion event
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) return true;
- final float x = ev.getX(pointerIndex);
- final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
-
- mTotalMotionX += Math.abs(deltaX);
+ float direction = mOrientationHandler.getPrimaryDirection(ev, pointerIndex);
+ float delta = mLastMotion + mLastMotionRemainder - direction;
+ mTotalMotion += Math.abs(delta);
// Only scroll and update mLastMotionX if we have moved some discrete amount. We
// keep the remainder because we are actually testing if we've moved from the last
// scrolled position (which is discrete).
- if (Math.abs(deltaX) >= 1.0f) {
- scrollBy((int) deltaX, 0);
- mLastMotionX = x;
- mLastMotionXRemainder = deltaX - (int) deltaX;
+ if (Math.abs(delta) >= 1.0f) {
+ mLastMotion = direction;
+ mLastMotionRemainder = delta - (int) delta;
+
+ mOrientationHandler.set(this, VIEW_SCROLL_BY, (int) delta);
} else {
awakenScrollBars();
}
@@ -1163,27 +1211,31 @@
if (mIsBeingDragged) {
final int activePointerId = mActivePointerId;
final int pointerIndex = ev.findPointerIndex(activePointerId);
- final float x = ev.getX(pointerIndex);
+ final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev,
+ pointerIndex);
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
- final int deltaX = (int) (x - mDownMotionX);
- final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
- boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
- SIGNIFICANT_MOVE_THRESHOLD;
- mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
- boolean isFling = mTotalMotionX > mTouchSlop && shouldFlingForVelocity(velocityX);
- boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
- boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
+ int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
+ mActivePointerId);
+ int delta = (int) (primaryDirection - mDownMotionPrimary);
+ int pageOrientedSize = mOrientationHandler.getMeasuredSize(getPageAt(mCurrentPage));
+
+ boolean isSignificantMove = Math.abs(delta) > pageOrientedSize *
+ SIGNIFICANT_MOVE_THRESHOLD;
+
+ mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
+ boolean isFling = mTotalMotion > mTouchSlop && shouldFlingForVelocity(velocity);
+ boolean isDeltaLeft = mIsRtl ? delta > 0 : delta < 0;
+ boolean isVelocityLeft = mIsRtl ? velocity > 0 : velocity < 0;
if (!mFreeScroll) {
// In the case that the page is moved far to one direction and then is flung
// in the opposite direction, we use a threshold to determine whether we should
// just return to the starting page, or if we should skip one further.
boolean returnToOriginalPage = false;
- if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
- Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
+ if (Math.abs(delta) > pageOrientedSize * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
+ Math.signum(velocity) != Math.signum(delta) && isFling) {
returnToOriginalPage = true;
}
@@ -1192,15 +1244,15 @@
// test for a large move if a fling has been registered. That is, a large
// move to the left and fling to the right will register as a fling to the right.
- if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
- (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
+ if (((isSignificantMove && !isDeltaLeft && !isFling) ||
+ (isFling && !isVelocityLeft)) && mCurrentPage > 0) {
finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
- snapToPageWithVelocity(finalPage, velocityX);
- } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
- (isFling && isVelocityXLeft)) &&
+ snapToPageWithVelocity(finalPage, velocity);
+ } else if (((isSignificantMove && isDeltaLeft && !isFling) ||
+ (isFling && isVelocityLeft)) &&
mCurrentPage < getChildCount() - 1) {
finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
- snapToPageWithVelocity(finalPage, velocityX);
+ snapToPageWithVelocity(finalPage, velocity);
} else {
snapToDestination();
}
@@ -1209,38 +1261,40 @@
abortScrollerAnimation(true);
}
- int initialScrollX = getScrollX();
+ int initialScroll = mOrientationHandler.getPrimaryScroll(this);
+ int maxScroll = mMaxScroll;
+ int minScroll = mMinScroll;
- if (((initialScrollX >= mMaxScrollX) && (isVelocityXLeft || !isFling)) ||
- ((initialScrollX <= mMinScrollX) && (!isVelocityXLeft || !isFling))) {
- mScroller.springBack(getScrollX(), mMinScrollX, mMaxScrollX);
+ if (((initialScroll >= maxScroll) && (isVelocityLeft || !isFling)) ||
+ ((initialScroll <= minScroll) && (!isVelocityLeft || !isFling))) {
+ mScroller.springBack(initialScroll, minScroll, maxScroll);
mNextPage = getPageNearestToCenterOfScreen();
} else {
mScroller.setInterpolator(mDefaultInterpolator);
- mScroller.fling(initialScrollX, -velocityX,
- mMinScrollX, mMaxScrollX,
- Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR));
+ mScroller.fling(initialScroll, -velocity,
+ minScroll, maxScroll,
+ Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR));
- int finalX = mScroller.getFinalPos();
- mNextPage = getPageNearestToCenterOfScreen(finalX);
+ int finalPos = mScroller.getFinalPos();
+ mNextPage = getPageNearestToCenterOfScreen(finalPos);
int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
- if (finalX > mMinScrollX && finalX < mMaxScrollX) {
+ if (finalPos > minScroll && finalPos < maxScroll) {
// If scrolling ends in the half of the added space that is closer to
// the end, settle to the end. Otherwise snap to the nearest page.
// If flinging past one of the ends, don't change the velocity as it
// will get stopped at the end anyway.
- int pageSnappedX = finalX < (firstPageScroll + mMinScrollX) / 2
- ? mMinScrollX
- : finalX > (lastPageScroll + mMaxScrollX) / 2
- ? mMaxScrollX
- : getScrollForPage(mNextPage);
+ int pageSnapped = finalPos < (firstPageScroll + minScroll) / 2
+ ? minScroll
+ : finalPos > (lastPageScroll + maxScroll) / 2
+ ? maxScroll
+ : getScrollForPage(mNextPage);
- mScroller.setFinalPos(pageSnappedX);
+ mScroller.setFinalPos(pageSnapped);
// Ensure the scroll/snap doesn't happen too fast;
int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
- - mScroller.getDuration();
+ - mScroller.getDuration();
if (extraScrollDuration > 0) {
mScroller.extendDuration(extraScrollDuration);
}
@@ -1272,8 +1326,8 @@
return true;
}
- protected boolean shouldFlingForVelocity(int velocityX) {
- return Math.abs(velocityX) > mFlingThresholdVelocity;
+ protected boolean shouldFlingForVelocity(int velocity) {
+ return Math.abs(velocity) > mFlingThresholdVelocity;
}
private void resetTouchState() {
@@ -1357,8 +1411,9 @@
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
- mLastMotionXRemainder = 0;
+ mLastMotion = mDownMotionPrimary = mOrientationHandler.getPrimaryDirection(ev,
+ newPointerIndex);
+ mLastMotionRemainder = 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
@@ -1376,19 +1431,20 @@
}
public int getPageNearestToCenterOfScreen() {
- return getPageNearestToCenterOfScreen(getScrollX());
+ return getPageNearestToCenterOfScreen(mOrientationHandler.getPrimaryScroll(this));
}
- private int getPageNearestToCenterOfScreen(int scaledScrollX) {
- int screenCenter = scaledScrollX + (getMeasuredWidth() / 2);
+ private int getPageNearestToCenterOfScreen(int scaledScroll) {
+ int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
+ int screenCenter = scaledScroll + (pageOrientationSize / 2);
int minDistanceFromScreenCenter = Integer.MAX_VALUE;
int minDistanceFromScreenCenterIndex = -1;
final int childCount = getChildCount();
for (int i = 0; i < childCount; ++i) {
View layout = getPageAt(i);
- int childWidth = layout.getMeasuredWidth();
- int halfChildWidth = (childWidth / 2);
- int childCenter = getChildOffset(i) + halfChildWidth;
+ int childSize = mOrientationHandler.getMeasuredSize(layout);
+ int halfChildSize = (childSize / 2);
+ int childCenter = getChildOffset(i) + halfChildSize;
int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
minDistanceFromScreenCenter = distanceFromScreenCenter;
@@ -1403,7 +1459,8 @@
}
protected boolean isInOverScroll() {
- return (getScrollX() > mMaxScrollX || getScrollX() < mMinScrollX);
+ int scroll = mOrientationHandler.getPrimaryScroll(this);
+ return scroll > mMaxScroll || scroll < mMinScroll;
}
protected int getPageSnapDuration() {
@@ -1425,10 +1482,10 @@
protected boolean snapToPageWithVelocity(int whichPage, int velocity) {
whichPage = validateNewPage(whichPage);
- int halfScreenSize = getMeasuredWidth() / 2;
+ int halfScreenSize = mOrientationHandler.getMeasuredSize(this) / 2;
- final int newX = getScrollForPage(whichPage);
- int delta = newX - getUnboundedScrollX();
+ final int newLoc = getScrollForPage(whichPage);
+ int delta = newLoc - getUnboundedScroll();
int duration = 0;
if (Math.abs(velocity) < mMinFlingVelocity) {
@@ -1453,9 +1510,9 @@
// interpolator at zero, ie. 5. We use 4 to make it a little slower.
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
- if (QUICKSTEP_SPRINGS.get()) {
+ if (QUICKSTEP_SPRINGS.get() && mCurrentPage != whichPage) {
return snapToPage(whichPage, delta, duration, false, null,
- velocity * Math.signum(newX - getUnboundedScrollX()), true);
+ velocity * Math.signum(delta), true);
} else {
return snapToPage(whichPage, delta, duration);
}
@@ -1481,8 +1538,8 @@
TimeInterpolator interpolator) {
whichPage = validateNewPage(whichPage);
- int newX = getScrollForPage(whichPage);
- final int delta = newX - getUnboundedScrollX();
+ int newLoc = getScrollForPage(whichPage);
+ final int delta = newLoc - getUnboundedScroll();
return snapToPage(whichPage, delta, duration, immediate, interpolator, 0, false);
}
@@ -1497,7 +1554,7 @@
return false;
}
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
duration *= Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.WINDOW_ANIMATION_SCALE, 1);
}
@@ -1528,9 +1585,9 @@
}
if (spring && QUICKSTEP_SPRINGS.get()) {
- mScroller.startScrollSpring(getUnboundedScrollX(), delta, duration, velocity);
+ mScroller.startScrollSpring(getUnboundedScroll(), delta, duration, velocity);
} else {
- mScroller.startScroll(getUnboundedScrollX(), delta, duration);
+ mScroller.startScroll(getUnboundedScroll(), delta, duration);
}
updatePageIndicator();
diff --git a/src/com/android/launcher3/Partner.java b/src/com/android/launcher3/Partner.java
index af5402a..d79f62d 100644
--- a/src/com/android/launcher3/Partner.java
+++ b/src/com/android/launcher3/Partner.java
@@ -53,21 +53,12 @@
public static final String RES_GRID_NUM_COLUMNS = "grid_num_columns";
public static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp";
- private static boolean sSearched = false;
- private static Partner sPartner;
-
/**
* Find and return partner details, or {@code null} if none exists.
*/
public static synchronized Partner get(PackageManager pm) {
- if (!sSearched) {
- Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
- if (apkInfo != null) {
- sPartner = new Partner(apkInfo.first, apkInfo.second);
- }
- sSearched = true;
- }
- return sPartner;
+ Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
+ return apkInfo != null ? new Partner(apkInfo.first, apkInfo.second) : null;
}
private final String mPackageName;
diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/PromiseAppInfo.java
index 4ad0b3d..e55e4bd 100644
--- a/src/com/android/launcher3/PromiseAppInfo.java
+++ b/src/com/android/launcher3/PromiseAppInfo.java
@@ -19,16 +19,16 @@
import android.content.Context;
import android.content.Intent;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.util.PackageManagerHelper;
-
import androidx.annotation.NonNull;
+import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.util.PackageManagerHelper;
+
public class PromiseAppInfo extends AppInfo {
public int level = 0;
- public PromiseAppInfo(@NonNull PackageInstallerCompat.PackageInstallInfo installInfo) {
+ public PromiseAppInfo(@NonNull PackageInstallInfo installInfo) {
componentName = installInfo.componentName;
intent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 73e705b..403d779 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -21,15 +21,15 @@
import android.util.TypedValue;
public class ResourceUtils {
+ public static final int DEFAULT_NAVBAR_VALUE = 48;
public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
-
public static int getNavbarSize(String resName, Resources res) {
- return getDimenByName(resName, res, 48);
+ return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE);
}
- private static int getDimenByName(String resName, Resources res, int defaultValue) {
+ public static int getDimenByName(String resName, Resources res, int defaultValue) {
final int frameSize;
final int frameSizeResID = res.getIdentifier(resName, "dimen", "android");
if (frameSizeResID != 0) {
@@ -40,6 +40,17 @@
return frameSize;
}
+ public static boolean getBoolByName(String resName, Resources res, boolean defaultValue) {
+ final boolean val;
+ final int resId = res.getIdentifier(resName, "bool", "android");
+ if (resId != 0) {
+ val = res.getBoolean(resId);
+ } else {
+ val = defaultValue;
+ }
+ return val;
+ }
+
public static int pxFromDp(float size, DisplayMetrics metrics) {
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index c8c590d..2430d5e 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -6,6 +6,7 @@
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
@@ -16,6 +17,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
@@ -28,20 +30,24 @@
import android.widget.Toast;
import com.android.launcher3.Launcher.OnResumeCallback;
-import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Themes;
import java.net.URISyntaxException;
+import java.util.ArrayList;
/**
* Drop target which provides a secondary option for an item.
* For app targets: shows as uninstall
* For configurable widgets: shows as setup
+ * For predicted app icons: don't suggest app
*/
public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {
@@ -51,6 +57,7 @@
private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
private final Alarm mCacheExpireAlarm;
+ private boolean mHadPendingAlarm;
protected int mCurrentAccessibilityAction = -1;
public SecondaryDropTarget(Context context, AttributeSet attrs) {
@@ -61,7 +68,26 @@
super(context, attrs, defStyle);
mCacheExpireAlarm = new Alarm();
- mCacheExpireAlarm.setOnAlarmListener(this);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mHadPendingAlarm) {
+ mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+ mCacheExpireAlarm.setOnAlarmListener(this);
+ mHadPendingAlarm = false;
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mCacheExpireAlarm.alarmPending()) {
+ mCacheExpireAlarm.cancelAlarm();
+ mCacheExpireAlarm.setOnAlarmListener(null);
+ mHadPendingAlarm = true;
+ }
}
@Override
@@ -80,7 +106,11 @@
mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
setDrawable(R.drawable.ic_uninstall_shadow);
updateText(R.string.uninstall_drop_target_label);
- } else {
+ } else if (action == DISMISS_PREDICTION) {
+ mHoverColor = Themes.getColorAccent(getContext());
+ setDrawable(R.drawable.ic_block);
+ updateText(R.string.dismiss_prediction_label);
+ } else if (action == RECONFIGURE) {
mHoverColor = Themes.getColorAccent(getContext());
setDrawable(R.drawable.ic_setup_shadow);
updateText(R.string.gadget_setup_text);
@@ -100,8 +130,13 @@
@Override
public Target getDropTargetForLogging() {
Target t = LoggerUtils.newTarget(Target.Type.CONTROL);
- t.controlType = mCurrentAccessibilityAction == UNINSTALL ? ControlType.UNINSTALL_TARGET
- : ControlType.SETTINGS_BUTTON;
+ if (mCurrentAccessibilityAction == UNINSTALL) {
+ t.controlType = ControlType.UNINSTALL_TARGET;
+ } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+ t.controlType = ControlType.DISMISS_PREDICTION;
+ } else {
+ t.controlType = ControlType.SETTINGS_BUTTON;
+ }
return t;
}
@@ -118,6 +153,9 @@
return true;
}
return false;
+ } else if (FeatureFlags.ENABLE_PREDICTION_DISMISS.get() && info.isPredictedItem()) {
+ setupUi(DISMISS_PREDICTION);
+ return true;
}
setupUi(UNINSTALL);
@@ -132,6 +170,7 @@
}
// Cancel any pending alarm and set cache expiry after some time
mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+ mCacheExpireAlarm.setOnAlarmListener(this);
if (uninstallDisabled) {
return false;
}
@@ -157,7 +196,7 @@
user = item.user;
}
if (intent != null) {
- LauncherActivityInfo info = LauncherAppsCompat.getInstance(mLauncher)
+ LauncherActivityInfo info = mLauncher.getSystemService(LauncherApps.class)
.resolveActivity(intent, user);
if (info != null
&& (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -228,6 +267,11 @@
}
return null;
}
+ if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+ AppLaunchTracker.INSTANCE.get(getContext()).onDismissApp(info.getTargetComponent(),
+ info.user, AppLaunchTracker.CONTAINER_PREDICTIONS);
+ return null;
+ }
// else: mCurrentAccessibilityAction == UNINSTALL
ComponentName cn = getUninstallTarget(info);
@@ -279,17 +323,16 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target,
- Target targetParent) {
- mOriginal.fillInLogContainerData(v, info, target, targetParent);
+ public void fillInLogContainerData(ItemInfo childInfo, Target child,
+ ArrayList<Target> parents) {
+ mOriginal.fillInLogContainerData(childInfo, child, parents);
}
@Override
public void onLauncherResume() {
// We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
- if (LauncherAppsCompat.getInstance(mContext)
- .getApplicationInfo(mPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
- mDragObject.dragInfo.user) == null) {
+ if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
+ mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
mDragObject.dragSource = mOriginal;
mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
} else {
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index a87c446..89f0a3d 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
+
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -23,6 +25,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -37,14 +40,11 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.compat.PackageInstallerCompat;
import java.util.List;
-import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle;
-
/**
* BroadcastReceiver to handle session commit intent.
*/
@@ -77,7 +77,8 @@
return;
}
- PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
+ InstallSessionHelper packageInstallerCompat = InstallSessionHelper.INSTANCE.get(context);
+ packageInstallerCompat.restoreDbIfApplicable(info);
if (TextUtils.isEmpty(info.getAppPackageName())
|| info.getInstallReason() != PackageManager.INSTALL_REASON_USER
|| packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
@@ -90,9 +91,8 @@
public static void queuePromiseAppIconAddition(Context context, SessionInfo sessionInfo) {
String packageName = sessionInfo.getAppPackageName();
- List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
- .getActivityList(packageName, getUserHandle(sessionInfo));
- if (activities == null || activities.isEmpty()) {
+ if (context.getSystemService(LauncherApps.class)
+ .getActivityList(packageName, getUserHandle(sessionInfo)).isEmpty()) {
// Ensure application isn't already installed.
queueAppIconAddition(context, packageName, sessionInfo.getAppLabel(),
sessionInfo.getAppIcon(), getUserHandle(sessionInfo));
@@ -100,9 +100,9 @@
}
public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
- List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
+ List<LauncherActivityInfo> activities = context.getSystemService(LauncherApps.class)
.getActivityList(packageName, user);
- if (activities == null || activities.isEmpty()) {
+ if (activities.isEmpty()) {
// no activity found
return;
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 5d0effa..122b393 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
@@ -59,21 +60,22 @@
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.graphics.TintedDrawableSpan;
+import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.icons.ShortcutCachingLogic;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.Transposable;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
@@ -380,7 +382,7 @@
return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
- public static float dpiFromPx(int size, DisplayMetrics metrics){
+ public static float dpiFromPx(float size, DisplayMetrics metrics) {
float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
return (size / densityRatio);
}
@@ -463,15 +465,26 @@
}
public static SharedPreferences getPrefs(Context context) {
- return context.getSharedPreferences(
+ // Use application context for shared preferences, so that we use a single cached instance
+ return context.getApplicationContext().getSharedPreferences(
LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
}
public static SharedPreferences getDevicePrefs(Context context) {
- return context.getSharedPreferences(
+ // Use application context for shared preferences, so that we use a single cached instance
+ return context.getApplicationContext().getSharedPreferences(
LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
}
+ /**
+ * @return {@link SharedPreferences} that backs {@link FeatureFlags}
+ */
+ public static SharedPreferences getFeatureFlagsPrefs(Context context) {
+ // Use application context for shared preferences, so that we use a single cached instance
+ return context.getApplicationContext().getSharedPreferences(
+ FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+ }
+
public static boolean areAnimationsEnabled(Context context) {
return ATLEAST_OREO
? ValueAnimator.areAnimatorsEnabled()
@@ -518,19 +531,20 @@
}
/**
- * Returns the full drawable for {@param info}.
+ * Returns the full drawable for info without any flattening or pre-processing.
+ *
* @param outObj this is set to the internal data associated with {@param info},
* eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
*/
public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height,
- boolean flattenDrawable, Object[] outObj) {
+ Object[] outObj) {
LauncherAppState appState = LauncherAppState.getInstance(launcher);
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(launcher)
+ LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class)
.resolveActivity(info.getIntent(), info.user);
outObj[0] = activityInfo;
- return (activityInfo != null) ? appState.getIconCache()
- .getFullResIcon(activityInfo, flattenDrawable) : null;
+ return activityInfo == null ? null : new IconProvider(launcher).getIconForUI(
+ activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi);
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
if (info instanceof PendingAddShortcutInfo) {
ShortcutConfigActivityInfo activityInfo =
@@ -538,15 +552,14 @@
outObj[0] = activityInfo;
return activityInfo.getFullResIcon(appState.getIconCache());
}
- ShortcutKey key = ShortcutKey.fromItemInfo(info);
- DeepShortcutManager sm = DeepShortcutManager.getInstance(launcher);
- List<ShortcutInfo> si = sm.queryForFullDetails(
- key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user);
+ List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
+ .buildRequest(launcher)
+ .query(ShortcutRequest.ALL);
if (si.isEmpty()) {
return null;
} else {
outObj[0] = si.get(0);
- return sm.getShortcutIconDrawable(si.get(0),
+ return ShortcutCachingLogic.getIcon(launcher, si.get(0),
appState.getInvariantDeviceProfile().fillResIconDpi);
}
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
@@ -581,9 +594,8 @@
return new FixedSizeEmptyDrawable(iconSize);
}
ShortcutInfo si = (ShortcutInfo) obj;
- LauncherIcons li = LauncherIcons.obtain(appState.getContext());
- Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
- li.recycle();
+ Bitmap badge = LauncherAppState.getInstance(appState.getContext())
+ .getIconCache().getShortcutInfoBadge(si).icon;
float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize);
float insetFraction = (iconSize - badgeSize) / iconSize;
return new InsetDrawable(new FastBitmapDrawable(badge),
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index c6381b0..2c45c77 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -23,24 +23,27 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.os.Process;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShadowGenerator;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.PackageUserKey;
@@ -48,6 +51,7 @@
import com.android.launcher3.util.SQLiteCacheHelper;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -74,17 +78,65 @@
private final Context mContext;
private final IconCache mIconCache;
- private final UserManagerCompat mUserManager;
+ private final UserCache mUserCache;
private final CacheDb mDb;
+ private final UserHandle mMyUser = Process.myUserHandle();
+ private final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
+
public WidgetPreviewLoader(Context context, IconCache iconCache) {
mContext = context;
mIconCache = iconCache;
- mUserManager = UserManagerCompat.getInstance(context);
+ mUserCache = UserCache.INSTANCE.get(context);
mDb = new CacheDb(context);
}
/**
+ * Returns a drawable that can be used as a badge for the user or null.
+ */
+ @UiThread
+ public Drawable getBadgeForUser(UserHandle user, int badgeSize) {
+ if (mMyUser.equals(user)) {
+ return null;
+ }
+
+ Bitmap badgeBitmap = getUserBadge(user, badgeSize);
+ FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
+ d.setFilterBitmap(true);
+ d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
+ return d;
+ }
+
+ private Bitmap getUserBadge(UserHandle user, int badgeSize) {
+ synchronized (mUserBadges) {
+ Bitmap badgeBitmap = mUserBadges.get(user);
+ if (badgeBitmap != null) {
+ return badgeBitmap;
+ }
+
+ final Resources res = mContext.getResources();
+ badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+
+ Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity(
+ new BitmapDrawable(res, badgeBitmap), user,
+ new Rect(0, 0, badgeSize, badgeSize),
+ 0);
+ if (drawable instanceof BitmapDrawable) {
+ badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
+ } else {
+ badgeBitmap.eraseColor(Color.TRANSPARENT);
+ Canvas c = new Canvas(badgeBitmap);
+ drawable.setBounds(0, 0, badgeSize, badgeSize);
+ drawable.draw(c);
+ c.setBitmap(null);
+ }
+
+ mUserBadges.put(user, badgeBitmap);
+ return badgeBitmap;
+ }
+ }
+
+ /**
* Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be
* called on UI thread
*
@@ -105,8 +157,8 @@
public void refresh() {
mDb.clear();
-
}
+
/**
* The DB holds the generated previews for various components. Previews can also have different
* sizes (landscape vs portrait).
@@ -145,7 +197,7 @@
@Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
ContentValues values = new ContentValues();
values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
- values.put(CacheDb.COLUMN_USER, mUserManager.getSerialNumberForUser(key.user));
+ values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user));
values.put(CacheDb.COLUMN_SIZE, key.size);
values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
values.put(CacheDb.COLUMN_VERSION, versions[0]);
@@ -155,7 +207,7 @@
}
public void removePackage(String packageName, UserHandle user) {
- removePackage(packageName, user, mUserManager.getSerialNumberForUser(user));
+ removePackage(packageName, user, mUserCache.getSerialNumberForUser(user));
}
private void removePackage(String packageName, UserHandle user, long userSerial) {
@@ -184,7 +236,7 @@
LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
for (ComponentKey key : list) {
- final long userId = mUserManager.getSerialNumberForUser(key.user);
+ final long userId = mUserCache.getSerialNumberForUser(key.user);
HashSet<String> packages = validPackages.get(userId);
if (packages == null) {
packages = new HashSet<>();
@@ -195,7 +247,7 @@
LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
long passedUserId = packageUser == null ? 0
- : mUserManager.getSerialNumberForUser(packageUser.mUser);
+ : mUserCache.getSerialNumberForUser(packageUser.mUser);
Cursor c = null;
try {
c = mDb.query(
@@ -234,7 +286,7 @@
for (int i = 0; i < packagesToDelete.size(); i++) {
long userId = packagesToDelete.keyAt(i);
- UserHandle user = mUserManager.getUserForSerialNumber(userId);
+ UserHandle user = mUserCache.getUserForSerialNumber(userId);
for (String pkg : packagesToDelete.valueAt(i)) {
removePackage(pkg, user, userId);
}
@@ -260,7 +312,7 @@
+ CacheDb.COLUMN_SIZE + " = ?",
new String[]{
key.componentName.flattenToShortString(),
- Long.toString(mUserManager.getSerialNumberForUser(key.user)),
+ Long.toString(mUserCache.getSerialNumberForUser(key.user)),
key.size
});
// If cancelled, skip getting the blob and decoding it into a bitmap
@@ -289,19 +341,30 @@
return null;
}
- private Bitmap generatePreview(BaseActivity launcher, WidgetItem item, Bitmap recycle,
+ /**
+ * Returns generatedPreview for a widget and if the preview should be saved in persistent
+ * storage.
+ * @param launcher
+ * @param item
+ * @param recycle
+ * @param previewWidth
+ * @param previewHeight
+ * @return Pair<Bitmap, Boolean>
+ */
+ private Pair<Bitmap, Boolean> generatePreview(BaseActivity launcher, WidgetItem item,
+ Bitmap recycle,
int previewWidth, int previewHeight) {
if (item.widgetInfo != null) {
return generateWidgetPreview(launcher, item.widgetInfo,
previewWidth, recycle, null);
} else {
- return generateShortcutPreview(launcher, item.activityInfo,
- previewWidth, previewHeight, recycle);
+ return new Pair<>(generateShortcutPreview(launcher, item.activityInfo,
+ previewWidth, previewHeight, recycle), false);
}
}
/**
- * Generates the widget preview from either the {@link AppWidgetManagerCompat} or cache
+ * Generates the widget preview from either the {@link WidgetManagerHelper} or cache
* and add badge at the bottom right corner.
*
* @param launcher
@@ -309,9 +372,10 @@
* @param maxPreviewWidth width of the preview on either workspace or tray
* @param preview bitmap that can be recycled
* @param preScaledWidthOut return the width of the returned bitmap
- * @return
+ * @return Pair<Bitmap (the preview) , Boolean (should be stored in db)>
*/
- public Bitmap generateWidgetPreview(BaseActivity launcher, LauncherAppWidgetProviderInfo info,
+ public Pair<Bitmap, Boolean> generateWidgetPreview(BaseActivity launcher,
+ LauncherAppWidgetProviderInfo info,
int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
// Load the preview image if possible
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
@@ -341,6 +405,8 @@
int previewWidth;
int previewHeight;
+ boolean savePreviewImage = widgetPreviewExists || info.previewImage == 0;
+
if (widgetPreviewExists && drawable.getIntrinsicWidth() > 0
&& drawable.getIntrinsicHeight() > 0) {
previewWidth = drawable.getIntrinsicWidth();
@@ -427,10 +493,12 @@
icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
icon.draw(c);
}
- } catch (Resources.NotFoundException e) { }
+ } catch (Resources.NotFoundException e) {
+ savePreviewImage = false;
+ }
c.setBitmap(null);
}
- return preview;
+ return new Pair<>(preview, savePreviewImage);
}
private RectF drawBoxWithShadow(Canvas c, int width, int height) {
@@ -533,6 +601,8 @@
@Thunk long[] mVersions;
@Thunk Bitmap mBitmapToRecycle;
+ private boolean mSaveToDB = false;
+
PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth,
int previewHeight, WidgetCell caller) {
mKey = key;
@@ -588,7 +658,10 @@
: null;
// it's not in the db... we need to generate it
- preview = generatePreview(mActivity, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
+ Pair<Bitmap, Boolean> pair = generatePreview(mActivity, mInfo, unusedBitmap,
+ mPreviewWidth, mPreviewHeight);
+ preview = pair.first;
+ this.mSaveToDB = pair.second;
}
return preview;
}
@@ -602,7 +675,7 @@
MODEL_EXECUTOR.post(new Runnable() {
@Override
public void run() {
- if (!isCancelled()) {
+ if (!isCancelled() && mSaveToDB) {
// If we are still using this preview, then write it to the DB and then
// let the normal clear mechanism recycle the bitmap
writeToDb(mKey, mVersions, preview);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9eeb286..fc1a074 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,15 +16,15 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
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.LauncherState.SPRING_LOADED;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -39,7 +39,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -60,14 +59,12 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
-import com.android.launcher3.Launcher.LauncherOverlay;
import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
@@ -81,11 +78,11 @@
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -101,6 +98,8 @@
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
+import com.android.launcher3.widget.WidgetManagerHelper;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
import java.util.ArrayList;
import java.util.HashSet;
@@ -418,9 +417,6 @@
}
// Always enter the spring loaded mode
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Switching to SPRING_LOADED");
- }
mLauncher.getStateManager().goToState(SPRING_LOADED);
}
@@ -500,7 +496,7 @@
// In transposed layout, we add the QSB in the Grid. As workspace does not touch the
// edges, we do not need a full width QSB.
qsb = LayoutInflater.from(getContext())
- .inflate(R.layout.search_container_workspace,firstPage, false);
+ .inflate(R.layout.search_container_workspace, firstPage, false);
}
CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
@@ -965,11 +961,14 @@
onOverlayScrollChanged(0);
}
+ public boolean hasOverlay() {
+ return mLauncherOverlay != null;
+ }
private boolean isScrollingOverlay() {
return mLauncherOverlay != null &&
- ((mIsRtl && getUnboundedScrollX() > mMaxScrollX)
- || (!mIsRtl && getUnboundedScrollX() < mMinScrollX));
+ ((mIsRtl && getUnboundedScroll() > mMaxScroll)
+ || (!mIsRtl && getUnboundedScroll() < mMinScroll));
}
@Override
@@ -1005,7 +1004,7 @@
public void showPageIndicatorAtCurrentScroll() {
if (mPageIndicator != null) {
- mPageIndicator.setScroll(getScrollX(), computeMaxScrollX());
+ mPageIndicator.setScroll(getScrollX(), computeMaxScroll());
}
}
@@ -1351,7 +1350,7 @@
}
public void snapToPageFromOverView(int whichPage) {
- snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
+ snapToPage(whichPage, OVERVIEW.getTransitionDuration(mLauncher), Interpolators.ZOOM_IN);
}
private void onStartStateTransition(LauncherState state) {
@@ -1472,9 +1471,6 @@
public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
DragPreviewProvider previewProvider, DragOptions dragOptions) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_CONTEXT_MENU, "beginDragShared");
- }
float iconScale = 1f;
if (child instanceof BubbleTextView) {
Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1641,7 +1637,9 @@
return false;
}
- boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo);
+ boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) dropOverView.getTag()).container
+ != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
boolean willBecomeShortcut =
(info.itemType == ITEM_TYPE_APPLICATION ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
@@ -1675,7 +1673,7 @@
}
boolean createUserFolderIfNecessary(View newView, int container, CellLayout target,
- int[] targetCell, float distance, boolean external, DragView dragView) {
+ int[] targetCell, float distance, boolean external, DragObject d) {
if (distance > mMaxDistanceForFolderCreation) return false;
View v = target.getChildAt(targetCell[0], targetCell[1]);
@@ -1705,27 +1703,27 @@
float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
target.removeView(v);
- FolderIcon fi =
- mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
+ FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0],
+ targetCell[1]);
destInfo.cellX = -1;
destInfo.cellY = -1;
sourceInfo.cellX = -1;
sourceInfo.cellY = -1;
// If the dragView is null, we can't animate
- boolean animate = dragView != null;
+ boolean animate = d != null;
if (animate) {
// In order to keep everything continuous, we hand off the currently rendered
// folder background to the newly created icon. This preserves animation state.
fi.setFolderBackground(mFolderCreateBg);
mFolderCreateBg = new PreviewBackground();
- fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale
- );
+ fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale);
} else {
fi.prepareCreateAnimation(v);
fi.addItem(destInfo);
fi.addItem(sourceInfo);
}
+ mLauncher.folderCreatedFromItem(fi.getFolder(), destInfo);
return true;
}
return false;
@@ -1758,9 +1756,6 @@
public void prepareAccessibilityDrop() { }
public void onDrop(final DragObject d, DragOptions options) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDrop");
- }
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
CellLayout dropTargetLayout = mDropToLayout;
@@ -1804,8 +1799,8 @@
// If the item being dropped is a shortcut and the nearest drop
// cell also contains a shortcut, then create a folder with the two shortcuts.
if (createUserFolderIfNecessary(cell, container,
- dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
- addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
+ dropTargetLayout, mTargetCell, distance, false, d)
+ || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
distance, d, false)) {
mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
return;
@@ -1864,7 +1859,7 @@
CellLayout parentCell = getParentCellLayoutForView(cell);
if (parentCell != null) {
parentCell.removeView(cell);
- } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ } else if (FeatureFlags.IS_STUDIO_BUILD) {
throw new NullPointerException("mDragInfo.cell has null parent");
}
addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
@@ -1921,7 +1916,8 @@
// Animate the item to its original position, while simultaneously exiting
// spring-loaded mode so the page meets the icon where it was picked up.
mLauncher.getDragController().animateDragViewToOriginalPosition(
- onCompleteRunnable, cell, SPRING_LOADED_TRANSITION_MS);
+ onCompleteRunnable, cell,
+ SPRING_LOADED.getTransitionDuration(mLauncher));
mLauncher.getStateManager().goToState(NORMAL);
mLauncher.getDropTargetBar().onDragEnd();
parent.onDropChild(cell);
@@ -2158,7 +2154,7 @@
ItemInfo item = d.dragInfo;
if (item == null) {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
throw new NullPointerException("DragObject has null info");
}
return;
@@ -2438,9 +2434,6 @@
* to add an item to one of the workspace screens.
*/
private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDropExternal");
- }
if (d.dragInfo instanceof PendingAddShortcutInfo) {
WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo)
.activityInfo.createWorkspaceItemInfo();
@@ -2554,7 +2547,7 @@
view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
+ view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout,
(FolderInfo) info);
break;
default:
@@ -2569,7 +2562,7 @@
float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
mDragViewVisualCenter[1], mTargetCell);
if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
- true, d.dragView)) {
+ true, d)) {
return;
}
if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
@@ -2614,11 +2607,10 @@
int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
- Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
- Bitmap.Config.ARGB_8888);
layout.measure(width, height);
layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
- layout.draw(new Canvas(b));
+ Bitmap b = BitmapRenderer.createHardwareBitmap(
+ unScaledSize[0], unScaledSize[1], layout::draw);
layout.setVisibility(visibility);
return b;
}
@@ -2784,7 +2776,7 @@
mDragInfo.container, mDragInfo.screenId);
if (cellLayout != null) {
cellLayout.onDropChild(mDragInfo.cell);
- } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ } else if (FeatureFlags.IS_STUDIO_BUILD) {
throw new RuntimeException("Invalid state: cellLayout == null in "
+ "Workspace#onDropCompleted. Please file a bug. ");
}
@@ -2803,7 +2795,7 @@
CellLayout parentCell = getParentCellLayoutForView(v);
if (parentCell != null) {
parentCell.removeView(v);
- } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ } else if (FeatureFlags.IS_STUDIO_BUILD) {
// When an app is uninstalled using the drop target, we wait until resume to remove
// the icon. We also remove all the corresponding items from the workspace at
// {@link Launcher#bindComponentsRemoved}. That call can come before or after
@@ -3231,12 +3223,11 @@
LauncherAppWidgetInfo item = changedInfo.get(0);
final AppWidgetProviderInfo widgetInfo;
+ WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
- widgetInfo = AppWidgetManagerCompat
- .getInstance(mLauncher).findProvider(item.providerName, item.user);
+ widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
} else {
- widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher)
- .getLauncherAppWidgetInfo(item.appWidgetId);
+ widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId);
}
if (widgetInfo != null) {
@@ -3265,7 +3256,8 @@
return mOverlayShown;
}
- void moveToDefaultScreen() {
+ /** Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it. */
+ public void moveToDefaultScreen() {
int page = DEFAULT_PAGE;
if (!workspaceInModalState() && getNextPage() != page) {
snapToPage(page);
@@ -3318,17 +3310,21 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- target.gridX = info.cellX;
- target.gridY = info.cellY;
- target.pageIndex = getCurrentPage();
- targetParent.containerType = ContainerType.WORKSPACE;
- if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- target.rank = info.rank;
- targetParent.containerType = ContainerType.HOTSEAT;
- } else if (info.container >= 0) {
- targetParent.containerType = ContainerType.FOLDER;
+ public void fillInLogContainerData(ItemInfo childInfo, Target child,
+ ArrayList<Target> parents) {
+ if (childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ || childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ getHotseat().fillInLogContainerData(childInfo, child, parents);
+ return;
+ } else if (childInfo.container >= 0) {
+ FolderIcon icon = (FolderIcon) getHomescreenIconByItemId(childInfo.container);
+ icon.getFolder().fillInLogContainerData(childInfo, child, parents);
+ return;
}
+ child.gridX = childInfo.cellX;
+ child.gridY = childInfo.cellY;
+ child.pageIndex = getCurrentPage();
+ parents.add(newContainerTarget(ContainerType.WORKSPACE));
}
/**
diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java
index 23795c5..be907e5 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/WorkspaceItemInfo.java
@@ -28,7 +28,7 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ContentWriter;
import java.util.Arrays;
@@ -140,7 +140,7 @@
.put(Favorites.RESTORED, status);
if (!usingLowResIcon()) {
- writer.putIcon(iconBitmap, user);
+ writer.putIcon(bitmap, user);
}
if (iconResource != null) {
writer.put(Favorites.ICON_PACKAGE, iconResource.packageName)
@@ -192,7 +192,7 @@
}
disabledMessage = shortcutInfo.getDisabledMessage();
- Person[] persons = UiFactory.getPersons(shortcutInfo);
+ Person[] persons = ApiWrapper.getPersons(shortcutInfo);
personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
: Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
}
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index ea2d4d0..0b9d602 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -39,7 +39,8 @@
default void addInScreenFromBind(View child, ItemInfo info) {
int x = info.cellX;
int y = info.cellY;
- if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ || info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
int screenId = info.screenId;
x = getHotseat().getCellXFromOrder(screenId);
y = getHotseat().getCellYFromOrder(screenId);
@@ -83,7 +84,8 @@
}
final CellLayout layout;
- if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ || container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
layout = getHotseat();
// Hide folder title in the hotseat
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 7a7e1fe..6653426 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,6 +18,9 @@
import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
@@ -36,6 +39,7 @@
import com.android.launcher3.LauncherState.PageAlphaProvider;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
@@ -77,6 +81,7 @@
ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
mLauncher);
+ ScaleAndTranslation qsbScaleAndTranslation = state.getQsbScaleAndTranslation(mLauncher);
mNewScale = scaleAndTranslation.scale;
PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
final int childCount = mWorkspace.getChildCount();
@@ -90,24 +95,24 @@
pageAlphaProvider.interpolator);
boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
Hotseat hotseat = mWorkspace.getHotseat();
+ // Since we set the pivot relative to mWorkspace, we need to scale a sibling of Workspace.
+ AllAppsContainerView qsbScaleView = mLauncher.getAppsView();
+ View qsbView = qsbScaleView.getSearchView();
if (playAtomicComponent) {
Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
if (!hotseat.getRotationMode().isTransposed) {
- // Set the hotseat's pivot point to match the workspace's, so that it scales
- // together. Since both hotseat and workspace can move, transform the point
- // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
- // related methods.
- hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
- hotseat.setPivotX(mWorkspace.getPivotX()
- + mWorkspace.getLeft() - hotseat.getLeft());
+ setPivotToScaleWithWorkspace(hotseat);
+ setPivotToScaleWithWorkspace(qsbScaleView);
}
float hotseatScale = hotseatScaleAndTranslation.scale;
Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
scaleInterpolator);
propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
hotseatScaleInterpolator);
+ propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
+ hotseatScaleInterpolator);
float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
@@ -115,7 +120,7 @@
hotseatIconsAlpha, fadeInterpolator);
}
- if (!config.playNonAtomicComponent()) {
+ if (config.onlyPlayAtomicComponent()) {
// Only the alpha and scale, handled above, are included in the atomic animation.
return;
}
@@ -123,21 +128,35 @@
Interpolator translationInterpolator = !playAtomicComponent
? LINEAR
: builder.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
- propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
+ propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_X,
scaleAndTranslation.translationX, translationInterpolator);
- propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
+ propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_Y,
scaleAndTranslation.translationY, translationInterpolator);
Interpolator hotseatTranslationInterpolator = builder.getInterpolator(
ANIM_HOTSEAT_TRANSLATE, translationInterpolator);
- propertySetter.setFloat(hotseat, View.TRANSLATION_Y,
+ propertySetter.setFloat(hotseat, VIEW_TRANSLATE_Y,
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
- propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y,
+ propertySetter.setFloat(mWorkspace.getPageIndicator(), VIEW_TRANSLATE_Y,
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
+ propertySetter.setFloat(qsbView, VIEW_TRANSLATE_Y,
+ qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator);
setScrim(propertySetter, state);
}
+ /**
+ * Set the given view's pivot point to match the workspace's, so that it scales together. Since
+ * both this view and workspace can move, transform the point manually instead of using
+ * dragLayer.getDescendantCoordRelativeToSelf and related methods.
+ */
+ private void setPivotToScaleWithWorkspace(View sibling) {
+ sibling.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop()
+ - sibling.getTop() - sibling.getTranslationY());
+ sibling.setPivotX(mWorkspace.getPivotX() + mWorkspace.getLeft()
+ - sibling.getLeft() - sibling.getTranslationX());
+ }
+
public void setScrim(PropertySetter propertySetter, LauncherState state) {
WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
@@ -156,14 +175,15 @@
float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
- if (config.playNonAtomicComponent()) {
+ if (!config.onlyPlayAtomicComponent()) {
+ // Don't update the scrim during the atomic animation.
propertySetter.setInt(cl.getScrimBackground(),
DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
}
if (config.playAtomicOverviewScaleComponent()) {
Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
pageAlphaProvider.interpolator);
- propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+ propertySetter.setFloat(cl.getShortcutsAndWidgets(), VIEW_ALPHA,
pageAlpha, fadeInterpolator);
}
}
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0c12c60..0b439ec 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -54,6 +54,7 @@
public static final int REMOVE = R.id.action_remove;
public static final int UNINSTALL = R.id.action_uninstall;
+ public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction;
public static final int RECONFIGURE = R.id.action_reconfigure;
protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
protected static final int MOVE = R.id.action_move;
@@ -86,6 +87,8 @@
launcher.getText(R.string.remove_drop_target_label)));
mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
launcher.getText(R.string.uninstall_drop_target_label)));
+ mActions.put(DISMISS_PREDICTION, new AccessibilityAction(DISMISS_PREDICTION,
+ launcher.getText(R.string.dismiss_prediction_label)));
mActions.put(RECONFIGURE, new AccessibilityAction(RECONFIGURE,
launcher.getText(R.string.gadget_setup_text)));
mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
@@ -166,13 +169,22 @@
}
public boolean performAction(final View host, final ItemInfo item, int action) {
- if (action == ACTION_LONG_CLICK && ShortcutUtil.isDeepShortcut(item)) {
- CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
- if (popup.canShow()) {
- popup.show();
+ if (action == ACTION_LONG_CLICK) {
+ if (ShortcutUtil.isDeepShortcut(item)) {
+ CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
+ if (popup.canShow()) {
+ popup.show();
+ return true;
+ }
+ } else if (host instanceof BubbleTextView) {
+ // Long press should be consumed for workspace items, and it should invoke the
+ // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
+ // standard long press path does.
+ PopupContainerWithArrow.showForIcon((BubbleTextView) host);
return true;
}
}
+
if (action == MOVE) {
beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
@@ -191,6 +203,7 @@
ArrayList<ItemInfo> itemList = new ArrayList<>();
itemList.add(info);
mLauncher.bindItems(itemList, true);
+ announceConfirmation(R.string.item_added_to_workspace);
} else if (item instanceof PendingAddItemInfo) {
PendingAddItemInfo info = (PendingAddItemInfo) item;
Workspace workspace = mLauncher.getWorkspace();
@@ -198,7 +211,6 @@
mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
screenId, coordinates, info.spanX, info.spanY);
}
- announceConfirmation(R.string.item_added_to_workspace);
}
});
return true;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 2d5b040..e085ff0 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,13 +15,14 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Bundle;
import android.os.Process;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -32,13 +33,16 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DragSource;
@@ -46,23 +50,21 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.BottomUserEducationView;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.launcher3.views.WorkFooterContainer;
+
+import java.util.ArrayList;
/**
* The all apps view container.
@@ -75,8 +77,8 @@
private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
private static final int ALPHA_CHANNEL_COUNT = 2;
- private final Launcher mLauncher;
- private final AdapterHolder[] mAH;
+ protected final BaseDraggingActivity mLauncher;
+ protected final AdapterHolder[] mAH;
private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
private final AllAppsStore mAllAppsStore = new AllAppsStore();
@@ -84,21 +86,26 @@
private final Paint mNavBarScrimPaint;
private int mNavBarScrimHeight = 0;
- private SearchUiManager mSearchUiManager;
+ protected SearchUiManager mSearchUiManager;
private View mSearchContainer;
private AllAppsPagedView mViewPager;
+
private FloatingHeaderView mHeader;
+ private WorkFooterContainer mWorkFooterContainer;
+
private SpannableStringBuilder mSearchQueryBuilder = null;
- private boolean mUsingTabs;
+ protected boolean mUsingTabs;
private boolean mSearchModeWhileUsingTabs = false;
- private RecyclerViewFastScroller mTouchHandler;
- private final Point mFastScrollerOffset = new Point();
+ protected RecyclerViewFastScroller mTouchHandler;
+ protected final Point mFastScrollerOffset = new Point();
private final MultiValueAlpha mMultiValueAlpha;
+ Rect mInsets = new Rect();
+
public AllAppsContainerView(Context context) {
this(context, null);
}
@@ -110,7 +117,7 @@
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mLauncher.addOnDeviceProfileChangeListener(this);
mSearchQueryBuilder = new SpannableStringBuilder();
@@ -132,6 +139,15 @@
mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
}
+ /**
+ * Sets the long click listener for icons
+ */
+ public void setOnIconLongClickListener(OnLongClickListener listener) {
+ for (AdapterHolder holder : mAH) {
+ holder.adapter.setOnIconLongClickListener(listener);
+ }
+ }
+
public AllAppsStore getAppsStore() {
return mAllAppsStore;
}
@@ -140,6 +156,11 @@
return mMultiValueAlpha.getProperty(index);
}
+ public WorkFooterContainer getWorkFooterContainer() {
+ return mWorkFooterContainer;
+ }
+
+
@Override
protected void setDampedScrollShift(float shift) {
// Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
@@ -160,16 +181,23 @@
}
private void onAppsUpdated() {
- if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
- boolean hasWorkApps = false;
- for (AppInfo app : mAllAppsStore.getApps()) {
- if (mWorkMatcher.matches(app, null)) {
- hasWorkApps = true;
- break;
- }
+ boolean hasWorkApps = false;
+ for (AppInfo app : mAllAppsStore.getApps()) {
+ if (mWorkMatcher.matches(app, null)) {
+ hasWorkApps = true;
+ break;
}
- rebindAdapters(hasWorkApps);
}
+ rebindAdapters(hasWorkApps);
+ if (hasWorkApps) {
+ resetWorkProfile();
+ }
+ }
+
+ private void resetWorkProfile() {
+ mWorkFooterContainer.refresh();
+ mAH[AdapterHolder.WORK].setupOverlay();
+ mAH[AdapterHolder.WORK].applyPadding();
}
/**
@@ -194,14 +222,6 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
-
- // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
- // Overview states. We shouldn't intercept for the scrubber in these cases.
- if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
- mTouchHandler = null;
- return false;
- }
-
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
AllAppsRecyclerView rv = getActiveRecyclerView();
if (rv != null &&
@@ -221,14 +241,14 @@
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
AllAppsRecyclerView rv = getActiveRecyclerView();
- if (rv != null && rv.getScrollbar()
- .isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
+ if (rv != null && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(),
+ mFastScrollerOffset)) {
mTouchHandler = rv.getScrollbar();
} else {
mTouchHandler = null;
+
}
}
-
if (mTouchHandler != null) {
mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
return true;
@@ -304,28 +324,28 @@
}
@Override
- public void onDropCompleted(View target, DragObject d, boolean success) { }
+ public void onDropCompleted(View target, DragObject d, boolean success) {
+ }
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- if (getApps().hasFilter()) {
- targetParent.containerType = ContainerType.SEARCHRESULT;
- } else {
- targetParent.containerType = ContainerType.ALLAPPS;
- }
+ public void fillInLogContainerData(ItemInfo childInfo, Target child,
+ ArrayList<Target> parents) {
+ parents.add(newContainerTarget(
+ getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
}
@Override
public void setInsets(Rect insets) {
+ mInsets.set(insets);
DeviceProfile grid = mLauncher.getDeviceProfile();
int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
+ grid.cellLayoutPaddingLeftRightPx;
for (int i = 0; i < mAH.length; i++) {
- mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns);
mAH[i].padding.bottom = insets.bottom;
mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
mAH[i].applyPadding();
+ mAH[i].setupOverlay();
}
ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
@@ -340,8 +360,6 @@
setLayoutParams(mlp);
InsettableFrameLayout.dispatchInsets(this, insets);
- mLauncher.getAllAppsController()
- .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
}
@Override
@@ -374,7 +392,7 @@
rebindAdapters(showTabs, false /* force */);
}
- private void rebindAdapters(boolean showTabs, boolean force) {
+ protected void rebindAdapters(boolean showTabs, boolean force) {
if (showTabs == mUsingTabs && !force) {
return;
}
@@ -385,12 +403,17 @@
mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
if (mUsingTabs) {
+ setupWorkToggle();
mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
onTabChanged(mViewPager.getNextPage());
} else {
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
mAH[AdapterHolder.WORK].recyclerView = null;
+ if (mWorkFooterContainer != null) {
+ ((ViewGroup) mWorkFooterContainer.getParent()).removeView(mWorkFooterContainer);
+ mWorkFooterContainer = null;
+ }
}
setupHeader();
@@ -398,6 +421,17 @@
mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
}
+ private void setupWorkToggle() {
+ mWorkFooterContainer = (WorkFooterContainer) mLauncher.getLayoutInflater().inflate(
+ R.layout.work_tab_footer, findViewById(R.id.work_toggle_container));
+ mWorkFooterContainer.setLayoutParams(
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ this.addView(mWorkFooterContainer);
+ mWorkFooterContainer.setInsets(mInsets);
+ mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+ }
+
private void replaceRVContainer(boolean showTabs) {
for (int i = 0; i < mAH.length; i++) {
if (mAH[i].recyclerView != null) {
@@ -426,6 +460,7 @@
public void onTabChanged(int pos) {
mHeader.setMainActive(pos == 0);
reset(true /* animate */);
+ mViewPager.getPageIndicator().updateTabTextColor(pos);
if (mAH[pos].recyclerView != null) {
mAH[pos].recyclerView.bindFastScrollbar();
@@ -433,10 +468,9 @@
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
-
}
- if (pos == AdapterHolder.WORK) {
- BottomUserEducationView.showIfNeeded(mLauncher);
+ if (mWorkFooterContainer != null) {
+ mWorkFooterContainer.setWorkTabVisible(pos == AdapterHolder.WORK);
}
}
@@ -533,17 +567,6 @@
return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
}
- public void onScrollUpEnd() {
- highlightWorkTabIfNecessary();
- }
-
- void highlightWorkTabIfNecessary() {
- if (mUsingTabs) {
- ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs))
- .highlightWorkTabIfNecessary();
- }
- }
-
/**
* Adds an update listener to {@param animator} that adds springs to the animation.
*/
@@ -583,6 +606,8 @@
public static final int MAIN = 0;
public static final int WORK = 1;
+ private ItemInfoMatcher mInfoMatcher;
+ private final boolean mIsWork;
public final AllAppsGridAdapter adapter;
final LinearLayoutManager layoutManager;
final AlphabeticalAppsList appsList;
@@ -590,7 +615,10 @@
AllAppsRecyclerView recyclerView;
boolean verticalFadingEdge;
+ boolean mWorkDisabled;
+
AdapterHolder(boolean isWork) {
+ mIsWork = isWork;
appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
adapter = new AllAppsGridAdapter(mLauncher, appsList);
appsList.setAdapter(adapter);
@@ -598,10 +626,11 @@
}
void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
+ mInfoMatcher = matcher;
appsList.updateItemFilter(matcher);
recyclerView = (AllAppsRecyclerView) rv;
recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
- recyclerView.setApps(appsList, mUsingTabs);
+ recyclerView.setApps(appsList);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
@@ -612,11 +641,31 @@
adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
applyVerticalFadingEdgeEnabled(verticalFadingEdge);
applyPadding();
+ setupOverlay();
+ }
+
+ void setupOverlay() {
+ if (!mIsWork || recyclerView == null) return;
+ boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
+ if (mWorkDisabled == workDisabled) return;
+ if (workDisabled) {
+ appsList.updateItemFilter((info, cn) -> false);
+ recyclerView.addAutoSizedOverlay(
+ mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null));
+ } else if (mInfoMatcher != null) {
+ appsList.updateItemFilter(mInfoMatcher);
+ recyclerView.clearAutoSizedOverlays();
+ }
+ mWorkDisabled = workDisabled;
}
void applyPadding() {
if (recyclerView != null) {
- recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ int bottomOffset =
+ mWorkFooterContainer != null && mIsWork ? mWorkFooterContainer.getHeight()
+ : 0;
+ recyclerView.setPadding(padding.left, padding.top, padding.right,
+ padding.bottom + bottomOffset);
}
}
@@ -626,17 +675,4 @@
&& verticalFadingEdge);
}
}
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (AccessibilityManagerCompat.processTestRequest(
- mLauncher, TestProtocol.GET_SCROLL_MESSAGE, action, arguments,
- response ->
- response.putInt(TestProtocol.SCROLL_Y_FIELD,
- getActiveRecyclerView().getCurrentScrollY()))) {
- return true;
- }
-
- return super.performAccessibilityAction(action, arguments);
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index bb21268..1f861bc 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -15,36 +15,38 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.util.PackageManagerHelper;
-
-import java.util.List;
-
+import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
+import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.util.List;
+
/**
* The grid view adapter of all the apps.
*/
@@ -64,7 +66,6 @@
// A divider that separates the apps list and the search market button
public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
- public static final int VIEW_TYPE_WORK_TAB_FOOTER = 1 << 5;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
@@ -174,23 +175,26 @@
}
}
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
private final LayoutInflater mLayoutInflater;
private final AlphabeticalAppsList mApps;
private final GridLayoutManager mGridLayoutMgr;
private final GridSpanSizer mGridSizer;
+ private final OnClickListener mOnIconClickListener;
+ private OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
+
private int mAppsPerRow;
private BindViewCallback mBindViewCallback;
private OnFocusChangeListener mIconFocusListener;
// The text to show when there are no search results and no market search handler.
- private String mEmptySearchMessage;
+ protected String mEmptySearchMessage;
// The intent to send off to the market app, updated each time the search query changes.
private Intent mMarketSearchIntent;
- public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
+ public AllAppsGridAdapter(BaseDraggingActivity launcher, AlphabeticalAppsList apps) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -200,6 +204,8 @@
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mLayoutInflater = LayoutInflater.from(launcher);
+ mOnIconClickListener = launcher.getItemOnClickListener();
+
setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
}
@@ -208,6 +214,13 @@
mGridLayoutMgr.setSpanCount(mAppsPerRow);
}
+ /**
+ * Sets the long click listener for icons
+ */
+ public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
+ mOnIconLongClickListener = listener;
+ }
+
public static boolean isDividerViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
}
@@ -254,8 +267,8 @@
case VIEW_TYPE_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
- icon.setOnClickListener(ItemClickHandler.INSTANCE);
- icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
+ icon.setOnClickListener(mOnIconClickListener);
+ icon.setOnLongClickListener(mOnIconLongClickListener);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
@@ -274,9 +287,6 @@
case VIEW_TYPE_ALL_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
- case VIEW_TYPE_WORK_TAB_FOOTER:
- View footer = mLayoutInflater.inflate(R.layout.work_tab_footer, parent, false);
- return new ViewHolder(footer);
default:
throw new RuntimeException("Unexpected view type");
}
@@ -308,15 +318,6 @@
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
break;
- case VIEW_TYPE_WORK_TAB_FOOTER:
- WorkModeSwitch workModeToggle = holder.itemView.findViewById(R.id.work_mode_toggle);
- workModeToggle.refresh();
- TextView managedByLabel = holder.itemView.findViewById(R.id.managed_by_label);
- boolean anyProfileQuietModeEnabled = UserManagerCompat.getInstance(
- managedByLabel.getContext()).isAnyProfileQuietModeEnabled();
- managedByLabel.setText(anyProfileQuietModeEnabled
- ? R.string.work_mode_off_label : R.string.work_mode_on_label);
- break;
}
if (mBindViewCallback != null) {
mBindViewCallback.onBindView(holder);
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 5b73940..ab4cb6b 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -48,7 +48,7 @@
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
- mPageIndicator.setScroll(l, mMaxScrollX);
+ mPageIndicator.setScroll(l, mMaxScroll);
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index f82e380..8fe4633 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,7 +15,11 @@
*/
package com.android.launcher3.allapps;
+import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import android.content.Context;
import android.content.res.Resources;
@@ -28,19 +32,19 @@
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.views.RecyclerViewFastScroller;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -60,6 +64,8 @@
private AllAppsBackgroundDrawable mEmptySearchBackground;
private int mEmptySearchBackgroundTopOffset;
+ private ArrayList<View> mAutoSizedOverlays = new ArrayList<>();
+
public AllAppsRecyclerView(Context context) {
this(context, null);
}
@@ -84,7 +90,7 @@
/**
* Sets the list of apps in this view, used to determine the fastscroll position.
*/
- public void setApps(AlphabeticalAppsList apps, boolean usingTabs) {
+ public void setApps(AlphabeticalAppsList apps) {
mApps = apps;
mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
}
@@ -94,7 +100,7 @@
}
private void updatePoolSize() {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
@@ -143,15 +149,37 @@
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateEmptySearchBackgroundBounds();
updatePoolSize();
+ for (int i = 0; i < mAutoSizedOverlays.size(); i++) {
+ View overlay = mAutoSizedOverlays.get(i);
+ overlay.measure(makeMeasureSpec(w, EXACTLY), makeMeasureSpec(w, EXACTLY));
+ overlay.layout(0, 0, w, h);
+ }
+ }
+
+ /**
+ * Adds an overlay that automatically rescales with the recyclerview.
+ */
+ public void addAutoSizedOverlay(View overlay) {
+ mAutoSizedOverlays.add(overlay);
+ getOverlay().add(overlay);
+ onSizeChanged(getWidth(), getHeight(), getWidth(), getHeight());
+ }
+
+ /**
+ * Clears auto scaling overlay views added by #addAutoSizedOverlay
+ */
+ public void clearAutoSizedOverlays() {
+ for (View v : mAutoSizedOverlays) {
+ getOverlay().remove(v);
+ }
+ mAutoSizedOverlays.clear();
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- if (mApps.hasFilter()) {
- targetParent.containerType = ContainerType.SEARCHRESULT;
- } else {
- targetParent.containerType = ContainerType.ALLAPPS;
- }
+ public void fillInLogContainerData(ItemInfo childInfo, Target child,
+ ArrayList<Target> parents) {
+ parents.add(newContainerTarget(
+ getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
}
public void onSearchResultsChanged() {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 08ce9c2..744f4eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,6 +2,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
+import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
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;
@@ -15,6 +16,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.util.FloatProperty;
import android.view.animation.Interpolator;
@@ -28,7 +30,6 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
@@ -44,9 +45,6 @@
*/
public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
- private static final float SPRING_DAMPING_RATIO = 0.9f;
- private static final float SPRING_STIFFNESS = 600f;
-
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -164,7 +162,7 @@
return;
}
- if (!config.playNonAtomicComponent()) {
+ if (config.onlyPlayAtomicComponent()) {
// There is no atomic component for the all apps transition, so just return early.
return;
}
@@ -183,8 +181,7 @@
}
public Animator createSpringAnimation(float... progressValues) {
- return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
- SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
+ return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues);
}
private void setAlphas(LauncherState toState, AnimationConfig config,
@@ -198,6 +195,8 @@
boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
+ boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
+
Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
@@ -208,15 +207,12 @@
setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
(visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
+
+ setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, allAppsFade);
}
public AnimatorListenerAdapter getProgressAnimatorListener() {
- return new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- onProgressAnimationEnd();
- }
- };
+ return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd);
}
public void setupViews(AllAppsContainerView appsView) {
@@ -243,18 +239,6 @@
private void onProgressAnimationEnd() {
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
- } else if (isAllAppsExpanded()) {
- mAppsView.onScrollUpEnd();
- }
- }
-
- private boolean isAllAppsExpanded() {
- return Float.compare(mProgress, 0f) == 0;
- }
-
- public void highlightWorkTabIfNecessary() {
- if (isAllAppsExpanded()) {
- mAppsView.highlightWorkTabIfNecessary();
}
}
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 0c4be62..b501c82 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,13 +15,11 @@
*/
package com.android.launcher3.allapps;
+
import android.content.Context;
-import android.content.pm.PackageManager;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
@@ -116,16 +114,9 @@
item.position = pos;
return item;
}
-
- public static AdapterItem asWorkTabFooter(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = AllAppsGridAdapter.VIEW_TYPE_WORK_TAB_FOOTER;
- item.position = pos;
- return item;
- }
}
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
// The set of apps from the system
private final List<AppInfo> mApps = new ArrayList<>();
@@ -150,7 +141,7 @@
public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) {
mAllAppsStore = appsStore;
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mAppNameComparator = new AppInfoComparator(context);
mIsWork = isWork;
mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
@@ -389,18 +380,6 @@
break;
}
}
-
- // Add the work profile footer if required.
- if (shouldShowWorkFooter()) {
- mAdapterItems.add(AdapterItem.asWorkTabFooter(position++));
- }
- }
-
- private boolean shouldShowWorkFooter() {
- return mIsWork && Utilities.ATLEAST_P &&
- (DeepShortcutManager.getInstance(mLauncher).hasHostPermission()
- || mLauncher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
- == PackageManager.PERMISSION_GRANTED);
}
private List<AppInfo> getFiltersAppInfos() {
diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
index 80577a7..8baf56c 100644
--- a/src/com/android/launcher3/allapps/AppInfoComparator.java
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -20,7 +20,7 @@
import android.os.UserHandle;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LabelComparator;
import java.util.Comparator;
@@ -30,12 +30,12 @@
*/
public class AppInfoComparator implements Comparator<AppInfo> {
- private final UserManagerCompat mUserManager;
+ private final UserCache mUserManager;
private final UserHandle mMyUser;
private final LabelComparator mLabelComparator;
public AppInfoComparator(Context context) {
- mUserManager = UserManagerCompat.getInstance(context);
+ mUserManager = UserCache.INSTANCE.get(context);
mMyUser = Process.myUserHandle();
mLabelComparator = new LabelComparator();
}
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 8c59626..0f0fc3a 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -26,17 +26,15 @@
import android.animation.AnimatorListenerAdapter;
import android.content.SharedPreferences;
import android.os.Handler;
+import android.os.UserManager;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.states.InternalStateHandler;
/**
* Abstract base class of floating view responsible for showing discovery bounce animation
@@ -145,10 +143,9 @@
private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) {
if (!launcher.isInState(NORMAL)
- || (launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)
- && !shouldShowForWorkProfile(launcher))
+ || launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)
|| AbstractFloatingView.getTopOpenView(launcher) != null
- || UserManagerCompat.getInstance(launcher).isDemoUser()
+ || launcher.getSystemService(UserManager.class).isDemoUser()
|| Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return;
}
@@ -171,9 +168,8 @@
|| !launcher.hasBeenResumed()
|| launcher.isForceInvisible()
|| launcher.getDeviceProfile().isVerticalBarLayout()
- || (launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)
- && !shouldShowForWorkProfile(launcher))
- || UserManagerCompat.getInstance(launcher).isDemoUser()
+ || launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)
+ || launcher.getSystemService(UserManager.class).isDemoUser()
|| Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return;
}
@@ -181,7 +177,7 @@
if (withDelay) {
new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS);
return;
- } else if (InternalStateHandler.hasPending()
+ } else if (Launcher.ACTIVITY_TRACKER.hasPending()
|| AbstractFloatingView.getTopOpenView(launcher) != null) {
// TODO: Move these checks to the top and call this method after invalidate handler.
return;
@@ -214,12 +210,6 @@
}
}
- private static boolean shouldShowForWorkProfile(Launcher launcher) {
- return !launcher.getSharedPrefs().getBoolean(
- PersonalWorkSlidingTabStrip.KEY_SHOWED_PEEK_WORK_TAB, false)
- && UserManagerCompat.getInstance(launcher).hasWorkProfile();
- }
-
private static void incrementShelfBounceCount(Launcher launcher) {
SharedPreferences sharedPrefs = launcher.getSharedPrefs();
int count = sharedPrefs.getInt(SHELF_BOUNCE_COUNT, 0);
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 42a0eee..81e1b94 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
@@ -31,9 +33,9 @@
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -370,7 +372,7 @@
}
allowTouchForwarding(hasAllAppsContent);
- setter.setFloat(mTabLayout, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
+ setter.setFloat(mTabLayout, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
}
protected void allowTouchForwarding(boolean allow) {
@@ -393,7 +395,7 @@
@Override
public void setInsets(Rect insets) {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
for (FloatingHeaderRow row : mAllRows) {
row.setInsets(insets, grid);
}
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
new file mode 100644
index 0000000..f6766c4
--- /dev/null
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.views.WorkEduView;
+
+/**
+ * AllAppsContainerView with launcher specific callbacks
+ */
+public class LauncherAllAppsContainerView extends AllAppsContainerView {
+
+ private final Launcher mLauncher;
+
+ private LauncherStateManager.StateListener mWorkTabListener;
+
+ public LauncherAllAppsContainerView(Context context) {
+ this(context, null);
+ }
+
+ public LauncherAllAppsContainerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LauncherAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
+ // Overview states. We shouldn't intercept for the scrubber in these cases.
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+ mTouchHandler = null;
+ return false;
+ }
+
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ super.setInsets(insets);
+ mLauncher.getAllAppsController()
+ .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
+ }
+
+ @Override
+ public void setupHeader() {
+ super.setupHeader();
+ if (mWorkTabListener != null && !mUsingTabs) {
+ mLauncher.getStateManager().removeStateListener(mWorkTabListener);
+ }
+ }
+
+ @Override
+ public void onTabChanged(int pos) {
+ super.onTabChanged(pos);
+ if (mUsingTabs) {
+ if (pos == AdapterHolder.WORK) {
+ WorkEduView.showWorkEduIfNeeded(mLauncher);
+ } else {
+ mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index decdcc0..3e40392 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -16,7 +16,6 @@
package com.android.launcher3.allapps;
import android.content.Context;
-import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
@@ -24,15 +23,14 @@
import android.widget.Button;
import android.widget.LinearLayout;
-import com.android.launcher3.Launcher;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.util.Themes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
* Supports two indicator colors, dedicated for personal and work tabs.
*/
@@ -40,11 +38,8 @@
private static final int POSITION_PERSONAL = 0;
private static final int POSITION_WORK = 1;
- public static final String KEY_SHOWED_PEEK_WORK_TAB = "showed_peek_work_tab";
-
private final Paint mSelectedIndicatorPaint;
private final Paint mDividerPaint;
- private final SharedPreferences mSharedPreferences;
private int mSelectedIndicatorHeight;
private int mIndicatorLeft = -1;
@@ -73,16 +68,13 @@
mDividerPaint.setStrokeWidth(
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
- mSharedPreferences = Launcher.getLauncher(getContext()).getSharedPrefs();
mIsRtl = Utilities.isRtl(getResources());
}
- private void updateIndicatorPosition(float scrollOffset) {
- mScrollOffset = scrollOffset;
- updateIndicatorPosition();
- }
-
- private void updateTabTextColor(int pos) {
+ /**
+ * Highlights tab with index pos
+ */
+ public void updateTabTextColor(int pos) {
mSelectedPosition = pos;
for (int i = 0; i < getChildCount(); i++) {
Button tab = (Button) getChildAt(i);
@@ -90,6 +82,11 @@
}
}
+ private void updateIndicatorPosition(float scrollOffset) {
+ mScrollOffset = scrollOffset;
+ updateIndicatorPosition();
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
@@ -129,25 +126,6 @@
mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
}
- public void highlightWorkTabIfNecessary() {
- if (mSharedPreferences.getBoolean(KEY_SHOWED_PEEK_WORK_TAB, false)) {
- return;
- }
- if (mLastActivePage != POSITION_PERSONAL) {
- return;
- }
- highlightWorkTab();
- mSharedPreferences.edit().putBoolean(KEY_SHOWED_PEEK_WORK_TAB, true).apply();
- }
-
- private void highlightWorkTab() {
- View v = getChildAt(POSITION_WORK);
- v.post(() -> {
- v.setPressed(true);
- v.setPressed(false);
- });
- }
-
@Override
public void setScroll(int currentScroll, int totalScroll) {
float scrollOffset = ((float) currentScroll) / totalScroll;
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index 535ef54..3089b18 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -15,10 +15,11 @@
*/
package com.android.launcher3.allapps;
-import static android.view.View.ALPHA;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
+
import android.graphics.Rect;
import android.view.View;
import android.view.animation.Interpolator;
@@ -67,7 +68,7 @@
public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
// Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(mView, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
+ setter.setFloat(mView, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
}
@Override
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 717bbd4..aadb297 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -19,14 +19,15 @@
import android.os.AsyncTask;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Switch;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.pm.UserCache;
import java.lang.ref.WeakReference;
-import java.util.List;
public class WorkModeSwitch extends Switch {
@@ -57,7 +58,7 @@
}
public void refresh() {
- UserManagerCompat userManager = UserManagerCompat.getInstance(getContext());
+ UserCache userManager = UserCache.INSTANCE.get(getContext());
setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
setEnabled(true);
}
@@ -95,14 +96,14 @@
@Override
protected Boolean doInBackground(Void... voids) {
WorkModeSwitch workModeSwitch = switchWeakReference.get();
- if (workModeSwitch == null) {
+ if (workModeSwitch == null || !Utilities.ATLEAST_P) {
return false;
}
- UserManagerCompat userManager =
- UserManagerCompat.getInstance(workModeSwitch.getContext());
- List<UserHandle> userProfiles = userManager.getUserProfiles();
+
+ Context context = workModeSwitch.getContext();
+ UserManager userManager = context.getSystemService(UserManager.class);
boolean showConfirm = false;
- for (UserHandle userProfile : userProfiles) {
+ for (UserHandle userProfile : UserCache.INSTANCE.get(context).getUserProfiles()) {
if (Process.myUserHandle().equals(userProfile)) {
continue;
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4515dde..ed45749 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -25,8 +25,8 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.util.ComponentKey;
@@ -41,7 +41,7 @@
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
OnFocusChangeListener {
- protected Launcher mLauncher;
+ protected BaseDraggingActivity mLauncher;
protected Callbacks mCb;
protected ExtendedEditText mInput;
protected String mQuery;
@@ -56,7 +56,7 @@
*/
public final void initialize(
SearchAlgorithm searchAlgorithm, ExtendedEditText input,
- Launcher launcher, Callbacks cb) {
+ BaseDraggingActivity launcher, Callbacks cb) {
mCb = cb;
mLauncher = launcher;
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 31fcc8c..d497c3a 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -26,8 +26,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.text.Selection;
-import android.text.Spannable;
-import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
@@ -36,18 +34,16 @@
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.Interpolator;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
@@ -59,8 +55,7 @@
implements SearchUiManager, AllAppsSearchBarController.Callbacks,
AllAppsStore.OnUpdateListener, Insettable {
-
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
private final AllAppsSearchBarController mSearchBarController;
private final SpannableStringBuilder mSearchQueryBuilder;
@@ -82,7 +77,7 @@
public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mSearchBarController = new AllAppsSearchBarController();
mSearchQueryBuilder = new SpannableStringBuilder();
@@ -97,13 +92,13 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mLauncher.getAppsView().getAppsStore().addUpdateListener(this);
+ mAppsView.getAppsStore().addUpdateListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mLauncher.getAppsView().getAppsStore().removeUpdateListener(this);
+ mAppsView.getAppsStore().removeUpdateListener(this);
}
@Override
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index 9448632..9905e81 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -39,4 +39,25 @@
}
public abstract void onAnimationSuccess(Animator animator);
+
+ /**
+ * Returns an AnimationSuccessListener which runs the provided action on success
+ */
+ public static AnimationSuccessListener forRunnable(Runnable r) {
+ return new RunnableSuccessListener(r);
+ }
+
+ private static class RunnableSuccessListener extends AnimationSuccessListener {
+
+ private final Runnable mRunnable;
+
+ private RunnableSuccessListener(Runnable r) {
+ mRunnable = r;
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mRunnable.run();
+ }
+ }
}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 4a52795..1fc21fd 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -16,7 +16,9 @@
package com.android.launcher3.anim;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
@@ -24,16 +26,18 @@
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.util.Log;
+import android.content.Context;
+import android.util.FloatProperty;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Utilities;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
/**
* Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
@@ -42,14 +46,7 @@
* Note: The implementation does not support start delays on child animations or
* sequential playbacks.
*/
-public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
-
- private static final String TAG = "AnimatorPlaybackCtrler";
- private static boolean DEBUG = false;
-
- public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
- return wrap(anim, duration, null);
- }
+public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
/**
* Creates an animation controller for the provided animation.
@@ -57,20 +54,41 @@
* needs to be larger than the total number of pixels so that we don't have jittering due
* to float (animation-fraction * total duration) to int conversion.
*/
- public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration,
- Runnable onCancelRunnable) {
-
+ public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
/**
* TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
*/
- return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable);
+ ArrayList<Holder> childAnims = new ArrayList<>();
+ addAnimationHoldersRecur(anim, SpringProperty.DEFAULT, childAnims);
+
+ return new AnimatorPlaybackController(anim, duration, childAnims);
}
+ public static AnimatorPlaybackController wrap(PendingAnimation anim, long duration) {
+ /**
+ * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
+ */
+ return new AnimatorPlaybackController(anim.anim, duration, anim.animHolders);
+ }
+
+ private static final FloatProperty<ValueAnimator> CURRENT_PLAY_TIME =
+ new FloatProperty<ValueAnimator>("current-play-time") {
+ @Override
+ public void setValue(ValueAnimator animator, float v) {
+ animator.setCurrentPlayTime((long) v);
+ }
+
+ @Override
+ public Float get(ValueAnimator animator) {
+ return (float) animator.getCurrentPlayTime();
+ }
+ };
+
private final ValueAnimator mAnimationPlayer;
private final long mDuration;
- protected final AnimatorSet mAnim;
- private Set<SpringAnimation> mSprings;
+ private final AnimatorSet mAnim;
+ private final Holder[] mChildAnimations;
protected float mCurrentFraction;
private Runnable mEndAction;
@@ -78,22 +96,14 @@
protected boolean mTargetCancelled = false;
protected Runnable mOnCancelRunnable;
- private OnAnimationEndDispatcher mEndListener;
- private DynamicAnimation.OnAnimationEndListener mSpringEndListener;
- // We need this variable to ensure the end listener is called immediately, otherwise we run into
- // issues where the callback interferes with the states of the swipe detector.
- private boolean mSkipToEnd = false;
-
- protected AnimatorPlaybackController(AnimatorSet anim, long duration,
- Runnable onCancelRunnable) {
+ private AnimatorPlaybackController(
+ AnimatorSet anim, long duration, ArrayList<Holder> childAnims) {
mAnim = anim;
mDuration = duration;
- mOnCancelRunnable = onCancelRunnable;
mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
mAnimationPlayer.setInterpolator(LINEAR);
- mEndListener = new OnAnimationEndDispatcher();
- mAnimationPlayer.addListener(mEndListener);
+ mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
mAnimationPlayer.addUpdateListener(this);
mAnim.addListener(new AnimatorListenerAdapter() {
@@ -118,14 +128,7 @@
}
});
- mSprings = new HashSet<>();
- mSpringEndListener = (animation, canceled, value, velocity1) -> {
- if (canceled) {
- mEndListener.onAnimationCancel(mAnimationPlayer);
- } else {
- mEndListener.onAnimationEnd(mAnimationPlayer);
- }
- };
+ mChildAnimations = childAnims.toArray(new Holder[childAnims.size()]);
}
public AnimatorSet getTarget() {
@@ -159,9 +162,68 @@
}
/**
+ * Starts playing the animation with the provided velocity optionally playing any
+ * physics based animations
+ */
+ public void startWithVelocity(Context context, boolean goingToEnd,
+ float velocity, float scale, long animationDuration) {
+ float scaleInverse = 1 / Math.abs(scale);
+ float scaledVelocity = velocity * scaleInverse;
+
+ float nextFrameProgress = Utilities.boundToRange(getProgressFraction()
+ + scaledVelocity * getSingleFrameMs(context), 0f, 1f);
+
+ // Update setters for spring
+ int springFlag = goingToEnd
+ ? SpringProperty.FLAG_CAN_SPRING_ON_END
+ : SpringProperty.FLAG_CAN_SPRING_ON_START;
+
+ long springDuration = animationDuration;
+ for (Holder h : mChildAnimations) {
+ if ((h.springProperty.flags & springFlag) != 0) {
+ SpringAnimationBuilder s = new SpringAnimationBuilder(h.anim, CURRENT_PLAY_TIME)
+ .setStartValue(clampDuration(mCurrentFraction))
+ .setEndValue(goingToEnd ? h.anim.getDuration() : 0)
+ .setStartVelocity(scaledVelocity * h.anim.getDuration())
+ .setMinimumVisibleChange(scaleInverse)
+ .setDampingRatio(h.springProperty.mDampingRatio)
+ .setStiffness(h.springProperty.mStiffness);
+
+ long expectedDurationL = s.build(context).getDuration();
+ springDuration = Math.max(expectedDurationL, springDuration);
+
+ float expectedDuration = expectedDurationL;
+ h.setter = (a, l) ->
+ s.setValue(a, mAnimationPlayer.getCurrentPlayTime() / expectedDuration);
+ h.anim.setInterpolator(LINEAR);
+ }
+ }
+
+ mAnimationPlayer.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
+
+ if (springDuration <= animationDuration) {
+ mAnimationPlayer.setDuration(animationDuration);
+ mAnimationPlayer.setInterpolator(scrollInterpolatorForVelocity(velocity));
+ } else {
+ // Since spring requires more time to run, we let the other animations play with
+ // current time and interpolation and by clamping the duration.
+ mAnimationPlayer.setDuration(springDuration);
+
+ float cutOff = animationDuration / (float) springDuration;
+ mAnimationPlayer.setInterpolator(
+ clampToProgress(scrollInterpolatorForVelocity(velocity), 0, cutOff));
+ }
+ mAnimationPlayer.start();
+ }
+
+ /**
* Pauses the currently playing animation.
*/
public void pause() {
+ // Reset property setters
+ for (Holder h : mChildAnimations) {
+ h.reset();
+ }
mAnimationPlayer.cancel();
}
@@ -175,7 +237,18 @@
/**
* Sets the current animation position and updates all the child animators accordingly.
*/
- public abstract void setPlayFraction(float fraction);
+ public void setPlayFraction(float fraction) {
+ mCurrentFraction = fraction;
+ // Let the animator report the progress but don't apply the progress to child
+ // animations if it has been cancelled.
+ if (mTargetCancelled) {
+ return;
+ }
+ long playPos = clampDuration(fraction);
+ for (Holder holder : mChildAnimations) {
+ holder.setter.set(holder.anim, playPos);
+ }
+ }
public float getProgressFraction() {
return mCurrentFraction;
@@ -207,169 +280,68 @@
}
}
- /**
- * Starts playback and sets the spring.
- */
- public void dispatchOnStartWithVelocity(float end, float velocity) {
- if (!QUICKSTEP_SPRINGS.get()) {
- dispatchOnStart();
- return;
- }
-
- if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity);
-
- for (Animator a : mAnim.getChildAnimations()) {
- if (a instanceof SpringObjectAnimator) {
- if (DEBUG) Log.d(TAG, "Found springAnimator=" + a);
- SpringObjectAnimator springAnimator = (SpringObjectAnimator) a;
- mSprings.add(springAnimator.getSpring());
- springAnimator.startSpring(end, velocity, mSpringEndListener);
- }
- }
-
- dispatchOnStart();
- }
-
- public void dispatchOnStart() {
- dispatchOnStartRecursively(mAnim);
- }
-
- private void dispatchOnStartRecursively(Animator animator) {
- List<AnimatorListener> listeners = animator instanceof SpringObjectAnimator
- ? nonNullList(((SpringObjectAnimator) animator).getObjectAnimatorListeners())
- : nonNullList(animator.getListeners());
-
- for (AnimatorListener l : listeners) {
- l.onAnimationStart(animator);
- }
-
- if (animator instanceof AnimatorSet) {
- for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
- dispatchOnStartRecursively(anim);
- }
- }
+ /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */
+ public void dispatchOnCancelWithoutCancelRunnable() {
+ dispatchOnCancelWithoutCancelRunnable(null);
}
/**
* Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
* is intended to be used only if you need to cancel but want to defer cleaning up yourself.
+ * @param callback An optional callback to run after dispatching the cancel but before resetting
+ * the onCancelRunnable.
*/
- public void dispatchOnCancelWithoutCancelRunnable() {
+ public void dispatchOnCancelWithoutCancelRunnable(@Nullable Runnable callback) {
Runnable onCancel = mOnCancelRunnable;
setOnCancelRunnable(null);
dispatchOnCancel();
+ if (callback != null) {
+ callback.run();
+ }
setOnCancelRunnable(onCancel);
}
- public void dispatchOnCancel() {
- dispatchOnCancelRecursively(mAnim);
+
+ public AnimatorPlaybackController setOnCancelRunnable(Runnable runnable) {
+ mOnCancelRunnable = runnable;
+ return this;
}
- private void dispatchOnCancelRecursively(Animator animator) {
- for (AnimatorListener l : nonNullList(animator.getListeners())) {
- l.onAnimationCancel(animator);
- }
+ public void dispatchOnStart() {
+ callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart);
+ }
- if (animator instanceof AnimatorSet) {
- for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
- dispatchOnCancelRecursively(anim);
- }
- }
+ public void dispatchOnCancel() {
+ callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationCancel);
}
public void dispatchSetInterpolator(TimeInterpolator interpolator) {
- dispatchSetInterpolatorRecursively(mAnim, interpolator);
+ callAnimatorCommandRecursively(mAnim, a -> a.setInterpolator(interpolator));
}
- private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) {
- anim.setInterpolator(interpolator);
+ private static void callListenerCommandRecursively(
+ Animator anim, BiConsumer<AnimatorListener, Animator> command) {
+ callAnimatorCommandRecursively(anim, a-> {
+ for (AnimatorListener l : nonNullList(a.getListeners())) {
+ command.accept(l, a);
+ }
+ });
+ }
+
+ private static void callAnimatorCommandRecursively(Animator anim, Consumer<Animator> command) {
+ command.accept(anim);
if (anim instanceof AnimatorSet) {
for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) {
- dispatchSetInterpolatorRecursively(child, interpolator);
+ callAnimatorCommandRecursively(child, command);
}
}
}
- public void setOnCancelRunnable(Runnable runnable) {
- mOnCancelRunnable = runnable;
- }
-
- public void skipToEnd() {
- mSkipToEnd = true;
- for (SpringAnimation spring : mSprings) {
- if (spring.canSkipToEnd()) {
- spring.skipToEnd();
- }
- }
- mAnimationPlayer.end();
- mSkipToEnd = false;
- }
-
- public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
-
- private final ValueAnimator[] mChildAnimations;
-
- private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration,
- Runnable onCancelRunnable) {
- super(anim, duration, onCancelRunnable);
-
- // Build animation list
- ArrayList<ValueAnimator> childAnims = new ArrayList<>();
- getAnimationsRecur(mAnim, childAnims);
- mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
- }
-
- private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
- long forceDuration = anim.getDuration();
- TimeInterpolator forceInterpolator = anim.getInterpolator();
- for (Animator child : anim.getChildAnimations()) {
- if (forceDuration > 0) {
- child.setDuration(forceDuration);
- }
- if (forceInterpolator != null) {
- child.setInterpolator(forceInterpolator);
- }
- if (child instanceof ValueAnimator) {
- out.add((ValueAnimator) child);
- } else if (child instanceof AnimatorSet) {
- getAnimationsRecur((AnimatorSet) child, out);
- } else {
- throw new RuntimeException("Unknown animation type " + child);
- }
- }
- }
-
- @Override
- public void setPlayFraction(float fraction) {
- mCurrentFraction = fraction;
- // Let the animator report the progress but don't apply the progress to child
- // animations if it has been cancelled.
- if (mTargetCancelled) {
- return;
- }
- long playPos = clampDuration(fraction);
- for (ValueAnimator anim : mChildAnimations) {
- anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
- }
- }
- }
-
- private boolean isAnySpringRunning() {
- for (SpringAnimation spring : mSprings) {
- if (spring.isRunning()) {
- return true;
- }
- }
- return false;
- }
-
/**
* Only dispatches the on end actions once the animator and all springs have completed running.
*/
private class OnAnimationEndDispatcher extends AnimationSuccessListener {
- boolean mAnimatorDone = false;
- boolean mSpringsDone = false;
boolean mDispatched = false;
@Override
@@ -380,39 +352,76 @@
@Override
public void onAnimationSuccess(Animator animator) {
- if (mSprings.isEmpty()) {
- mSpringsDone = mAnimatorDone = true;
- }
- if (isAnySpringRunning()) {
- mAnimatorDone = true;
- } else {
- mSpringsDone = true;
- }
-
// We wait for the spring (if any) to finish running before completing the end callback.
- if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) {
- dispatchOnEndRecursively(mAnim);
+ if (!mDispatched) {
+ callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationEnd);
if (mEndAction != null) {
mEndAction.run();
}
mDispatched = true;
}
}
-
- private void dispatchOnEndRecursively(Animator animator) {
- for (AnimatorListener l : nonNullList(animator.getListeners())) {
- l.onAnimationEnd(animator);
- }
-
- if (animator instanceof AnimatorSet) {
- for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
- dispatchOnEndRecursively(anim);
- }
- }
- }
}
private static <T> List<T> nonNullList(ArrayList<T> list) {
return list == null ? Collections.emptyList() : list;
}
+
+ /**
+ * Interface for setting position of value animator
+ */
+ private interface PositionSetter {
+
+ PositionSetter DEFAULT = (anim, playPos) ->
+ anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
+
+ void set(ValueAnimator anim, long position);
+ }
+
+ /**
+ * Holder class for various child animations
+ */
+ static class Holder {
+
+ public final ValueAnimator anim;
+
+ public final SpringProperty springProperty;
+
+ public final TimeInterpolator interpolator;
+
+ public PositionSetter setter;
+
+ Holder(Animator anim, SpringProperty springProperty) {
+ this.anim = (ValueAnimator) anim;
+ this.springProperty = springProperty;
+ this.interpolator = this.anim.getInterpolator();
+ this.setter = PositionSetter.DEFAULT;
+ }
+
+ public void reset() {
+ anim.setInterpolator(interpolator);
+ setter = PositionSetter.DEFAULT;
+ }
+ }
+
+ static void addAnimationHoldersRecur(
+ Animator anim, SpringProperty springProperty, ArrayList<Holder> out) {
+ long forceDuration = anim.getDuration();
+ TimeInterpolator forceInterpolator = anim.getInterpolator();
+ if (anim instanceof ValueAnimator) {
+ out.add(new Holder(anim, springProperty));
+ } else if (anim instanceof AnimatorSet) {
+ for (Animator child : ((AnimatorSet) anim).getChildAnimations()) {
+ if (forceDuration > 0) {
+ child.setDuration(forceDuration);
+ }
+ if (forceInterpolator != null) {
+ child.setInterpolator(forceInterpolator);
+ }
+ addAnimationHoldersRecur(child, springProperty, out);
+ }
+ } else {
+ throw new RuntimeException("Unknown animation type " + anim);
+ }
+ }
}
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index cd30dea..d814b19 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -21,7 +21,6 @@
import android.view.animation.Interpolator;
import java.util.ArrayList;
-import java.util.List;
/**
* Utility class for building animator set
@@ -42,36 +41,17 @@
public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
- public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
-
protected final ArrayList<Animator> mAnims = new ArrayList<>();
private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
- private List<Runnable> mOnFinishRunnables = new ArrayList<>();
- private int mFlags = 0;
public void play(Animator anim) {
mAnims.add(anim);
}
- public void addOnFinishRunnable(Runnable onFinishRunnable) {
- mOnFinishRunnables.add(onFinishRunnable);
- }
-
public AnimatorSet build() {
AnimatorSet anim = new AnimatorSet();
anim.playTogether(mAnims);
- if (!mOnFinishRunnables.isEmpty()) {
- anim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animation) {
- for (Runnable onFinishRunnable : mOnFinishRunnables) {
- onFinishRunnable.run();
- }
- mOnFinishRunnables.clear();
- }
- });
- }
return anim;
}
@@ -82,12 +62,4 @@
public void setInterpolator(int animId, Interpolator interpolator) {
mInterpolators.put(animId, interpolator);
}
-
- public void addFlag(int flag) {
- mFlags |= flag;
- }
-
- public boolean hasFlag(int flag) {
- return (mFlags & flag) != 0;
- }
}
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
index eaf3b1c..06d0f1c 100644
--- a/src/com/android/launcher3/anim/FlingSpringAnim.java
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -15,32 +15,40 @@
*/
package com.android.launcher3.anim;
+import android.content.Context;
+
import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
import androidx.dynamicanimation.animation.FlingAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.launcher3.R;
+import com.android.launcher3.util.DynamicResource;
+import com.android.systemui.plugins.ResourceProvider;
+
/**
* Given a property to animate and a target value and starting velocity, first apply friction to
* the fling until we pass the target, then apply a spring force to pull towards the target.
*/
public class FlingSpringAnim {
- private static final float FLING_FRICTION = 1.5f;
- private static final float SPRING_STIFFNESS = 200;
- private static final float SPRING_DAMPING = 0.8f;
-
private final FlingAnimation mFlingAnim;
private SpringAnimation mSpringAnim;
private float mTargetPosition;
- public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
- float targetPosition, float startVelocity, float minVisChange, float minValue,
- float maxValue, float springVelocityFactor, OnAnimationEndListener onEndListener) {
+ public <K> FlingSpringAnim(K object, Context context, FloatPropertyCompat<K> property,
+ float startPosition, float targetPosition, float startVelocity, float minVisChange,
+ float minValue, float maxValue, float springVelocityFactor,
+ OnAnimationEndListener onEndListener) {
+ ResourceProvider rp = DynamicResource.provider(context);
+ float damping = rp.getFloat(R.dimen.swipe_up_rect_xy_damping_ratio);
+ float stiffness = rp.getFloat(R.dimen.swipe_up_rect_xy_stiffness);
+ float friction = rp.getFloat(R.dimen.swipe_up_rect_xy_fling_friction);
+
mFlingAnim = new FlingAnimation(object, property)
- .setFriction(FLING_FRICTION)
+ .setFriction(friction)
// Have the spring pull towards the target if we've slowed down too much before
// reaching it.
.setMinimumVisibleChange(minVisChange)
@@ -54,8 +62,8 @@
.setStartValue(value)
.setStartVelocity(velocity * springVelocityFactor)
.setSpring(new SpringForce(mTargetPosition)
- .setStiffness(SPRING_STIFFNESS)
- .setDampingRatio(SPRING_DAMPING));
+ .setStiffness(stiffness)
+ .setDampingRatio(damping));
mSpringAnim.addEndListener(onEndListener);
mSpringAnim.animateToFinalPosition(mTargetPosition);
}));
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
new file mode 100644
index 0000000..562d160
--- /dev/null
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -0,0 +1,90 @@
+/*
+ * 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.anim;
+
+import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import com.android.launcher3.anim.AnimatorPlaybackController.Holder;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to keep track of a running animation.
+ *
+ * This class allows attaching end callbacks to an animation is intended to be used with
+ * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
+ * AnimationListeners are not properly dispatched.
+ *
+ * TODO: Find a better name
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class PendingAnimation {
+
+ private final ArrayList<Consumer<EndState>> mEndListeners = new ArrayList<>();
+
+ /** package private **/
+ final AnimatorSet anim = new AnimatorSet();
+ final ArrayList<Holder> animHolders = new ArrayList<>();
+
+ /**
+ * Utility method to sent an interpolator on an animation and add it to the list
+ */
+ public void add(Animator anim, TimeInterpolator interpolator) {
+ add(anim, interpolator, SpringProperty.DEFAULT);
+ }
+
+ public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) {
+ anim.setInterpolator(interpolator);
+ add(anim, springProperty);
+ }
+
+ public void add(Animator anim) {
+ add(anim, SpringProperty.DEFAULT);
+ }
+
+ public void add(Animator a, SpringProperty springProperty) {
+ anim.play(a);
+ addAnimationHoldersRecur(a, springProperty, animHolders);
+ }
+
+ public void finish(boolean isSuccess, int logAction) {
+ for (Consumer<EndState> listeners : mEndListeners) {
+ listeners.accept(new EndState(isSuccess, logAction));
+ }
+ mEndListeners.clear();
+ }
+
+ public void addEndListener(Consumer<EndState> listener) {
+ mEndListeners.add(listener);
+ }
+
+ public static class EndState {
+ public boolean isSuccess;
+ public int logAction;
+
+ public EndState(boolean isSuccess, int logAction) {
+ this.isSuccess = isSuccess;
+ this.logAction = logAction;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index 757edff..0b2eb48 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -19,7 +19,8 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.util.Property;
+import android.util.FloatProperty;
+import android.util.IntProperty;
import android.view.View;
/**
@@ -36,14 +37,14 @@
}
}
- public <T> void setFloat(T target, Property<T, Float> property, float value,
+ public <T> void setFloat(T target, FloatProperty<T> property, float value,
TimeInterpolator interpolator) {
- property.set(target, value);
+ property.setValue(target, value);
}
- public <T> void setInt(T target, Property<T, Integer> property, int value,
+ public <T> void setInt(T target, IntProperty<T> property, int value,
TimeInterpolator interpolator) {
- property.set(target, value);
+ property.setValue(target, value);
}
public static class AnimatedPropertySetter extends PropertySetter {
@@ -68,7 +69,7 @@
}
@Override
- public <T> void setFloat(T target, Property<T, Float> property, float value,
+ public <T> void setFloat(T target, FloatProperty<T> property, float value,
TimeInterpolator interpolator) {
if (property.get(target) == value) {
return;
@@ -79,7 +80,7 @@
}
@Override
- public <T> void setInt(T target, Property<T, Integer> property, int value,
+ public <T> void setInt(T target, IntProperty<T> property, int value,
TimeInterpolator interpolator) {
if (property.get(target) == value) {
return;
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index 0f34c1e..f22a9f0 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -15,16 +15,15 @@
*/
package com.android.launcher3.anim;
-import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.FloatProperty;
-import com.android.launcher3.util.DefaultDisplay;
-
import androidx.annotation.FloatRange;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.launcher3.util.DefaultDisplay;
+
/**
* Utility class to build an object animator which follows the same path as a spring animation for
* an underdamped spring.
@@ -192,12 +191,8 @@
long durationMs = (long) (1000.0 * duration);
ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
- animator.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- mProperty.setValue(mTarget, mEndValue);
- }
- });
+ animator.addListener(AnimationSuccessListener.forRunnable(
+ () -> mProperty.setValue(mTarget, mEndValue)));
return animator;
}
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
deleted file mode 100644
index 91a3106..0000000
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.anim;
-
-import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
-
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.FloatProperty;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-/**
- * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
- * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
- */
-public class SpringObjectAnimator<T> extends ValueAnimator {
-
- private static final String TAG = "SpringObjectAnimator";
- private static boolean DEBUG = false;
-
- private ObjectAnimator mObjectAnimator;
- private float[] mValues;
-
- private SpringAnimation mSpring;
- private SpringProperty<T> mProperty;
-
- private ArrayList<AnimatorListener> mListeners;
- private boolean mSpringEnded = true;
- private boolean mAnimatorEnded = true;
- private boolean mEnded = true;
-
- public SpringObjectAnimator(T object, FloatProperty<T> property, float minimumVisibleChange,
- float damping, float stiffness, float... values) {
- mSpring = new SpringAnimation(object, createFloatPropertyCompat(property));
- mSpring.setMinimumVisibleChange(minimumVisibleChange);
- mSpring.setSpring(new SpringForce(0)
- .setDampingRatio(damping)
- .setStiffness(stiffness));
- mSpring.setStartVelocity(0.01f);
- mProperty = new SpringProperty<>(property, mSpring);
- mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values);
- mValues = values;
- mListeners = new ArrayList<>();
- setFloatValues(values);
-
- // We use this listener and track mListeners so that we can sync the animator and spring
- // listeners.
- mObjectAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mAnimatorEnded = false;
- mEnded = false;
- for (AnimatorListener l : mListeners) {
- l.onAnimationStart(animation);
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimatorEnded = true;
- tryEnding();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- for (AnimatorListener l : mListeners) {
- l.onAnimationCancel(animation);
- }
- mSpring.cancel();
- }
- });
-
- mSpring.addUpdateListener((animation, value, velocity) -> {
- mSpringEnded = false;
- mEnded = false;
- });
- mSpring.addEndListener((animation, canceled, value, velocity) -> {
- mSpringEnded = true;
- tryEnding();
- });
- }
-
- private void tryEnding() {
- if (DEBUG) {
- Log.d(TAG, "tryEnding#mAnimatorEnded=" + mAnimatorEnded + ", mSpringEnded="
- + mSpringEnded + ", mEnded=" + mEnded);
- }
-
- // If springs are disabled, ignore value of mSpringEnded
- if (mAnimatorEnded && (mSpringEnded || !QUICKSTEP_SPRINGS.get()) && !mEnded) {
- for (AnimatorListener l : mListeners) {
- l.onAnimationEnd(this);
- }
- mEnded = true;
- }
- }
-
- public SpringAnimation getSpring() {
- return mSpring;
- }
-
- /**
- * Initializes and sets up the spring to take over controlling the object.
- */
- public void startSpring(float end, float velocity, OnAnimationEndListener endListener) {
- // Cancel the spring so we can set new start velocity and final position. We need to remove
- // the listener since the spring is not actually ending.
- mSpring.removeEndListener(endListener);
- mSpring.cancel();
- mSpring.addEndListener(endListener);
-
- mProperty.switchToSpring();
-
- mSpring.setStartVelocity(velocity);
-
- float startValue = end == 0 ? mValues[1] : mValues[0];
- float endValue = end == 0 ? mValues[0] : mValues[1];
- mSpring.setStartValue(startValue);
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
- mSpring.animateToFinalPosition(endValue);
- }, getStartDelay());
- }
-
- @Override
- public void addListener(AnimatorListener listener) {
- mListeners.add(listener);
- }
-
- public ArrayList<AnimatorListener> getObjectAnimatorListeners() {
- return mObjectAnimator.getListeners();
- }
-
- @Override
- public ArrayList<AnimatorListener> getListeners() {
- return mListeners;
- }
-
- @Override
- public void removeAllListeners() {
- mListeners.clear();
- }
-
- @Override
- public void removeListener(AnimatorListener listener) {
- mListeners.remove(listener);
- }
-
- @Override
- public void addPauseListener(AnimatorPauseListener listener) {
- mObjectAnimator.addPauseListener(listener);
- }
-
- @Override
- public void cancel() {
- mObjectAnimator.cancel();
- mSpring.cancel();
- }
-
- @Override
- public void end() {
- mObjectAnimator.end();
- }
-
- @Override
- public long getDuration() {
- return mObjectAnimator.getDuration();
- }
-
- @Override
- public TimeInterpolator getInterpolator() {
- return mObjectAnimator.getInterpolator();
- }
-
- @Override
- public long getStartDelay() {
- return mObjectAnimator.getStartDelay();
- }
-
- @Override
- public long getTotalDuration() {
- return mObjectAnimator.getTotalDuration();
- }
-
- @Override
- public boolean isPaused() {
- return mObjectAnimator.isPaused();
- }
-
- @Override
- public boolean isRunning() {
- return mObjectAnimator.isRunning();
- }
-
- @Override
- public boolean isStarted() {
- return mObjectAnimator.isStarted();
- }
-
- @Override
- public void pause() {
- mObjectAnimator.pause();
- }
-
- @Override
- public void removePauseListener(AnimatorPauseListener listener) {
- mObjectAnimator.removePauseListener(listener);
- }
-
- @Override
- public void resume() {
- mObjectAnimator.resume();
- }
-
- @Override
- public ValueAnimator setDuration(long duration) {
- return mObjectAnimator.setDuration(duration);
- }
-
- @Override
- public void setInterpolator(TimeInterpolator value) {
- mObjectAnimator.setInterpolator(value);
- }
-
- @Override
- public void setStartDelay(long startDelay) {
- mObjectAnimator.setStartDelay(startDelay);
- }
-
- @Override
- public void setTarget(Object target) {
- mObjectAnimator.setTarget(target);
- }
-
- @Override
- public void start() {
- mObjectAnimator.start();
- }
-
- @Override
- public void setCurrentFraction(float fraction) {
- mObjectAnimator.setCurrentFraction(fraction);
- }
-
- @Override
- public void setCurrentPlayTime(long playTime) {
- mObjectAnimator.setCurrentPlayTime(playTime);
- }
-
- public static class SpringProperty<T> extends FloatProperty<T> {
-
- boolean useSpring = false;
- final FloatProperty<T> mProperty;
- final SpringAnimation mSpring;
-
- public SpringProperty(FloatProperty<T> property, SpringAnimation spring) {
- super(property.getName());
- mProperty = property;
- mSpring = spring;
- }
-
- public void switchToSpring() {
- useSpring = true;
- }
-
- @Override
- public Float get(T object) {
- return mProperty.get(object);
- }
-
- @Override
- public void setValue(T object, float progress) {
- if (useSpring) {
- mSpring.animateToFinalPosition(progress);
- } else {
- mProperty.setValue(object, progress);
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/anim/SpringProperty.java b/src/com/android/launcher3/anim/SpringProperty.java
new file mode 100644
index 0000000..caedd6c
--- /dev/null
+++ b/src/com/android/launcher3/anim/SpringProperty.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.anim;
+
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * Utility class to store configurations for spring animation
+ */
+public class SpringProperty {
+
+ public static final SpringProperty DEFAULT = new SpringProperty();
+
+ // Play spring when the animation is going towards the end
+ public static final int FLAG_CAN_SPRING_ON_END = 1 << 0;
+ // Play spring when animation is going towards the start (in reverse direction)
+ public static final int FLAG_CAN_SPRING_ON_START = 1 << 1;
+
+ public final int flags;
+
+ float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+ float mStiffness = SpringForce.STIFFNESS_MEDIUM;
+
+ public SpringProperty() {
+ this(0);
+ }
+
+ public SpringProperty(int flags) {
+ this.flags = flags;
+ }
+
+ public SpringProperty setDampingRatio(float dampingRatio) {
+ mDampingRatio = dampingRatio;
+ return this;
+ }
+
+ public SpringProperty setStiffness(float stiffness) {
+ mStiffness = stiffness;
+ return this;
+ }
+}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 81c95cb..1d32d1d 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -18,15 +18,16 @@
import android.content.Context;
import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import com.android.launcher3.testing.TestProtocol;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Utilities;
-
-import java.util.function.Consumer;
+import com.android.launcher3.testing.TestProtocol;
public class AccessibilityManagerCompat {
@@ -39,11 +40,21 @@
return isAccessibilityEnabled(context);
}
- public static void sendCustomAccessibilityEvent(View target, int type, String text) {
- if (isObservedEventType(target.getContext(), type)) {
+ /**
+ *
+ * @param target The view the accessibility event is initialized on.
+ * If null, this method has no effect.
+ * @param type See TYPE_ constants defined in {@link AccessibilityEvent}.
+ * @param text Optional text to add to the event, which will be announced to the user.
+ */
+ public static void sendCustomAccessibilityEvent(@Nullable View target, int type,
+ @Nullable String text) {
+ if (target != null && isObservedEventType(target.getContext(), type)) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
target.onInitializeAccessibilityEvent(event);
- event.getText().add(text);
+ if (!TextUtils.isEmpty(text)) {
+ event.getText().add(text);
+ }
getManager(target.getContext()).sendAccessibilityEvent(event);
}
}
@@ -60,6 +71,7 @@
parcel.putInt(TestProtocol.STATE_FIELD, stateOrdinal);
sendEventToTest(accessibilityManager, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
+ Log.d(TestProtocol.PERMANENT_DIAG_TAG, "sendStateEventToTest: " + stateOrdinal);
}
public static void sendScrollFinishedEventToTest(Context context) {
@@ -103,24 +115,6 @@
return accessibilityManager;
}
- public static boolean processTestRequest(Context context, String eventTag, int action,
- Bundle request, Consumer<Bundle> responseFiller) {
- final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
- if (accessibilityManager == null) return false;
-
- // The test sends a request via a ACTION_SET_TEXT.
- if (action == AccessibilityNodeInfo.ACTION_SET_TEXT &&
- eventTag.equals(request.getCharSequence(
- AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE))) {
- final Bundle response = new Bundle();
- responseFiller.accept(response);
- AccessibilityManagerCompat.sendEventToTest(
- accessibilityManager, eventTag + TestProtocol.RESPONSE_MESSAGE_POSTFIX, response);
- return true;
- }
- return false;
- }
-
public static int getRecommendedTimeoutMillis(Context context, int originalTimeout, int flags) {
if (Utilities.ATLEAST_Q) {
return getManager(context).getRecommendedTimeoutMillis(originalTimeout, flags);
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
deleted file mode 100644
index fc5d11c..0000000
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
-
-import java.util.HashMap;
-import java.util.List;
-
-public abstract class AppWidgetManagerCompat {
-
- private static final Object sInstanceLock = new Object();
- private static AppWidgetManagerCompat sInstance;
-
- public static AppWidgetManagerCompat getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- if (Utilities.ATLEAST_OREO) {
- sInstance = new AppWidgetManagerCompatVO(context.getApplicationContext());
- } else {
- sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
- }
- }
- return sInstance;
- }
- }
-
- final AppWidgetManager mAppWidgetManager;
- final Context mContext;
-
- AppWidgetManagerCompat(Context context) {
- mContext = context;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
- }
-
- public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) {
- if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
- return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId);
- }
- AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
- return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
- }
-
- public abstract List<AppWidgetProviderInfo> getAllProviders(
- @Nullable PackageUserKey packageUser);
-
- public abstract boolean bindAppWidgetIdIfAllowed(
- int appWidgetId, AppWidgetProviderInfo info, Bundle options);
-
- public abstract LauncherAppWidgetProviderInfo findProvider(
- ComponentName provider, UserHandle user);
-
- public abstract HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap();
-}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
deleted file mode 100644
index c8b1f67..0000000
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomAppWidgetProviderInfo;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-class AppWidgetManagerCompatVL extends AppWidgetManagerCompat {
-
- private final UserManager mUserManager;
-
- AppWidgetManagerCompatVL(Context context) {
- super(context);
- mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- }
-
- @Override
- public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
- return Collections.emptyList();
- }
- if (packageUser == null) {
- ArrayList<AppWidgetProviderInfo> providers = new ArrayList<>();
- for (UserHandle user : mUserManager.getUserProfiles()) {
- providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
- }
- providers.addAll(getCustomWidgets());
- return providers;
- }
- // Only get providers for the given package/user.
- List<AppWidgetProviderInfo> providers = new ArrayList<>(mAppWidgetManager
- .getInstalledProvidersForProfile(packageUser.mUser));
- Iterator<AppWidgetProviderInfo> iterator = providers.iterator();
- while (iterator.hasNext()) {
- if (!iterator.next().provider.getPackageName().equals(packageUser.mPackageName)) {
- iterator.remove();
- }
- }
-
- if (Process.myUserHandle().equals(packageUser.mUser)
- && mContext.getPackageName().equals(packageUser.mPackageName)) {
- providers.addAll(getCustomWidgets());
- }
- return providers;
- }
-
- @Override
- public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
- Bundle options) {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
- return false;
- }
- if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
- return true;
- }
- return mAppWidgetManager.bindAppWidgetIdIfAllowed(
- appWidgetId, info.getProfile(), info.provider, options);
- }
-
- @Override
- public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandle user) {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
- return null;
- }
- for (AppWidgetProviderInfo info :
- getAllProviders(new PackageUserKey(provider.getPackageName(), user))) {
- if (info.provider.equals(provider)) {
- return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
- }
- }
-
- if (Process.myUserHandle().equals(user)) {
- for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
- if (info.provider.equals(provider)) {
- return info;
- }
- }
- }
- return null;
- }
-
- @Override
- public HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap() {
- HashMap<ComponentKey, AppWidgetProviderInfo> result = new HashMap<>();
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
- return result;
- }
- for (UserHandle user : mUserManager.getUserProfiles()) {
- for (AppWidgetProviderInfo info :
- mAppWidgetManager.getInstalledProvidersForProfile(user)) {
- result.put(new ComponentKey(info.provider, user), info);
- }
- }
- for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
- result.put(new ComponentKey(info.provider, info.getProfile()), info);
- }
- return result;
- }
-
- List<CustomAppWidgetProviderInfo> getCustomWidgets() {
- return CustomWidgetManager.INSTANCE.get(mContext).getCustomWidgets();
- }
-}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
deleted file mode 100644
index 11ec333..0000000
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.compat;
-
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.PackageUserKey;
-
-import java.util.Collections;
-import java.util.List;
-
-class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL {
-
- AppWidgetManagerCompatVO(Context context) {
- super(context);
- }
-
- @Override
- public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
- return Collections.emptyList();
- }
- if (packageUser == null) {
- return super.getAllProviders(null);
- }
- return mAppWidgetManager.getInstalledProvidersForPackage(packageUser.mPackageName,
- packageUser.mUser);
- }
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
deleted file mode 100644
index 39f6949..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionCallback;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.PackageUserKey;
-
-import java.util.List;
-
-public abstract class LauncherAppsCompat {
-
- public interface OnAppsChangedCallbackCompat {
- default void onPackageRemoved(String packageName, UserHandle user) { }
- default void onPackageAdded(String packageName, UserHandle user) { }
- default void onPackageChanged(String packageName, UserHandle user) { }
- default void onPackagesAvailable(String[] packageNames, UserHandle user,
- boolean replacing) { }
- default void onPackagesUnavailable(String[] packageNames, UserHandle user,
- boolean replacing) { }
- default void onPackagesSuspended(String[] packageNames, UserHandle user) { }
- default void onPackagesUnsuspended(String[] packageNames, UserHandle user) { }
- default void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
- UserHandle user) { }
- }
-
- protected LauncherAppsCompat() {
- }
-
- private static LauncherAppsCompat sInstance;
- private static final Object sInstanceLock = new Object();
-
- public static LauncherAppsCompat getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- if (Utilities.ATLEAST_Q) {
- sInstance = new LauncherAppsCompatVQ(context.getApplicationContext());
- } else if (Utilities.ATLEAST_OREO) {
- sInstance = new LauncherAppsCompatVO(context.getApplicationContext());
- } else {
- sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
- }
- }
- return sInstance;
- }
- }
-
- public abstract List<LauncherActivityInfo> getActivityList(String packageName,
- UserHandle user);
- public abstract LauncherActivityInfo resolveActivity(Intent intent,
- UserHandle user);
- public abstract void startActivityForProfile(ComponentName component, UserHandle user,
- Rect sourceBounds, Bundle opts);
- public abstract ApplicationInfo getApplicationInfo(
- String packageName, int flags, UserHandle user);
- public abstract void showAppDetailsForProfile(ComponentName component, UserHandle user,
- Rect sourceBounds, Bundle opts);
- public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
- public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
- public abstract boolean isPackageEnabledForProfile(String packageName, UserHandle user);
- public abstract boolean isActivityEnabledForProfile(ComponentName component,
- UserHandle user);
- public abstract List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
- @Nullable PackageUserKey packageUser);
-
- public abstract List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
-
- public abstract void registerSessionCallback(LooperExecutor executor,
- SessionCallback sessionCallback);
- public abstract void unregisterSessionCallback(SessionCallback sessionCallback);
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
deleted file mode 100644
index 281274c..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionCallback;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.PackageUserKey;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class LauncherAppsCompatVL extends LauncherAppsCompat {
-
- protected final LauncherApps mLauncherApps;
- protected final Context mContext;
-
- private final ArrayMap<OnAppsChangedCallbackCompat, WrappedCallback> mCallbacks =
- new ArrayMap<>();
-
- LauncherAppsCompatVL(Context context) {
- mContext = context;
- mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
- }
-
- @Override
- public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
- return mLauncherApps.getActivityList(packageName, user);
- }
-
- @Override
- public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
- return mLauncherApps.resolveActivity(intent, user);
- }
-
- @Override
- public void startActivityForProfile(ComponentName component, UserHandle user,
- Rect sourceBounds, Bundle opts) {
- mLauncherApps.startMainActivity(component, user, sourceBounds, opts);
- }
-
- @Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
- final boolean isPrimaryUser = Process.myUserHandle().equals(user);
- if (!isPrimaryUser && (flags == 0)) {
- // We are looking for an installed app on a secondary profile. Prior to O, the only
- // entry point for work profiles is through the LauncherActivity.
- List<LauncherActivityInfo> activityList =
- mLauncherApps.getActivityList(packageName, user);
- return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
- }
- try {
- ApplicationInfo info =
- mContext.getPackageManager().getApplicationInfo(packageName, flags);
- // There is no way to check if the app is installed for managed profile. But for
- // primary profile, we can still have this check.
- if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
- || !info.enabled) {
- return null;
- }
- return info;
- } catch (PackageManager.NameNotFoundException e) {
- // Package not found
- return null;
- }
- }
-
- @Override
- public void showAppDetailsForProfile(ComponentName component, UserHandle user,
- Rect sourceBounds, Bundle opts) {
- mLauncherApps.startAppDetailsActivity(component, user, sourceBounds, opts);
- }
-
- @Override
- public void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
- WrappedCallback wrappedCallback = new WrappedCallback(callback);
- synchronized (mCallbacks) {
- mCallbacks.put(callback, wrappedCallback);
- }
- mLauncherApps.registerCallback(wrappedCallback);
- }
-
- @Override
- public void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
- final WrappedCallback wrappedCallback;
- synchronized (mCallbacks) {
- wrappedCallback = mCallbacks.remove(callback);
- }
- if (wrappedCallback != null) {
- mLauncherApps.unregisterCallback(wrappedCallback);
- }
- }
-
- @Override
- public boolean isPackageEnabledForProfile(String packageName, UserHandle user) {
- return mLauncherApps.isPackageEnabled(packageName, user);
- }
-
- @Override
- public boolean isActivityEnabledForProfile(ComponentName component, UserHandle user) {
- return mLauncherApps.isActivityEnabled(component, user);
- }
-
- private static class WrappedCallback extends LauncherApps.Callback {
- private final LauncherAppsCompat.OnAppsChangedCallbackCompat mCallback;
-
- public WrappedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
- mCallback = callback;
- }
-
- @Override
- public void onPackageRemoved(String packageName, UserHandle user) {
- mCallback.onPackageRemoved(packageName, user);
- }
-
- @Override
- public void onPackageAdded(String packageName, UserHandle user) {
- mCallback.onPackageAdded(packageName, user);
- }
-
- @Override
- public void onPackageChanged(String packageName, UserHandle user) {
- mCallback.onPackageChanged(packageName, user);
- }
-
- @Override
- public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
- mCallback.onPackagesAvailable(packageNames, user, replacing);
- }
-
- @Override
- public void onPackagesUnavailable(String[] packageNames, UserHandle user,
- boolean replacing) {
- mCallback.onPackagesUnavailable(packageNames, user, replacing);
- }
-
- @Override
- public void onPackagesSuspended(String[] packageNames, UserHandle user) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.APP_NOT_DISABLED, "onPackagesSuspended: " +
- Arrays.toString(packageNames));
- }
- mCallback.onPackagesSuspended(packageNames, user);
- }
-
- @Override
- public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
- mCallback.onPackagesUnsuspended(packageNames, user);
- }
-
- @Override
- public void onShortcutsChanged(@NonNull String packageName,
- @NonNull List<ShortcutInfo> shortcuts,
- @NonNull UserHandle user) {
- mCallback.onShortcutsChanged(packageName, shortcuts, user);
- }
- }
-
- @Override
- public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
- @Nullable PackageUserKey packageUser) {
- List<ShortcutConfigActivityInfo> result = new ArrayList<>();
- if (packageUser != null && !packageUser.mUser.equals(Process.myUserHandle())) {
- return result;
- }
- PackageManager pm = mContext.getPackageManager();
- for (ResolveInfo info :
- pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) {
- if (packageUser == null || packageUser.mPackageName
- .equals(info.activityInfo.packageName)) {
- result.add(new ShortcutConfigActivityInfoVL(info.activityInfo));
- }
- }
- return result;
- }
-
- @Override
- public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
- return mContext.getPackageManager().getPackageInstaller().getAllSessions();
- }
-
- @Override
- public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
- mContext.getPackageManager().getPackageInstaller().registerSessionCallback(sessionCallback,
- executor.getHandler());
- }
-
- @Override
- public void unregisterSessionCallback(SessionCallback sessionCallback) {
- mContext.getPackageManager().getPackageInstaller()
- .unregisterSessionCallback(sessionCallback);
- }
-}
-
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
deleted file mode 100644
index 5e13d00..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.compat;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.PinItemRequest;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.os.Build;
-import android.os.Parcelable;
-import android.os.Process;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.util.PackageUserKey;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@TargetApi(26)
-public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
-
- LauncherAppsCompatVO(Context context) {
- super(context);
- }
-
- @Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
- try {
- ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
- return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
- ? null : info;
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- @Override
- public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
- @Nullable PackageUserKey packageUser) {
- List<ShortcutConfigActivityInfo> result = new ArrayList<>();
- UserHandle myUser = Process.myUserHandle();
-
- final List<UserHandle> users;
- final String packageName;
- if (packageUser == null) {
- users = UserManagerCompat.getInstance(mContext).getUserProfiles();
- packageName = null;
- } else {
- users = new ArrayList<>(1);
- users.add(packageUser.mUser);
- packageName = packageUser.mPackageName;
- }
- for (UserHandle user : users) {
- boolean ignoreTargetSdk = myUser.equals(user);
- List<LauncherActivityInfo> activities =
- mLauncherApps.getShortcutConfigActivityList(packageName, user);
- for (LauncherActivityInfo activityInfo : activities) {
- if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion >=
- Build.VERSION_CODES.O) {
- result.add(new ShortcutConfigActivityInfoVO(activityInfo));
- }
- }
- }
-
- return result;
- }
-
- /**
- * request.accept() will initiate the following flow:
- * -> go-to-system-process for actual processing (a)
- * -> callback-to-launcher on UI thread (b)
- * -> post callback on the worker thread (c)
- * -> Update model and unpin (in system) any shortcut not in out model. (d)
- *
- * Note that (b) will take at-least one frame as it involves posting callback from binder
- * thread to UI thread.
- * If (d) happens before we add this shortcut to our model, we will end up unpinning
- * the shortcut in the system.
- * Here its the caller's responsibility to add the newly created WorkspaceItemInfo immediately
- * to the model (which may involves a single post-to-worker-thread). That will guarantee
- * that (d) happens after model is updated.
- */
- @Nullable
- public static WorkspaceItemInfo createWorkspaceItemFromPinItemRequest(
- Context context, final PinItemRequest request, final long acceptDelay) {
- if (request != null &&
- request.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT &&
- request.isValid()) {
-
- if (acceptDelay <= 0) {
- if (!request.accept()) {
- return null;
- }
- } else {
- // Block the worker thread until the accept() is called.
- MODEL_EXECUTOR.execute(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(acceptDelay);
- } catch (InterruptedException e) {
- // Ignore
- }
- if (request.isValid()) {
- request.accept();
- }
- }
- });
- }
-
- ShortcutInfo si = request.getShortcutInfo();
- WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
- // Apply the unbadged icon and fetch the actual icon asynchronously.
- LauncherIcons li = LauncherIcons.obtain(context);
- info.applyFrom(li.createShortcutIcon(si, false /* badged */));
- li.recycle();
- LauncherAppState.getInstance(context).getModel()
- .updateAndBindWorkspaceItem(info, si);
- return info;
- } else {
- return null;
- }
- }
-
- public static PinItemRequest getPinItemRequest(Intent intent) {
- Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);
- return extra instanceof PinItemRequest ? (PinItemRequest) extra : null;
- }
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
deleted file mode 100644
index 48805af..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionCallback;
-
-import com.android.launcher3.util.LooperExecutor;
-
-import java.util.List;
-
-@TargetApi(29)
-public class LauncherAppsCompatVQ extends LauncherAppsCompatVO {
-
- LauncherAppsCompatVQ(Context context) {
- super(context);
- }
-
- public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
- return mLauncherApps.getAllPackageInstallerSessions();
- }
-
- @Override
- public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
- mLauncherApps.registerPackageInstallerSessionCallback(executor, sessionCallback);
- }
-
- @Override
- public void unregisterSessionCallback(SessionCallback sessionCallback) {
- mLauncherApps.unregisterPackageInstallerSessionCallback(sessionCallback);
- }
-}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
deleted file mode 100644
index 55df98b..0000000
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.os.Process;
-import android.os.UserHandle;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.PackageUserKey;
-
-public abstract class PackageInstallerCompat {
-
- // Set<String> of session ids of promise icons that have been added to the home screen
- // as FLAG_PROMISE_NEW_INSTALLS.
- protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
-
- public static final int STATUS_INSTALLED = 0;
- public static final int STATUS_INSTALLING = 1;
- public static final int STATUS_FAILED = 2;
-
- private static final Object sInstanceLock = new Object();
- private static PackageInstallerCompat sInstance;
-
- public static PackageInstallerCompat getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- sInstance = new PackageInstallerCompatVL(context);
- }
- return sInstance;
- }
- }
-
- public static UserHandle getUserHandle(SessionInfo info) {
- return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
- }
-
- /**
- * @return a map of active installs to their progress
- */
- public abstract HashMap<PackageUserKey, SessionInfo> updateAndGetActiveSessionCache();
-
- /**
- * @return an active SessionInfo for {@param pkg} or null if none exists.
- */
- public abstract SessionInfo getActiveSessionInfo(UserHandle user, String pkg);
-
- public abstract void onStop();
-
- public static final class PackageInstallInfo {
- public final ComponentName componentName;
- public final String packageName;
- public final int state;
- public final int progress;
- public final UserHandle user;
-
- private PackageInstallInfo(@NonNull SessionInfo info) {
- this.state = STATUS_INSTALLING;
- this.packageName = info.getAppPackageName();
- this.componentName = new ComponentName(packageName, "");
- this.progress = (int) (info.getProgress() * 100f);
- this.user = getUserHandle(info);
- }
-
- public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) {
- this.state = state;
- this.packageName = packageName;
- this.componentName = new ComponentName(packageName, "");
- this.progress = progress;
- this.user = user;
- }
-
- public static PackageInstallInfo fromInstallingState(SessionInfo info) {
- return new PackageInstallInfo(info);
- }
-
- public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) {
- return new PackageInstallInfo(packageName, state, 0 /* progress */, user);
- }
-
- }
-
- public abstract List<SessionInfo> getAllVerifiedSessions();
-
- /**
- * Returns true if a promise icon was already added to the home screen for {@param sessionId}.
- * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS.
- */
- public abstract boolean promiseIconAddedForId(int sessionId);
-
- /**
- * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS.
- */
- public abstract void removePromiseIconId(int sessionId);
-}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
deleted file mode 100644
index 409b21d..0000000
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionCallback;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.SparseArray;
-
-import com.android.launcher3.SessionCommitReceiver;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-import static com.android.launcher3.Utilities.getPrefs;
-
-public class PackageInstallerCompatVL extends PackageInstallerCompat {
-
- private static final boolean DEBUG = false;
-
- @Thunk final SparseArray<PackageUserKey> mActiveSessions = new SparseArray<>();
-
- @Thunk final PackageInstaller mInstaller;
- private final IconCache mCache;
- private final Context mAppContext;
- private final HashMap<String,Boolean> mSessionVerifiedMap = new HashMap<>();
- private final LauncherAppsCompat mLauncherApps;
- private final IntSet mPromiseIconIds;
-
- PackageInstallerCompatVL(Context context) {
- mAppContext = context.getApplicationContext();
- mInstaller = context.getPackageManager().getPackageInstaller();
- mCache = LauncherAppState.getInstance(context).getIconCache();
- mLauncherApps = LauncherAppsCompat.getInstance(context);
- mLauncherApps.registerSessionCallback(MODEL_EXECUTOR, mCallback);
- mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
- getPrefs(context).getString(PROMISE_ICON_IDS, "")));
-
- cleanUpPromiseIconIds();
- }
-
- private void cleanUpPromiseIconIds() {
- IntArray existingIds = new IntArray();
- for (SessionInfo info : updateAndGetActiveSessionCache().values()) {
- existingIds.add(info.getSessionId());
- }
- IntArray idsToRemove = new IntArray();
-
- for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) {
- if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) {
- idsToRemove.add(mPromiseIconIds.getArray().get(i));
- }
- }
- for (int i = idsToRemove.size() - 1; i >= 0; --i) {
- mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
- }
- }
-
- @Override
- public HashMap<PackageUserKey, SessionInfo> updateAndGetActiveSessionCache() {
- HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
- for (SessionInfo info : getAllVerifiedSessions()) {
- addSessionInfoToCache(info, getUserHandle(info));
- if (info.getAppPackageName() != null) {
- activePackages.put(new PackageUserKey(info.getAppPackageName(),
- getUserHandle(info)), info);
- mActiveSessions.put(info.getSessionId(),
- new PackageUserKey(info.getAppPackageName(), getUserHandle(info)));
- }
- }
- return activePackages;
- }
-
- public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
- for (SessionInfo info : getAllVerifiedSessions()) {
- boolean match = pkg.equals(info.getAppPackageName());
- if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) {
- match = false;
- }
- if (match) {
- return info;
- }
- }
- return null;
- }
-
- @Thunk void addSessionInfoToCache(SessionInfo info, UserHandle user) {
- String packageName = info.getAppPackageName();
- if (packageName != null) {
- mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
- info.getAppLabel());
- }
- }
-
- @Override
- public void onStop() {
- mLauncherApps.unregisterSessionCallback(mCallback);
- }
-
- @Thunk void sendUpdate(PackageInstallInfo info) {
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app != null) {
- app.getModel().setPackageState(info);
- }
- }
-
- /**
- * Add a promise app icon to the workspace iff:
- * - The settings for it are enabled
- * - The user installed the app
- * - There is an app icon and label (For apps with no launching activity, no icon is provided).
- * - The app is not already installed
- * - A promise icon for the session has not already been created
- */
- private void tryQueuePromiseAppIcon(SessionInfo sessionInfo) {
- if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
- && SessionCommitReceiver.isEnabled(mAppContext)
- && verify(sessionInfo) != null
- && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
- && sessionInfo.getAppIcon() != null
- && !TextUtils.isEmpty(sessionInfo.getAppLabel())
- && !mPromiseIconIds.contains(sessionInfo.getSessionId())
- && mLauncherApps.getApplicationInfo(sessionInfo.getAppPackageName(), 0,
- getUserHandle(sessionInfo)) == null) {
- SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
- mPromiseIconIds.add(sessionInfo.getSessionId());
- updatePromiseIconPrefs();
- }
- }
-
- private final SessionCallback mCallback = new SessionCallback() {
-
- @Override
- public void onCreated(int sessionId) {
- SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
- if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS && sessionInfo != null) {
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app != null) {
- app.getModel().onInstallSessionCreated(
- PackageInstallInfo.fromInstallingState(sessionInfo));
- }
- }
-
- tryQueuePromiseAppIcon(sessionInfo);
- }
-
- @Override
- public void onFinished(int sessionId, boolean success) {
- // For a finished session, we can't get the session info. So use the
- // packageName from our local cache.
- PackageUserKey key = mActiveSessions.get(sessionId);
- mActiveSessions.remove(sessionId);
-
- if (key != null && key.mPackageName != null) {
- String packageName = key.mPackageName;
- sendUpdate(PackageInstallInfo.fromState(success ? STATUS_INSTALLED : STATUS_FAILED,
- packageName, key.mUser));
-
- if (!success && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
- && mPromiseIconIds.contains(sessionId)) {
- LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- if (appState != null) {
- appState.getModel().onSessionFailure(packageName, key.mUser);
- }
- // If it is successful, the id is removed in the the package added flow.
- removePromiseIconId(sessionId);
- }
- }
- }
-
- @Override
- public void onProgressChanged(int sessionId, float progress) {
- SessionInfo session = verify(mInstaller.getSessionInfo(sessionId));
- if (session != null && session.getAppPackageName() != null) {
- sendUpdate(PackageInstallInfo.fromInstallingState(session));
- }
- }
-
- @Override
- public void onActiveChanged(int sessionId, boolean active) { }
-
- @Override
- public void onBadgingChanged(int sessionId) {
- SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
- if (sessionInfo != null) {
- tryQueuePromiseAppIcon(sessionInfo);
- }
- }
-
- private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
- SessionInfo session = verify(mInstaller.getSessionInfo(sessionId));
- if (session != null && session.getAppPackageName() != null) {
- UserHandle user = getUserHandle(session);
- mActiveSessions.put(session.getSessionId(),
- new PackageUserKey(session.getAppPackageName(), user));
- addSessionInfoToCache(session, user);
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app != null) {
- app.getModel().updateSessionDisplayInfo(session.getAppPackageName(),
- user);
- }
- return session;
- }
- return null;
- }
- };
-
- private PackageInstaller.SessionInfo verify(PackageInstaller.SessionInfo sessionInfo) {
- if (sessionInfo == null
- || sessionInfo.getInstallerPackageName() == null
- || TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
- return null;
- }
- String pkg = sessionInfo.getInstallerPackageName();
- synchronized (mSessionVerifiedMap) {
- if (!mSessionVerifiedMap.containsKey(pkg)) {
- LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext);
- boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg,
- ApplicationInfo.FLAG_SYSTEM, getUserHandle(sessionInfo)) != null;
- mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
- }
- }
- return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
- }
-
- @Override
- public List<SessionInfo> getAllVerifiedSessions() {
- List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q
- ? mLauncherApps.getAllPackageInstallerSessions()
- : mInstaller.getAllSessions());
- Iterator<SessionInfo> it = list.iterator();
- while (it.hasNext()) {
- if (verify(it.next()) == null) {
- it.remove();
- }
- }
- return list;
- }
-
- @Override
- public boolean promiseIconAddedForId(int sessionId) {
- return mPromiseIconIds.contains(sessionId);
- }
-
- @Override
- public void removePromiseIconId(int sessionId) {
- if (mPromiseIconIds.contains(sessionId)) {
- mPromiseIconIds.getArray().removeValue(sessionId);
- updatePromiseIconPrefs();
- }
- }
-
- private void updatePromiseIconPrefs() {
- getPrefs(mAppContext).edit()
- .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString())
- .apply();
- }
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
deleted file mode 100644
index 2c0088e..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import com.android.launcher3.Utilities;
-
-import java.util.List;
-
-public abstract class UserManagerCompat {
- protected UserManagerCompat() {
- }
-
- private static final Object sInstanceLock = new Object();
- private static UserManagerCompat sInstance;
-
- public static UserManagerCompat getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- if (Utilities.ATLEAST_P) {
- sInstance = new UserManagerCompatVP(context.getApplicationContext());
- } else {
- sInstance = new UserManagerCompatVNMr1(context.getApplicationContext());
- }
- }
- return sInstance;
- }
- }
-
- /**
- * Creates a cache for users.
- */
- public abstract void enableAndResetCache();
-
- public abstract List<UserHandle> getUserProfiles();
- public abstract long getSerialNumberForUser(UserHandle user);
- public abstract UserHandle getUserForSerialNumber(long serialNumber);
- public abstract boolean isQuietModeEnabled(UserHandle user);
- public abstract boolean isUserUnlocked(UserHandle user);
-
- public abstract boolean isDemoUser();
- public abstract boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user);
- public abstract boolean isAnyProfileQuietModeEnabled();
-
- public abstract boolean hasWorkProfile();
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java b/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java
deleted file mode 100644
index 18d9053..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatVNMr1.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.ArrayMap;
-import android.util.LongSparseArray;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-@TargetApi(Build.VERSION_CODES.N_MR1)
-public class UserManagerCompatVNMr1 extends UserManagerCompat {
-
- protected final UserManager mUserManager;
-
- protected LongSparseArray<UserHandle> mUsers;
- // Create a separate reverse map as LongSparseArray.indexOfValue checks if objects are same
- // and not {@link Object#equals}
- protected ArrayMap<UserHandle, Long> mUserToSerialMap;
-
- UserManagerCompatVNMr1(Context context) {
- mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- }
-
- @Override
- public boolean isQuietModeEnabled(UserHandle user) {
- return mUserManager.isQuietModeEnabled(user);
- }
-
- @Override
- public boolean isUserUnlocked(UserHandle user) {
- return mUserManager.isUserUnlocked(user);
- }
-
- @Override
- public boolean isAnyProfileQuietModeEnabled() {
- List<UserHandle> userProfiles = getUserProfiles();
- for (UserHandle userProfile : userProfiles) {
- if (Process.myUserHandle().equals(userProfile)) {
- continue;
- }
- if (isQuietModeEnabled(userProfile)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public long getSerialNumberForUser(UserHandle user) {
- synchronized (this) {
- if (mUserToSerialMap != null) {
- Long serial = mUserToSerialMap.get(user);
- return serial == null ? 0 : serial;
- }
- }
- return mUserManager.getSerialNumberForUser(user);
- }
-
- @Override
- public UserHandle getUserForSerialNumber(long serialNumber) {
- synchronized (this) {
- if (mUsers != null) {
- return mUsers.get(serialNumber);
- }
- }
- return mUserManager.getUserForSerialNumber(serialNumber);
- }
-
- @Override
- public boolean isDemoUser() {
- return mUserManager.isDemoUser();
- }
-
- @Override
- public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
- return false;
- }
-
- @Override
- public void enableAndResetCache() {
- synchronized (this) {
- mUsers = new LongSparseArray<>();
- mUserToSerialMap = new ArrayMap<>();
- List<UserHandle> users = mUserManager.getUserProfiles();
- if (users != null) {
- for (UserHandle user : users) {
- long serial = mUserManager.getSerialNumberForUser(user);
- mUsers.put(serial, user);
- mUserToSerialMap.put(user, serial);
- }
- }
- }
- }
-
- @Override
- public List<UserHandle> getUserProfiles() {
- synchronized (this) {
- if (mUsers != null) {
- return new ArrayList<>(mUserToSerialMap.keySet());
- }
- }
-
- List<UserHandle> users = mUserManager.getUserProfiles();
- return users == null ? Collections.<UserHandle>emptyList() : users;
- }
-
- @Override
- public boolean hasWorkProfile() {
- synchronized (this) {
- if (mUsers != null) {
- return mUsers.size() > 1;
- }
- }
- return getUserProfiles().size() > 1;
- }
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVP.java b/src/com/android/launcher3/compat/UserManagerCompatVP.java
deleted file mode 100644
index fa3902b..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatVP.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.UserHandle;
-
-@TargetApi(Build.VERSION_CODES.P)
-public class UserManagerCompatVP extends UserManagerCompatVNMr1 {
-
- UserManagerCompatVP(Context context) {
- super(context);
- }
-
- @Override
- public boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle user) {
- return mUserManager.requestQuietModeEnabled(enableQuietMode, user);
- }
-}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
deleted file mode 100644
index 9c9001a..0000000
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.config;
-
-import static androidx.core.util.Preconditions.checkNotNull;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Keep;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.TogglableFlag;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/**
- * Defines a set of flags used to control various launcher behaviors.
- *
- * <p>All the flags should be defined here with appropriate default values.
- */
-@Keep
-public abstract class BaseFlags {
-
- private static final Object sLock = new Object();
- @GuardedBy("sLock")
- private static final List<TogglableFlag> sFlags = new ArrayList<>();
-
- static final String FLAGS_PREF_NAME = "featureFlags";
-
- BaseFlags() {
- throw new UnsupportedOperationException("Don't instantiate BaseFlags");
- }
-
- public static boolean showFlagTogglerUi(Context context) {
- return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
- }
-
- public static final boolean IS_DOGFOOD_BUILD = false;
-
- // When enabled the promise icon is visible in all apps while installation an app.
- public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
-
- // When enabled a promise icon is added to the home screen when install session is active.
- public static final TogglableFlag PROMISE_APPS_NEW_INSTALLS =
- new TogglableFlag("PROMISE_APPS_NEW_INSTALLS", true,
- "Adds a promise icon to the home screen for new install sessions.");
-
- // Enable moving the QSB on the 0th screen of the workspace
- public static final boolean QSB_ON_FIRST_SCREEN = true;
-
- public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
- "An example flag that doesn't do anything. Useful for testing");
-
- //Feature flag to enable pulling down navigation shade from workspace.
- public static final boolean PULL_DOWN_STATUS_BAR = true;
-
- // Features to control Launcher3Go behavior
- public static final boolean GO_DISABLE_WIDGETS = false;
-
- // When enabled shows a work profile tab in all apps
- public static final boolean ALL_APPS_TABS_ENABLED = true;
-
- // When true, overview shows screenshots in the orientation they were taken rather than
- // trying to make them fit the orientation the device is in.
- public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
-
- /**
- * Feature flag to handle define config changes dynamically instead of killing the process.
- */
- public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag(
- "APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically");
-
- public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS",
- false, "Enable springs for quickstep animations");
-
- public static final TogglableFlag ADAPTIVE_ICON_WINDOW_ANIM = new TogglableFlag(
- "ADAPTIVE_ICON_WINDOW_ANIM", true,
- "Use adaptive icons for window animations.");
-
- public static final TogglableFlag ENABLE_QUICKSTEP_LIVE_TILE = new TogglableFlag(
- "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
-
- public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag(
- "ENABLE_HINTS_IN_OVERVIEW", true,
- "Show chip hints and gleams on the overview screen");
-
- public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
- "FAKE_LANDSCAPE_UI", false,
- "Rotate launcher UI instead of using transposed layout");
-
- public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag(
- "FOLDER_NAME_SUGGEST", false,
- "Suggests folder names instead of blank text.");
-
- public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag(
- "APP_SEARCH_IMPROVEMENTS", true,
- "Adds localized title and keyword search and ranking");
-
- public static final TogglableFlag ENABLE_PREDICTION_DISMISS = new TogglableFlag(
- "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
-
- public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag(
- "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
- "Allow Launcher to handle nav bar gestures while Assistant is running over it");
-
- public static void initialize(Context context) {
- // Avoid the disk read for user builds
- if (Utilities.IS_DEBUG_DEVICE) {
- synchronized (sLock) {
- for (BaseTogglableFlag flag : sFlags) {
- flag.initialize(context);
- }
- }
- }
- APP_SEARCH_IMPROVEMENTS.initialize(context);
- }
-
- static List<TogglableFlag> getTogglableFlags() {
- // By Java Language Spec 12.4.2
- // https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2, the
- // TogglableFlag instances on BaseFlags will be created before those on the FeatureFlags
- // subclass. This code handles flags that are redeclared in FeatureFlags, ensuring the
- // FeatureFlags one takes priority.
- SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>();
- synchronized (sLock) {
- for (TogglableFlag flag : sFlags) {
- flagsByKey.put(((BaseTogglableFlag) flag).getKey(), flag);
- }
- }
- return new ArrayList<>(flagsByKey.values());
- }
-
- public static abstract class BaseTogglableFlag {
- private final String key;
- // should be value that is hardcoded in client side.
- // Comparatively, getDefaultValue() can be overridden.
- private final boolean defaultValue;
- private final String description;
- private boolean currentValue;
-
- public BaseTogglableFlag(
- String key,
- boolean defaultValue,
- String description) {
- this.key = checkNotNull(key);
- this.currentValue = this.defaultValue = defaultValue;
- this.description = checkNotNull(description);
-
- synchronized (sLock) {
- sFlags.add((TogglableFlag)this);
- }
- }
-
- /** Set the value of this flag. This should only be used in tests. */
- @VisibleForTesting
- void setForTests(boolean value) {
- currentValue = value;
- }
-
- public String getKey() {
- return key;
- }
-
- protected void initialize(Context context) {
- currentValue = getFromStorage(context, getDefaultValue());
- }
-
- protected abstract boolean getOverridenDefaultValue(boolean value);
-
- protected abstract void addChangeListener(Context context, Runnable r);
-
- public void updateStorage(Context context, boolean value) {
- SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME,
- Context.MODE_PRIVATE).edit();
- if (value == getDefaultValue()) {
- editor.remove(key).apply();
- } else {
- editor.putBoolean(key, value).apply();
- }
- }
-
- boolean getFromStorage(Context context, boolean defaultValue) {
- return context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
- .getBoolean(key, getDefaultValue());
- }
-
- boolean getDefaultValue() {
- return getOverridenDefaultValue(defaultValue);
- }
-
- /** Returns the value of the flag at process start, including any overrides present. */
- public boolean get() {
- return currentValue;
- }
-
- String getDescription() {
- return description;
- }
-
- @Override
- public String toString() {
- return "TogglableFlag{"
- + "key=" + key + ", "
- + "defaultValue=" + defaultValue + ", "
- + "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", "
- + "currentValue=" + currentValue + ", "
- + "description=" + description
- + "}";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof TogglableFlag) {
- BaseTogglableFlag that = (BaseTogglableFlag) o;
- return (this.key.equals(that.getKey()))
- && (this.getDefaultValue() == that.getDefaultValue())
- && (this.description.equals(that.getDescription()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- int h$ = 1;
- h$ *= 1000003;
- h$ ^= key.hashCode();
- h$ *= 1000003;
- h$ ^= getDefaultValue() ? 1231 : 1237;
- h$ *= 1000003;
- h$ ^= description.hashCode();
- return h$;
- }
- }
-}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
new file mode 100644
index 0000000..8f58865
--- /dev/null
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.config;
+
+import android.content.Context;
+
+import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.DeviceFlag;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines a set of flags used to control various launcher behaviors.
+ *
+ * <p>All the flags should be defined here with appropriate default values.
+ */
+public final class FeatureFlags {
+
+ private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
+
+ public static final String FLAGS_PREF_NAME = "featureFlags";
+ public static final String FLAG_ENABLE_FIXED_ROTATION_TRANSFORM =
+ "ENABLE_FIXED_ROTATION_TRANSFORM";
+
+ private FeatureFlags() { }
+
+ public static boolean showFlagTogglerUi(Context context) {
+ return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
+ }
+
+ /**
+ * True when the build has come from Android Studio and is being used for local debugging.
+ */
+ public static final boolean IS_STUDIO_BUILD = BuildConfig.DEBUG;
+
+ /**
+ * Enable moving the QSB on the 0th screen of the workspace. This is not a configuration feature
+ * and should be modified at a project level.
+ */
+ public static final boolean QSB_ON_FIRST_SCREEN = true;
+
+ /**
+ * Feature flag to handle define config changes dynamically instead of killing the process.
+ *
+ *
+ * To add a new flag that can be toggled through the flags UI:
+ *
+ * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
+ * and set a default value for the flag. This will be the default value on Debug builds.
+ */
+ // When enabled the promise icon is visible in all apps while installation an app.
+ public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(
+ "PROMISE_APPS_IN_ALL_APPS", false, "Add promise icon in all-apps");
+
+ // When enabled a promise icon is added to the home screen when install session is active.
+ public static final BooleanFlag PROMISE_APPS_NEW_INSTALLS = getDebugFlag(
+ "PROMISE_APPS_NEW_INSTALLS", true,
+ "Adds a promise icon to the home screen for new install sessions.");
+
+ public static final BooleanFlag APPLY_CONFIG_AT_RUNTIME = getDebugFlag(
+ "APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically");
+
+ public static final BooleanFlag QUICKSTEP_SPRINGS = getDebugFlag(
+ "QUICKSTEP_SPRINGS", true, "Enable springs for quickstep animations");
+
+ public static final BooleanFlag UNSTABLE_SPRINGS = getDebugFlag(
+ "UNSTABLE_SPRINGS", false, "Enable unstable springs for quickstep animations");
+
+ public static final BooleanFlag ADAPTIVE_ICON_WINDOW_ANIM = getDebugFlag(
+ "ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
+
+ public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
+ "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
+
+ public static final BooleanFlag ENABLE_HINTS_IN_OVERVIEW = getDebugFlag(
+ "ENABLE_HINTS_IN_OVERVIEW", false, "Show chip hints and gleams on the overview screen");
+
+ public static final BooleanFlag FAKE_LANDSCAPE_UI = getDebugFlag(
+ "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout");
+
+ public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
+ "FOLDER_NAME_SUGGEST", true,
+ "Suggests folder names instead of blank text.");
+
+ public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
+ "APP_SEARCH_IMPROVEMENTS", true,
+ "Adds localized title and keyword search and ranking");
+
+ public static final BooleanFlag ENABLE_PREDICTION_DISMISS = new DeviceFlag(
+ "ENABLE_PREDICTION_DISMISS", true, "Allow option to dimiss apps from predicted list");
+
+ public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag(
+ "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture");
+
+ public static final BooleanFlag FORCE_LOCAL_OVERSCROLL_PLUGIN = getDebugFlag(
+ "FORCE_LOCAL_OVERSCROLL_PLUGIN", false,
+ "Use a launcher-provided OverscrollPlugin if available");
+
+ public static final BooleanFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = getDebugFlag(
+ "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
+ "Allow Launcher to handle nav bar gestures while Assistant is running over it");
+
+ public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
+ "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
+
+ public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = new DeviceFlag(
+ "HOTSEAT_MIGRATE_NEW_PAGE", true,
+ "Migrates hotseat to a new workspace page instead of same page");
+
+ public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
+ "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
+
+ public static final BooleanFlag MULTI_DB_GRID_MIRATION_ALGO = getDebugFlag(
+ "MULTI_DB_GRID_MIRATION_ALGO", false, "Use the multi-db grid migration algorithm");
+
+ public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
+ "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
+
+ public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag(
+ "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", false, "Use surface view for grid preview");
+
+ public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
+ "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
+ + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
+
+ public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
+ "ENABLE_DATABASE_RESTORE", true,
+ "Enable database restore when new restore session is created");
+
+ public static final BooleanFlag ENABLE_UNIVERSAL_SMARTSPACE = getDebugFlag(
+ "ENABLE_UNIVERSAL_SMARTSPACE", false,
+ "Replace Smartspace with a version rendered by System UI.");
+
+ public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
+ "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+ "Use Least Square algorithm for motion pause detection.");
+
+ public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
+ getDebugFlag(
+ "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
+ "Always use hardware optimization for folder animations.");
+
+ public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
+ FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
+ "Launch/close apps without rotation animation. Fix Launcher to portrait");
+
+ public static void initialize(Context context) {
+ synchronized (sDebugFlags) {
+ for (DebugFlag flag : sDebugFlags) {
+ flag.initialize(context);
+ }
+ }
+ }
+
+ static List<DebugFlag> getDebugFlags() {
+ synchronized (sDebugFlags) {
+ return new ArrayList<>(sDebugFlags);
+ }
+ }
+
+ public static void dump(PrintWriter pw) {
+ pw.println("FeatureFlags:");
+ synchronized (sDebugFlags) {
+ for (DebugFlag flag : sDebugFlags) {
+ pw.println(" " + flag.key + "=" + flag.get());
+ }
+ }
+ }
+
+ public static class BooleanFlag {
+
+ public final String key;
+ public boolean defaultValue;
+
+ public BooleanFlag(String key, boolean defaultValue) {
+ this.key = key;
+ this.defaultValue = defaultValue;
+ }
+
+ public boolean get() {
+ return defaultValue;
+ }
+
+ @Override
+ public String toString() {
+ return appendProps(new StringBuilder()
+ .append(getClass().getSimpleName()).append('{'))
+ .append('}').toString();
+ }
+
+ protected StringBuilder appendProps(StringBuilder src) {
+ return src.append("key=").append(key).append(", defaultValue=").append(defaultValue);
+ }
+
+ public void addChangeListener(Context context, Runnable r) { }
+ }
+
+ public static class DebugFlag extends BooleanFlag {
+
+ public final String description;
+ private boolean mCurrentValue;
+
+ public DebugFlag(String key, boolean defaultValue, String description) {
+ super(key, defaultValue);
+ this.description = description;
+ mCurrentValue = this.defaultValue;
+ synchronized (sDebugFlags) {
+ sDebugFlags.add(this);
+ }
+ }
+
+ @Override
+ public boolean get() {
+ return mCurrentValue;
+ }
+
+ public void initialize(Context context) {
+ mCurrentValue = context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+ .getBoolean(key, defaultValue);
+ }
+
+ @Override
+ protected StringBuilder appendProps(StringBuilder src) {
+ return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue)
+ .append(", description=").append(description);
+ }
+ }
+
+ private static BooleanFlag getDebugFlag(String key, boolean defaultValue, String description) {
+ return Utilities.IS_DEBUG_DEVICE
+ ? new DebugFlag(key, defaultValue, description)
+ : new BooleanFlag(key, defaultValue);
+ }
+}
diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
index 54e5322..6729f74 100644
--- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java
+++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
@@ -16,6 +16,8 @@
package com.android.launcher3.config;
+import static com.android.launcher3.config.FeatureFlags.FLAGS_PREF_NAME;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Process;
@@ -25,14 +27,13 @@
import android.view.MenuItem;
import android.widget.Toast;
-import com.android.launcher3.R;
-
import androidx.preference.PreferenceDataStore;
-import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
-import com.android.launcher3.uioverrides.TogglableFlag;
+
+import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags.DebugFlag;
/**
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
@@ -41,7 +42,7 @@
private static final String TAG = "FlagTogglerPrefFrag";
- private final PreferenceFragment mFragment;
+ private final PreferenceFragmentCompat mFragment;
private final Context mContext;
private final SharedPreferences mSharedPreferences;
@@ -49,34 +50,37 @@
@Override
public void putBoolean(String key, boolean value) {
- for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
- if (flag.getKey().equals(key)) {
- boolean prevValue = flag.get();
- flag.updateStorage(mContext, value);
- updateMenu();
- if (flag.get() != prevValue) {
- Toast.makeText(mContext, "Flag applied", Toast.LENGTH_SHORT).show();
+ for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
+ if (flag.key.equals(key)) {
+ SharedPreferences.Editor editor = mContext.getSharedPreferences(
+ FLAGS_PREF_NAME, Context.MODE_PRIVATE).edit();
+ if (value == flag.defaultValue) {
+ editor.remove(key).apply();
+ } else {
+ editor.putBoolean(key, value).apply();
}
+ updateMenu();
}
}
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
- for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
- if (flag.getKey().equals(key)) {
- return flag.getFromStorage(mContext, defaultValue);
+ for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
+ if (flag.key.equals(key)) {
+ return mContext.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+ .getBoolean(key, flag.defaultValue);
}
}
return defaultValue;
}
};
- public FlagTogglerPrefUi(PreferenceFragment fragment) {
+ public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) {
mFragment = fragment;
mContext = fragment.getActivity();
mSharedPreferences = mContext.getSharedPreferences(
- FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+ FLAGS_PREF_NAME, Context.MODE_PRIVATE);
}
public void applyTo(PreferenceGroup parent) {
@@ -84,12 +88,12 @@
// flag with a different value than the default. That way, when we flip flags in
// future, engineers will pick up the new value immediately. To accomplish this, we use a
// custom preference data store.
- for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+ for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
SwitchPreference switchPreference = new SwitchPreference(mContext);
- switchPreference.setKey(flag.getKey());
- switchPreference.setDefaultValue(flag.getDefaultValue());
+ switchPreference.setKey(flag.key);
+ switchPreference.setDefaultValue(flag.defaultValue);
switchPreference.setChecked(getFlagStateFromSharedPrefs(flag));
- switchPreference.setTitle(flag.getKey());
+ switchPreference.setTitle(flag.key);
updateSummary(switchPreference, flag);
switchPreference.setPreferenceDataStore(mDataStore);
parent.addPreference(switchPreference);
@@ -100,11 +104,11 @@
/**
* Updates the summary to show the description and whether the flag overrides the default value.
*/
- private void updateSummary(SwitchPreference switchPreference, BaseTogglableFlag flag) {
- String onWarning = flag.getDefaultValue() ? "" : "<b>OVERRIDDEN</b><br>";
- String offWarning = flag.getDefaultValue() ? "<b>OVERRIDDEN</b><br>" : "";
- switchPreference.setSummaryOn(Html.fromHtml(onWarning + flag.getDescription()));
- switchPreference.setSummaryOff(Html.fromHtml(offWarning + flag.getDescription()));
+ private void updateSummary(SwitchPreference switchPreference, DebugFlag flag) {
+ String onWarning = flag.defaultValue ? "" : "<b>OVERRIDDEN</b><br>";
+ String offWarning = flag.defaultValue ? "<b>OVERRIDDEN</b><br>" : "";
+ switchPreference.setSummaryOn(Html.fromHtml(onWarning + flag.description));
+ switchPreference.setSummaryOff(Html.fromHtml(offWarning + flag.description));
}
private void updateMenu() {
@@ -135,12 +139,12 @@
}
}
- private boolean getFlagStateFromSharedPrefs(BaseTogglableFlag flag) {
- return mDataStore.getBoolean(flag.getKey(), flag.getDefaultValue());
+ private boolean getFlagStateFromSharedPrefs(DebugFlag flag) {
+ return mDataStore.getBoolean(flag.key, flag.defaultValue);
}
private boolean anyChanged() {
- for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+ for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
return true;
}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 9fb1090..6c40b8a 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -45,13 +45,13 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompatVO;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.InstantAppResolver;
@@ -60,6 +60,7 @@
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetImageView;
+import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.function.Supplier;
@@ -81,7 +82,7 @@
// Widget request specific options.
private LauncherAppWidgetHost mAppWidgetHost;
- private AppWidgetManagerCompat mAppWidgetManager;
+ private WidgetManagerHelper mAppWidgetManager;
private int mPendingBindWidgetId;
private Bundle mWidgetOptions;
@@ -92,7 +93,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mRequest = LauncherAppsCompatVO.getPinItemRequest(getIntent());
+ mRequest = PinRequestHelper.getPinItemRequest(getIntent());
if (mRequest == null) {
finish();
return;
@@ -176,7 +177,7 @@
.setPackage(getPackageName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- listener.initWhenReady();
+ Launcher.ACTIVITY_TRACKER.schedule(listener);
startActivity(homeIntent,
ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
mFinishOnPause = true;
@@ -208,7 +209,7 @@
}
mWidgetCell.setPreview(PinItemDragListener.getPreview(mRequest));
- mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
+ mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHost = new LauncherAppWidgetHost(this);
PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(widgetInfo);
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 1b08723..75693c6 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -36,8 +36,7 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
import com.android.launcher3.widget.PendingItemDragHelper;
import java.util.UUID;
@@ -45,8 +44,8 @@
/**
* {@link DragSource} for handling drop from a different window.
*/
-public abstract class BaseItemDragListener extends InternalStateHandler implements
- View.OnDragListener, DragSource, DragOptions.PreDragCondition {
+public abstract class BaseItemDragListener implements View.OnDragListener, DragSource,
+ DragOptions.PreDragCondition, SchedulerCallback<Launcher> {
private static final String TAG = "BaseItemDragListener";
@@ -165,7 +164,7 @@
}
protected void postCleanup() {
- clearReference();
+ Launcher.ACTIVITY_TRACKER.clearReference(this);
if (mLauncher != null) {
// Remove any drag params from the launcher intent since the drag operation is complete.
Intent newIntent = new Intent(mLauncher.getIntent());
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index dcdf5d6..de7bc6d 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -28,7 +28,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
-import android.util.Log;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -43,7 +42,6 @@
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
@@ -162,8 +160,8 @@
mOptions = options;
if (mOptions.systemDndStartPoint != null) {
- mMotionDownX = mOptions.systemDndStartPoint.x;
- mMotionDownY = mOptions.systemDndStartPoint.y;
+ mLastTouch[0] = mMotionDownX = mOptions.systemDndStartPoint.x;
+ mLastTouch[1] = mMotionDownY = mOptions.systemDndStartPoint.y;
}
final int registrationX = mMotionDownX - dragLayerX;
@@ -174,7 +172,7 @@
mLastDropTarget = null;
- mDragObject = new DropTarget.DragObject();
+ mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext());
mIsInPreDrag = mOptions.preDragCondition != null
&& !mOptions.preDragCondition.shouldStartDrag(0);
@@ -567,11 +565,13 @@
/**
* Since accessible drag and drop won't cause the same sequence of touch events, we manually
- * inject the appropriate state.
+ * inject the appropriate state which would have been otherwise initiated via touch events.
*/
public void prepareAccessibleDrag(int x, int y) {
mMotionDownX = x;
mMotionDownY = y;
+ mLastTouch[0] = x;
+ mLastTouch[1] = y;
}
/**
@@ -594,9 +594,6 @@
}
private void drop(DropTarget dropTarget, Runnable flingAnimation) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragController.drop");
- }
final int[] coordinates = mCoordinatesTemp;
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 01e0f92..87461d5 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -54,16 +54,10 @@
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_UP");
- }
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_CANCEL");
- }
mEventListener.onDriverDragCancel();
break;
}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index cdc7061..369bf28 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -57,7 +57,6 @@
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.views.Transposable;
@@ -120,8 +119,9 @@
recreateControllers();
}
+ @Override
public void recreateControllers() {
- mControllers = UiFactory.createTouchControllers(mActivity);
+ mControllers = mActivity.createTouchControllers();
}
public ViewGroupFocusHelper getFocusIndicatorHelper() {
@@ -477,14 +477,14 @@
public void onViewAdded(View child) {
super.onViewAdded(child);
updateChildIndices();
- UiFactory.onLauncherStateOrFocusChanged(mActivity);
+ mActivity.onDragLayerHierarchyChanged();
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
updateChildIndices();
- UiFactory.onLauncherStateOrFocusChanged(mActivity);
+ mActivity.onDragLayerHierarchyChanged();
}
@Override
@@ -537,6 +537,9 @@
mOverviewScrim.updateCurrentScrimmedView(this);
mFocusIndicatorHelper.draw(canvas);
super.dispatchDraw(canvas);
+ if (mOverviewScrim.getScrimmedView() == null) {
+ mOverviewScrim.draw(canvas);
+ }
}
@Override
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index f66d07e..145885a 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -216,8 +216,7 @@
Object[] outObj = new Object[1];
int w = mBitmap.getWidth();
int h = mBitmap.getHeight();
- Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h,
- false /* flattenDrawable */, outObj);
+ Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
if (dr instanceof AdaptiveIconDrawable) {
int blurMargin = (int) mLauncher.getResources()
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
index e36f607..a9389bc 100644
--- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -2,7 +2,6 @@
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
@@ -11,6 +10,7 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.widget.WidgetCell;
/**
@@ -88,11 +88,9 @@
bitmapHeight = viewHeight;
}
- Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(preview);
- c.scale(scale, scale);
- v.draw(c);
- c.setBitmap(null);
- return preview;
+ return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
+ c.scale(scale, scale);
+ v.draw(c);
+ });
}
}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 07eb0d6..77c6306 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -16,6 +16,8 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+
import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
import android.content.pm.LauncherApps.PinItemRequest;
@@ -32,13 +34,14 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingItemDragHelper;
import com.android.launcher3.widget.WidgetAddFlowHandler;
+import java.util.ArrayList;
+
/**
* {@link DragSource} for handling drop from a different window. This object is initialized
* in the source window and is passed on to the Launcher activity as an Intent extra.
@@ -68,7 +71,7 @@
public boolean init(Launcher launcher, boolean alreadyOnHome) {
super.init(launcher, alreadyOnHome);
if (!alreadyOnHome) {
- UiFactory.useFadeOutAnimationForLauncherStart(launcher, mCancelSignal);
+ launcher.useFadeOutAnimationForLauncherStart(mCancelSignal);
}
return false;
}
@@ -104,9 +107,9 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
- LauncherLogProto.Target targetParent) {
- targetParent.containerType = LauncherLogProto.ContainerType.PINITEM;
+ public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
+ ArrayList<LauncherLogProto.Target> parents) {
+ parents.add(newContainerTarget(LauncherLogProto.ContainerType.PINITEM));
}
@Override
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 91a31aa..f71cfb8 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -29,14 +29,16 @@
import android.os.Process;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.compat.LauncherAppsCompatVO;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
/**
* Extension of ShortcutConfigActivityInfo to be used in the confirmation prompt for pin item
@@ -86,9 +88,9 @@
// Total duration for the drop animation to complete.
long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) +
LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY +
- LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
+ LauncherState.SPRING_LOADED.getTransitionDuration(Launcher.getLauncher(mContext));
// Delay the actual accept() call until the drop animation is complete.
- return LauncherAppsCompatVO.createWorkspaceItemFromPinItemRequest(
+ return PinRequestHelper.createWorkspaceItemFromPinItemRequest(
mContext, mRequest, duration);
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 0bd2c9a..2be8ff4 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -16,9 +16,26 @@
package com.android.launcher3.folder;
+import static android.text.TextUtils.isEmpty;
+
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import static com.android.launcher3.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
+import static java.util.Optional.ofNullable;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -26,6 +43,7 @@
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
@@ -72,13 +90,14 @@
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.pageindicators.PageIndicatorDots;
-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;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.userevent.LauncherLogProto.Action;
+import com.android.launcher3.userevent.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
+import com.android.launcher3.userevent.LauncherLogProto.Target;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ClipPathView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -87,6 +106,10 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* Represents a set of icons chosen by the user or generated by the system.
@@ -95,7 +118,7 @@
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
private static final String TAG = "Launcher.Folder";
-
+ private static final boolean DEBUG = false;
/**
* We avoid measuring {@link #mContent} with a 0 width or height, as this
* results in CellLayout being measured as UNSPECIFIED, which it does not support.
@@ -145,7 +168,7 @@
@Thunk FolderIcon mFolderIcon;
@Thunk FolderPagedView mContent;
- public ExtendedEditText mFolderName;
+ public FolderNameEditText mFolderName;
private PageIndicatorDots mPageIndicator;
protected View mFooter;
@@ -184,6 +207,9 @@
@Thunk int mScrollHintDir = SCROLL_NONE;
@Thunk int mCurrentScrollDir = SCROLL_NONE;
+ private String mPreviousLabel;
+ private boolean mIsPreviousLabelSuggested;
+
/**
* Used to inflate the Workspace from XML.
*
@@ -217,7 +243,7 @@
& ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
& ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
- mFolderName.forceDisableSuggestions(true);
+ mFolderName.forceDisableSuggestions(!FeatureFlags.FOLDER_NAME_SUGGEST.get());
mFooter = findViewById(R.id.folder_footer);
@@ -296,27 +322,39 @@
}
public void startEditingFolderName() {
- post(new Runnable() {
- @Override
- public void run() {
- mFolderName.setHint("");
- mIsEditingName = true;
+ post(() -> {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ if (isEmpty(mFolderName.getText())) {
+ ofNullable(mInfo)
+ .map(info -> info.suggestedFolderNames)
+ .map(folderNames -> (FolderNameInfo[]) folderNames
+ .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+ .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
+ }
}
+ mFolderName.setHint("");
+ mIsEditingName = true;
});
}
-
@Override
public boolean onBackKey() {
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
String newTitle = mFolderName.getText().toString();
+ if (DEBUG) {
+ Log.d(TAG, "onBackKey newTitle=" + newTitle);
+ }
+
mInfo.title = newTitle;
+ mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, getAcceptedSuggestionIndex() < 0,
+ mLauncher.getModelWriter());
mFolderIcon.onTitleChanged(newTitle);
mLauncher.getModelWriter().updateItemInDatabase(mInfo);
if (TextUtils.isEmpty(mInfo.title)) {
mFolderName.setHint(R.string.folder_hint_text);
+ mFolderName.setText("");
} else {
mFolderName.setHint(null);
}
@@ -335,6 +373,10 @@
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (DEBUG) {
+ Log.d(TAG, "onEditorAction actionId=" + actionId + " key="
+ + (event != null ? event.getKeyCode() : "null event"));
+ }
if (actionId == EditorInfo.IME_ACTION_DONE) {
mFolderName.dispatchBackKey();
return true;
@@ -386,7 +428,7 @@
mInfo = info;
ArrayList<WorkspaceItemInfo> children = info.contents;
Collections.sort(children, ITEM_POS_COMPARATOR);
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(true);
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
if (lp == null) {
@@ -396,8 +438,10 @@
}
mItemsInvalidated = true;
mInfo.addListener(this);
+ mPreviousLabel = mInfo.title.toString();
+ mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
- if (!TextUtils.isEmpty(mInfo.title)) {
+ if (!isEmpty(mInfo.title)) {
mFolderName.setText(mInfo.title);
mFolderName.setHint(null);
} else {
@@ -412,19 +456,49 @@
});
}
+ /**
+ * Show suggested folder title in FolderEditText, push InputMethodManager suggestions and save
+ * the suggestedFolderNames.
+ */
+ public void showSuggestedTitle(FolderNameInfo[] nameInfos) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ if (isEmpty(mFolderName.getText().toString())
+ && !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME)) {
+ showLabelSuggestion(nameInfos, true);
+ }
+ }
+ }
/**
- * Show suggested folder title.
+ * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
+ * InputMethodManager suggestions.
*/
- public void showSuggestedTitle(CharSequence suggestName) {
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
- if (!TextUtils.isEmpty(suggestName)) {
- mFolderName.setHint(suggestName);
- mFolderName.setText(suggestName);
- mFolderName.showKeyboard();
- mInfo.title = suggestName;
+ private void showLabelSuggestion(FolderNameInfo[] nameInfos, boolean animate) {
+ if (nameInfos == null) {
+ return;
+ }
+ // Open the Folder and Keyboard when the first or second suggestion is valid non-empty
+ // string.
+ boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !isEmpty(
+ nameInfos[0].getLabel())
+ || nameInfos.length > 1 && nameInfos[1] != null && !isEmpty(
+ nameInfos[1].getLabel());
+
+ if (shouldOpen) {
+ CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
+ if (!isEmpty(firstLabel)) {
+ mFolderName.setHint("");
+ mFolderName.setText(firstLabel);
}
- animateOpen();
+ if (animate) {
+ animateOpen(mInfo.contents, 0, true);
+ }
+ mFolderName.showKeyboard();
+ mFolderName.displayCompletions(
+ asList(nameInfos).subList(1, nameInfos.length).stream()
+ .filter(Objects::nonNull)
+ .map(s -> s.getLabel().toString())
+ .collect(Collectors.toList()));
}
}
@@ -473,6 +547,8 @@
}
private boolean shouldUseHardwareLayerForAnimation(CellLayout currentCellLayout) {
+ if (ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS.get()) return true;
+
int folderCount = 0;
final ShortcutAndWidgetContainer container = currentCellLayout.getShortcutsAndWidgets();
for (int i = container.getChildCount() - 1; i >= 0; --i) {
@@ -516,6 +592,15 @@
* is played.
*/
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
+ animateOpen(items, pageNo, false);
+ }
+
+ /**
+ * Opens the user folder described by the specified tag. The opening of the folder
+ * is animated relative to the specified View. If the View is null, no animation
+ * is played.
+ */
+ private void animateOpen(List<WorkspaceItemInfo> items, int pageNo, boolean skipUserEventLog) {
Folder openFolder = getOpen(mLauncher);
if (openFolder != null && openFolder != this) {
// Close any open folder before opening a folder.
@@ -536,7 +621,7 @@
dragLayer.addView(this);
mDragController.addDropTarget(this);
} else {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
+ getParent());
}
@@ -562,10 +647,13 @@
mState = STATE_OPEN;
announceAccessibilityChanges();
- mLauncher.getUserEventDispatcher().logActionOnItem(
- Touch.TAP,
- Direction.NONE,
- ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+ if (!skipUserEventLog) {
+ mLauncher.getUserEventDispatcher().logActionOnItem(
+ LauncherLogProto.Action.Touch.TAP,
+ LauncherLogProto.Action.Direction.NONE,
+ LauncherLogProto.ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+ }
+
mContent.setFocusOnFirstChild();
}
@@ -895,7 +983,7 @@
// Reordering may have occured, and we need to save the new item locations. We do this once
// at the end to prevent unnecessary database operations.
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(false);
// Use the item count to check for multi-page as the folder UI may not have
// been refreshed yet.
if (getItemCount() <= mContent.itemsPerPage()) {
@@ -905,7 +993,7 @@
}
}
- private void updateItemLocationsInDatabaseBatch() {
+ private void updateItemLocationsInDatabaseBatch(boolean isBind) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
@@ -921,6 +1009,18 @@
if (!items.isEmpty()) {
mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
}
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind) {
+ Executors.MODEL_EXECUTOR.post(() -> {
+ FolderNameInfo[] nameInfos =
+ new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
+ fnp.getSuggestedFolderName(
+ getContext(), mInfo.contents, nameInfos);
+ mInfo.suggestedFolderNames = new Intent().putExtra(
+ FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
+ nameInfos);
+ });
+ }
}
public void notifyDrop() {
@@ -1062,13 +1162,14 @@
int itemCount = getItemCount();
if (itemCount <= 1) {
View newIcon = null;
+ WorkspaceItemInfo finalItem = null;
if (itemCount == 1) {
// Move the item from the folder to the workspace, in the position of the
// folder
CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container,
mInfo.screenId);
- WorkspaceItemInfo finalItem = mInfo.contents.remove(0);
+ finalItem = mInfo.contents.remove(0);
newIcon = mLauncher.createShortcut(cellLayout, finalItem);
mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
mInfo.container, mInfo.screenId, mInfo.cellX, mInfo.cellY);
@@ -1089,6 +1190,9 @@
// Focus the newly created child
newIcon.requestFocus();
}
+ if (finalItem != null) {
+ mLauncher.folderConvertedToItem(mFolderIcon.getFolder(), finalItem);
+ }
}
}
};
@@ -1221,7 +1325,7 @@
// We only need to update the locations if it doesn't get handled in
// #onDropCompleted.
if (d.dragSource != this) {
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(false);
}
}
@@ -1262,7 +1366,7 @@
verifier.updateRankAndPos(item, rank);
mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
item.cellY);
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(false);
if (mContent.areViewsBound()) {
mContent.createAndAddViewForRank(item, rank);
@@ -1335,11 +1439,13 @@
return itemsOnCurrentPage;
}
+ @Override
public void onFocusChange(View v, boolean hasFocus) {
if (v == mFolderName) {
if (hasFocus) {
startEditingFolderName();
} else {
+ logEditFolderLabel();
mFolderName.dispatchBackKey();
}
}
@@ -1353,11 +1459,24 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- target.gridX = info.cellX;
- target.gridY = info.cellY;
- target.pageIndex = mContent.getCurrentPage();
- targetParent.containerType = ContainerType.FOLDER;
+ public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
+ ArrayList<LauncherLogProto.Target> targets) {
+ child.gridX = childInfo.cellX;
+ child.gridY = childInfo.cellY;
+ child.pageIndex = mContent.getCurrentPage();
+
+ LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.FOLDER);
+ target.pageIndex = mInfo.screenId;
+ target.gridX = mInfo.cellX;
+ target.gridY = mInfo.cellY;
+ targets.add(target);
+
+ // continue to parent
+ if (mInfo.container == CONTAINER_HOTSEAT) {
+ mLauncher.getHotseat().fillInLogContainerData(mInfo, target, targets);
+ } else {
+ mLauncher.getWorkspace().fillInLogContainerData(mInfo, target, targets);
+ }
}
private class OnScrollHintListener implements OnAlarmListener {
@@ -1455,9 +1574,12 @@
@Override
public int getLogContainerType() {
- return ContainerType.FOLDER;
+ return LauncherLogProto.ContainerType.FOLDER;
}
+ /**
+ * Navigation bar back key or hardware input back key has been issued.
+ */
@Override
public boolean onBackPressed() {
if (isEditingName()) {
@@ -1487,7 +1609,7 @@
}
} else {
mLauncher.getUserEventDispatcher().logActionTapOutside(
- LoggerUtils.newContainerTarget(ContainerType.FOLDER));
+ newContainerTarget(LauncherLogProto.ContainerType.FOLDER));
close(true);
return true;
}
@@ -1517,4 +1639,128 @@
super.draw(canvas);
}
}
+
+ private void logEditFolderLabel() {
+ LauncherEvent launcherEvent = LauncherEvent.newBuilder()
+ .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
+ .addSrcTarget(newEditTextTargetBuilder()
+ .setFromFolderLabelState(getFromFolderLabelState())
+ .setToFolderLabelState(getToFolderLabelState()))
+ .addSrcTarget(newFolderTargetBuilder())
+ .addSrcTarget(newParentContainerTarget())
+ .build();
+ mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent);
+ mPreviousLabel = mFolderName.getText().toString();
+ mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
+ }
+
+ private Target.FromFolderLabelState getFromFolderLabelState() {
+ return mPreviousLabel == null
+ ? FROM_FOLDER_LABEL_STATE_UNSPECIFIED
+ : mPreviousLabel.isEmpty()
+ ? FROM_EMPTY
+ : mIsPreviousLabelSuggested
+ ? FROM_SUGGESTED
+ : FROM_CUSTOM;
+ }
+
+ private Target.ToFolderLabelState getToFolderLabelState() {
+ String newLabel =
+ checkNotNull(mFolderName.getText().toString(),
+ "Expected valid folder label, but found null");
+
+ Optional<String[]> suggestedLabels = getSuggestedLabels();
+ int accepted_suggestion_index = getAcceptedSuggestionIndex();
+ boolean hasValidPrimary = suggestedLabels
+ .map(labels -> labels.length > 0 && !isEmpty(labels[0]))
+ .orElse(false);
+ String primarySuffix = hasValidPrimary
+ ? "_WITH_VALID_PRIMARY"
+ : "_WITH_EMPTY_PRIMARY";
+
+ boolean isEmptySuggestions = suggestedLabels
+ .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
+ .orElse(true);
+ boolean isSuggestionsEnabled = FeatureFlags.FOLDER_NAME_SUGGEST.get();
+ String suggestionsSuffix = isSuggestionsEnabled
+ ? isEmptySuggestions
+ ? "_WITH_EMPTY_SUGGESTIONS"
+ : "_WITH_VALID_SUGGESTIONS"
+ : "_WITH_SUGGESTIONS_DISABLED";
+
+ return newLabel.equals(mPreviousLabel)
+ ? Target.ToFolderLabelState.UNCHANGED
+ : newLabel.isEmpty()
+ ? Target.ToFolderLabelState.valueOf("TO_EMPTY" + suggestionsSuffix)
+ : accepted_suggestion_index >= 0
+ ? Target.ToFolderLabelState.valueOf("TO_SUGGESTION"
+ + accepted_suggestion_index
+ + primarySuffix)
+ : Target.ToFolderLabelState.valueOf("TO_CUSTOM" + suggestionsSuffix);
+ }
+
+ private Optional<String[]> getSuggestedLabels() {
+ return ofNullable(mInfo)
+ .map(info -> info.suggestedFolderNames)
+ .map(
+ folderNames ->
+ (FolderNameInfo[])
+ folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+ .map(
+ folderNameInfoArray ->
+ stream(folderNameInfoArray)
+ .filter(Objects::nonNull)
+ .map(FolderNameInfo::getLabel)
+ .filter(Objects::nonNull)
+ .map(CharSequence::toString)
+ .toArray(String[]::new));
+ }
+
+ private int getAcceptedSuggestionIndex() {
+ String newLabel =
+ checkNotNull(mFolderName.getText().toString(),
+ "Expected valid folder label, but found null");
+
+ return getSuggestedLabels()
+ .map(suggestionsArray ->
+ IntStream.range(0, suggestionsArray.length)
+ .filter(index -> newLabel.equalsIgnoreCase(
+ suggestionsArray[index]))
+ .findFirst()
+ .orElse(-1)
+ ).orElse(-1);
+
+ }
+
+
+ private Target.Builder newEditTextTargetBuilder() {
+ return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
+ }
+
+ private Target.Builder newFolderTargetBuilder() {
+ return Target.newBuilder()
+ .setType(Target.Type.CONTAINER)
+ .setContainerType(ContainerType.FOLDER)
+ .setPageIndex(mInfo.screenId)
+ .setGridX(mInfo.cellX)
+ .setGridY(mInfo.cellY)
+ .setCardinality(mInfo.contents.size());
+ }
+
+ private Target.Builder newParentContainerTarget() {
+ Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
+
+ switch (mInfo.container) {
+ case CONTAINER_HOTSEAT:
+ return builder.setContainerType(ContainerType.HOTSEAT);
+ case CONTAINER_DESKTOP:
+ return builder.setContainerType(ContainerType.WORKSPACE);
+ default:
+ throw new AssertionError(String
+ .format("Expected container to be either %s or %s but found %s.",
+ CONTAINER_HOTSEAT,
+ CONTAINER_DESKTOP,
+ mInfo.container));
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 1310d37..f72e674 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -46,6 +47,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.util.Themes;
import java.util.List;
@@ -220,6 +222,13 @@
Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
play(a, z, mIsOpening ? midDuration : 0, midDuration);
+ BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
+ int stateBackgroundBlur = mLauncher.getStateManager().getState()
+ .getBackgroundBlurRadius(mLauncher);
+ int folderBackgroundBlurAdjustment = blurController.getFolderBackgroundBlurAdjustment();
+ play(a, ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, mIsOpening
+ ? stateBackgroundBlur + folderBackgroundBlurAdjustment
+ : stateBackgroundBlur));
// Store clip variables
CellLayout cellLayout = mContent.getCurrentCellLayout();
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index fd6d1e3..ab1ff10 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -67,6 +67,7 @@
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconLabelDotView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -79,7 +80,7 @@
*/
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
- @Thunk Launcher mLauncher;
+ @Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
private FolderInfo mInfo;
@@ -153,7 +154,21 @@
mDotParams = new DotRenderer.DrawParams();
}
- public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
+ public static FolderIcon inflateFolderAndIcon(int resId, Launcher launcher, ViewGroup group,
+ FolderInfo folderInfo) {
+ Folder folder = Folder.fromXml(launcher);
+ folder.setDragController(launcher.getDragController());
+
+ FolderIcon icon = inflateIcon(resId, launcher, group, folderInfo);
+ folder.setFolderIcon(icon);
+ folder.bind(folderInfo);
+ icon.setFolder(folder);
+
+ icon.setOnFocusChangeListener(launcher.getFocusHandler());
+ return icon;
+ }
+
+ public static FolderIcon inflateIcon(int resId, ActivityContext activity, ViewGroup group,
FolderInfo folderInfo) {
@SuppressWarnings("all") // suppress dead code warning
final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
@@ -163,7 +178,7 @@
"is dependent on this");
}
- DeviceProfile grid = launcher.getWallpaperDeviceProfile();
+ DeviceProfile grid = activity.getWallpaperDeviceProfile();
FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
.inflate(resId, group, false);
@@ -177,19 +192,27 @@
icon.setTag(folderInfo);
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.mInfo = folderInfo;
- icon.mLauncher = launcher;
+ icon.mActivity = activity;
icon.mDotRenderer = grid.mDotRendererWorkSpace;
- icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
- Folder folder = Folder.fromXml(launcher);
- folder.setDragController(launcher.getDragController());
- folder.setFolderIcon(icon);
- folder.bind(folderInfo);
- icon.setFolder(folder);
- icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
+
+ icon.setContentDescription(
+ group.getContext().getString(R.string.folder_name_format, folderInfo.title));
+
+ // Keep the notification dot up to date with the sum of all the content's dots.
+ FolderDotInfo folderDotInfo = new FolderDotInfo();
+ for (WorkspaceItemInfo si : folderInfo.contents) {
+ folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
+ }
+ icon.setDotInfo(folderDotInfo);
+
+ icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
+
+ icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
+ icon.mPreviewVerifier.setFolderInfo(folderInfo);
+ icon.updatePreviewItems(false);
folderInfo.addListener(icon);
- icon.setOnFocusChangeListener(launcher.mFocusHandler);
return icon;
}
@@ -217,9 +240,6 @@
private void setFolder(Folder folder) {
mFolder = folder;
- mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
- mPreviewVerifier.setFolderInfo(mFolder.getInfo());
- updatePreviewItems(false);
}
private boolean willAcceptItem(ItemInfo item) {
@@ -268,8 +288,9 @@
}
public void performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView,
- final WorkspaceItemInfo srcInfo, final DragView srcView, Rect dstRect,
+ final WorkspaceItemInfo srcInfo, final DragObject d, Rect dstRect,
float scaleRelativeToDragLayer) {
+ final DragView srcView = d.dragView;
prepareCreateAnimation(destView);
addItem(destInfo);
// This will animate the first item from it's position as an icon into its
@@ -278,7 +299,7 @@
.start();
// This will animate the dragView (srcView) into the new folder
- onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1,
+ onDrop(srcInfo, d, dstRect, scaleRelativeToDragLayer, 1,
false /* itemReturnedOnFailedDrop */);
}
@@ -293,22 +314,23 @@
mOpenAlarm.cancelAlarm();
}
- private void onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect,
+ private void onDrop(final WorkspaceItemInfo item, DragObject d, Rect finalRect,
float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
item.cellX = -1;
item.cellY = -1;
-
+ DragView animateView = d.dragView;
// Typically, the animateView corresponds to the DragView; however, if this is being done
// after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
// will not have a view to animate
- if (animateView != null) {
- DragLayer dragLayer = mLauncher.getDragLayer();
+ if (animateView != null && mActivity instanceof Launcher) {
+ final Launcher launcher = (Launcher) mActivity;
+ DragLayer dragLayer = launcher.getDragLayer();
Rect from = new Rect();
dragLayer.getViewRectRelativeToSelf(animateView, from);
Rect to = finalRect;
if (to == null) {
to = new Rect();
- Workspace workspace = mLauncher.getWorkspace();
+ Workspace workspace = launcher.getWorkspace();
// Set cellLayout and this to it's final state to compute final animation locations
workspace.setFinalTransitionTransform();
float scaleX = getScaleX();
@@ -371,22 +393,32 @@
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
final int finalIndex = index;
- String[] suggestedNameOut = new String[1];
+ FolderNameInfo[] nameInfos =
+ new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
- .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
+ Executors.UI_HELPER_EXECUTOR.post(() -> {
+ d.folderNameProvider.getSuggestedFolderName(
+ getContext(), mInfo.contents, nameInfos);
+ showFinalView(finalIndex, item, nameInfos);
+ });
+ } else {
+ showFinalView(finalIndex, item, nameInfos);
}
- postDelayed(() -> {
- mPreviewItemManager.hidePreviewItem(finalIndex, false);
- mFolder.showItem(item);
- invalidate();
- mFolder.showSuggestedTitle(suggestedNameOut[0]);
- }, DROP_IN_ANIMATION_DURATION);
} else {
addItem(item);
}
}
+ private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
+ FolderNameInfo[] nameInfos) {
+ postDelayed(() -> {
+ mPreviewItemManager.hidePreviewItem(finalIndex, false);
+ mFolder.showItem(item);
+ invalidate();
+ mFolder.showSuggestedTitle(nameInfos);
+ }, DROP_IN_ANIMATION_DURATION);
+ }
+
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
WorkspaceItemInfo item;
if (d.dragInfo instanceof AppInfo) {
@@ -399,9 +431,10 @@
item = (WorkspaceItemInfo) d.dragInfo;
}
mFolder.notifyDrop();
- onDrop(item, d.dragView, null, 1.0f,
+ onDrop(item, d, null, 1.0f,
itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
- itemReturnedOnFailedDrop);
+ itemReturnedOnFailedDrop
+ );
}
public void setDotInfo(FolderDotInfo dotInfo) {
@@ -530,7 +563,7 @@
if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
Rect iconBounds = mDotParams.iconBounds;
BubbleTextView.getIconBounds(this, iconBounds,
- mLauncher.getWallpaperDeviceProfile().iconSizePx);
+ mActivity.getWallpaperDeviceProfile().iconSizePx);
float iconScale = (float) mBackground.previewSize / iconBounds.width();
Utilities.scaleRectAboutCenter(iconBounds, iconScale);
@@ -588,7 +621,7 @@
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
boolean wasDotted = mDotInfo.hasDot();
- mDotInfo.addDotInfo(mLauncher.getDotInfoForItem(item));
+ mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted);
invalidate();
@@ -598,7 +631,7 @@
@Override
public void onRemove(WorkspaceItemInfo item) {
boolean wasDotted = mDotInfo.hasDot();
- mDotInfo.subtractDotInfo(mLauncher.getDotInfoForItem(item));
+ mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted);
invalidate();
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
new file mode 100644
index 0000000..edf2c70
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.folder;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.ExtendedEditText;
+
+import java.util.List;
+
+/**
+ * Handles additional edit text functionality to better support folder name suggestion.
+ * First, makes suggestion to the InputMethodManager via {@link #displayCompletions(List)}
+ * Second, intercepts whether user accepted the suggestion or manually edited their
+ * folder names.
+ */
+public class FolderNameEditText extends ExtendedEditText {
+ private static final String TAG = "FolderNameEditText";
+ private static final boolean DEBUG = false;
+
+ private boolean mEnteredCompose = false;
+
+ public FolderNameEditText(Context context) {
+ super(context);
+ }
+
+ public FolderNameEditText(Context context, AttributeSet attrs) {
+ // ctor chaining breaks the touch handling
+ super(context, attrs);
+ }
+
+ public FolderNameEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ InputConnection con = super.onCreateInputConnection(outAttrs);
+ FolderNameEditTextInputConnection connectionWrapper =
+ new FolderNameEditTextInputConnection(con, true);
+ return connectionWrapper;
+ }
+
+ /**
+ * Send strings in @param suggestList to the IME to show up as suggestions.
+ */
+ public void displayCompletions(List<String> suggestList) {
+ int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX);
+ CompletionInfo[] cInfo = new CompletionInfo[cnt];
+ for (int i = 0; i < cnt; i++) {
+ cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
+ }
+ post(() -> getContext().getSystemService(InputMethodManager.class)
+ .displayCompletions(this, cInfo));
+ }
+
+ /**
+ * Within 's', the 'count' characters beginning at 'start' have just replaced
+ * old text 'before'
+ */
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ String reason = "unknown";
+ if (start == 0 && count == 0 && before > 0) {
+ reason = "suggestion was rejected";
+ mEnteredCompose = true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onTextChanged " + start + "," + before + "," + count
+ + ", " + reason);
+ }
+ }
+
+ @Override
+ public void onCommitCompletion(CompletionInfo text) {
+ setText(text.getText());
+ setSelection(text.getText().length());
+ mEnteredCompose = false;
+ }
+
+ protected void setEnteredCompose(boolean value) {
+ mEnteredCompose = value;
+ }
+
+ private class FolderNameEditTextInputConnection extends InputConnectionWrapper {
+
+ FolderNameEditTextInputConnection(InputConnection target, boolean mutable) {
+ super(target, mutable);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence cs, int newCursorPos) {
+ mEnteredCompose = true;
+ return super.setComposingText(cs, newCursorPos);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
new file mode 100644
index 0000000..1287219
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.folder;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Information about a single label suggestions of the Folder.
+ */
+
+public final class FolderNameInfo implements Parcelable {
+ private final double mScore;
+ private final CharSequence mLabel;
+
+ /**
+ * Create a simple completion with label.
+ *
+ * @param label The text that should be inserted into the editor and pushed to
+ * InputMethodManager suggestions.
+ * @param score The score for the label between 0.0 and 1.0.
+ */
+ public FolderNameInfo(CharSequence label, double score) {
+ mScore = score;
+ mLabel = label;
+ }
+
+ private FolderNameInfo(Parcel source) {
+ mScore = source.readDouble();
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeDouble(mScore);
+ TextUtils.writeToParcel(mLabel, dest, flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ @NonNull
+ public static final Parcelable.Creator<FolderNameInfo> CREATOR =
+ new Parcelable.Creator<FolderNameInfo>() {
+ public FolderNameInfo createFromParcel(Parcel source) {
+ return new FolderNameInfo(source);
+ }
+
+ public FolderNameInfo[] newArray(int size) {
+ return new FolderNameInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("%s:%.2f", mLabel, mScore);
+ }
+}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 0a1221e..07161da 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -17,45 +17,165 @@
import android.content.ComponentName;
import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.ResourceBasedOverride;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Locates provider for the folder name.
*/
-public class FolderNameProvider {
+public class FolderNameProvider implements ResourceBasedOverride {
+
+ private static final String TAG = "FolderNameProvider";
+ private static final boolean DEBUG = true;
/**
- * Returns suggested folder name.
+ * IME usually has up to 3 suggest slots. In total, there are 4 suggest slots as the folder
+ * name edit box can also be used to provide suggestion.
*/
- public CharSequence getSuggestedFolderName(Context context,
- ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) {
- // Currently only run the algorithm on initial folder creation.
- // For more than 2 items in the folder, the ranking algorithm for finding
- // candidate folder name should be rewritten.
- if (workspaceItemInfos.size() == 2) {
- ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent();
- ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent();
+ public static final int SUGGEST_MAX = 4;
+ protected IntSparseArrayMap<FolderInfo> mFolderInfos;
+ protected List<AppInfo> mAppInfos;
- String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName();
- String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName();
- // If the two icons are from the same package,
- // then assign the main icon's name
- if (pkgName0.equals(pkgName1)) {
- WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0);
- WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1);
- if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) {
- suggestName[0] = wInfo0.title;
- } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) {
- suggestName[0] = wInfo1.title;
- }
- return suggestName[0];
- // two icons are all shortcuts. Don't assign title
+ /**
+ * Retrieve instance of this object that can be overridden in runtime based on the build
+ * variant of the application.
+ */
+ public static FolderNameProvider newInstance(Context context) {
+ FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
+ context.getApplicationContext(), R.string.folder_name_provider_class);
+ fnp.load(context);
+
+ return fnp;
+ }
+
+ public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
+ IntSparseArrayMap<FolderInfo> folderInfos) {
+ FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
+ context.getApplicationContext(), R.string.folder_name_provider_class);
+ fnp.load(appInfos, folderInfos);
+
+ return fnp;
+ }
+
+ private void load(Context context) {
+ LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(
+ new FolderNameWorker());
+ }
+
+ private void load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
+ mAppInfos = appInfos;
+ mFolderInfos = folderInfos;
+ }
+
+ /**
+ * Generate and rank the suggested Folder names.
+ */
+ public void getSuggestedFolderName(Context context,
+ ArrayList<WorkspaceItemInfo> workspaceItemInfos,
+ FolderNameInfo[] nameInfos) {
+
+ if (DEBUG) {
+ Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+ }
+ // If all the icons are from work profile,
+ // Then, suggest "Work" as the folder name
+ Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
+ .collect(Collectors.toSet());
+ if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
+ setAsLastSuggestion(nameInfos,
+ context.getResources().getString(R.string.work_folder_name));
+ }
+
+ // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
+ // Then, suggest the package's title as the folder name
+ Set<String> packageNames = workspaceItemInfos.stream()
+ .map(WorkspaceItemInfo::getTargetComponent)
+ .filter(Objects::nonNull)
+ .map(ComponentName::getPackageName)
+ .collect(Collectors.toSet());
+
+ if (packageNames.size() == 1) {
+ Optional<AppInfo> info = getAppInfoByPackageName(packageNames.iterator().next());
+ // Place it as first viable suggestion and shift everything else
+ info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
+ }
+ if (DEBUG) {
+ Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+ }
+ }
+
+ private Optional<AppInfo> getAppInfoByPackageName(String packageName) {
+ if (mAppInfos == null || mAppInfos.isEmpty()) {
+ return Optional.empty();
+ }
+ return mAppInfos.stream()
+ .filter(info -> info.componentName != null)
+ .filter(info -> info.componentName.getPackageName().equals(packageName))
+ .findAny();
+ }
+
+ private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+ if (nameInfos.length == 0 || contains(nameInfos, label)) {
+ return;
+ }
+ for (int i = nameInfos.length - 1; i > 0; i--) {
+ if (nameInfos[i - 1] != null && !TextUtils.isEmpty(nameInfos[i - 1].getLabel())) {
+ nameInfos[i] = nameInfos[i - 1];
}
}
- return suggestName[0];
+ nameInfos[0] = new FolderNameInfo(label, 1.0);
}
+
+ private void setAsLastSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+ if (nameInfos.length == 0 || contains(nameInfos, label)) {
+ return;
+ }
+
+ for (int i = 0; i < nameInfos.length; i++) {
+ if (nameInfos[i] == null || TextUtils.isEmpty(nameInfos[i].getLabel())) {
+ nameInfos[i] = new FolderNameInfo(label, 1.0);
+ return;
+ }
+ }
+ // Overwrite the last suggestion.
+ int lastIndex = nameInfos.length - 1;
+ nameInfos[lastIndex] = new FolderNameInfo(label, 1.0);
+ }
+
+ private boolean contains(FolderNameInfo[] nameInfos, CharSequence label) {
+ return Arrays.stream(nameInfos)
+ .filter(Objects::nonNull)
+ .anyMatch(nameInfo -> nameInfo.getLabel().toString().equalsIgnoreCase(
+ label.toString()));
+ }
+
+ private class FolderNameWorker extends BaseModelUpdateTask {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ mFolderInfos = dataModel.folders.clone();
+ mAppInfos = Arrays.asList(apps.copyData());
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 3b5fd59..c6d62f8 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -258,7 +258,7 @@
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
- mPageIndicator.setScroll(l, mMaxScrollX);
+ mPageIndicator.setScroll(l, mMaxScroll);
}
/**
diff --git a/src/com/android/launcher3/folder/FolderPreviewItemAnim.java b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
index 1e56f7d..22f7333 100644
--- a/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
+++ b/src/com/android/launcher3/folder/FolderPreviewItemAnim.java
@@ -44,7 +44,8 @@
}
};
- private static PreviewItemDrawingParams sTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+ private static final PreviewItemDrawingParams sTmpParams =
+ new PreviewItemDrawingParams(0, 0, 0, 0);
private static final float[] sTempParamsArray = new float[3];
private final ObjectAnimator mAnimator;
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index b2c0ca7..2d177d2 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -48,7 +48,7 @@
* This object represents a FolderIcon preview background. It stores drawing / measurement
* information, handles drawing, and animation (accept state <--> rest state).
*/
-public class PreviewBackground {
+public class PreviewBackground extends CellLayout.DelegatedCellDrawing {
private static final int CONSUMPTION_ANIMATION_DURATION = 100;
@@ -76,8 +76,6 @@
int basePreviewOffsetY;
private CellLayout mDrawingDelegate;
- public int delegateCellX;
- public int delegateCellY;
// When the PreviewBackground is drawn under an icon (for creating a folder) the border
// should not occlude the icon
@@ -124,6 +122,27 @@
}
};
+ /**
+ * Draws folder background under cell layout
+ */
+ @Override
+ public void drawUnderItem(Canvas canvas) {
+ drawBackground(canvas);
+ if (!isClipping) {
+ drawBackgroundStroke(canvas);
+ }
+ }
+
+ /**
+ * Draws folder background on cell layout
+ */
+ @Override
+ public void drawOverItem(Canvas canvas) {
+ if (isClipping) {
+ drawBackgroundStroke(canvas);
+ }
+ }
+
public void setup(Context context, ActivityContext activity, View invalidateDelegate,
int availableSpaceX, int topPadding) {
mInvalidateDelegate = invalidateDelegate;
@@ -317,19 +336,19 @@
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
if (mDrawingDelegate != delegate) {
- delegate.addFolderBackground(this);
+ delegate.addDelegatedCellDrawing(this);
}
mDrawingDelegate = delegate;
- delegateCellX = cellX;
- delegateCellY = cellY;
+ mDelegateCellX = cellX;
+ mDelegateCellY = cellY;
invalidate();
}
private void clearDrawingDelegate() {
if (mDrawingDelegate != null) {
- mDrawingDelegate.removeFolderBackground(this);
+ mDrawingDelegate.removeDelegatedCellDrawing(this);
}
mDrawingDelegate = null;
@@ -395,8 +414,8 @@
// is saved and restored at the beginning of the animation, since cancelling the
// existing animation can clear the delgate.
CellLayout cl = mDrawingDelegate;
- int cellX = delegateCellX;
- int cellY = delegateCellY;
+ int cellX = mDelegateCellX;
+ int cellY = mDelegateCellY;
animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate);
}
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2d817e6..27aa43e 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -16,10 +16,12 @@
package com.android.launcher3.folder;
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION;
+import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -35,11 +37,10 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
import java.util.List;
@@ -66,7 +67,6 @@
private final Context mContext;
private final FolderIcon mIcon;
- private final DrawableFactory mDrawableFactory;
private final int mIconSize;
// These variables are all associated with the drawing of the preview; they are stored
@@ -94,8 +94,8 @@
public PreviewItemManager(FolderIcon icon) {
mContext = icon.getContext();
mIcon = icon;
- mDrawableFactory = DrawableFactory.INSTANCE.get(mContext);
- mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx;
+ mIconSize = ActivityContext.lookupContext(
+ mContext).getDeviceProfile().folderChildIconSizePx;
}
/**
@@ -133,7 +133,7 @@
mTotalWidth = totalSize;
mPrevTopPadding = mIcon.getPaddingTop();
- mIcon.mBackground.setup(mIcon.mLauncher, mIcon.mLauncher, mIcon, mTotalWidth,
+ mIcon.mBackground.setup(mIcon.getContext(), mIcon.mActivity, mIcon, mTotalWidth,
mIcon.getPaddingTop());
mIcon.mPreviewLayoutRule.init(mIcon.mBackground.previewSize, mIntrinsicIconSize,
Utilities.isRtl(mIcon.getResources()));
@@ -153,7 +153,7 @@
}
private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
- float iconSize = mIcon.mLauncher.getDeviceProfile().iconSizePx;
+ float iconSize = mIcon.mActivity.getDeviceProfile().iconSizePx;
final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
final float trans = (mIcon.mBackground.previewSize - iconSize) / 2;
@@ -395,11 +395,11 @@
private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
if (item.hasPromiseIconUi()) {
- PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item);
+ PreloadIconDrawable drawable = newPendingIcon(mContext, item);
drawable.setLevel(item.getInstallProgress());
p.drawable = drawable;
} else {
- p.drawable = mDrawableFactory.newIcon(mContext, item);
+ p.drawable = newIcon(mContext, item);
}
p.drawable.setBounds(0, 0, mIconSize, mIconSize);
p.item = item;
diff --git a/src/com/android/launcher3/graphics/BitmapCreationCheck.java b/src/com/android/launcher3/graphics/BitmapCreationCheck.java
new file mode 100644
index 0000000..e63e542
--- /dev/null
+++ b/src/com/android/launcher3/graphics/BitmapCreationCheck.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.graphics;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.GraphicsUtils;
+
+/**
+ * Utility class to check bitmap creation during draw pass.
+ */
+public class BitmapCreationCheck {
+
+ private static final String TAG = "BitmapCreationCheck";
+
+ public static final boolean ENABLED = false;
+
+ /**
+ * Starts tracking bitmap creations during {@link View#draw(Canvas)} calls
+ */
+ public static void startTracking(Context context) {
+ MyTracker tracker = new MyTracker();
+ ((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks(tracker);
+ GraphicsUtils.sOnNewBitmapRunnable = tracker::onBitmapCreated;
+ }
+
+ @TargetApi(VERSION_CODES.Q)
+ private static class MyTracker
+ implements ActivityLifecycleCallbacks, OnAttachStateChangeListener {
+
+ private final ThreadLocal<Boolean> mCurrentThreadDrawing =
+ ThreadLocal.withInitial(() -> false);
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ activity.getWindow().getDecorView().addOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) { }
+
+ @Override
+ public void onActivityResumed(Activity activity) { }
+
+ @Override
+ public void onActivityPaused(Activity activity) { }
+
+ @Override
+ public void onActivityStopped(Activity activity) { }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) { }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ view.getViewTreeObserver().addOnDrawListener(new MyViewDrawListener(view.getHandler()));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) { }
+
+ private class MyViewDrawListener implements OnDrawListener, Runnable {
+
+ private final Handler mHandler;
+
+ MyViewDrawListener(Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void onDraw() {
+ mCurrentThreadDrawing.set(true);
+ Utilities.postAsyncCallback(mHandler, this);
+ }
+
+ @Override
+ public void run() {
+ mCurrentThreadDrawing.set(false);
+ }
+ }
+
+ private void onBitmapCreated() {
+ if (mCurrentThreadDrawing.get()) {
+ Log.e(TAG, "Bitmap created during draw pass", new Exception());
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index f579451..94d30f6 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -152,7 +152,7 @@
}
public final void generateDragOutline(Bitmap preview) {
- if (FeatureFlags.IS_DOGFOOD_BUILD && mOutlineGeneratorCallback != null) {
+ if (FeatureFlags.IS_STUDIO_BUILD && mOutlineGeneratorCallback != null) {
throw new RuntimeException("Drag outline generated twice");
}
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
deleted file mode 100644
index 837301f..0000000
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.graphics;
-
-import static com.android.launcher3.graphics.IconShape.getShapePath;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Process;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Factory for creating new drawables.
- */
-public class DrawableFactory implements ResourceBasedOverride {
-
- public static final MainThreadInitializedObject<DrawableFactory> INSTANCE =
- forOverride(DrawableFactory.class, R.string.drawable_factory_class);
-
- protected final UserHandle mMyUser = Process.myUserHandle();
- protected final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
-
- /**
- * Returns a FastBitmapDrawable with the icon.
- */
- public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
- FastBitmapDrawable drawable = info.usingLowResIcon()
- ? new PlaceHolderIconDrawable(info, getShapePath(), context)
- : new FastBitmapDrawable(info);
- drawable.setIsDisabled(info.isDisabled());
- return drawable;
- }
-
- public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) {
- return info.isLowRes()
- ? new PlaceHolderIconDrawable(info, getShapePath(), context)
- : new FastBitmapDrawable(info);
- }
-
- /**
- * Returns a FastBitmapDrawable with the icon.
- */
- public PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) {
- return new PreloadIconDrawable(info, getShapePath(), context);
- }
-
- /**
- * Returns a drawable that can be used as a badge for the user or null.
- */
- @UiThread
- public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) {
- if (mMyUser.equals(user)) {
- return null;
- }
-
- Bitmap badgeBitmap = getUserBadge(user, context, badgeSize);
- FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
- d.setFilterBitmap(true);
- d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
- return d;
- }
-
- protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) {
- Bitmap badgeBitmap = mUserBadges.get(user);
- if (badgeBitmap != null) {
- return badgeBitmap;
- }
-
- final Resources res = context.getApplicationContext().getResources();
- badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
-
- Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity(
- new BitmapDrawable(res, badgeBitmap), user, new Rect(0, 0, badgeSize, badgeSize),
- 0);
- if (drawable instanceof BitmapDrawable) {
- badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
- } else {
- badgeBitmap.eraseColor(Color.TRANSPARENT);
- Canvas c = new Canvas(badgeBitmap);
- drawable.setBounds(0, 0, badgeSize, badgeSize);
- drawable.draw(c);
- c.setBitmap(null);
- }
-
- mUserBadges.put(user, badgeBitmap);
- return badgeBitmap;
- }
-}
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index 71b4366..607aba9 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,5 +1,6 @@
package com.android.launcher3.graphics;
+import static com.android.launcher3.config.FeatureFlags.USE_SURFACE_VIEW_FOR_GRID_PREVIEW;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.ContentProvider;
@@ -19,6 +20,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.PreviewSurfaceRenderer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,6 +64,11 @@
private static final String KEY_PREVIEW = "preview";
private static final String MIME_TYPE_PNG = "image/png";
+ private static final String METHOD_GET_PREVIEW = "get_preview";
+ private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version";
+
+
+
public static final PipeDataWriter<Future<Bitmap>> BITMAP_WRITER =
new PipeDataWriter<Future<Bitmap>>() {
@Override
@@ -98,6 +105,10 @@
.add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
&& idp.numRows == gridOption.numRows);
}
+ Bundle metadata = new Bundle();
+ metadata.putString(METADATA_KEY_PREVIEW_VERSION,
+ USE_SURFACE_VIEW_FOR_GRID_PREVIEW.get() ? "V2" : "V1");
+ cursor.setExtras(metadata);
return cursor;
}
@@ -188,4 +199,14 @@
throw new FileNotFoundException(e.getMessage());
}
}
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ if (!METHOD_GET_PREVIEW.equals(method)) {
+ return null;
+ }
+
+ PreviewSurfaceRenderer.render(getContext(), extras);
+ return null;
+ }
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index d7b845b..5bc6610 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -19,10 +19,20 @@
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
+import static com.android.launcher3.model.GridSizeMigrationTask.needsToMigrate;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.annotation.TargetApi;
import android.app.Fragment;
+import android.appwidget.AppWidgetHostView;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -45,24 +55,59 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.GridSizeMigrationTaskV2;
+import com.android.launcher3.model.LoaderResults;
+import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -77,6 +122,90 @@
private static final String TAG = "LauncherPreviewRenderer";
+ /**
+ * Context used just for preview. It also provides a few objects (e.g. UserCache) just for
+ * preview purposes.
+ */
+ public static class PreviewContext extends ContextWrapper {
+
+ private static final Set<MainThreadInitializedObject> WHITELIST = new HashSet<>(
+ Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
+ LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
+ CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE));
+
+ private final InvariantDeviceProfile mIdp;
+ private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
+ private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
+ new ConcurrentLinkedQueue<>();
+
+ public PreviewContext(Context base, InvariantDeviceProfile idp) {
+ super(base);
+ mIdp = idp;
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ public void onDestroy() {
+ CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get(
+ CustomWidgetManager.INSTANCE);
+ if (customWidgetManager != null) {
+ customWidgetManager.onDestroy();
+ }
+ }
+
+ /**
+ * Find a cached object from mObjectMap if we have already created one. If not, generate
+ * an object using the provider.
+ */
+ public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
+ MainThreadInitializedObject.ObjectProvider<T> provider) {
+ if (!WHITELIST.contains(mainThreadInitializedObject)) {
+ throw new IllegalStateException("Leaking unknown objects");
+ }
+ if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
+ throw new IllegalStateException(
+ "Should not use MainThreadInitializedObject to initialize this with "
+ + "PreviewContext");
+ }
+ if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) {
+ return (T) mIdp;
+ }
+ if (mObjectMap.containsKey(mainThreadInitializedObject)) {
+ return (T) mObjectMap.get(mainThreadInitializedObject);
+ }
+ T t = provider.get(this);
+ mObjectMap.put(mainThreadInitializedObject, t);
+ return t;
+ }
+
+ public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) {
+ LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
+ if (launcherIconsForPreview != null) {
+ return launcherIconsForPreview;
+ }
+ return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
+ -1 /* poolId */, shapeDetection);
+ }
+
+ private final class LauncherIconsForPreview extends LauncherIcons {
+
+ private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
+ int poolId, boolean shapeDetection) {
+ super(context, fillResIconDpi, iconBitmapSize, poolId, shapeDetection);
+ }
+
+ @Override
+ public void recycle() {
+ // Clear any temporary state variables
+ clear();
+ mIconPool.offer(this);
+ }
+ }
+ }
+
private final Handler mUiHandler;
private final Context mContext;
private final InvariantDeviceProfile mIdp;
@@ -105,7 +234,7 @@
Build.VERSION.SDK_INT);
mWorkspaceItemInfo = new WorkspaceItemInfo();
- mWorkspaceItemInfo.applyFrom(iconInfo);
+ mWorkspaceItemInfo.bitmap = iconInfo;
mWorkspaceItemInfo.intent = new Intent();
mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
context.getString(R.string.label_application);
@@ -133,6 +262,13 @@
});
}
+ /** Populate preview and render it. */
+ public View getRenderedView() {
+ MainThreadRenderer renderer = new MainThreadRenderer(mContext);
+ renderer.populate();
+ return renderer.mRootView;
+ }
+
private class MainThreadRenderer extends ContextThemeWrapper
implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
@@ -222,6 +358,25 @@
addInScreenFromBind(icon, info);
}
+ private void inflateAndAddFolder(FolderInfo info) {
+ FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
+ info);
+ addInScreenFromBind(folderIcon, info);
+ }
+
+ private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
+ WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
+ info.providerName);
+ if (widgetItem == null) {
+ return;
+ }
+ AppWidgetHostView view = new AppWidgetHostView(mContext);
+ view.setAppWidget(-1, widgetItem.widgetInfo);
+ view.updateAppWidget(null);
+ view.setTag(info);
+ addInScreenFromBind(view, info);
+ }
+
private void dispatchVisibilityAggregated(View view, boolean isVisible) {
// Similar to View.dispatchVisibilityAggregated implementation.
final boolean thisVisible = view.getVisibility() == VISIBLE;
@@ -240,23 +395,94 @@
}
}
- private void renderScreenShot(Canvas canvas) {
- // Add hotseat icons
- for (int i = 0; i < mIdp.numHotseatIcons; i++) {
- WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
- info.container = Favorites.CONTAINER_HOTSEAT;
- info.screenId = i;
- inflateAndAddIcon(info);
- }
+ private void populate() {
+ if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
+ boolean needsToMigrate = needsToMigrate(mContext, mIdp);
+ boolean success = false;
+ if (needsToMigrate) {
+ success = MULTI_DB_GRID_MIRATION_ALGO.get()
+ ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
+ : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
+ }
- // Add workspace icons
- for (int i = 0; i < mIdp.numColumns; i++) {
- WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
- info.container = Favorites.CONTAINER_DESKTOP;
- info.screenId = 0;
- info.cellX = i;
- info.cellY = mIdp.numRows - 1;
- inflateAndAddIcon(info);
+ WorkspaceFetcher fetcher;
+ PreviewContext previewContext = null;
+ if (needsToMigrate && success) {
+ previewContext = new PreviewContext(mContext, mIdp);
+ LauncherAppState appForPreview = new LauncherAppState(
+ previewContext, null /* iconCacheFileName */);
+ fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
+ MODEL_EXECUTOR.execute(fetcher);
+ } else {
+ fetcher = new WorkspaceItemsInfoFetcher();
+ LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
+ (LauncherModel.ModelUpdateTask) fetcher);
+ }
+ WorkspaceResult workspaceResult = fetcher.get();
+ if (previewContext != null) {
+ previewContext.onDestroy();
+ }
+
+ if (workspaceResult == null) {
+ return;
+ }
+
+ // Separate the items that are on the current screen, and all the other remaining
+ // items
+ ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+ ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+
+ filterCurrentWorkspaceItems(0 /* currentScreenId */,
+ workspaceResult.mWorkspaceItems, currentWorkspaceItems,
+ otherWorkspaceItems);
+ filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
+ currentAppWidgets, otherAppWidgets);
+ sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
+
+ for (ItemInfo itemInfo : currentWorkspaceItems) {
+ switch (itemInfo.itemType) {
+ case Favorites.ITEM_TYPE_APPLICATION:
+ case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ inflateAndAddFolder((FolderInfo) itemInfo);
+ break;
+ default:
+ break;
+ }
+ }
+ for (ItemInfo itemInfo : currentAppWidgets) {
+ switch (itemInfo.itemType) {
+ case Favorites.ITEM_TYPE_APPWIDGET:
+ case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+ workspaceResult.mWidgetsModel);
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ // Add hotseat icons
+ for (int i = 0; i < mIdp.numHotseatIcons; i++) {
+ WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+ info.container = Favorites.CONTAINER_HOTSEAT;
+ info.screenId = i;
+ inflateAndAddIcon(info);
+ }
+ // Add workspace icons
+ for (int i = 0; i < mIdp.numColumns; i++) {
+ WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+ info.container = Favorites.CONTAINER_DESKTOP;
+ info.screenId = 0;
+ info.cellX = i;
+ info.cellY = mIdp.numRows - 1;
+ inflateAndAddIcon(info);
+ }
}
// Add first page QSB
@@ -280,7 +506,10 @@
measureView(mRootView, mDp.widthPx, mDp.heightPx);
// Additional measure for views which use auto text size API
measureView(mRootView, mDp.widthPx, mDp.heightPx);
+ }
+ private void renderScreenShot(Canvas canvas) {
+ populate();
mRootView.draw(canvas);
dispatchVisibilityAggregated(mRootView, false);
}
@@ -290,4 +519,102 @@
view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
view.layout(0, 0, width, height);
}
+
+ private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask,
+ WorkspaceFetcher {
+
+ private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
+
+ private LauncherAppState mApp;
+ private LauncherModel mModel;
+ private BgDataModel mBgDataModel;
+ private AllAppsList mAllAppsList;
+
+ @Override
+ public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
+ AllAppsList allAppsList, Executor uiExecutor) {
+ mApp = app;
+ mModel = model;
+ mBgDataModel = dataModel;
+ mAllAppsList = allAppsList;
+ }
+
+ @Override
+ public FutureTask<WorkspaceResult> getTask() {
+ return mTask;
+ }
+
+ @Override
+ public void run() {
+ mTask.run();
+ }
+
+ @Override
+ public WorkspaceResult call() throws Exception {
+ if (!mModel.isModelLoaded()) {
+ Log.d(TAG, "Workspace not loaded, loading now");
+ mModel.startLoaderForResults(
+ new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
+ return null;
+ }
+
+ return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
+ mBgDataModel.widgetsModel);
+ }
+ }
+
+ private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements
+ WorkspaceFetcher {
+
+ private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
+
+ WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
+ super(app, null, new BgDataModel(), null);
+ }
+
+ @Override
+ public FutureTask<WorkspaceResult> getTask() {
+ return mTask;
+ }
+
+ @Override
+ public void run() {
+ mTask.run();
+ }
+
+ @Override
+ public WorkspaceResult call() throws Exception {
+ List<ShortcutInfo> allShortcuts = new ArrayList<>();
+ loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
+ mBgDataModel.widgetsModel.update(mApp, null);
+ return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
+ mBgDataModel.widgetsModel);
+ }
+ }
+
+ private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> {
+ FutureTask<WorkspaceResult> getTask();
+
+ default WorkspaceResult get() {
+ try {
+ return getTask().get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.d(TAG, "Error fetching workspace items info", e);
+ return null;
+ }
+ }
+ }
+
+ private static class WorkspaceResult {
+ private final ArrayList<ItemInfo> mWorkspaceItems;
+ private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+ private final WidgetsModel mWidgetsModel;
+
+ private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
+ ArrayList<LauncherAppWidgetInfo> appWidgets, WidgetsModel widgetsModel) {
+ mWorkspaceItems = workspaceItems;
+ mAppWidgets = appWidgets;
+ mWidgetsModel = widgetsModel;
+ }
+ }
}
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
index d707403..94acbfd 100644
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -55,16 +55,17 @@
mCurrentScrimmedView = mStableScrimmedView;
int currentIndex = root.indexOfChild(mCurrentScrimmedView);
final int childCount = root.getChildCount();
- while (mCurrentScrimmedView.getVisibility() != VISIBLE && currentIndex < childCount) {
+ while (mCurrentScrimmedView != null && mCurrentScrimmedView.getVisibility() != VISIBLE
+ && currentIndex < childCount) {
currentIndex++;
mCurrentScrimmedView = root.getChildAt(currentIndex);
}
}
/**
- * @return The view to draw the scrim behind.
+ * @return The view to draw the scrim behind, or null if all visible views should be scrimmed.
*/
- public View getScrimmedView() {
+ public @Nullable View getScrimmedView() {
return mCurrentScrimmedView;
}
}
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
index 23745cb..d347e8f 100644
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
@@ -17,14 +17,14 @@
import static androidx.core.graphics.ColorUtils.compositeColors;
+import static com.android.launcher3.graphics.IconShape.getShapePath;
+
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.util.Themes;
@@ -37,20 +37,12 @@
// Path in [0, 100] bounds.
private final Path mProgressPath;
- public PlaceHolderIconDrawable(BitmapInfo info, Path progressPath, Context context) {
- this(info.icon, info.color, progressPath, context);
- }
+ public PlaceHolderIconDrawable(BitmapInfo info, Context context) {
+ super(info);
- public PlaceHolderIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
- this(info.iconBitmap, info.iconColor, progressPath, context);
- }
-
- protected PlaceHolderIconDrawable(Bitmap b, int iconColor, Path progressPath, Context context) {
- super(b, iconColor);
-
- mProgressPath = progressPath;
+ mProgressPath = getShapePath();
mPaint.setColor(compositeColors(
- Themes.getAttrColor(context, R.attr.loadingIconColor), iconColor));
+ Themes.getAttrColor(context, R.attr.loadingIconColor), info.color));
}
@Override
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index cc4c2ef..b0e1db1 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -18,6 +18,7 @@
package com.android.launcher3.graphics;
import static com.android.launcher3.graphics.IconShape.DEFAULT_PATH_SIZE;
+import static com.android.launcher3.graphics.IconShape.getShapePath;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -101,13 +102,10 @@
private ObjectAnimator mCurrentAnim;
- /**
- * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
- */
- public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
- super(info);
+ public PreloadIconDrawable(ItemInfoWithIcon info, Context context) {
+ super(info.bitmap);
mItem = info;
- mProgressPath = progressPath;
+ mProgressPath = getShapePath();
mScaledTrackPath = new Path();
mScaledProgressPath = new Path();
@@ -289,4 +287,11 @@
}
invalidateSelf();
}
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public static PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) {
+ return new PreloadIconDrawable(info, context);
+ }
}
diff --git a/src/com/android/launcher3/graphics/RotationMode.java b/src/com/android/launcher3/graphics/RotationMode.java
index b06305f..6dd356a 100644
--- a/src/com/android/launcher3/graphics/RotationMode.java
+++ b/src/com/android/launcher3/graphics/RotationMode.java
@@ -20,7 +20,7 @@
public abstract class RotationMode {
- public static RotationMode NORMAL = new RotationMode(0) { };
+ public static final RotationMode NORMAL = new RotationMode(0) { };
public final float surfaceRotation;
public final boolean isTransposed;
diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java
index 5c14f8d..f90962d 100644
--- a/src/com/android/launcher3/graphics/Scrim.java
+++ b/src/com/android/launcher3/graphics/Scrim.java
@@ -19,7 +19,7 @@
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.graphics.Canvas;
-import android.util.Property;
+import android.util.FloatProperty;
import android.view.View;
import com.android.launcher3.Launcher;
@@ -31,16 +31,16 @@
public class Scrim implements View.OnAttachStateChangeListener,
WallpaperColorInfo.OnChangeListener {
- public static Property<Scrim, Float> SCRIM_PROGRESS =
- new Property<Scrim, Float>(Float.TYPE, "scrimProgress") {
+ public static final FloatProperty<Scrim> SCRIM_PROGRESS =
+ new FloatProperty<Scrim>("scrimProgress") {
@Override
public Float get(Scrim scrim) {
return scrim.mScrimProgress;
}
@Override
- public void set(Scrim scrim, Float value) {
- scrim.setScrimProgress(value);
+ public void setValue(Scrim scrim, float v) {
+ scrim.setScrimProgress(v);
}
};
@@ -55,7 +55,7 @@
public Scrim(View view) {
mRoot = view;
mLauncher = Launcher.getLauncher(view.getContext());
- mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+ mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(mLauncher);
view.addOnAttachStateChangeListener(this);
}
diff --git a/src/com/android/launcher3/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java
index f10b972..d8a7070 100644
--- a/src/com/android/launcher3/graphics/ShadowDrawable.java
+++ b/src/com/android/launcher3/graphics/ShadowDrawable.java
@@ -120,36 +120,35 @@
}
private void regenerateBitmapCache() {
- Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight,
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
-
// Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
Drawable d = mState.mChildState.newDrawable().mutate();
d.setBounds(mState.mShadowSize, mState.mShadowSize,
mState.mIntrinsicWidth - mState.mShadowSize,
mState.mIntrinsicHeight - mState.mShadowSize);
d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE);
- d.draw(canvas);
- // Do not draw shadow on dark theme
- if (!mState.mIsDark) {
+ if (mState.mIsDark) {
+ // Dark text do not have any shadow, but just the bitmap
+ mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
+ mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw);
+ } else {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL));
+
+ // Generate the shadow bitmap
int[] offset = new int[2];
- Bitmap shadow = bitmap.extractAlpha(paint, offset);
+ Bitmap shadow = BitmapRenderer.createSoftwareBitmap(
+ mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw)
+ .extractAlpha(paint, offset);
paint.setMaskFilter(null);
paint.setColor(mState.mShadowColor);
- bitmap.eraseColor(Color.TRANSPARENT);
- canvas.drawBitmap(shadow, offset[0], offset[1], paint);
- d.draw(canvas);
+ mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
+ mState.mIntrinsicWidth, mState.mIntrinsicHeight, c -> {
+ c.drawBitmap(shadow, offset[0], offset[1], paint);
+ d.draw(c);
+ });
}
-
- if (BitmapRenderer.USE_HARDWARE_BITMAP) {
- bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
- }
- mState.mLastDrawnBitmap = bitmap;
}
@Override
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 6740fa1..83349bc 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -37,7 +37,7 @@
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
-import android.util.Property;
+import android.util.FloatProperty;
import android.view.View;
import androidx.core.graphics.ColorUtils;
@@ -54,28 +54,28 @@
*/
public class WorkspaceAndHotseatScrim extends Scrim {
- public static Property<WorkspaceAndHotseatScrim, Float> SYSUI_PROGRESS =
- new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiProgress") {
+ public static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_PROGRESS =
+ new FloatProperty<WorkspaceAndHotseatScrim>("sysUiProgress") {
@Override
public Float get(WorkspaceAndHotseatScrim scrim) {
return scrim.mSysUiProgress;
}
@Override
- public void set(WorkspaceAndHotseatScrim scrim, Float value) {
+ public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
scrim.setSysUiProgress(value);
}
};
- private static Property<WorkspaceAndHotseatScrim, Float> SYSUI_ANIM_MULTIPLIER =
- new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiAnimMultiplier") {
+ private static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_ANIM_MULTIPLIER =
+ new FloatProperty<WorkspaceAndHotseatScrim>("sysUiAnimMultiplier") {
@Override
public Float get(WorkspaceAndHotseatScrim scrim) {
return scrim.mSysUiAnimMultiplier;
}
@Override
- public void set(WorkspaceAndHotseatScrim scrim, Float value) {
+ public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
scrim.mSysUiAnimMultiplier = value;
scrim.reapplySysUiAlpha();
}
@@ -259,7 +259,7 @@
}
}
- public Bitmap createDitheredAlphaMask() {
+ private Bitmap createDitheredAlphaMask() {
DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
int gradientHeight = ResourceUtils.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
diff --git a/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/src/com/android/launcher3/icons/ClockDrawableWrapper.java
new file mode 100644
index 0000000..b7dd092
--- /dev/null
+++ b/src/com/android/launcher3/icons/ClockDrawableWrapper.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.launcher3.FastBitmapDrawable;
+
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
+ * clock icons
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
+
+ private static final String TAG = "ClockDrawableWrapper";
+
+ private static final boolean DISABLE_SECONDS = true;
+
+ // Time after which the clock icon should check for an update. The actual invalidate
+ // will only happen in case of any change.
+ public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L;
+
+ private static final String LAUNCHER_PACKAGE = "com.android.launcher3";
+ private static final String ROUND_ICON_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".LEVEL_PER_TICK_ICON_ROUND";
+ private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX";
+ private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".MINUTE_LAYER_INDEX";
+ private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".SECOND_LAYER_INDEX";
+ private static final String DEFAULT_HOUR_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".DEFAULT_HOUR";
+ private static final String DEFAULT_MINUTE_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".DEFAULT_MINUTE";
+ private static final String DEFAULT_SECOND_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".DEFAULT_SECOND";
+
+ /* Number of levels to jump per second for the second hand */
+ private static final int LEVELS_PER_SECOND = 10;
+
+ public static final int INVALID_VALUE = -1;
+
+ private final AnimationInfo mAnimationInfo = new AnimationInfo();
+ private int mTargetSdkVersion;
+
+ public ClockDrawableWrapper(AdaptiveIconDrawable base) {
+ super(base.getBackground(), base.getForeground());
+ }
+
+ /**
+ * Loads and returns the wrapper from the provided package, or returns null
+ * if it is unable to load.
+ */
+ public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+ final Bundle metadata = appInfo.metaData;
+ if (metadata == null) {
+ return null;
+ }
+ int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0);
+ if (drawableId == 0) {
+ return null;
+ }
+
+ Drawable drawable = pm.getResourcesForApplication(appInfo).getDrawableForDensity(
+ drawableId, iconDpi).mutate();
+ if (!(drawable instanceof AdaptiveIconDrawable)) {
+ return null;
+ }
+
+ ClockDrawableWrapper wrapper =
+ new ClockDrawableWrapper((AdaptiveIconDrawable) drawable);
+ wrapper.mTargetSdkVersion = appInfo.targetSdkVersion;
+ AnimationInfo info = wrapper.mAnimationInfo;
+
+ info.baseDrawableState = drawable.getConstantState();
+
+ info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE);
+ info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE);
+ info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE);
+
+ info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0);
+ info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0);
+ info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0);
+
+ LayerDrawable foreground = (LayerDrawable) wrapper.getForeground();
+ int layerCount = foreground.getNumberOfLayers();
+ if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) {
+ info.hourLayerIndex = INVALID_VALUE;
+ }
+ if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) {
+ info.minuteLayerIndex = INVALID_VALUE;
+ }
+ if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) {
+ info.secondLayerIndex = INVALID_VALUE;
+ } else if (DISABLE_SECONDS) {
+ foreground.setDrawable(info.secondLayerIndex, null);
+ info.secondLayerIndex = INVALID_VALUE;
+ }
+ return wrapper;
+ } catch (Exception e) {
+ Log.d(TAG, "Unable to load clock drawable info", e);
+ }
+ return null;
+ }
+
+ @Override
+ public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) {
+ iconFactory.disableColorExtraction();
+ float [] scale = new float[1];
+ AdaptiveIconDrawable background = new AdaptiveIconDrawable(
+ getBackground().getConstantState().newDrawable(), null);
+ BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background,
+ Process.myUserHandle(), mTargetSdkVersion, false, scale);
+
+ return new ClockBitmapInfo(bitmap, color, scale[0], mAnimationInfo, bitmapInfo.icon);
+ }
+
+ @Override
+ public void prepareToDrawOnUi() {
+ mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground());
+ }
+
+ private static class AnimationInfo {
+
+ public ConstantState baseDrawableState;
+
+ public int hourLayerIndex;
+ public int minuteLayerIndex;
+ public int secondLayerIndex;
+ public int defaultHour;
+ public int defaultMinute;
+ public int defaultSecond;
+
+ boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) {
+ time.setTimeInMillis(System.currentTimeMillis());
+
+ // We need to rotate by the difference from the default time if one is specified.
+ int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12;
+ int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60;
+ int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60;
+
+ boolean invalidate = false;
+ if (hourLayerIndex != INVALID_VALUE) {
+ final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex);
+ if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) {
+ invalidate = true;
+ }
+ }
+
+ if (minuteLayerIndex != INVALID_VALUE) {
+ final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex);
+ if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) {
+ invalidate = true;
+ }
+ }
+
+ if (secondLayerIndex != INVALID_VALUE) {
+ final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex);
+ if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) {
+ invalidate = true;
+ }
+ }
+
+ return invalidate;
+ }
+ }
+
+ private static class ClockBitmapInfo extends BitmapInfo implements FastBitmapDrawable.Factory {
+
+ public final float scale;
+ public final int offset;
+ public final AnimationInfo animInfo;
+ public final Bitmap mFlattenedBackground;
+
+ ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo,
+ Bitmap background) {
+ super(icon, color);
+ this.scale = scale;
+ this.animInfo = animInfo;
+ this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth());
+ this.mFlattenedBackground = background;
+ }
+
+ @Override
+ public FastBitmapDrawable newDrawable() {
+ return new ClockIconDrawable(this);
+ }
+ }
+
+ private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
+
+ private final Calendar mTime = Calendar.getInstance();
+
+ private final ClockBitmapInfo mInfo;
+
+ private final AdaptiveIconDrawable mFullDrawable;
+ private final LayerDrawable mForeground;
+
+ ClockIconDrawable(ClockBitmapInfo clockInfo) {
+ super(clockInfo);
+
+ mInfo = clockInfo;
+
+ mFullDrawable = (AdaptiveIconDrawable) mInfo.animInfo.baseDrawableState.newDrawable();
+ mForeground = (LayerDrawable) mFullDrawable.getForeground();
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mFullDrawable.setBounds(bounds);
+ }
+
+ @Override
+ public void drawInternal(Canvas canvas, Rect bounds) {
+ if (mInfo == null) {
+ super.drawInternal(canvas, bounds);
+ return;
+ }
+ // draw the background that is already flattened to a bitmap
+ canvas.drawBitmap(mInfo.mFlattenedBackground, null, bounds, mPaint);
+
+ // prepare and draw the foreground
+ mInfo.animInfo.applyTime(mTime, mForeground);
+
+ canvas.scale(mInfo.scale, mInfo.scale,
+ bounds.exactCenterX() + mInfo.offset, bounds.exactCenterY() + mInfo.offset);
+ canvas.clipPath(mFullDrawable.getIconMask());
+ mForeground.draw(canvas);
+
+ reschedule();
+ }
+
+ @Override
+ protected void updateFilter() {
+ super.updateFilter();
+ mFullDrawable.setColorFilter(mPaint.getColorFilter());
+ }
+
+ @Override
+ public void run() {
+ if (mInfo.animInfo.applyTime(mTime, mForeground)) {
+ invalidateSelf();
+ } else {
+ reschedule();
+ }
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean result = super.setVisible(visible, restart);
+ if (visible) {
+ reschedule();
+ } else {
+ unscheduleSelf(this);
+ }
+ return result;
+ }
+
+ private void reschedule() {
+ if (!isVisible()) {
+ return;
+ }
+
+ unscheduleSelf(this);
+ final long upTime = SystemClock.uptimeMillis();
+ final long step = TICK_MS; /* tick every 200 ms */
+ scheduleSelf(this, upTime - ((upTime % step)) + step);
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return new ClockConstantState(mInfo, isDisabled());
+ }
+
+ private static class ClockConstantState extends MyConstantState {
+
+ private final ClockBitmapInfo mInfo;
+
+ ClockConstantState(ClockBitmapInfo info, boolean isDisabled) {
+ super(info.icon, info.color, isDisabled);
+ mInfo = info;
+ }
+
+ @Override
+ public FastBitmapDrawable newDrawable() {
+ ClockIconDrawable drawable = new ClockIconDrawable(mInfo);
+ drawable.setIsDisabled(mIsDisabled);
+ return drawable;
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
index 832956d..372c591 100644
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ b/src/com/android/launcher3/icons/ComponentWithLabel.java
@@ -31,7 +31,7 @@
CharSequence getLabel(PackageManager pm);
- class ComponentCachingLogic implements CachingLogic<ComponentWithLabel> {
+ class ComponentCachingLogic<T extends ComponentWithLabel> implements CachingLogic<T> {
private final PackageManager mPackageManager;
private final boolean mAddToMemCache;
@@ -42,25 +42,23 @@
}
@Override
- public ComponentName getComponent(ComponentWithLabel object) {
+ public ComponentName getComponent(T object) {
return object.getComponent();
}
@Override
- public UserHandle getUser(ComponentWithLabel object) {
+ public UserHandle getUser(T object) {
return object.getUser();
}
@Override
- public CharSequence getLabel(ComponentWithLabel object) {
+ public CharSequence getLabel(T object) {
return object.getLabel(mPackageManager);
}
@Override
- public void loadIcon(Context context,
- ComponentWithLabel object, BitmapInfo target) {
- // Do not load icon.
- target.icon = BitmapInfo.LOW_RES_ICON;
+ public BitmapInfo loadIcon(Context context, T object) {
+ return BitmapInfo.LOW_RES_INFO;
}
@Override
diff --git a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
new file mode 100644
index 0000000..248a57d
--- /dev/null
+++ b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.LauncherAppState;
+
+/**
+ * Extension of ComponentWithLabel to also support loading icons
+ */
+public interface ComponentWithLabelAndIcon extends ComponentWithLabel {
+
+ /**
+ * Provide an icon for this object
+ */
+ Drawable getFullResIcon(IconCache cache);
+
+ class ComponentWithIconCachingLogic extends ComponentCachingLogic<ComponentWithLabelAndIcon> {
+
+ public ComponentWithIconCachingLogic(Context context, boolean addToMemCache) {
+ super(context, addToMemCache);
+ }
+
+ @NonNull
+ @Override
+ public BitmapInfo loadIcon(Context context, ComponentWithLabelAndIcon object) {
+ Drawable d = object.getFullResIcon(LauncherAppState.getInstance(context)
+ .getIconCache());
+ if (d == null) {
+ return super.loadIcon(context, object);
+ }
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ return li.createBadgedIconBitmap(d, object.getUser(), 0);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 9c46260..f27c9da 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -19,13 +19,17 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Process;
@@ -35,23 +39,25 @@
import androidx.annotation.NonNull;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.IconProvider;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherFiles;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
+import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@@ -61,25 +67,34 @@
private static final String TAG = "Launcher.IconCache";
+ private final Predicate<ItemInfoWithIcon> mIsUsingFallbackIconCheck = w -> w.bitmap != null
+ && w.bitmap.isNullOrLowRes() && !isDefaultIcon(w.bitmap, w.user);
+
private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
+ private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
- private final LauncherAppsCompat mLauncherApps;
- private final UserManagerCompat mUserManager;
+ private final LauncherApps mLauncherApps;
+ private final UserCache mUserManager;
private final InstantAppResolver mInstantAppResolver;
private final IconProvider mIconProvider;
private int mPendingIconRequestCount = 0;
- public IconCache(Context context, InvariantDeviceProfile inv) {
- super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
- inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
+ public IconCache(Context context, InvariantDeviceProfile idp) {
+ this(context, idp, LauncherFiles.APP_ICONS_DB);
+ }
+
+ public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName) {
+ super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
+ idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
- mLauncherApps = LauncherAppsCompat.getInstance(mContext);
- mUserManager = UserManagerCompat.getInstance(mContext);
+ mShortcutCachingLogic = new ShortcutCachingLogic();
+ mLauncherApps = mContext.getSystemService(LauncherApps.class);
+ mUserManager = UserCache.INSTANCE.get(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
- mIconProvider = IconProvider.INSTANCE.get(context);
+ mIconProvider = new IconProvider(context);
}
@Override
@@ -159,7 +174,7 @@
CacheEntry entry = cacheLocked(application.componentName,
application.user, () -> null, mLauncherActivityInfoCachingLogic,
false, application.usingLowResIcon());
- if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
+ if (entry.bitmap != null && !isDefaultIcon(entry.bitmap, application.user)) {
applyCacheEntry(entry, application);
}
}
@@ -174,6 +189,85 @@
}
/**
+ * Fill in {@param info} with the icon for {@param si}
+ */
+ public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
+ getShortcutIcon(info, si, true, mIsUsingFallbackIconCheck);
+ }
+
+ /**
+ * Fill in {@param info} with an unbadged icon for {@param si}
+ */
+ public void getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
+ getShortcutIcon(info, si, false, mIsUsingFallbackIconCheck);
+ }
+
+ /**
+ * Fill in {@param info} with the icon and label for {@param si}. If the icon is not
+ * available, and fallback check returns true, it keeps the old icon.
+ */
+ public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
+ @NonNull Predicate<T> fallbackIconCheck) {
+ getShortcutIcon(info, si, true /* use badged */, fallbackIconCheck);
+ }
+
+ private synchronized <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
+ boolean useBadged, @NonNull Predicate<T> fallbackIconCheck) {
+ BitmapInfo bitmapInfo;
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(),
+ () -> si, mShortcutCachingLogic, false, false).bitmap;
+ } else {
+ // If caching is disabled, load the full icon
+ bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si);
+ }
+ if (bitmapInfo.isNullOrLowRes()) {
+ bitmapInfo = getDefaultIcon(si.getUserHandle());
+ }
+
+ if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) {
+ return;
+ }
+ info.bitmap = bitmapInfo;
+ if (useBadged) {
+ BitmapInfo badgeInfo = getShortcutInfoBadge(si);
+ try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+ info.bitmap = li.badgeBitmap(info.bitmap.icon, badgeInfo);
+ }
+ }
+ }
+
+ /**
+ * Returns the badging info for the shortcut
+ */
+ public BitmapInfo getShortcutInfoBadge(ShortcutInfo shortcutInfo) {
+ ComponentName cn = shortcutInfo.getActivity();
+ if (cn != null) {
+ // Get the app info for the source activity.
+ AppInfo appInfo = new AppInfo();
+ appInfo.user = shortcutInfo.getUserHandle();
+ appInfo.componentName = cn;
+ appInfo.intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setComponent(cn);
+ getTitleAndIcon(appInfo, false);
+ return appInfo.bitmap;
+ } else {
+ PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
+ getTitleAndIconForApp(pkgInfo, false);
+ return pkgInfo.bitmap;
+ }
+ }
+
+ /**
+ * Fill in info with the icon and label for deep shortcut.
+ */
+ public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
+ return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
+ () -> info, mShortcutCachingLogic, false, false);
+ }
+
+ /**
* Fill in {@param info} with the icon and label. If the
* corresponding activity is not found, it reverts to the package icon.
*/
@@ -181,7 +275,7 @@
// null info means not installed, but if we have a component from the intent then
// we should still look in the cache for restored app icons.
if (info.getTargetComponent() == null) {
- info.applyFrom(getDefaultIcon(info.user));
+ info.bitmap = getDefaultIcon(info.user);
info.title = "";
info.contentDescription = "";
} else {
@@ -224,15 +318,15 @@
protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
info.title = Utilities.trim(entry.title);
info.contentDescription = entry.contentDescription;
- info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry);
+ info.bitmap = (entry.bitmap == null) ? getDefaultIcon(info.user) : entry.bitmap;
}
public Drawable getFullResIcon(LauncherActivityInfo info) {
- return getFullResIcon(info, true);
+ return mIconProvider.getIcon(info, mIconDpi);
}
- public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
- return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
+ public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
+ cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel());
}
@Override
@@ -241,6 +335,15 @@
+ ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get();
}
+ @Override
+ protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
+ if (mIconProvider.isClockIcon(cacheKey)) {
+ // For clock icon, we always load the dynamic icon
+ return false;
+ }
+ return super.getEntryFromDB(cacheKey, entry, lowRes);
+ }
+
public static abstract class IconLoadRequest extends HandlerRunnable {
IconLoadRequest(Handler handler, Runnable endRunnable) {
super(handler, endRunnable);
diff --git a/src/com/android/launcher3/icons/IconProvider.java b/src/com/android/launcher3/icons/IconProvider.java
new file mode 100644
index 0000000..1468b27
--- /dev/null
+++ b/src/com/android/launcher3/icons/IconProvider.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo.Extender;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.SafeCloseable;
+
+import java.util.Calendar;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+/**
+ * Class to handle icon loading from different packages
+ */
+public class IconProvider {
+
+ private static final String TAG = "IconProvider";
+ private static final boolean DEBUG = false;
+
+ private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
+
+ private static final String SYSTEM_STATE_SEPARATOR = " ";
+
+ // Default value returned if there are problems getting resources.
+ private static final int NO_ID = 0;
+
+ private static final BiFunction<LauncherActivityInfo, Integer, Drawable> LAI_LOADER =
+ LauncherActivityInfo::getIcon;
+
+ private static final BiFunction<ActivityInfo, PackageManager, Drawable> AI_LOADER =
+ ActivityInfo::loadUnbadgedIcon;
+
+
+ private final Context mContext;
+ private final ComponentName mCalendar;
+ private final ComponentName mClock;
+
+ public IconProvider(Context context) {
+ mContext = context;
+ mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
+ mClock = parseComponentOrNull(context, R.string.clock_component_name);
+ }
+
+ /**
+ * Adds any modification to the provided systemState for dynamic icons. This system state
+ * is used by caches to check for icon invalidation.
+ */
+ public String getSystemStateForPackage(String systemState, String packageName) {
+ if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
+ return systemState + SYSTEM_STATE_SEPARATOR + getDay();
+ } else {
+ return systemState;
+ }
+ }
+
+ /**
+ * Loads the icon for the provided LauncherActivityInfo such that it can be drawn directly
+ * on the UI
+ */
+ public Drawable getIconForUI(LauncherActivityInfo info, int iconDpi) {
+ Drawable icon = getIcon(info, iconDpi);
+ if (icon instanceof BitmapInfo.Extender) {
+ ((Extender) icon).prepareToDrawOnUi();
+ }
+ return icon;
+ }
+
+ /**
+ * Loads the icon for the provided LauncherActivityInfo
+ */
+ public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
+ return getIcon(info.getApplicationInfo().packageName, info.getUser(),
+ info, iconDpi, LAI_LOADER);
+ }
+
+ /**
+ * Loads the icon for the provided activity info
+ */
+ public Drawable getIcon(ActivityInfo info, UserHandle user) {
+ return getIcon(info.applicationInfo.packageName, user, info, mContext.getPackageManager(),
+ AI_LOADER);
+ }
+
+ private <T, P> Drawable getIcon(String packageName, UserHandle user, T obj, P param,
+ BiFunction<T, P, Drawable> loader) {
+ Drawable icon = null;
+ if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
+ icon = loadCalendarDrawable(0);
+ } else if (mClock != null
+ && mClock.getPackageName().equals(packageName)
+ && Process.myUserHandle().equals(user)) {
+ icon = loadClockDrawable(0);
+ }
+ return icon == null ? loader.apply(obj, param) : icon;
+ }
+
+ private Drawable loadCalendarDrawable(int iconDpi) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ final Bundle metadata = pm.getActivityInfo(
+ mCalendar,
+ PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
+ .metaData;
+ final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
+ final int id = getDynamicIconId(metadata, resources);
+ if (id != NO_ID) {
+ if (DEBUG) Log.d(TAG, "Got icon #" + id);
+ return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DEBUG) {
+ Log.d(TAG, "Could not get activityinfo or resources for package: "
+ + mCalendar.getPackageName());
+ }
+ }
+ return null;
+ }
+
+ private Drawable loadClockDrawable(int iconDpi) {
+ return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
+ }
+
+ protected boolean isClockIcon(ComponentKey key) {
+ return mClock != null && mClock.equals(key.componentName)
+ && Process.myUserHandle().equals(key.user);
+ }
+
+ /**
+ * @param metadata metadata of the default activity of Calendar
+ * @param resources from the Calendar package
+ * @return the resource id for today's Calendar icon; 0 if resources cannot be found.
+ */
+ private int getDynamicIconId(Bundle metadata, Resources resources) {
+ if (metadata == null) {
+ return NO_ID;
+ }
+ String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
+ final int arrayId = metadata.getInt(key, NO_ID);
+ if (arrayId == NO_ID) {
+ return NO_ID;
+ }
+ try {
+ return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID);
+ } catch (Resources.NotFoundException e) {
+ if (DEBUG) {
+ Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
+ }
+ return NO_ID;
+ }
+ }
+
+ /**
+ * @return Today's day of the month, zero-indexed.
+ */
+ private int getDay() {
+ return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
+ }
+
+
+ /**
+ * Registers a callback to listen for calendar icon changes.
+ * The callback receives the packageName for the calendar icon
+ */
+ public static SafeCloseable registerIconChangeListener(Context context,
+ BiConsumer<String, UserHandle> callback, Handler handler) {
+ ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name);
+ ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
+
+ if (calendar == null && clock == null) {
+ return () -> { };
+ }
+
+ BroadcastReceiver receiver = new DateTimeChangeReceiver(callback);
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ if (calendar != null) {
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_DATE_CHANGED);
+ }
+ context.registerReceiver(receiver, filter, null, handler);
+
+ return () -> context.unregisterReceiver(receiver);
+ }
+
+ private static class DateTimeChangeReceiver extends BroadcastReceiver {
+
+ private final BiConsumer<String, UserHandle> mCallback;
+
+ DateTimeChangeReceiver(BiConsumer<String, UserHandle> callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
+ ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
+ if (clock != null) {
+ mCallback.accept(clock.getPackageName(), Process.myUserHandle());
+ }
+ }
+
+ ComponentName calendar =
+ parseComponentOrNull(context, R.string.calendar_component_name);
+ if (calendar != null) {
+ for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
+ mCallback.accept(calendar.getPackageName(), user);
+ }
+ }
+
+ }
+ }
+
+ private static ComponentName parseComponentOrNull(Context context, int resId) {
+ String cn = context.getString(resId);
+ return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
+
+ }
+}
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index f9a94da..93de35a 100644
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -20,7 +20,6 @@
import android.content.pm.LauncherActivityInfo;
import android.os.UserHandle;
-import com.android.launcher3.IconProvider;
import com.android.launcher3.R;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -55,13 +54,11 @@
}
@Override
- public void loadIcon(Context context, LauncherActivityInfo object,
- BitmapInfo target) {
- LauncherIcons li = LauncherIcons.obtain(context);
- li.createBadgedIconBitmap(
- IconProvider.INSTANCE.get(context)
- .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */),
- object.getUser(), object.getApplicationInfo().targetSdkVersion).applyTo(target);
- li.recycle();
+ public BitmapInfo loadIcon(Context context, LauncherActivityInfo object) {
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ return li.createBadgedIconBitmap(new IconProvider(context)
+ .getIcon(object, li.mFillResIconDpi),
+ object.getUser(), object.getApplicationInfo().targetSdkVersion);
+ }
}
}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index adc92c4..bf7897e 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -16,28 +16,11 @@
package com.android.launcher3.icons;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.Process;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.util.Themes;
-
-import java.util.function.Supplier;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
/**
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
@@ -45,8 +28,6 @@
*/
public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
- private static final String EXTRA_BADGEPKG = "badge_package";
-
private static final Object sPoolSync = new Object();
private static LauncherIcons sPool;
private static int sPoolId = 0;
@@ -60,6 +41,11 @@
* avoid allocating new objects in many cases.
*/
public static LauncherIcons obtain(Context context, boolean shapeDetection) {
+ if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+ return ((LauncherPreviewRenderer.PreviewContext) context).newLauncherIcons(context,
+ shapeDetection);
+ }
+
int poolId;
synchronized (sPoolSync) {
if (sPool != null) {
@@ -71,7 +57,7 @@
poolId = sPoolId;
}
- InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId,
shapeDetection);
}
@@ -87,7 +73,7 @@
private LauncherIcons next;
- private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
+ protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
boolean shapeDetection) {
super(context, fillResIconDpi, iconBitmapSize, shapeDetection);
mPoolId = poolId;
@@ -113,86 +99,4 @@
public void close() {
recycle();
}
-
- // below methods should also migrate to BaseIconFactory
-
- public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo) {
- return createShortcutIcon(shortcutInfo, true /* badged */);
- }
-
- public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged) {
- return createShortcutIcon(shortcutInfo, badged, null);
- }
-
- public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo,
- boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
- Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
- .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
- IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
-
- final Bitmap unbadgedBitmap;
- if (unbadgedDrawable != null) {
- unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
- } else {
- if (fallbackIconProvider != null) {
- // Fallback icons are already badged and with appropriate shadow
- ItemInfoWithIcon fullIcon = fallbackIconProvider.get();
- if (fullIcon != null && fullIcon.iconBitmap != null) {
- BitmapInfo result = new BitmapInfo();
- result.icon = fullIcon.iconBitmap;
- result.color = fullIcon.iconColor;
- return result;
- }
- }
- unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
- }
-
- BitmapInfo result = new BitmapInfo();
- if (!badged) {
- result.color = Themes.getColorAccent(mContext);
- result.icon = unbadgedBitmap;
- return result;
- }
-
- final Bitmap unbadgedfinal = unbadgedBitmap;
- final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
-
- result.color = badge.iconColor;
- result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
- getShadowGenerator().recreateIcon(unbadgedfinal, c);
- badgeWithDrawable(c, new FastBitmapDrawable(badge));
- });
- return result;
- }
-
- public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) {
- ComponentName cn = shortcutInfo.getActivity();
- String badgePkg = getBadgePackage(shortcutInfo);
- boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage());
- if (cn != null && !hasBadgePkgSet) {
- // Get the app info for the source activity.
- AppInfo appInfo = new AppInfo();
- appInfo.user = shortcutInfo.getUserHandle();
- appInfo.componentName = cn;
- appInfo.intent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_LAUNCHER)
- .setComponent(cn);
- cache.getTitleAndIcon(appInfo, false);
- return appInfo;
- } else {
- PackageItemInfo pkgInfo = new PackageItemInfo(badgePkg);
- cache.getTitleAndIconForApp(pkgInfo, false);
- return pkgInfo;
- }
- }
-
- private String getBadgePackage(ShortcutInfo si) {
- String whitelistedPkg = mContext.getString(R.string.shortcutinfo_badgepkg_whitelist);
- if (whitelistedPkg.equals(si.getPackage())
- && si.getExtras() != null
- && si.getExtras().containsKey(EXTRA_BADGEPKG)) {
- return si.getExtras().getString(EXTRA_BADGEPKG);
- }
- return si.getPackage();
- }
}
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
new file mode 100644
index 0000000..d7eed06
--- /dev/null
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Caching logic for shortcuts.
+ */
+public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
+
+ private static final String TAG = "ShortcutCachingLogic";
+
+ @Override
+ public ComponentName getComponent(ShortcutInfo info) {
+ return ShortcutKey.fromInfo(info).componentName;
+ }
+
+ @Override
+ public UserHandle getUser(ShortcutInfo info) {
+ return info.getUserHandle();
+ }
+
+ @Override
+ public CharSequence getLabel(ShortcutInfo info) {
+ return info.getShortLabel();
+ }
+
+ @Override
+ public CharSequence getDescription(ShortcutInfo object, CharSequence fallback) {
+ CharSequence label = object.getLongLabel();
+ return TextUtils.isEmpty(label) ? fallback : label;
+ }
+
+ @NonNull
+ @Override
+ public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
+ context, info, LauncherAppState.getIDP(context).fillResIconDpi);
+ if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
+ return new BitmapInfo(li.createScaledBitmapWithoutShadow(
+ unbadgedDrawable, 0), Themes.getColorAccent(context));
+ }
+ }
+
+ @Override
+ public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) {
+ if (shortcutInfo == null || !FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ return info.lastUpdateTime;
+ }
+ return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
+ }
+
+ @Override
+ public boolean addToMemCache() {
+ return false;
+ }
+
+ /**
+ * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} with additional
+ * Launcher specific checks
+ */
+ public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
+ if (GO_DISABLE_WIDGETS) {
+ return null;
+ }
+ try {
+ return context.getSystemService(LauncherApps.class)
+ .getShortcutIconDrawable(shortcutInfo, density);
+ } catch (SecurityException | IllegalStateException e) {
+ Log.e(TAG, "Failed to get shortcut icon", e);
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 923a89b..bfeb1dc 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -8,6 +8,9 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.IOUtils;
import java.io.BufferedReader;
@@ -35,11 +38,13 @@
private static final DateFormat DATE_FORMAT =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
- private static final long MAX_LOG_FILE_SIZE = 4 << 20; // 4 mb
+ private static final long MAX_LOG_FILE_SIZE = 8 << 20; // 4 mb
private static Handler sHandler = null;
private static File sLogsDirectory = null;
+ public static final int LOG_DAYS = FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() ? 10 : 4;
+
public static void setDir(File logsDir) {
if (ENABLED) {
synchronized (DATE_FORMAT) {
@@ -88,7 +93,8 @@
Message.obtain(getHandler(), LogWriterCallback.MSG_WRITE, out).sendToTarget();
}
- private static Handler getHandler() {
+ @VisibleForTesting
+ static Handler getHandler() {
synchronized (DATE_FORMAT) {
if (sHandler == null) {
sHandler = new Handler(createAndStartNewLooper("file-logger"),
@@ -102,15 +108,16 @@
* Blocks until all the pending logs are written to the disk
* @param out if not null, all the persisted logs are copied to the writer.
*/
- public static void flushAll(PrintWriter out) throws InterruptedException {
+ public static boolean flushAll(PrintWriter out) throws InterruptedException {
if (!ENABLED) {
- return;
+ return false;
}
CountDownLatch latch = new CountDownLatch(1);
Message.obtain(getHandler(), LogWriterCallback.MSG_FLUSH,
Pair.create(out, latch)).sendToTarget();
latch.await(2, TimeUnit.SECONDS);
+ return latch.getCount() == 0;
}
/**
@@ -143,7 +150,7 @@
case MSG_WRITE: {
Calendar cal = Calendar.getInstance();
// suffix with 0 or 1 based on the day of the year.
- String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) & 1);
+ String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) % LOG_DAYS);
if (!fileName.equals(mCurrentFileName)) {
closeWriter();
@@ -191,8 +198,9 @@
(Pair<PrintWriter, CountDownLatch>) msg.obj;
if (p.first != null) {
- dumpFile(p.first, FILE_NAME_PREFIX + 0);
- dumpFile(p.first, FILE_NAME_PREFIX + 1);
+ for (int i = 0; i < LOG_DAYS; i++) {
+ dumpFile(p.first, FILE_NAME_PREFIX + i);
+ }
}
p.second.countDown();
return true;
@@ -222,4 +230,15 @@
}
}
}
+
+ /**
+ * Gets files used for FileLog
+ */
+ public static File[] getLogFiles() {
+ File[] files = new File[LOG_DAYS];
+ for (int i = 0; i < LOG_DAYS; i++) {
+ files[i] = new File(sLogsDirectory, FILE_NAME_PREFIX + i);
+ }
+ return files;
+ }
}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 598792a..a9d10d7 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -37,6 +37,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
/**
* Helper methods for logging.
@@ -44,7 +45,7 @@
public class LoggerUtils {
private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
private static final String UNKNOWN = "UNKNOWN";
- private static final int DEFAULT_PREDICTED_RANK = -100;
+ private static final int DEFAULT_PREDICTED_RANK = 10000;
public static String getFieldName(int value, Class c) {
SparseArray<String> cache;
@@ -142,8 +143,10 @@
typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
} else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
&& t.itemType != ItemType.TASK) {
- typeStr += ", predictiveRank=" + t.predictedRank + ", grid(" + t.gridX + "," + t.gridY
- + "), span(" + t.spanX + "," + t.spanY + "), pageIdx=" + t.pageIndex;
+ typeStr +=
+ ", isWorkApp=" + t.isWorkApp + ", predictiveRank=" + t.predictedRank + ", grid("
+ + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+ + "), pageIdx=" + t.pageIndex;
}
if (t.searchQueryLength != 0) {
typeStr += ", searchQueryLength=" + t.searchQueryLength;
@@ -253,4 +256,13 @@
event.action = action;
return event;
}
+
+ /**
+ * Creates LauncherEvent using Action and ArrayList of Targets
+ */
+ public static LauncherEvent newLauncherEvent(Action action, ArrayList<Target> targets) {
+ Target[] targetsArray = new Target[targets.size()];
+ targets.toArray(targetsArray);
+ return newLauncherEvent(action, targetsArray);
+ }
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index cad95b0..9dfd7ab 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -17,12 +17,15 @@
import android.content.Context;
import android.content.Intent;
+import android.os.UserHandle;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.R;
+import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
/**
* Handles the user event logging in Q.
@@ -38,7 +41,10 @@
return mgr;
}
- public void logAppLaunch(View v, Intent intent) { }
+ /**
+ * Logs app launches
+ */
+ public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) { }
public void logTaskLaunch(View v, ComponentKey key) { }
public void logTaskDismiss(View v, ComponentKey key) { }
public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) { }
diff --git a/src/com/android/launcher3/logging/StatsLogUtils.java b/src/com/android/launcher3/logging/StatsLogUtils.java
index b02a050..8449612 100644
--- a/src/com/android/launcher3/logging/StatsLogUtils.java
+++ b/src/com/android/launcher3/logging/StatsLogUtils.java
@@ -5,11 +5,13 @@
import android.view.View;
import android.view.ViewParent;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.ItemInfo;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import androidx.annotation.Nullable;
+import java.util.ArrayList;
public class StatsLogUtils {
@@ -35,14 +37,9 @@
public interface LogContainerProvider {
/**
- * Copies data from the source to the destination proto.
- *
- * @param v source of the data
- * @param info source of the data
- * @param target dest of the data
- * @param targetParent dest of the data
+ * Populates parent container targets for an item
*/
- void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
+ void fillInLogContainerData(ItemInfo childInfo, Target child, ArrayList<Target> parents);
}
/**
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 99906fe..3770d17 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -25,6 +25,9 @@
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import static com.android.launcher3.logging.LoggerUtils.newTarget;
import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
import static java.util.Optional.ofNullable;
@@ -33,7 +36,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.Process;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Log;
import android.view.View;
@@ -46,7 +51,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -55,7 +60,11 @@
import com.android.launcher3.util.LogConfig;
import com.android.launcher3.util.ResourceBasedOverride;
-import java.util.Locale;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import java.util.ArrayList;
import java.util.UUID;
/**
@@ -67,12 +76,13 @@
public class UserEventDispatcher implements ResourceBasedOverride {
private static final String TAG = "UserEvent";
- private static final boolean IS_VERBOSE =
- FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+ private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.USEREVENT);
private static final String UUID_STORAGE = "uuid";
- public static UserEventDispatcher newInstance(Context context,
- UserEventDelegate delegate) {
+ /**
+ * A factory method for UserEventDispatcher
+ */
+ public static UserEventDispatcher newInstance(Context context) {
SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context);
String uuidStr = sharedPrefs.getString(UUID_STORAGE, null);
if (uuidStr == null) {
@@ -81,41 +91,31 @@
}
UserEventDispatcher ued = Overrides.getObject(UserEventDispatcher.class,
context.getApplicationContext(), R.string.user_event_dispatcher_class);
- ued.mDelegate = delegate;
ued.mUuidStr = uuidStr;
ued.mInstantAppResolver = InstantAppResolver.newInstance(context);
return ued;
}
- public static UserEventDispatcher newInstance(Context context) {
- return newInstance(context, null);
- }
-
- public interface UserEventDelegate {
- void modifyUserEvent(LauncherEvent event);
- }
/**
* Fills in the container data on the given event if the given view is not null.
*
* @return whether container data was added.
*/
- public boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
- // Fill in grid(x,y), pageIndex of the child and container type of the parent
- LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
- if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
+ public boolean fillLogContainer(@Nullable View v, Target child,
+ @Nullable ArrayList<Target> targets) {
+ LogContainerProvider firstParent = StatsLogUtils.getLaunchProviderRecursive(v);
+ if (v == null || !(v.getTag() instanceof ItemInfo) || firstParent == null) {
return false;
}
final ItemInfo itemInfo = (ItemInfo) v.getTag();
- final Target target = event.srcTarget[0];
- final Target targetParent = event.srcTarget[1];
- onFillInLogContainerData(itemInfo, target, targetParent);
- provider.fillInLogContainerData(v, itemInfo, target, targetParent);
+ firstParent.fillInLogContainerData(itemInfo, child, targets);
return true;
}
- protected void onFillInLogContainerData(
- @NonNull ItemInfo itemInfo, @NonNull Target target, @NonNull Target targetParent) { }
+ protected void onFillInLogContainerData(@NonNull ItemInfo itemInfo, @NonNull Target target,
+ @NonNull ArrayList<Target> targets) {
+ }
private boolean mSessionStarted;
private long mElapsedContainerMillis;
@@ -124,7 +124,6 @@
private String mUuidStr;
protected InstantAppResolver mInstantAppResolver;
private boolean mAppOrTaskLaunch;
- private UserEventDelegate mDelegate;
private boolean mPreviousHomeGesture;
// APP_ICON SHORTCUT WIDGET
@@ -135,15 +134,20 @@
// --------------------------------------------------------------
@Deprecated
- public void logAppLaunch(View v, Intent intent) {
- LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
- newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
-
- if (fillInLogContainerData(event, v)) {
- if (mDelegate != null) {
- mDelegate.modifyUserEvent(event);
- }
- fillIntentInfo(event.srcTarget[0], intent);
+ public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
+ Target itemTarget = newItemTarget(v, mInstantAppResolver);
+ Action action = newTouchAction(Action.Touch.TAP);
+ ArrayList<Target> targets = makeTargetsList(itemTarget);
+ if (fillLogContainer(v, itemTarget, targets)) {
+ onFillInLogContainerData((ItemInfo) v.getTag(), itemTarget, targets);
+ fillIntentInfo(itemTarget, intent, userHandle);
+ }
+ LauncherEvent event = newLauncherEvent(action, targets);
+ ItemInfo info = (ItemInfo) v.getTag();
+ if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+ FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
+ + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
+ userHandle)) + ",launchLocation:" + info.container);
}
dispatchUserEvent(event, intent);
mAppOrTaskLaunch = true;
@@ -164,15 +168,16 @@
// Direction DOWN means the task was launched, UP means it was dismissed.
event.action.dir = direction;
}
- event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK;
+ event.srcTarget[0].itemType = ItemType.TASK;
event.srcTarget[0].pageIndex = taskIndex;
fillComponentInfo(event.srcTarget[0], componentKey.componentName);
dispatchUserEvent(event, null);
mAppOrTaskLaunch = true;
}
- protected void fillIntentInfo(Target target, Intent intent) {
+ protected void fillIntentInfo(Target target, Intent intent, @Nullable UserHandle userHandle) {
target.intentHash = intent.hashCode();
+ target.isWorkApp = userHandle != null && !userHandle.equals(Process.myUserHandle());
fillComponentInfo(target, intent.getComponent());
}
@@ -186,8 +191,11 @@
public void logNotificationLaunch(View v, PendingIntent intent) {
LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
- if (fillInLogContainerData(event, v)) {
- event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
+ Target itemTarget = newItemTarget(v, mInstantAppResolver);
+ ArrayList<Target> targets = makeTargetsList(itemTarget);
+
+ if (fillLogContainer(v, itemTarget, targets)) {
+ itemTarget.packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
}
dispatchUserEvent(event, null);
}
@@ -233,50 +241,45 @@
LauncherEvent event = newLauncherEvent(newCommandAction(command),
newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
- if (fillInLogContainerData(event, itemView)) {
+ Target itemTarget = newItemTarget(itemView, mInstantAppResolver);
+ ArrayList<Target> targets = makeTargetsList(itemTarget);
+
+ if (fillLogContainer(itemView, itemTarget, targets)) {
// TODO: Remove the following two lines once fillInLogContainerData can take in a
// container view.
- event.srcTarget[0].type = Target.Type.CONTAINER;
- event.srcTarget[0].containerType = srcContainerType;
+ itemTarget.type = Target.Type.CONTAINER;
+ itemTarget.containerType = srcContainerType;
}
dispatchUserEvent(event, null);
}
public void logActionOnControl(int action, int controlType) {
- logActionOnControl(action, controlType, null, -1);
+ logActionOnControl(action, controlType, null);
}
public void logActionOnControl(int action, int controlType, int parentContainerType) {
logActionOnControl(action, controlType, null, parentContainerType);
}
- public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) {
- logActionOnControl(action, controlType, controlInContainer, -1);
- }
+ /**
+ * Logs control action with proper parent hierarchy
+ */
+ public void logActionOnControl(int actionType, int controlType,
+ @Nullable View controlInContainer, int... parentTypes) {
+ Target control = newTarget(Target.Type.CONTROL);
+ control.controlType = controlType;
+ Action action = newAction(actionType);
- public void logActionOnControl(int action, int controlType, int parentContainer,
- int grandParentContainer) {
- LauncherEvent event = newLauncherEvent(newTouchAction(action),
- newControlTarget(controlType),
- newContainerTarget(parentContainer),
- newContainerTarget(grandParentContainer));
- dispatchUserEvent(event, null);
- }
-
- public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
- int parentContainerType) {
- final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
- ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
- : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
- newTarget(Target.Type.CONTAINER));
- event.srcTarget[0].controlType = controlType;
+ ArrayList<Target> targets = makeTargetsList(control);
if (controlInContainer != null) {
- fillInLogContainerData(event, controlInContainer);
+ fillLogContainer(controlInContainer, control, targets);
}
- if (parentContainerType >= 0) {
- event.srcTarget[1].containerType = parentContainerType;
+ for (int parentContainerType : parentTypes) {
+ if (parentContainerType < 0) continue;
+ targets.add(newContainerTarget(parentContainerType));
}
- if (action == Action.Touch.DRAGDROP) {
+ LauncherEvent event = newLauncherEvent(action, targets);
+ if (actionType == Action.Touch.DRAGDROP) {
event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
}
dispatchUserEvent(event, null);
@@ -292,7 +295,7 @@
public void logActionBounceTip(int containerType) {
LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP),
newContainerTarget(containerType));
- event.srcTarget[0].tipType = LauncherLogProto.TipType.BOUNCE;
+ event.srcTarget[0].tipType = TipType.BOUNCE;
dispatchUserEvent(event, null);
}
@@ -319,7 +322,7 @@
int srcChildTargetType, int srcParentContainerType, int dstContainerType,
int pageIndex) {
LauncherEvent event;
- if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
+ if (srcChildTargetType == ItemType.TASK) {
event = newLauncherEvent(newTouchAction(action),
newItemTarget(srcChildTargetType),
newContainerTarget(srcParentContainerType));
@@ -363,37 +366,57 @@
dispatchUserEvent(event, null);
}
- public void logDeepShortcutsOpen(View icon) {
- LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(icon);
- if (icon == null || !(icon.getTag() instanceof ItemInfo || provider == null)) {
- return;
- }
- ItemInfo info = (ItemInfo) icon.getTag();
- LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
- newItemTarget(info, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
- provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
- dispatchUserEvent(event, null);
+ /**
+ * Logs proto lite version of LauncherEvent object to clearcut.
+ */
+ public void logLauncherEvent(
+ com.android.launcher3.userevent.LauncherLogProto.LauncherEvent launcherEvent) {
+ if (mPreviousHomeGesture) {
+ mPreviousHomeGesture = false;
+ }
+ mAppOrTaskLaunch = false;
+ launcherEvent.toBuilder()
+ .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
+ .setElapsedSessionMillis(
+ SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
+ try {
+ dispatchUserEvent(LauncherEvent.parseFrom(launcherEvent.toByteArray()), null);
+ } catch (InvalidProtocolBufferNanoException e) {
+ throw new RuntimeException("Cannot convert LauncherEvent from Lite to Nano version.");
+ }
+ }
+
+ public void logDeepShortcutsOpen(View icon) {
+ ItemInfo info = (ItemInfo) icon.getTag();
+ Target child = newItemTarget(info, mInstantAppResolver);
+ ArrayList<Target> targets = makeTargetsList(child);
+ fillLogContainer(icon, child, targets);
+ dispatchUserEvent(newLauncherEvent(newTouchAction(Action.Touch.TAP), targets), null);
resetElapsedContainerMillis("deep shortcut open");
}
public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
- LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
- newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
- newTarget(Target.Type.CONTAINER));
- event.destTarget = new Target[]{
- newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
- newDropTarget(dropTargetAsView)
- };
+ Target srcChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
+ ArrayList<Target> srcTargets = makeTargetsList(srcChild);
- dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
- event.srcTarget[0], event.srcTarget[1]);
+ Target destChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
+ ArrayList<Target> destTargets = makeTargetsList(destChild);
+
+ dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
if (dropTargetAsView instanceof LogContainerProvider) {
- ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
- dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
-
+ ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(dragObj.dragInfo,
+ destChild, destTargets);
}
+ else {
+ destTargets.add(newDropTarget(dropTargetAsView));
+ }
+ LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP), srcTargets);
+ Target[] destTargetsArray = new Target[destTargets.size()];
+ destTargets.toArray(destTargetsArray);
+ event.destTarget = destTargetsArray;
+
event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
dispatchUserEvent(event, null);
}
@@ -405,8 +428,8 @@
action.command = Action.Command.BACK;
action.dir = isButton ? Action.Direction.NONE :
gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
- Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
- LauncherLogProto.ControlType.BACK_GESTURE);
+ Target target = newControlTarget(isButton ? ControlType.BACK_BUTTON :
+ ControlType.BACK_GESTURE);
target.spanX = downX;
target.spanY = downY;
target.cardinality = completed ? 1 : 0;
@@ -417,8 +440,6 @@
/**
* Currently logs following containers: workspace, allapps, widget tray.
- *
- * @param reason
*/
public final void resetElapsedContainerMillis(String reason) {
mElapsedContainerMillis = SystemClock.uptimeMillis();
@@ -457,34 +478,23 @@
if (!IS_VERBOSE) {
return;
}
- Log.d(TAG, generateLog(ev));
+ LauncherLogProto.LauncherEvent liteLauncherEvent;
+ try {
+ liteLauncherEvent =
+ LauncherLogProto.LauncherEvent.parseFrom(MessageNano.toByteArray(ev));
+ } catch (InvalidProtocolBufferException e) {
+ throw new RuntimeException("Cannot parse LauncherEvent from Nano to Lite version");
+ }
+ Log.d(TAG, liteLauncherEvent.toString());
}
/**
- * Returns a human-readable log for given user event.
+ * Constructs an ArrayList with targets
*/
- public static String generateLog(LauncherEvent ev) {
- String log = "\n-----------------------------------------------------"
- + "\naction:" + LoggerUtils.getActionStr(ev.action);
- if (ev.srcTarget != null && ev.srcTarget.length > 0) {
- log += "\n Source " + getTargetsStr(ev.srcTarget);
- }
- if (ev.destTarget != null && ev.destTarget.length > 0) {
- log += "\n Destination " + getTargetsStr(ev.destTarget);
- }
- log += String.format(Locale.US,
- "\n Elapsed container %d ms, session %d ms, action %d ms",
- ev.elapsedContainerMillis,
- ev.elapsedSessionMillis,
- ev.actionDurationMillis);
- log += "\n\n";
- return log;
- }
-
- private static String getTargetsStr(Target[] targets) {
- String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
- for (int i = 1; i < targets.length; i++) {
- result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
+ public static ArrayList<Target> makeTargetsList(Target... targets) {
+ ArrayList<Target> result = new ArrayList<>();
+ for (Target target : targets) {
+ result.add(target);
}
return result;
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index b8c583c..eb95395 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -17,6 +17,7 @@
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller.SessionInfo;
import android.os.UserHandle;
import android.util.LongSparseArray;
@@ -29,11 +30,10 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.PackageManagerHelper;
@@ -93,9 +93,9 @@
}
}
- PackageInstallerCompat packageInstaller =
- PackageInstallerCompat.getInstance(app.getContext());
- LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(app.getContext());
+ InstallSessionHelper packageInstaller =
+ InstallSessionHelper.INSTANCE.get(app.getContext());
+ LauncherApps launcherApps = app.getContext().getSystemService(LauncherApps.class);
for (ItemInfo item : filteredItems) {
// Find appropriate space for the item.
@@ -151,7 +151,7 @@
WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
wii.title = "";
- wii.applyFrom(app.getIconCache().getDefaultIcon(item.user));
+ wii.bitmap = app.getIconCache().getDefaultIcon(item.user);
app.getIconCache().getTitleAndIcon(wii,
((WorkspaceItemInfo) itemInfo).usingLowResIcon());
}
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 3873a17..9f1843f 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -23,21 +23,24 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.os.LocaleList;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AppFilter;
import com.android.launcher3.AppInfo;
import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SafeCloseable;
import java.util.ArrayList;
@@ -46,9 +49,6 @@
import java.util.List;
import java.util.function.Consumer;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
* Stores the list of all applications for the all apps view.
@@ -110,10 +110,9 @@
mDataChanged = true;
}
- public void addPromiseApp(Context context,
- PackageInstallerCompat.PackageInstallInfo installInfo) {
- ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context)
- .getApplicationInfo(installInfo.packageName, 0, installInfo.user);
+ public void addPromiseApp(Context context, PackageInstallInfo installInfo) {
+ ApplicationInfo applicationInfo = new PackageManagerHelper(context)
+ .getApplicationInfo(installInfo.packageName, installInfo.user, 0);
// only if not yet installed
if (applicationInfo == null) {
PromiseAppInfo info = new PromiseAppInfo(installInfo);
@@ -134,10 +133,10 @@
&& appInfo.user.equals(user)
&& appInfo instanceof PromiseAppInfo) {
final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
- if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
+ if (installInfo.state == PackageInstallInfo.STATUS_INSTALLING) {
promiseAppInfo.level = installInfo.progress;
return promiseAppInfo;
- } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+ } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED) {
removeApp(i);
}
}
@@ -164,11 +163,8 @@
* Add the icons for the supplied apk called packageName.
*/
public void addPackage(Context context, String packageName, UserHandle user) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
- user);
-
- for (LauncherActivityInfo info : matches) {
+ for (LauncherActivityInfo info : context.getSystemService(LauncherApps.class)
+ .getActivityList(packageName, user)) {
add(new AppInfo(context, info, user), info);
}
}
@@ -214,9 +210,8 @@
* Add and remove icons for this package which has been updated.
*/
public void updatePackage(Context context, String packageName, UserHandle user) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
- user);
+ final List<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class)
+ .getActivityList(packageName, user);
if (matches.size() > 0) {
// Find disabled/removed activities and remove them from data and add them
// to the removed list.
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 0a4f005..0d12183 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,9 +16,9 @@
package com.android.launcher3.model;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
-import android.os.Looper;
import android.util.Log;
import com.android.launcher3.AppInfo;
@@ -27,20 +27,16 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PagedView;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.ViewOnDrawExecutor;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -52,40 +48,29 @@
protected static final int INVALID_SCREEN_ID = -1;
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
- protected final Executor mUiExecutor;
+ protected final LooperExecutor mUiExecutor;
protected final LauncherAppState mApp;
protected final BgDataModel mBgDataModel;
private final AllAppsList mBgAllAppsList;
- protected final int mPageToBindFirst;
- protected final WeakReference<Callbacks> mCallbacks;
+ private final Callbacks[] mCallbacksList;
private int mMyBindingId;
public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
- AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
- mUiExecutor = MAIN_EXECUTOR;
+ AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) {
+ mUiExecutor = uiExecutor;
mApp = app;
mBgDataModel = dataModel;
mBgAllAppsList = allAppsList;
- mPageToBindFirst = pageToBindFirst;
- mCallbacks = callbacks == null ? new WeakReference<>(null) : callbacks;
+ mCallbacksList = callbacksList;
}
/**
* Binds all loaded data to actual views on the main thread.
*/
public void bindWorkspace() {
- Callbacks callbacks = mCallbacks.get();
- // Don't use these two variables in any of the callback runnables.
- // Otherwise we hold a reference to them.
- if (callbacks == null) {
- // This launcher has exited and nobody bothered to tell us. Just bail.
- Log.w(TAG, "LoaderTask running with no launcher");
- return;
- }
-
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
@@ -99,179 +84,9 @@
mMyBindingId = mBgDataModel.lastBindId;
}
- final int currentScreen;
- {
- int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE
- ? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen();
- if (currScreen >= orderedScreenIds.size()) {
- // There may be no workspace screens (just hotseat items and an empty page).
- currScreen = PagedView.INVALID_RESTORE_PAGE;
- }
- currentScreen = currScreen;
- }
- final boolean validFirstPage = currentScreen >= 0;
- final int currentScreenId =
- validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
-
- // Separate the items that are on the current screen, and all the other remaining items
- ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
- ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
-
- filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
- otherWorkspaceItems);
- filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
- otherAppWidgets);
- sortWorkspaceItemsSpatially(currentWorkspaceItems);
- sortWorkspaceItemsSpatially(otherWorkspaceItems);
-
- // Tell the workspace that we're about to start binding items
- executeCallbacksTask(c -> {
- c.clearPendingBinds();
- c.startBinding();
- }, mUiExecutor);
-
- // Bind workspace screens
- executeCallbacksTask(c -> c.bindScreens(orderedScreenIds), mUiExecutor);
-
- Executor mainExecutor = mUiExecutor;
- // Load items on the current page.
- bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
- bindAppWidgets(currentAppWidgets, mainExecutor);
- // In case of validFirstPage, only bind the first screen, and defer binding the
- // remaining screens after first onDraw (and an optional the fade animation whichever
- // happens later).
- // This ensures that the first screen is immediately visible (eg. during rotation)
- // In case of !validFirstPage, bind all pages one after other.
- final Executor deferredExecutor =
- validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
-
- executeCallbacksTask(c -> c.finishFirstPageBind(
- validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor);
-
- bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
- bindAppWidgets(otherAppWidgets, deferredExecutor);
- // Tell the workspace that we're done binding items
- executeCallbacksTask(c -> c.finishBindingItems(mPageToBindFirst), deferredExecutor);
-
- if (validFirstPage) {
- executeCallbacksTask(c -> {
- // We are loading synchronously, which means, some of the pages will be
- // bound after first draw. Inform the callbacks that page binding is
- // not complete, and schedule the remaining pages.
- if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
- c.onPageBoundSynchronously(currentScreen);
- }
- c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
-
- }, mUiExecutor);
- }
- }
-
-
- /** Filters the set of items who are directly or indirectly (via another container) on the
- * specified screen. */
- public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
- ArrayList<T> allWorkspaceItems,
- ArrayList<T> currentScreenItems,
- ArrayList<T> otherScreenItems) {
- // Purge any null ItemInfos
- Iterator<T> iter = allWorkspaceItems.iterator();
- while (iter.hasNext()) {
- ItemInfo i = iter.next();
- if (i == null) {
- iter.remove();
- }
- }
-
- // Order the set of items by their containers first, this allows use to walk through the
- // list sequentially, build up a list of containers that are in the specified screen,
- // as well as all items in those containers.
- IntSet itemsOnScreen = new IntSet();
- Collections.sort(allWorkspaceItems,
- (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
-
- for (T info : allWorkspaceItems) {
- if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (info.screenId == currentScreenId) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- if (itemsOnScreen.contains(info.container)) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- }
- }
- }
-
- /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
- * right) */
- protected void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
- final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
- final int screenCols = profile.numColumns;
- final int screenCellCount = profile.numColumns * profile.numRows;
- Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
- @Override
- public int compare(ItemInfo lhs, ItemInfo rhs) {
- if (lhs.container == rhs.container) {
- // Within containers, order by their spatial position in that container
- switch (lhs.container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
- int lr = (lhs.screenId * screenCellCount +
- lhs.cellY * screenCols + lhs.cellX);
- int rr = (rhs.screenId * screenCellCount +
- rhs.cellY * screenCols + rhs.cellX);
- return Integer.compare(lr, rr);
- }
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
- // We currently use the screen id as the rank
- return Integer.compare(lhs.screenId, rhs.screenId);
- }
- default:
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
- throw new RuntimeException("Unexpected container type when " +
- "sorting workspace items.");
- }
- return 0;
- }
- } else {
- // Between containers, order by hotseat, desktop
- return Integer.compare(lhs.container, rhs.container);
- }
- }
- });
- }
-
- protected void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
- final Executor executor) {
- // Bind the workspace items
- int N = workspaceItems.size();
- for (int i = 0; i < N; i += ITEMS_CHUNK) {
- final int start = i;
- final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
- executeCallbacksTask(
- c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
- executor);
- }
- }
-
- private void bindAppWidgets(ArrayList<LauncherAppWidgetInfo> appWidgets, Executor executor) {
- int N;// Bind the widgets, one at a time
- N = appWidgets.size();
- for (int i = 0; i < N; i++) {
- final ItemInfo widget = appWidgets.get(i);
- executeCallbacksTask(
- c -> c.bindItems(Collections.singletonList(widget), false), executor);
+ for (Callbacks cb : mCallbacksList) {
+ new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
+ workspaceItems, appWidgets, orderedScreenIds).bind();
}
}
@@ -291,19 +106,155 @@
Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
return;
}
- Callbacks callbacks = mCallbacks.get();
- if (callbacks != null) {
- task.execute(callbacks);
+ for (Callbacks cb : mCallbacksList) {
+ task.execute(cb);
}
});
}
public LooperIdleLock newIdleLock(Object lock) {
- LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper());
- // If we are not binding, there is no reason to wait for idle.
- if (mCallbacks.get() == null) {
+ LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper());
+ // If we are not binding or if the main looper is already idle, there is no reason to wait
+ if (mUiExecutor.getLooper().getQueue().isIdle()) {
idleLock.queueIdle();
}
return idleLock;
}
+
+ private static class WorkspaceBinder {
+
+ private final Executor mUiExecutor;
+ private final Callbacks mCallbacks;
+
+ private final LauncherAppState mApp;
+ private final BgDataModel mBgDataModel;
+
+ private final int mMyBindingId;
+ private final ArrayList<ItemInfo> mWorkspaceItems;
+ private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+ private final IntArray mOrderedScreenIds;
+
+
+ WorkspaceBinder(Callbacks callbacks,
+ Executor uiExecutor,
+ LauncherAppState app,
+ BgDataModel bgDataModel,
+ int myBindingId,
+ ArrayList<ItemInfo> workspaceItems,
+ ArrayList<LauncherAppWidgetInfo> appWidgets,
+ IntArray orderedScreenIds) {
+ mCallbacks = callbacks;
+ mUiExecutor = uiExecutor;
+ mApp = app;
+ mBgDataModel = bgDataModel;
+ mMyBindingId = myBindingId;
+ mWorkspaceItems = workspaceItems;
+ mAppWidgets = appWidgets;
+ mOrderedScreenIds = orderedScreenIds;
+ }
+
+ private void bind() {
+ final int currentScreen;
+ {
+ // Create an anonymous scope to calculate currentScreen as it has to be a
+ // final variable.
+ int currScreen = mCallbacks.getPageToBindSynchronously();
+ if (currScreen >= mOrderedScreenIds.size()) {
+ // There may be no workspace screens (just hotseat items and an empty page).
+ currScreen = PagedView.INVALID_PAGE;
+ }
+ currentScreen = currScreen;
+ }
+ final boolean validFirstPage = currentScreen >= 0;
+ final int currentScreenId =
+ validFirstPage ? mOrderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
+
+ // Separate the items that are on the current screen, and all the other remaining items
+ ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+ ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+
+ filterCurrentWorkspaceItems(currentScreenId, mWorkspaceItems, currentWorkspaceItems,
+ otherWorkspaceItems);
+ filterCurrentWorkspaceItems(currentScreenId, mAppWidgets, currentAppWidgets,
+ otherAppWidgets);
+ final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
+ sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
+ sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
+
+ // Tell the workspace that we're about to start binding items
+ executeCallbacksTask(c -> {
+ c.clearPendingBinds();
+ c.startBinding();
+ }, mUiExecutor);
+
+ // Bind workspace screens
+ executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
+
+ Executor mainExecutor = mUiExecutor;
+ // Load items on the current page.
+ bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
+ bindAppWidgets(currentAppWidgets, mainExecutor);
+ // In case of validFirstPage, only bind the first screen, and defer binding the
+ // remaining screens after first onDraw (and an optional the fade animation whichever
+ // happens later).
+ // This ensures that the first screen is immediately visible (eg. during rotation)
+ // In case of !validFirstPage, bind all pages one after other.
+ final Executor deferredExecutor =
+ validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
+
+ executeCallbacksTask(c -> c.finishFirstPageBind(
+ validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor);
+
+ bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
+ bindAppWidgets(otherAppWidgets, deferredExecutor);
+ // Tell the workspace that we're done binding items
+ executeCallbacksTask(c -> c.finishBindingItems(currentScreen), deferredExecutor);
+
+ if (validFirstPage) {
+ executeCallbacksTask(c -> {
+ // We are loading synchronously, which means, some of the pages will be
+ // bound after first draw. Inform the mCallbacks that page binding is
+ // not complete, and schedule the remaining pages.
+ c.onPageBoundSynchronously(currentScreen);
+ c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
+
+ }, mUiExecutor);
+ }
+ }
+
+ private void bindWorkspaceItems(
+ final ArrayList<ItemInfo> workspaceItems, final Executor executor) {
+ // Bind the workspace items
+ int count = workspaceItems.size();
+ for (int i = 0; i < count; i += ITEMS_CHUNK) {
+ final int start = i;
+ final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
+ executeCallbacksTask(
+ c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
+ executor);
+ }
+ }
+
+ private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor) {
+ // Bind the widgets, one at a time
+ int count = appWidgets.size();
+ for (int i = 0; i < count; i++) {
+ final ItemInfo widget = appWidgets.get(i);
+ executeCallbacksTask(
+ c -> c.bindItems(Collections.singletonList(widget), false), executor);
+ }
+ }
+
+ protected void executeCallbacksTask(CallbackTask task, Executor executor) {
+ executor.execute(() -> {
+ if (mMyBindingId != mBgDataModel.lastBindId) {
+ Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
+ return;
+ }
+ task.execute(mCallbacks);
+ });
+ }
+ }
}
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index e12633b..5a7b4d3 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -20,17 +20,16 @@
import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.widget.WidgetListRowEntry;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -78,13 +77,9 @@
* Schedules a {@param task} to be executed on the current callbacks.
*/
public final void scheduleCallbackTask(final CallbackTask task) {
- final Callbacks callbacks = mModel.getCallback();
- mUiExecutor.execute(() -> {
- Callbacks cb = mModel.getCallback();
- if (callbacks == cb && cb != null) {
- task.execute(callbacks);
- }
- });
+ for (final Callbacks cb : mModel.getCallbacks()) {
+ mUiExecutor.execute(() -> task.execute(cb));
+ }
}
public ModelWriter getModelWriter() {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 0e20270..32fce0b 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -15,7 +15,11 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
+
import android.content.Context;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -29,15 +33,15 @@
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PromiseAppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.DumpTargetWrapper;
import com.android.launcher3.model.nano.LauncherDumpProto;
import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -59,6 +63,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
/**
* All the data stored in-memory and managed by the LauncherModel
@@ -267,7 +273,7 @@
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
folders.remove(item.id);
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
for (ItemInfo info : itemsIdMap) {
if (info.container == item.id) {
// We are deleting a folder which still contains items that
@@ -287,7 +293,7 @@
if ((count == null || --count.value == 0)
&& !InstallShortcutReceiver.getPendingShortcuts(context)
.contains(pinnedShortcut)) {
- DeepShortcutManager.getInstance(context).unpinShortcut(pinnedShortcut);
+ unpinShortcut(context, pinnedShortcut);
}
// Fall through.
}
@@ -324,7 +330,7 @@
// Since this is a new item, pin the shortcut in the system server.
if (newItem && count.value == 1) {
- DeepShortcutManager.getInstance(context).pinShortcut(pinnedShortcut);
+ updatePinnedShortcuts(context, pinnedShortcut, List::add);
}
// Fall through
}
@@ -355,6 +361,36 @@
}
/**
+ * Removes the given shortcut from the current list of pinned shortcuts.
+ * (Runs on background thread)
+ */
+ public void unpinShortcut(Context context, ShortcutKey key) {
+ updatePinnedShortcuts(context, key, List::remove);
+ }
+
+ private void updatePinnedShortcuts(Context context, ShortcutKey key,
+ BiConsumer<List<String>, String> idOp) {
+ if (GO_DISABLE_WIDGETS) {
+ return;
+ }
+ String packageName = key.componentName.getPackageName();
+ String id = key.getId();
+ UserHandle user = key.user;
+ List<String> pinnedIds = new ShortcutRequest(context, user)
+ .forPackage(packageName)
+ .query(PINNED)
+ .stream()
+ .map(ShortcutInfo::getId)
+ .collect(Collectors.toCollection(ArrayList::new));
+ idOp.accept(pinnedIds, id);
+ try {
+ context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user);
+ } catch (SecurityException | IllegalStateException e) {
+ Log.w(TAG, "Failed to pin shortcut", e);
+ }
+ }
+
+ /**
* Return an existing FolderInfo object if we have encountered this ID previously,
* or make a new one.
*/
@@ -400,9 +436,10 @@
}
public interface Callbacks {
- void rebindModel();
-
- int getCurrentWorkspaceScreen();
+ /**
+ * Returns the page number to bind first, synchronously if possible or -1
+ */
+ int getPageToBindSynchronously();
void clearPendingBinds();
void startBinding();
void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
index 804a040..4a1bc4d 100644
--- a/src/com/android/launcher3/model/GridBackupTable.java
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -27,9 +27,11 @@
import android.os.Process;
import android.util.Log;
+import androidx.annotation.IntDef;
+
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.pm.UserCache;
/**
* Helper class to backup and restore Favorites table into a separate table
@@ -45,6 +47,19 @@
private static final String KEY_GRID_Y_SIZE = Favorites.SPANY;
private static final String KEY_DB_VERSION = Favorites.RANK;
+ public static final int OPTION_REQUIRES_SANITIZATION = 1;
+
+ /** STATE_NOT_FOUND indicates backup doesn't exist in the db. */
+ private static final int STATE_NOT_FOUND = 0;
+ /**
+ * STATE_RAW indicates the backup has not yet been sanitized. This implies it might still
+ * posses app info that doesn't exist in the workspace and needed to be sanitized before
+ * put into use.
+ */
+ private static final int STATE_RAW = 1;
+ /** STATE_SANITIZED indicates the backup has already been sanitized, thus can be used as-is. */
+ private static final int STATE_SANITIZED = 2;
+
private final Context mContext;
private final SQLiteDatabase mDb;
@@ -56,8 +71,11 @@
private int mRestoredGridX;
private int mRestoredGridY;
- public GridBackupTable(Context context, SQLiteDatabase db,
- int hotseatSize, int gridX, int gridY) {
+ @IntDef({STATE_NOT_FOUND, STATE_RAW, STATE_SANITIZED})
+ private @interface BackupState { }
+
+ public GridBackupTable(Context context, SQLiteDatabase db, int hotseatSize, int gridX,
+ int gridY) {
mContext = context;
mDb = db;
@@ -66,6 +84,10 @@
mOldGridY = gridY;
}
+ /**
+ * Create a backup from current workspace layout if one isn't created already (Note backup
+ * created this way is always sanitized). Otherwise restore from the backup instead.
+ */
public boolean backupOrRestoreAsNeeded() {
// Check if backup table exists
if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
@@ -74,16 +96,28 @@
// No need to copy if empty DB was created.
return false;
}
+ doBackup(UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
+ Process.myUserHandle()), 0);
+ return false;
+ }
+ return restoreIfBackupExists(Favorites.TABLE_NAME);
+ }
- copyTable(Favorites.TABLE_NAME, BACKUP_TABLE_NAME);
- encodeDBProperties();
+ public boolean restoreToPreviewIfBackupExists() {
+ if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
return false;
}
- if (!loadDbProperties()) {
+ return restoreIfBackupExists(Favorites.PREVIEW_TABLE_NAME);
+ }
+
+ private boolean restoreIfBackupExists(String toTableName) {
+ if (loadDBProperties() != STATE_SANITIZED) {
return false;
}
- copyTable(BACKUP_TABLE_NAME, Favorites.TABLE_NAME);
+ long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
+ Process.myUserHandle());
+ copyTable(mDb, BACKUP_TABLE_NAME, toTableName, userSerial);
Log.d(TAG, "Backup table found");
return true;
}
@@ -93,43 +127,84 @@
return mRestoredHotseatSize;
}
- private void copyTable(String from, String to) {
- long userSerial = UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
- Process.myUserHandle());
- dropTable(mDb, to);
- Favorites.addTableToDb(mDb, userSerial, false, to);
- mDb.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY);
+ /**
+ * Copy valid grid entries from one table to another.
+ */
+ private static void copyTable(SQLiteDatabase db, String from, String to, long userSerial) {
+ dropTable(db, to);
+ Favorites.addTableToDb(db, userSerial, false, to);
+ db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY);
}
- private void encodeDBProperties() {
+ private void encodeDBProperties(int options) {
ContentValues values = new ContentValues();
values.put(Favorites._ID, ID_PROPERTY);
values.put(KEY_DB_VERSION, mDb.getVersion());
values.put(KEY_GRID_X_SIZE, mOldGridX);
values.put(KEY_GRID_Y_SIZE, mOldGridY);
values.put(KEY_HOTSEAT_SIZE, mOldHotseatSize);
+ values.put(Favorites.OPTIONS, options);
mDb.insert(BACKUP_TABLE_NAME, null, values);
}
- private boolean loadDbProperties() {
+ /**
+ * Load DB properties from grid backup table.
+ */
+ public @BackupState int loadDBProperties() {
try (Cursor c = mDb.query(BACKUP_TABLE_NAME, new String[] {
- KEY_DB_VERSION, // 0
- KEY_GRID_X_SIZE, // 1
- KEY_GRID_Y_SIZE, // 2
- KEY_HOTSEAT_SIZE}, // 3
+ KEY_DB_VERSION, // 0
+ KEY_GRID_X_SIZE, // 1
+ KEY_GRID_Y_SIZE, // 2
+ KEY_HOTSEAT_SIZE, // 3
+ Favorites.OPTIONS}, // 4
"_id=" + ID_PROPERTY, null, null, null, null)) {
if (!c.moveToNext()) {
Log.e(TAG, "Meta data not found in backup table");
- return false;
+ return STATE_NOT_FOUND;
}
- if (mDb.getVersion() != c.getInt(0)) {
- return false;
+ if (!validateDBVersion(mDb.getVersion(), c.getInt(0))) {
+ return STATE_NOT_FOUND;
}
mRestoredGridX = c.getInt(1);
mRestoredGridY = c.getInt(2);
mRestoredHotseatSize = c.getInt(3);
- return true;
+ boolean isSanitized = (c.getInt(4) & OPTION_REQUIRES_SANITIZATION) == 0;
+ return isSanitized ? STATE_SANITIZED : STATE_RAW;
}
}
+
+ /**
+ * Restore workspace from raw backup if available.
+ */
+ public boolean restoreFromRawBackupIfAvailable(long oldProfileId) {
+ if (!tableExists(mDb, Favorites.BACKUP_TABLE_NAME)
+ || loadDBProperties() != STATE_RAW
+ || mOldHotseatSize != mRestoredHotseatSize
+ || mOldGridX != mRestoredGridX
+ || mOldGridY != mRestoredGridY) {
+ // skip restore if dimensions in backup table differs from current setup.
+ return false;
+ }
+ copyTable(mDb, Favorites.BACKUP_TABLE_NAME, Favorites.TABLE_NAME, oldProfileId);
+ Log.d(TAG, "Backup restored");
+ return true;
+ }
+
+ /**
+ * Performs a backup on the workspace layout.
+ */
+ public void doBackup(long profileId, int options) {
+ copyTable(mDb, Favorites.TABLE_NAME, Favorites.BACKUP_TABLE_NAME, profileId);
+ encodeDBProperties(options);
+ }
+
+ private static boolean validateDBVersion(int expected, int actual) {
+ if (expected != actual) {
+ Log.e(TAG, String.format("Launcher.db version mismatch, expecting %d but %d was found",
+ expected, actual));
+ return false;
+ }
+ return true;
+ }
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 783e908..3ba740d 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -3,6 +3,7 @@
import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.Utilities.parsePoint;
+import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -14,9 +15,12 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
+import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
@@ -26,22 +30,19 @@
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
-import java.util.function.Consumer;
-
-import androidx.annotation.VisibleForTesting;
/**
* This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -71,6 +72,7 @@
private final SparseArray<ContentValues> mUpdateOperations = new SparseArray<>();
private final HashSet<String> mValidPackages;
+ private final String mTableName;
private final int mSrcX, mSrcY;
private final int mTrgX, mTrgY;
@@ -80,10 +82,12 @@
private final int mDestHotseatSize;
protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
- HashSet<String> validPackages, Point sourceSize, Point targetSize) {
+ HashSet<String> validPackages, boolean usePreviewTable, Point sourceSize,
+ Point targetSize) {
mContext = context;
mDb = db;
mValidPackages = validPackages;
+ mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
mSrcX = sourceSize.x;
mSrcY = sourceSize.y;
@@ -99,10 +103,12 @@
}
protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
- HashSet<String> validPackages, int srcHotseatSize, int destHotseatSize) {
+ HashSet<String> validPackages, boolean usePreviewTable, int srcHotseatSize,
+ int destHotseatSize) {
mContext = context;
mDb = db;
mValidPackages = validPackages;
+ mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
mSrcHotseatSize = srcHotseatSize;
@@ -122,7 +128,7 @@
// Update items
int updateCount = mUpdateOperations.size();
for (int i = 0; i < updateCount; i++) {
- mDb.update(Favorites.TABLE_NAME, mUpdateOperations.valueAt(i),
+ mDb.update(mTableName, mUpdateOperations.valueAt(i),
"_id=" + mUpdateOperations.keyAt(i), null);
}
@@ -130,8 +136,8 @@
if (DEBUG) {
Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
}
- mDb.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
- Favorites._ID, mEntryToRemove), null);
+ mDb.delete(mTableName, Utilities.createDbSelectionQuery(Favorites._ID, mEntryToRemove),
+ null);
}
return updateCount > 0 || !mEntryToRemove.isEmpty();
@@ -184,8 +190,8 @@
}
@VisibleForTesting
- static IntArray getWorkspaceScreenIds(SQLiteDatabase db) {
- return LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, Favorites.SCREEN,
+ static IntArray getWorkspaceScreenIds(SQLiteDatabase db, String tableName) {
+ return LauncherDbUtils.queryIntArray(db, tableName, Favorites.SCREEN,
Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
Favorites.SCREEN, Favorites.SCREEN);
}
@@ -194,7 +200,7 @@
* @return true if any DB change was made
*/
protected boolean migrateWorkspace() throws Exception {
- IntArray allScreens = getWorkspaceScreenIds(mDb);
+ IntArray allScreens = getWorkspaceScreenIds(mDb, mTableName);
if (allScreens.isEmpty()) {
throw new Exception("Unable to get workspace screens");
}
@@ -246,12 +252,12 @@
/**
* Migrate a particular screen id.
* Strategy:
- * 1) For all possible combinations of row and column, pick the one which causes the least
- * data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
- * 2) Maintain a list of all lost items before this screen, and add any new item lost from
- * this screen to that list as well.
- * 3) If all those items from the above list can be placed on this screen, place them
- * (otherwise they are placed on a new screen).
+ * 1) For all possible combinations of row and column, pick the one which causes the least
+ * data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
+ * 2) Maintain a list of all lost items before this screen, and add any new item lost from
+ * this screen to that list as well.
+ * 3) If all those items from the above list can be placed on this screen, place them
+ * (otherwise they are placed on a new screen).
*/
protected void migrateScreen(int screenId) {
// If we are migrating the first screen, do not touch the first row.
@@ -364,9 +370,9 @@
/**
* Tries the remove the provided row and column.
*
- * @param items all the items on the screen under operation
+ * @param items all the items on the screen under operation
* @param outLoss array of size 2. The first entry is filled with weight loss, and the second
- * with the overall item movement.
+ * with the overall item movement.
*/
private ArrayList<DbEntry> tryRemove(int col, int row, int startY,
ArrayList<DbEntry> items, float[] outLoss) {
@@ -381,13 +387,13 @@
for (DbEntry item : items) {
if ((item.cellX <= col && (item.spanX + item.cellX) > col)
- || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
+ || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
removedItems.add(item);
- if (item.cellX >= col) item.cellX --;
- if (item.cellY >= row) item.cellY --;
+ if (item.cellX >= col) item.cellX--;
+ if (item.cellY >= row) item.cellY--;
} else {
- if (item.cellX > col) item.cellX --;
- if (item.cellY > row) item.cellY --;
+ if (item.cellX > col) item.cellX--;
+ if (item.cellY > row) item.cellY--;
finalItems.add(item);
occupied.markCells(item, true);
}
@@ -440,9 +446,9 @@
/**
* Recursively finds a placement for the provided items.
*
- * @param index the position in {@link #itemsToPlace} to start looking at.
- * @param weightLoss total weight loss upto this point
- * @param moveCost total move cost upto this point
+ * @param index the position in {@link #itemsToPlace} to start looking at.
+ * @param weightLoss total weight loss upto this point
+ * @param moveCost total move cost upto this point
* @param itemsPlaced all the items already placed upto this point
*/
public void find(int index, float weightLoss, float moveCost,
@@ -483,11 +489,11 @@
float newMoveCost = moveCost;
if (x != myX) {
me.cellX = x;
- newMoveCost ++;
+ newMoveCost++;
}
if (y != myY) {
me.cellY = y;
- newMoveCost ++;
+ newMoveCost++;
}
if (ignoreMove) {
newMoveCost = moveCost;
@@ -502,35 +508,35 @@
// Try resizing horizontally
if (myW > me.minSpanX && occupied.isRegionVacant(x, y, myW - 1, myH)) {
- me.spanX --;
+ me.spanX--;
occupied.markCells(me, true);
// 1 extra move cost
find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
occupied.markCells(me, false);
- me.spanX ++;
+ me.spanX++;
}
// Try resizing vertically
if (myH > me.minSpanY && occupied.isRegionVacant(x, y, myW, myH - 1)) {
- me.spanY --;
+ me.spanY--;
occupied.markCells(me, true);
// 1 extra move cost
find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
occupied.markCells(me, false);
- me.spanY ++;
+ me.spanY++;
}
// Try resizing horizontally & vertically
if (myH > me.minSpanY && myW > me.minSpanX &&
occupied.isRegionVacant(x, y, myW - 1, myH - 1)) {
- me.spanX --;
- me.spanY --;
+ me.spanX--;
+ me.spanY--;
occupied.markCells(me, true);
// 2 extra move cost
find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
occupied.markCells(me, false);
- me.spanX ++;
- me.spanY ++;
+ me.spanX++;
+ me.spanY++;
}
me.cellX = myX;
me.cellY = myY;
@@ -567,11 +573,11 @@
float newMoveCost = moveCost;
if (newX != myX) {
me.cellX = newX;
- newMoveCost ++;
+ newMoveCost++;
}
if (newY != myY) {
me.cellY = newY;
- newMoveCost ++;
+ newMoveCost++;
}
if (ignoreMove) {
newMoveCost = moveCost;
@@ -604,7 +610,7 @@
}
private ArrayList<DbEntry> loadHotseatEntries() {
- Cursor c = queryWorkspace(
+ Cursor c = queryWorkspace(
new String[]{
Favorites._ID, // 0
Favorites.ITEM_TYPE, // 1
@@ -694,6 +700,7 @@
final int indexAppWidgetId = c.getColumnIndexOrThrow(Favorites.APPWIDGET_ID);
ArrayList<DbEntry> entries = new ArrayList<>();
+ WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(mContext);
while (c.moveToNext()) {
DbEntry entry = new DbEntry();
entry.id = c.getInt(indexId);
@@ -723,8 +730,8 @@
* entry.spanX * entry.spanY);
int widgetId = c.getInt(indexAppWidgetId);
- LauncherAppWidgetProviderInfo pInfo = AppWidgetManagerCompat.getInstance(
- mContext).getLauncherAppWidgetInfo(widgetId);
+ LauncherAppWidgetProviderInfo pInfo =
+ widgetManagerHelper.getLauncherAppWidgetInfo(widgetId);
Point spans = null;
if (pInfo != null) {
spans = pInfo.getMinSpans();
@@ -788,7 +795,7 @@
}
protected Cursor queryWorkspace(String[] columns, String where) {
- return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
+ return mDb.query(mTableName, columns, where, null, null, null, null);
}
/**
@@ -880,24 +887,44 @@
}
/**
- * Migrates the workspace and hotseat in case their sizes changed.
+ * Check given a new IDP, if migration is necessary.
+ */
+ public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+
+ return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
+ || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+ idp.numHotseatIcons);
+ }
+
+ /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
+ public static boolean migrateGridIfNeeded(Context context) {
+ if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+ return true;
+ }
+ return migrateGridIfNeeded(context, null);
+ }
+
+ /**
+ * Run the migration algorithm if needed. For preview, we provide the intended idp because it
+ * has not been changed. If idp is null, we read it from the context, for actual grid migration.
*
* @return false if the migration failed.
*/
- public static boolean migrateGridIfNeeded(Context context) {
- SharedPreferences prefs = Utilities.getPrefs(context);
- InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+ public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
+ boolean migrateForPreview = idp != null;
+ if (!migrateForPreview) {
+ idp = LauncherAppState.getIDP(context);
+ }
- String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-
- if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
- idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
- idp.numHotseatIcons)) {
- // Skip if workspace and hotseat sizes have not changed.
+ if (!needsToMigrate(context, idp)) {
return true;
}
- long migrationStartTime = System.currentTimeMillis();
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+ long migrationStartTime = SystemClock.elapsedRealtime();
try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
.getBinder(Settings.EXTRA_VALUE)) {
@@ -908,33 +935,39 @@
KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
boolean dbChanged = false;
+ if (migrateForPreview) {
+ copyTable(transaction.getDb(), Favorites.TABLE_NAME, Favorites.PREVIEW_TABLE_NAME,
+ context);
+ }
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
srcHotseatCount, sourceSize.x, sourceSize.y);
- if (backupTable.backupOrRestoreAsNeeded()) {
+ if (migrateForPreview ? backupTable.restoreToPreviewIfBackupExists()
+ : backupTable.backupOrRestoreAsNeeded()) {
dbChanged = true;
srcHotseatCount = backupTable.getRestoreHotseatAndGridSize(sourceSize);
}
HashSet<String> validPackages = getValidPackages(context);
- // Hotseat
+ // Hotseat.
if (srcHotseatCount != idp.numHotseatIcons) {
// Migrate hotseat.
- dbChanged = new GridSizeMigrationTask(context, transaction.getDb(),
- validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
+ dbChanged = new GridSizeMigrationTask(context, transaction.getDb(), validPackages,
+ migrateForPreview, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
}
// Grid size
Point targetSize = new Point(idp.numColumns, idp.numRows);
- if (new MultiStepMigrationTask(validPackages, context, transaction.getDb())
- .migrate(sourceSize, targetSize)) {
+ if (new MultiStepMigrationTask(validPackages, context, transaction.getDb(),
+ migrateForPreview).migrate(sourceSize, targetSize)) {
dbChanged = true;
}
if (dbChanged) {
// Make sure we haven't removed everything.
final Cursor c = context.getContentResolver().query(
- Favorites.CONTENT_URI, null, null, null, null);
+ migrateForPreview ? Favorites.PREVIEW_CONTENT_URI : Favorites.CONTENT_URI,
+ null, null, null, null);
boolean hasData = c.moveToNext();
c.close();
if (!hasData) {
@@ -943,21 +976,25 @@
}
transaction.commit();
- Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+ if (!migrateForPreview) {
+ Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+ }
return true;
} catch (Exception e) {
- Log.e(TAG, "Error during grid migration", e);
+ Log.e(TAG, "Error during preview grid migration", e);
return false;
} finally {
- Log.v(TAG, "Workspace migration completed in "
- + (System.currentTimeMillis() - migrationStartTime));
+ Log.v(TAG, "Preview workspace migration completed in "
+ + (SystemClock.elapsedRealtime() - migrationStartTime));
- // Save current configuration, so that the migration does not run again.
- prefs.edit()
- .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
- .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
- .apply();
+ if (!migrateForPreview) {
+ // Save current configuration, so that the migration does not run again.
+ prefs.edit()
+ .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
+ .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
+ .apply();
+ }
}
}
@@ -972,8 +1009,8 @@
.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
validPackages.add(info.packageName);
}
- PackageInstallerCompat.getInstance(context)
- .updateAndGetActiveSessionCache().keySet()
+ InstallSessionHelper.INSTANCE.get(context)
+ .getActiveSessions().keySet()
.forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName));
return validPackages;
}
@@ -990,7 +1027,7 @@
.getBinder(Settings.EXTRA_VALUE)) {
GridSizeMigrationTask task = new GridSizeMigrationTask(
context, transaction.getDb(), getValidPackages(context),
- Integer.MAX_VALUE, Integer.MAX_VALUE);
+ false /* usePreviewTable */, Integer.MAX_VALUE, Integer.MAX_VALUE);
// Load all the valid entries
ArrayList<DbEntry> items = task.loadHotseatEntries();
@@ -1012,12 +1049,14 @@
private final HashSet<String> mValidPackages;
private final Context mContext;
private final SQLiteDatabase mDb;
+ private final boolean mUsePreviewTable;
public MultiStepMigrationTask(HashSet<String> validPackages, Context context,
- SQLiteDatabase db) {
+ SQLiteDatabase db, boolean usePreviewTable) {
mValidPackages = validPackages;
mContext = context;
mDb = db;
+ mUsePreviewTable = usePreviewTable;
}
public boolean migrate(Point sourceSize, Point targetSize) throws Exception {
@@ -1053,8 +1092,8 @@
}
protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
- return new GridSizeMigrationTask(mContext, mDb,
- mValidPackages, sourceSize, nextSize).migrateWorkspace();
+ return new GridSizeMigrationTask(mContext, mDb, mValidPackages, mUsePreviewTable,
+ sourceSize, nextSize).migrateWorkspace();
}
}
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
new file mode 100644
index 0000000..197b29c
--- /dev/null
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import android.content.Context;
+
+import com.android.launcher3.InvariantDeviceProfile;
+
+/**
+ * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
+ * result of restoring from a larger device or device density change.
+ */
+public class GridSizeMigrationTaskV2 {
+
+ private GridSizeMigrationTaskV2(Context context) {
+
+ }
+
+ /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
+ public static boolean migrateGridIfNeeded(Context context) {
+ // To be implemented.
+ return true;
+ }
+
+ /**
+ * Run the migration algorithm if needed. For preview, we provide the intended idp because it
+ * has not been changed. If idp is null, we read it from the context, for actual grid migration.
+ *
+ * @return false if the migration failed.
+ */
+ public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
+ // To be implemented.
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 1c39d1f..2311dcc 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,16 +16,19 @@
package com.android.launcher3.model;
+import static android.graphics.BitmapFactory.decodeByteArray;
+
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.CursorWrapper;
-import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.os.UserHandle;
import android.provider.BaseColumns;
import android.text.TextUtils;
@@ -33,17 +36,16 @@
import android.util.LongSparseArray;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ContentWriter;
@@ -63,6 +65,7 @@
public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
+ private final Uri mContentUri;
private final Context mContext;
private final PackageManager mPM;
private final IconCache mIconCache;
@@ -95,8 +98,10 @@
public int itemType;
public int restoreFlag;
- public LoaderCursor(Cursor c, LauncherAppState app) {
- super(c);
+ public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app) {
+ super(cursor);
+
+ mContentUri = contentUri;
mContext = app.getContext();
mIconCache = app.getIconCache();
mIDP = app.getInvariantDeviceProfile();
@@ -153,7 +158,7 @@
info.title = getTitle();
// the fallback icon
if (!loadIcon(info)) {
- info.applyFrom(mIconCache.getDefaultIcon(info.user));
+ info.bitmap = mIconCache.getDefaultIcon(info.user);
}
// TODO: If there's an explicit component and we can't install that, delete it.
@@ -166,37 +171,30 @@
*/
protected boolean loadIcon(WorkspaceItemInfo info) {
try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
- return loadIcon(info, li);
- }
- }
-
- /**
- * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
- */
- protected boolean loadIcon(WorkspaceItemInfo info, LauncherIcons li) {
- if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
- String packageName = getString(iconPackageIndex);
- String resourceName = getString(iconResourceIndex);
- if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
- info.iconResource = new ShortcutIconResource();
- info.iconResource.packageName = packageName;
- info.iconResource.resourceName = resourceName;
- BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
- if (iconInfo != null) {
- info.applyFrom(iconInfo);
- return true;
+ if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ String packageName = getString(iconPackageIndex);
+ String resourceName = getString(iconResourceIndex);
+ if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
+ info.iconResource = new ShortcutIconResource();
+ info.iconResource.packageName = packageName;
+ info.iconResource.resourceName = resourceName;
+ BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
+ if (iconInfo != null) {
+ info.bitmap = iconInfo;
+ return true;
+ }
}
}
- }
- // Failed to load from resource, try loading from DB.
- byte[] data = getBlob(iconIndex);
- try {
- info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)));
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Failed to decode byte array for info " + info, e);
- return false;
+ // Failed to load from resource, try loading from DB.
+ byte[] data = getBlob(iconIndex);
+ try {
+ info.bitmap = li.createIconBitmap(decodeByteArray(data, 0, data.length));
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to decode byte array for info " + info, e);
+ return false;
+ }
}
}
@@ -260,7 +258,7 @@
Intent newIntent = new Intent(Intent.ACTION_MAIN, null);
newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
newIntent.setComponent(componentName);
- LauncherActivityInfo lai = LauncherAppsCompat.getInstance(mContext)
+ LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class)
.resolveActivity(newIntent, user);
if ((lai == null) && !allowMissingTarget) {
Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
@@ -273,7 +271,7 @@
info.intent = newIntent;
mIconCache.getTitleAndIcon(info, lai, useLowResIcon);
- if (mIconCache.isDefaultIcon(info.iconBitmap, user)) {
+ if (mIconCache.isDefaultIcon(info.bitmap, user)) {
loadIcon(info);
}
@@ -318,9 +316,8 @@
public boolean commitDeleted() {
if (itemsToRemove.size() > 0) {
// Remove dead items
- mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
- Utilities.createDbSelectionQuery(
- LauncherSettings.Favorites._ID, itemsToRemove), null);
+ mContext.getContentResolver().delete(mContentUri, Utilities.createDbSelectionQuery(
+ LauncherSettings.Favorites._ID, itemsToRemove), null);
return true;
}
return false;
@@ -345,7 +342,7 @@
// Update restored items that no longer require special handling
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites.RESTORED, 0);
- mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, values,
+ mContext.getContentResolver().update(mContentUri, values,
Utilities.createDbSelectionQuery(
LauncherSettings.Favorites._ID, restoredRows), null);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 50e1d56..62904ae 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -19,8 +19,10 @@
import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
import android.appwidget.AppWidgetProviderInfo;
@@ -30,45 +32,51 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.ShortcutInfo;
-import android.os.Process;
+import android.net.Uri;
import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.MutableInt;
+import android.util.TimingLogger;
+
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
+import com.android.launcher3.folder.FolderNameInfo;
+import com.android.launcher3.folder.FolderNameProvider;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherActivityCachingLogic;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.ImportDataTask;
import com.android.launcher3.qsb.QsbContainerView;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
@@ -76,6 +84,7 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -84,7 +93,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
-import java.util.function.Supplier;
/**
* Runnable for the thread that loads the contents of the launcher:
@@ -96,19 +104,19 @@
public class LoaderTask implements Runnable {
private static final String TAG = "LoaderTask";
- private final LauncherAppState mApp;
+ protected final LauncherAppState mApp;
private final AllAppsList mBgAllAppsList;
- private final BgDataModel mBgDataModel;
+ protected final BgDataModel mBgDataModel;
private FirstScreenBroadcast mFirstScreenBroadcast;
private final LoaderResults mResults;
- private final LauncherAppsCompat mLauncherApps;
- private final UserManagerCompat mUserManager;
- private final DeepShortcutManager mShortcutManager;
- private final PackageInstallerCompat mPackageInstaller;
- private final AppWidgetManagerCompat mAppWidgetManager;
+ private final LauncherApps mLauncherApps;
+ private final UserManager mUserManager;
+ private final UserCache mUserCache;
+
+ private final InstallSessionHelper mSessionHelper;
private final IconCache mIconCache;
private boolean mStopped;
@@ -120,11 +128,10 @@
mBgDataModel = dataModel;
mResults = results;
- mLauncherApps = LauncherAppsCompat.getInstance(mApp.getContext());
- mUserManager = UserManagerCompat.getInstance(mApp.getContext());
- mShortcutManager = DeepShortcutManager.getInstance(mApp.getContext());
- mPackageInstaller = PackageInstallerCompat.getInstance(mApp.getContext());
- mAppWidgetManager = AppWidgetManagerCompat.getInstance(mApp.getContext());
+ mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
+ mUserManager = mApp.getContext().getSystemService(UserManager.class);
+ mUserCache = UserCache.INSTANCE.get(mApp.getContext());
+ mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
mIconCache = mApp.getIconCache();
}
@@ -168,82 +175,106 @@
}
}
- TraceHelper.beginSection(TAG);
+ Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
+ TimingLogger logger = new TimingLogger(TAG, "run");
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
- TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
- loadWorkspace();
+ List<ShortcutInfo> allShortcuts = new ArrayList<>();
+ loadWorkspace(allShortcuts);
+ logger.addSplit("loadWorkspace");
verifyNotStopped();
- TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
mResults.bindWorkspace();
+ logger.addSplit("bindWorkspace");
// Notify the installer packages of packages with active installs on the first screen.
- TraceHelper.partitionSection(TAG, "step 1.3: send first screen broadcast");
sendFirstScreenActiveInstallsBroadcast();
+ logger.addSplit("sendFirstScreenActiveInstallsBroadcast");
// Take a break
- TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle");
waitForIdle();
+ logger.addSplit("step 1 complete");
verifyNotStopped();
// second step
- TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
List<LauncherActivityInfo> allActivityList = loadAllApps();
+ logger.addSplit("loadAllApps");
- TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
verifyNotStopped();
mResults.bindAllApps();
+ logger.addSplit("bindAllApps");
verifyNotStopped();
- TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
setIgnorePackages(updateHandler);
updateHandler.updateIcons(allActivityList,
LauncherActivityCachingLogic.newInstance(mApp.getContext()),
mApp.getModel()::onPackageIconsUpdated);
+ logger.addSplit("update icon cache");
+
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ verifyNotStopped();
+ logger.addSplit("save shortcuts in icon cache");
+ updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
+ mApp.getModel()::onPackageIconsUpdated);
+ }
// Take a break
- TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
waitForIdle();
+ logger.addSplit("step 2 complete");
verifyNotStopped();
// third step
- TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts");
- loadDeepShortcuts();
+ List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
+ logger.addSplit("loadDeepShortcuts");
verifyNotStopped();
- TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts");
mResults.bindDeepShortcuts();
+ logger.addSplit("bindDeepShortcuts");
+
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ verifyNotStopped();
+ logger.addSplit("save deep shortcuts in icon cache");
+ updateHandler.updateIcons(allDeepShortcuts,
+ new ShortcutCachingLogic(), (pkgs, user) -> { });
+ }
// Take a break
- TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle");
waitForIdle();
+ logger.addSplit("step 3 complete");
verifyNotStopped();
// fourth step
- TraceHelper.partitionSection(TAG, "step 4.1: loading widgets");
- List<ComponentWithLabel> allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null);
+ List<ComponentWithLabelAndIcon> allWidgetsList =
+ mBgDataModel.widgetsModel.update(mApp, null);
+ logger.addSplit("load widgets");
verifyNotStopped();
- TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets");
mResults.bindWidgets();
-
+ logger.addSplit("bindWidgets");
verifyNotStopped();
- TraceHelper.partitionSection(TAG, "step 4.3: save widgets in icon cache");
- updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(
- mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
+ updateHandler.updateIcons(allWidgetsList,
+ new ComponentWithIconCachingLogic(mApp.getContext(), true),
+ mApp.getModel()::onWidgetLabelsUpdated);
+ logger.addSplit("save widgets in icon cache");
+
+ // fifth step
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ loadFolderNames();
+ }
verifyNotStopped();
- TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update");
updateHandler.finish();
+ logger.addSplit("finish icon update");
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
- TraceHelper.partitionSection(TAG, "Cancelled");
+ logger.addSplit("Cancelled");
+ } finally {
+ logger.dumpToLog();
}
- TraceHelper.endSection(TAG);
+ TraceHelper.INSTANCE.endSection(traceToken);
}
public synchronized void stopLocked() {
@@ -251,7 +282,12 @@
this.notify();
}
- private void loadWorkspace() {
+ @VisibleForTesting
+ void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+ loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI);
+ }
+
+ protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri) {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -267,7 +303,9 @@
clearDb = true;
}
- if (!clearDb && !GridSizeMigrationTask.migrateGridIfNeeded(context)) {
+ if (!clearDb && (MULTI_DB_GRID_MIRATION_ALGO.get()
+ ? !GridSizeMigrationTaskV2.migrateGridIfNeeded(context)
+ : !GridSizeMigrationTask.migrateGridIfNeeded(context))) {
// Migration failed. Clear workspace.
clearDb = true;
}
@@ -286,15 +324,17 @@
mBgDataModel.clear();
final HashMap<PackageUserKey, SessionInfo> installingPkgs =
- mPackageInstaller.updateAndGetActiveSessionCache();
+ mSessionHelper.getActiveSessions();
+ installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
+
final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
- final LoaderCursor c = new LoaderCursor(contentResolver.query(
- LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
+ final LoaderCursor c = new LoaderCursor(
+ contentResolver.query(contentUri, null, null, null, null), contentUri, mApp);
- HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
+ Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
try {
final int appWidgetIdIndex = c.getColumnIndexOrThrow(
@@ -313,8 +353,8 @@
final LongSparseArray<UserHandle> allUsers = c.allUsers;
final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
- for (UserHandle user : mUserManager.getUserProfiles()) {
- long serialNo = mUserManager.getSerialNumberForUser(user);
+ for (UserHandle user : mUserCache.getUserProfiles()) {
+ long serialNo = mUserCache.getSerialNumberForUser(user);
allUsers.put(serialNo, user);
quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
@@ -322,8 +362,8 @@
// We can only query for shortcuts when the user is unlocked.
if (userUnlocked) {
- DeepShortcutManager.QueryResult pinnedShortcuts =
- mShortcutManager.queryForPinnedShortcuts(null, user);
+ QueryResult pinnedShortcuts = new ShortcutRequest(context, user)
+ .query(ShortcutRequest.PINNED);
if (pinnedShortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : pinnedShortcuts) {
shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
@@ -387,7 +427,7 @@
// If there is no target package, its an implicit intent
// (legacy shortcut) which is always valid
boolean validTarget = TextUtils.isEmpty(targetPkg) ||
- mLauncherApps.isPackageEnabledForProfile(targetPkg, c.user);
+ mLauncherApps.isPackageEnabled(targetPkg, c.user);
// If it's a deep shortcut, we'll use pinned shortcuts to restore it
if (cn != null && validTarget && c.itemType
@@ -396,7 +436,7 @@
// component.
// If the component is already present
- if (mLauncherApps.isActivityEnabledForProfile(cn, c.user)) {
+ if (mLauncherApps.isActivityEnabled(cn, c.user)) {
// no special handling necessary for this item
c.markRestored();
} else {
@@ -489,21 +529,16 @@
continue;
}
info = new WorkspaceItemInfo(pinnedShortcut, context);
- final WorkspaceItemInfo finalInfo = info;
-
- LauncherIcons li = LauncherIcons.obtain(context);
// If the pinned deep shortcut is no longer published,
// use the last saved icon instead of the default.
- Supplier<ItemInfoWithIcon> fallbackIconProvider = () ->
- c.loadIcon(finalInfo, li) ? finalInfo : null;
- info.applyFrom(li.createShortcutIcon(pinnedShortcut,
- true /* badged */, fallbackIconProvider));
- li.recycle();
+ mIconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon);
+
if (pmHelper.isAppSuspended(
pinnedShortcut.getPackage(), info.user)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
}
intent = info.intent;
+ allDeepShortcuts.add(pinnedShortcut);
} else {
// Create a shortcut info in disabled mode for now.
info = c.loadSimpleWorkspaceItem();
@@ -576,7 +611,7 @@
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- if (FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
c.markDeleted("Only legacy shortcuts can have null package");
continue;
}
@@ -607,7 +642,7 @@
LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
if (widgetProvidersMap == null) {
- widgetProvidersMap = mAppWidgetManager.getAllProvidersMap();
+ widgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(context);
}
final AppWidgetProviderInfo provider = widgetProvidersMap.get(
new ComponentKey(component, c.user));
@@ -757,7 +792,7 @@
if ((numTimesPinned == null || numTimesPinned.value == 0)
&& !pendingShortcuts.contains(key)) {
// Shortcut is pinned but doesn't exist on the workspace; unpin it.
- mShortcutManager.unpinShortcut(key);
+ mBgDataModel.unpinShortcut(context, key);
}
}
@@ -797,27 +832,27 @@
private void setIgnorePackages(IconCacheUpdateHandler updateHandler) {
// Ignore packages which have a promise icon.
- HashSet<String> packagesToIgnore = new HashSet<>();
synchronized (mBgDataModel) {
for (ItemInfo info : mBgDataModel.itemsIdMap) {
if (info instanceof WorkspaceItemInfo) {
WorkspaceItemInfo si = (WorkspaceItemInfo) info;
if (si.isPromise() && si.getTargetComponent() != null) {
- packagesToIgnore.add(si.getTargetComponent().getPackageName());
+ updateHandler.addPackagesToIgnore(
+ si.user, si.getTargetComponent().getPackageName());
}
} else if (info instanceof LauncherAppWidgetInfo) {
LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
- packagesToIgnore.add(lawi.providerName.getPackageName());
+ updateHandler.addPackagesToIgnore(
+ lawi.user, lawi.providerName.getPackageName());
}
}
}
}
- updateHandler.setPackagesToIgnore(Process.myUserHandle(), packagesToIgnore);
}
private List<LauncherActivityInfo> loadAllApps() {
- final List<UserHandle> profiles = mUserManager.getUserProfiles();
+ final List<UserHandle> profiles = mUserCache.getUserProfiles();
List<LauncherActivityInfo> allActivityList = new ArrayList<>();
// Clear the list of apps
mBgAllAppsList.clear();
@@ -839,12 +874,12 @@
allActivityList.addAll(apps);
}
- if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
+ if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
// get all active sessions and add them to the all apps list
for (PackageInstaller.SessionInfo info :
- mPackageInstaller.getAllVerifiedSessions()) {
+ mSessionHelper.getAllVerifiedSessions()) {
mBgAllAppsList.addPromiseApp(mApp.getContext(),
- PackageInstallerCompat.PackageInstallInfo.fromInstallingState(info));
+ PackageInstallInfo.fromInstallingState(info));
}
}
@@ -852,18 +887,39 @@
return allActivityList;
}
- private void loadDeepShortcuts() {
+ private List<ShortcutInfo> loadDeepShortcuts() {
+ List<ShortcutInfo> allShortcuts = new ArrayList<>();
mBgDataModel.deepShortcutMap.clear();
- mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission();
+ mBgDataModel.hasShortcutHostPermission = hasShortcutsPermission(mApp.getContext());
if (mBgDataModel.hasShortcutHostPermission) {
- for (UserHandle user : mUserManager.getUserProfiles()) {
+ for (UserHandle user : mUserCache.getUserProfiles()) {
if (mUserManager.isUserUnlocked(user)) {
- List<ShortcutInfo> shortcuts =
- mShortcutManager.queryForAllShortcuts(user);
+ List<ShortcutInfo> shortcuts = new ShortcutRequest(mApp.getContext(), user)
+ .query(ShortcutRequest.ALL);
+ allShortcuts.addAll(shortcuts);
mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
}
}
}
+ return allShortcuts;
+ }
+
+ private void loadFolderNames() {
+ FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
+ mBgAllAppsList.data, mBgDataModel.folders);
+
+ synchronized (mBgDataModel) {
+ for (int i = 0; i < mBgDataModel.folders.size(); i++) {
+ FolderNameInfo[] suggestionInfos =
+ new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderInfo info = mBgDataModel.folders.valueAt(i);
+ if (info.suggestedFolderNames == null) {
+ provider.getSuggestedFolderName(mApp.getContext(), info.contents,
+ suggestionInfos);
+ info.suggestedFolderNames = new Intent().putExtra("suggest", suggestionInfos);
+ }
+ }
+ }
}
public static boolean isValidProvider(AppWidgetProviderInfo provider) {
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
index 2bd6cd4..713492b 100644
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ b/src/com/android/launcher3/model/ModelPreload.java
@@ -18,14 +18,15 @@
import android.content.Context;
import android.util.Log;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import java.util.concurrent.Executor;
-import androidx.annotation.WorkerThread;
-
/**
* Utility class to preload LauncherModel
*/
@@ -50,7 +51,7 @@
@Override
public final void run() {
mModel.startLoaderForResultsIfNotLoaded(
- new LoaderResults(mApp, mBgDataModel, mAllAppsList, 0, null));
+ new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
Log.d(TAG, "Preload completed : " + mModel.isModelLoaded());
onComplete(mModel.isModelLoaded());
}
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
new file mode 100644
index 0000000..1473124
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IntSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * Utils class for {@link com.android.launcher3.LauncherModel}.
+ */
+public class ModelUtils {
+
+ /**
+ * Filters the set of items who are directly or indirectly (via another container) on the
+ * specified screen.
+ */
+ public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
+ ArrayList<T> allWorkspaceItems,
+ ArrayList<T> currentScreenItems,
+ ArrayList<T> otherScreenItems) {
+ // Purge any null ItemInfos
+ Iterator<T> iter = allWorkspaceItems.iterator();
+ while (iter.hasNext()) {
+ ItemInfo i = iter.next();
+ if (i == null) {
+ iter.remove();
+ }
+ }
+ // Order the set of items by their containers first, this allows use to walk through the
+ // list sequentially, build up a list of containers that are in the specified screen,
+ // as well as all items in those containers.
+ IntSet itemsOnScreen = new IntSet();
+ Collections.sort(allWorkspaceItems,
+ (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
+ for (T info : allWorkspaceItems) {
+ if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (info.screenId == currentScreenId) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ otherScreenItems.add(info);
+ }
+ } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ if (itemsOnScreen.contains(info.container)) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ otherScreenItems.add(info);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
+ */
+ public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
+ ArrayList<ItemInfo> workspaceItems) {
+ final int screenCols = profile.numColumns;
+ final int screenCellCount = profile.numColumns * profile.numRows;
+ Collections.sort(workspaceItems, (lhs, rhs) -> {
+ if (lhs.container == rhs.container) {
+ // Within containers, order by their spatial position in that container
+ switch (lhs.container) {
+ case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
+ int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
+ + lhs.cellX);
+ int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
+ + rhs.cellX);
+ return Integer.compare(lr, rr);
+ }
+ case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
+ // We currently use the screen id as the rank
+ return Integer.compare(lhs.screenId, rhs.screenId);
+ }
+ default:
+ if (FeatureFlags.IS_STUDIO_BUILD) {
+ throw new RuntimeException(
+ "Unexpected container type when sorting workspace items.");
+ }
+ return 0;
+ }
+ } else {
+ // Between containers, order by hotseat, desktop
+ return Integer.compare(lhs.container, rhs.container);
+ }
+ });
+ }
+}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index bdf3a69..27fa580 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -41,7 +41,6 @@
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -116,8 +115,9 @@
ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// check all the data is consistent
- if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
- modelItem instanceof WorkspaceItemInfo && item instanceof WorkspaceItemInfo) {
+ if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_STUDIO_BUILD
+ && modelItem instanceof WorkspaceItemInfo
+ && item instanceof WorkspaceItemInfo) {
if (modelItem.title.toString().equals(item.title.toString()) &&
modelItem.getIntent().filterEquals(item.getIntent()) &&
modelItem.id == item.id &&
@@ -321,7 +321,7 @@
*/
public void prepareToUndoDelete() {
if (!mPreparingToUndo) {
- if (!mDeleteRunnables.isEmpty() && FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (!mDeleteRunnables.isEmpty() && FeatureFlags.IS_STUDIO_BUILD) {
throw new IllegalStateException("There are still uncommitted delete operations!");
}
mDeleteRunnables.clear();
@@ -350,12 +350,15 @@
mDeleteRunnables.clear();
}
- public void abortDelete(int pageToBindFirst) {
+ /**
+ * Aborts a previous delete operation pending commit
+ */
+ public void abortDelete() {
mPreparingToUndo = false;
mDeleteRunnables.clear();
// We do a full reload here instead of just a rebind because Folders change their internal
// state when dragging an item out, which clobbers the rebind unless we load from the DB.
- mModel.forceReload(pageToBindFirst);
+ mModel.forceReload();
}
private class UpdateItemRunnable extends UpdateItemBaseRunnable {
@@ -472,7 +475,7 @@
}
void verifyModel() {
- if (!mVerifyChanges || mModel.getCallback() == null) {
+ if (!mVerifyChanges || !mModel.hasCallbacks()) {
return;
}
@@ -488,11 +491,9 @@
// Bound model has not changed during the job
return;
}
+
// Bound model was changed between submitting the job and executing the job
- Callbacks callbacks = mModel.getCallback();
- if (callbacks != null) {
- callbacks.rebindModel();
- }
+ mModel.rebindCallbacks();
});
}
}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 802cbc7..2832150 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -19,16 +19,14 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.util.InstantAppResolver;
import java.util.HashSet;
@@ -46,7 +44,7 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
- if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
+ if (mInstallInfo.state == PackageInstallInfo.STATUS_INSTALLED) {
try {
// For instant apps we do not get package-add. Use setting events to update
// any pinned icons.
@@ -79,7 +77,7 @@
if (si.hasPromiseIconUi() && (cn != null)
&& mInstallInfo.packageName.equals(cn.getPackageName())) {
si.setInstallProgress(mInstallInfo.progress);
- if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+ if (mInstallInfo.state == PackageInstallInfo.STATUS_FAILED) {
// Mark this info as broken.
si.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
}
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index 3ef48cd..2fc064c 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -19,6 +19,8 @@
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherSettings;
+import java.util.Objects;
+
/**
* Represents a {@link Package} in the widget tray section.
*/
@@ -48,4 +50,17 @@
public PackageItemInfo clone() {
return new PackageItemInfo(this);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PackageItemInfo that = (PackageItemInfo) o;
+ return Objects.equals(packageName, that.packageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packageName);
+ }
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d6ebaaf..48c56e9 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -16,13 +16,16 @@
package com.android.launcher3.model;
import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import com.android.launcher3.InstallShortcutReceiver;
@@ -33,14 +36,12 @@
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntSparseArrayMap;
@@ -55,9 +56,6 @@
import java.util.HashSet;
import java.util.List;
-import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
-import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON;
-
/**
* Handles updates due to changes in package manager (app installed/updated/removed)
* or when a user availability changes.
@@ -107,7 +105,7 @@
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
- if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
+ if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
appsList.removePackage(packages[i], mUser);
}
appsList.addPackage(context, packages[i], mUser);
@@ -157,7 +155,7 @@
appsList.updateDisabledFlags(matcher, flagOp);
break;
case OP_USER_AVAILABILITY_CHANGE:
- flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
+ flagOp = context.getSystemService(UserManager.class).isQuietModeEnabled(mUser)
? FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER)
: FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER);
// We want to update all packages for this user.
@@ -191,7 +189,7 @@
BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
li.recycle();
if (iconInfo != null) {
- si.applyFrom(iconInfo);
+ si.bitmap = iconInfo;
infoUpdated = true;
}
}
@@ -210,10 +208,11 @@
if (si.isPromise() && isNewApkAvailable) {
boolean isTargetValid = true;
if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- List<ShortcutInfo> shortcut = DeepShortcutManager
- .getInstance(context).queryForPinnedShortcuts(
- cn.getPackageName(),
- Arrays.asList(si.getDeepShortcutId()), mUser);
+ List<ShortcutInfo> shortcut =
+ new ShortcutRequest(context, mUser)
+ .forPackage(cn.getPackageName(),
+ si.getDeepShortcutId())
+ .query(ShortcutRequest.PINNED);
if (shortcut.isEmpty()) {
isTargetValid = false;
} else {
@@ -221,8 +220,8 @@
infoUpdated = true;
}
} else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
- isTargetValid = LauncherAppsCompat.getInstance(context)
- .isActivityEnabledForProfile(cn, mUser);
+ isTargetValid = context.getSystemService(LauncherApps.class)
+ .isActivityEnabled(cn, mUser);
}
if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
if (updateWorkspaceItemIntent(context, si, packageName)) {
@@ -305,9 +304,9 @@
// removedPackages is a super-set of removedComponents
} else if (mOp == OP_UPDATE) {
// Mark disabled packages in the broadcast to be removed
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
for (int i=0; i<N; i++) {
- if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
+ if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
removedPackages.add(packages[i]);
}
}
diff --git a/src/com/android/launcher3/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java
new file mode 100644
index 0000000..e48b8c1
--- /dev/null
+++ b/src/com/android/launcher3/model/PagedViewOrientedState.java
@@ -0,0 +1,106 @@
+/*
+ *
+ * * Copyright (C) 2020 The Android Open Source Project
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ */
+
+package com.android.launcher3.model;
+
+import android.view.Surface;
+
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.touch.LandscapePagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.SeascapePagedViewHandler;
+
+/**
+ * Container to hold orientation/rotation related information for Launcher.
+ * This is not meant to be an abstraction layer for applying different functionality between
+ * the different orientation/rotations. For that see {@link PagedOrientationHandler}
+ *
+ * This class has initial default state assuming the device and foreground app have
+ * no ({@link Surface.ROTATION_0} rotation.
+ *
+ * Currently this class resides in {@link com.android.launcher3.PagedView}, but there's a ticket
+ * to disassociate it from Launcher since it's needed before Launcher is instantiated
+ * See TODO(b/150300347)
+ */
+public final class PagedViewOrientedState {
+
+ private PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
+
+ private int mTouchRotation = Surface.ROTATION_0;
+ private int mDisplayRotation = Surface.ROTATION_0;
+ /**
+ * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
+ * launcher orientations.
+ */
+ private boolean mDisableMultipleOrientations;
+
+ /**
+ * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
+ * @param touchRotation The rotation the nav bar region that is touched is in
+ * @param displayRotation Rotation of the display/device
+ */
+ public void update(int touchRotation, int displayRotation) {
+ if (mDisableMultipleOrientations) {
+ return;
+ }
+
+ mDisplayRotation = displayRotation;
+ mTouchRotation = touchRotation;
+ if (mTouchRotation == Surface.ROTATION_90) {
+ mOrientationHandler = new LandscapePagedViewHandler();
+ } else if (mTouchRotation == Surface.ROTATION_270) {
+ mOrientationHandler = new SeascapePagedViewHandler();
+ } else {
+ mOrientationHandler = new PortraitPagedViewHandler();
+ }
+ }
+
+ public boolean areMultipleLayoutOrientationsDisabled() {
+ return mDisableMultipleOrientations;
+ }
+
+ /**
+ * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
+ */
+ public void disableMultipleOrientations(boolean disable) {
+ mDisableMultipleOrientations = disable;
+ if (disable) {
+ mOrientationHandler = new PortraitPagedViewHandler();
+ }
+ }
+
+ public int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
+ /**
+ * Gets the difference between the rotation of the device/display and which region the
+ * user is currently interacting with in factors of 90 degree clockwise rotations.
+ * Ex. Display is in portrait -> 0, user touches landscape region -> 1, this
+ * method would return 3 because it takes 3 clockwise 90 degree rotations from normal to
+ * landscape (portrait -> seascape -> reverse portrait -> landscape)
+ */
+ public int getTouchDisplayDelta() {
+ return RotationHelper.deltaRotation(mTouchRotation, mDisplayRotation);
+ }
+
+ public PagedOrientationHandler getOrientationHandler() {
+ return mOrientationHandler;
+ }
+}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index 3aedae6..eb3cb52 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -19,11 +19,11 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.os.UserHandle;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
@@ -53,7 +53,7 @@
@Override
public void onReceive(Context context, Intent intent) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
UserHandle user = entry.getKey();
@@ -62,7 +62,7 @@
final ArrayList<String> packagesUnavailable = new ArrayList<>();
for (String pkg : new HashSet<>(entry.getValue())) {
- if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
+ if (!launcherApps.isPackageEnabled(pkg, user)) {
if (pmHelper.isAppOnSdcard(pkg, user)) {
packagesUnavailable.add(pkg);
} else {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 6c358b1..3f79ad0 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -23,9 +23,8 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiHashMap;
@@ -54,8 +53,6 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
final Context context = app.getContext();
- DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
-
// Find WorkspaceItemInfo's that have changed on the workspace.
HashSet<ShortcutKey> removedKeys = new HashSet<>();
MultiHashMap<ShortcutKey, WorkspaceItemInfo> keyToShortcutInfo = new MultiHashMap<>();
@@ -74,8 +71,9 @@
final ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
if (!keyToShortcutInfo.isEmpty()) {
// Update the workspace to reflect the changes to updated shortcuts residing on it.
- List<ShortcutInfo> shortcuts = deepShortcutManager.queryForFullDetails(
- mPackageName, new ArrayList<>(allIds), mUser);
+ List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
+ .forPackage(mPackageName, new ArrayList<>(allIds))
+ .query(ShortcutRequest.ALL);
for (ShortcutInfo fullDetails : shortcuts) {
ShortcutKey key = ShortcutKey.fromInfo(fullDetails);
List<WorkspaceItemInfo> workspaceItemInfos = keyToShortcutInfo.remove(key);
@@ -90,12 +88,7 @@
}
for (final WorkspaceItemInfo workspaceItemInfo : workspaceItemInfos) {
workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
- // If the shortcut is pinned but no longer has an icon in the system,
- // keep the current icon instead of reverting to the default icon.
- LauncherIcons li = LauncherIcons.obtain(context);
- workspaceItemInfo.applyFrom(li.createShortcutIcon(fullDetails, true,
- () -> workspaceItemInfo));
- li.recycle();
+ app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
updatedWorkspaceItemInfos.add(workspaceItemInfo);
}
}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 4b773d7..a3adc82 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -20,15 +20,15 @@
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
+import android.os.UserManager;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -51,13 +51,12 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
Context context = app.getContext();
- boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser);
- DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
+ boolean isUserUnlocked = context.getSystemService(UserManager.class).isUserUnlocked(mUser);
HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
if (isUserUnlocked) {
- DeepShortcutManager.QueryResult shortcuts =
- deepShortcutManager.queryForPinnedShortcuts(null, mUser);
+ QueryResult shortcuts = new ShortcutRequest(context, mUser)
+ .query(ShortcutRequest.PINNED);
if (shortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : shortcuts) {
pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
@@ -89,11 +88,7 @@
}
si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
si.updateFromDeepShortcutInfo(shortcut, context);
- // If the shortcut is pinned but no longer has an icon in the system,
- // keep the current icon instead of reverting to the default icon.
- LauncherIcons li = LauncherIcons.obtain(context);
- si.applyFrom(li.createShortcutIcon(shortcut, true, () -> si));
- li.recycle();
+ app.getIconCache().getShortcutIcon(si, shortcut);
} else {
si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
}
@@ -115,7 +110,8 @@
if (isUserUnlocked) {
dataModel.updateDeepShortcutCounts(
- null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
+ null, mUser,
+ new ShortcutRequest(context, mUser).query(ShortcutRequest.ALL));
}
bindDeepShortcuts(dataModel);
}
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index e38529b..37c089e 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -2,27 +2,20 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.os.Process;
-import android.os.UserHandle;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ComponentKey;
-import java.text.Collator;
-
/**
* An wrapper over various items displayed in a widget picker,
* {@link LauncherAppWidgetProviderInfo} & {@link ActivityInfo}. This provides easier access to
* common attributes like spanX and spanY.
*/
-public class WidgetItem extends ComponentKey implements Comparable<WidgetItem> {
-
- private static UserHandle sMyUserHandle;
- private static Collator sCollator;
+public class WidgetItem extends ComponentKey {
public final LauncherAppWidgetProviderInfo widgetInfo;
public final ShortcutConfigActivityInfo activityInfo;
@@ -50,34 +43,4 @@
activityInfo = info;
spanX = spanY = 1;
}
-
- @Override
- public int compareTo(WidgetItem another) {
- if (sMyUserHandle == null) {
- // Delay these object creation until required.
- sMyUserHandle = Process.myUserHandle();
- sCollator = Collator.getInstance();
- }
-
- // Independent of how the labels compare, if only one of the two widget info belongs to
- // work profile, put that one in the back.
- boolean thisWorkProfile = !sMyUserHandle.equals(user);
- boolean otherWorkProfile = !sMyUserHandle.equals(another.user);
- if (thisWorkProfile ^ otherWorkProfile) {
- return thisWorkProfile ? 1 : -1;
- }
-
- int labelCompare = sCollator.compare(label, another.label);
- if (labelCompare != 0) {
- return labelCompare;
- }
-
- // If the label is same, put the smaller widget before the larger widget. If the area is
- // also same, put the widget with smaller height before.
- int thisArea = spanX * spanY;
- int otherArea = another.spanX * another.spanY;
- return thisArea == otherArea
- ? Integer.compare(spanY, another.spanY)
- : Integer.compare(thisArea, otherArea);
- }
}
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index c7de5b0..fd3d41a 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -80,17 +80,28 @@
int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
mIconLayoutParams = new LayoutParams(iconSize, iconSize);
mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
- // Compute margin start for each icon such that the icons between the first one
- // and the ellipsis are evenly spaced out.
+ setWidth((int) res.getDimension(R.dimen.bg_popup_item_width));
+ mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
+ }
+
+
+ /**
+ * Compute margin start for each icon such that the icons between the first one and the ellipsis
+ * are evenly spaced out.
+ */
+ public void setWidth(int width) {
+ if (getLayoutParams() != null) {
+ getLayoutParams().width = width;
+ }
+ Resources res = getResources();
+ int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
+
int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
+ res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
- int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
- int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
+ int availableIconRowSpace = width - paddingEnd - ellipsisSpace
- iconSize * MAX_FOOTER_NOTIFICATIONS;
mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
-
- mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
}
@Override
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 021fb30..0320aa3 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -86,6 +86,13 @@
}
}
+ /**
+ * Sets width for notification footer and spaces out items evenly
+ */
+ public void setFooterWidth(int footerWidth) {
+ mFooter.setWidth(footerWidth);
+ }
+
public void removeFooter() {
if (mContainer.indexOfChild(mFooter) >= 0) {
mContainer.removeView(mFooter);
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 10378ee..059ad18 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,6 +16,7 @@
package com.android.launcher3.notification;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
@@ -32,9 +33,10 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
-import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.SecureSettingsObserver;
@@ -44,6 +46,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* A {@link NotificationListenerService} that sends updates to its
@@ -59,16 +62,17 @@
private static final int MSG_NOTIFICATION_POSTED = 1;
private static final int MSG_NOTIFICATION_REMOVED = 2;
private static final int MSG_NOTIFICATION_FULL_REFRESH = 3;
+ private static final int MSG_CANCEL_NOTIFICATION = 4;
+ private static final int MSG_RANKING_UPDATE = 5;
private static NotificationListener sNotificationListenerInstance = null;
private static NotificationsChangedListener sNotificationsChangedListener;
- private static StatusBarNotificationsChangedListener sStatusBarNotificationsChangedListener;
private static boolean sIsConnected;
- private static boolean sIsCreated;
private final Handler mWorkerHandler;
private final Handler mUiHandler;
private final Ranking mTempRanking = new Ranking();
+
/** Maps groupKey's to the corresponding group of notifications. */
private final Map<String, NotificationGroup> mNotificationGroupMap = new HashMap<>();
/** Maps keys to their corresponding current group key */
@@ -79,85 +83,12 @@
private SecureSettingsObserver mNotificationDotsObserver;
- private final Handler.Callback mWorkerCallback = new Handler.Callback() {
- @Override
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case MSG_NOTIFICATION_POSTED:
- mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
- break;
- case MSG_NOTIFICATION_REMOVED:
- mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
- break;
- case MSG_NOTIFICATION_FULL_REFRESH:
- List<StatusBarNotification> activeNotifications;
- if (sIsConnected) {
- try {
- activeNotifications = filterNotifications(getActiveNotifications());
- } catch (SecurityException ex) {
- Log.e(TAG, "SecurityException: failed to fetch notifications");
- activeNotifications = new ArrayList<StatusBarNotification>();
-
- }
- } else {
- activeNotifications = new ArrayList<StatusBarNotification>();
- }
-
- mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
- break;
- }
- return true;
- }
- };
-
- private final Handler.Callback mUiCallback = new Handler.Callback() {
- @Override
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case MSG_NOTIFICATION_POSTED:
- if (sNotificationsChangedListener != null) {
- NotificationPostedMsg msg = (NotificationPostedMsg) message.obj;
- sNotificationsChangedListener.onNotificationPosted(msg.packageUserKey,
- msg.notificationKey, msg.shouldBeFilteredOut);
- }
- break;
- case MSG_NOTIFICATION_REMOVED:
- if (sNotificationsChangedListener != null) {
- Pair<PackageUserKey, NotificationKeyData> pair
- = (Pair<PackageUserKey, NotificationKeyData>) message.obj;
- sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second);
- }
- break;
- case MSG_NOTIFICATION_FULL_REFRESH:
- if (sNotificationsChangedListener != null) {
- sNotificationsChangedListener.onNotificationFullRefresh(
- (List<StatusBarNotification>) message.obj);
- }
- break;
- }
- return true;
- }
- };
-
public NotificationListener() {
- super();
- mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback);
- mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
+ mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
+ mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
sNotificationListenerInstance = this;
}
- @Override
- public void onCreate() {
- super.onCreate();
- sIsCreated = true;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- sIsCreated = false;
- }
-
public static @Nullable NotificationListener getInstanceIfConnected() {
return sIsConnected ? sNotificationListenerInstance : null;
}
@@ -168,25 +99,107 @@
NotificationListener notificationListener = getInstanceIfConnected();
if (notificationListener != null) {
notificationListener.onNotificationFullRefresh();
- } else if (!sIsCreated && sNotificationsChangedListener != null) {
+ } else {
// User turned off dots globally, so we unbound this service;
// tell the listener that there are no notifications to remove dots.
- sNotificationsChangedListener.onNotificationFullRefresh(
- Collections.<StatusBarNotification>emptyList());
+ MODEL_EXECUTOR.submit(() -> MAIN_EXECUTOR.submit(() ->
+ listener.onNotificationFullRefresh(Collections.emptyList())));
}
}
- public static void setStatusBarNotificationsChangedListener
- (StatusBarNotificationsChangedListener listener) {
- sStatusBarNotificationsChangedListener = listener;
- }
-
public static void removeNotificationsChangedListener() {
sNotificationsChangedListener = null;
}
- public static void removeStatusBarNotificationsChangedListener() {
- sStatusBarNotificationsChangedListener = null;
+ private boolean handleWorkerMessage(Message message) {
+ switch (message.what) {
+ case MSG_NOTIFICATION_POSTED: {
+ StatusBarNotification sbn = (StatusBarNotification) message.obj;
+ mUiHandler.obtainMessage(notificationIsValidForUI(sbn)
+ ? MSG_NOTIFICATION_POSTED : MSG_NOTIFICATION_REMOVED,
+ toKeyPair(sbn)).sendToTarget();
+ return true;
+ }
+ case MSG_NOTIFICATION_REMOVED: {
+ StatusBarNotification sbn = (StatusBarNotification) message.obj;
+ mUiHandler.obtainMessage(MSG_NOTIFICATION_REMOVED,
+ toKeyPair(sbn)).sendToTarget();
+
+ NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
+ String key = sbn.getKey();
+ if (notificationGroup != null) {
+ notificationGroup.removeChildKey(key);
+ if (notificationGroup.isEmpty()) {
+ if (key.equals(mLastKeyDismissedByLauncher)) {
+ // Only cancel the group notification if launcher dismissed the
+ // last child.
+ cancelNotification(notificationGroup.getGroupSummaryKey());
+ }
+ mNotificationGroupMap.remove(sbn.getGroupKey());
+ }
+ }
+ if (key.equals(mLastKeyDismissedByLauncher)) {
+ mLastKeyDismissedByLauncher = null;
+ }
+ return true;
+ }
+ case MSG_NOTIFICATION_FULL_REFRESH:
+ List<StatusBarNotification> activeNotifications = null;
+ if (sIsConnected) {
+ try {
+ activeNotifications = Arrays.stream(getActiveNotifications())
+ .filter(this::notificationIsValidForUI)
+ .collect(Collectors.toList());
+ } catch (SecurityException ex) {
+ Log.e(TAG, "SecurityException: failed to fetch notifications");
+ activeNotifications = new ArrayList<>();
+ }
+ } else {
+ activeNotifications = new ArrayList<>();
+ }
+
+ mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
+ return true;
+ case MSG_CANCEL_NOTIFICATION: {
+ mLastKeyDismissedByLauncher = (String) message.obj;
+ cancelNotification(mLastKeyDismissedByLauncher);
+ return true;
+ }
+ case MSG_RANKING_UPDATE: {
+ String[] keys = ((RankingMap) message.obj).getOrderedKeys();
+ for (StatusBarNotification sbn : getActiveNotifications(keys)) {
+ updateGroupKeyIfNecessary(sbn);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean handleUiMessage(Message message) {
+ switch (message.what) {
+ case MSG_NOTIFICATION_POSTED:
+ if (sNotificationsChangedListener != null) {
+ Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj;
+ sNotificationsChangedListener.onNotificationPosted(
+ msg.first, msg.second);
+ }
+ break;
+ case MSG_NOTIFICATION_REMOVED:
+ if (sNotificationsChangedListener != null) {
+ Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj;
+ sNotificationsChangedListener.onNotificationRemoved(
+ msg.first, msg.second);
+ }
+ break;
+ case MSG_NOTIFICATION_FULL_REFRESH:
+ if (sNotificationsChangedListener != null) {
+ sNotificationsChangedListener.onNotificationFullRefresh(
+ (List<StatusBarNotification>) message.obj);
+ }
+ break;
+ }
+ return true;
}
@Override
@@ -217,84 +230,37 @@
super.onListenerDisconnected();
sIsConnected = false;
mNotificationDotsObserver.unregister();
+ onNotificationFullRefresh();
}
@Override
public void onNotificationPosted(final StatusBarNotification sbn) {
- super.onNotificationPosted(sbn);
- if (sbn == null) {
- // There is a bug in platform where we can get a null notification; just ignore it.
- return;
- }
- mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, new NotificationPostedMsg(sbn))
- .sendToTarget();
- if (sStatusBarNotificationsChangedListener != null) {
- sStatusBarNotificationsChangedListener.onNotificationPosted(sbn);
- }
- }
-
- /**
- * An object containing data to send to MSG_NOTIFICATION_POSTED targets.
- */
- private class NotificationPostedMsg {
- final PackageUserKey packageUserKey;
- final NotificationKeyData notificationKey;
- final boolean shouldBeFilteredOut;
-
- NotificationPostedMsg(StatusBarNotification sbn) {
- packageUserKey = PackageUserKey.fromNotification(sbn);
- notificationKey = NotificationKeyData.fromNotification(sbn);
- shouldBeFilteredOut = shouldBeFilteredOut(sbn);
+ if (sbn != null) {
+ mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, sbn).sendToTarget();
}
}
@Override
public void onNotificationRemoved(final StatusBarNotification sbn) {
- super.onNotificationRemoved(sbn);
- if (sbn == null) {
- // There is a bug in platform where we can get a null notification; just ignore it.
- return;
+ if (sbn != null) {
+ mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, sbn).sendToTarget();
}
- Pair<PackageUserKey, NotificationKeyData> packageUserKeyAndNotificationKey
- = new Pair<>(PackageUserKey.fromNotification(sbn),
- NotificationKeyData.fromNotification(sbn));
- mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey)
- .sendToTarget();
- if (sStatusBarNotificationsChangedListener != null) {
- sStatusBarNotificationsChangedListener.onNotificationRemoved(sbn);
- }
-
- NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
- String key = sbn.getKey();
- if (notificationGroup != null) {
- notificationGroup.removeChildKey(key);
- if (notificationGroup.isEmpty()) {
- if (key.equals(mLastKeyDismissedByLauncher)) {
- // Only cancel the group notification if launcher dismissed the last child.
- cancelNotification(notificationGroup.getGroupSummaryKey());
- }
- mNotificationGroupMap.remove(sbn.getGroupKey());
- }
- }
- if (key.equals(mLastKeyDismissedByLauncher)) {
- mLastKeyDismissedByLauncher = null;
- }
- }
-
- public void cancelNotificationFromLauncher(String key) {
- mLastKeyDismissedByLauncher = key;
- cancelNotification(key);
}
@Override
public void onNotificationRankingUpdate(RankingMap rankingMap) {
- super.onNotificationRankingUpdate(rankingMap);
- String[] keys = rankingMap.getOrderedKeys();
- for (StatusBarNotification sbn : getActiveNotifications(keys)) {
- updateGroupKeyIfNecessary(sbn);
- }
+ mWorkerHandler.obtainMessage(MSG_RANKING_UPDATE, rankingMap).sendToTarget();
}
+ /**
+ * Cancels a notification
+ */
+ @AnyThread
+ public void cancelNotificationFromLauncher(String key) {
+ mWorkerHandler.obtainMessage(MSG_CANCEL_NOTIFICATION, key).sendToTarget();
+ }
+
+ @WorkerThread
private void updateGroupKeyIfNecessary(StatusBarNotification sbn) {
String childKey = sbn.getKey();
String oldGroupKey = mNotificationGroupKeyMap.get(childKey);
@@ -328,53 +294,33 @@
}
}
- /** This makes a potentially expensive binder call and should be run on a background thread. */
+ /**
+ * This makes a potentially expensive binder call and should be run on a background thread.
+ */
+ @WorkerThread
public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) {
- StatusBarNotification[] notifications = NotificationListener.this
- .getActiveNotifications(NotificationKeyData.extractKeysOnly(keys)
- .toArray(new String[keys.size()]));
- return notifications == null
- ? Collections.<StatusBarNotification>emptyList() : Arrays.asList(notifications);
+ StatusBarNotification[] notifications = getActiveNotifications(
+ keys.stream().map(n -> n.notificationKey).toArray(String[]::new));
+ return notifications == null ? Collections.emptyList() : Arrays.asList(notifications);
}
/**
- * Filter out notifications that don't have an intent
- * or are headers for grouped notifications.
- *
- * @see #shouldBeFilteredOut(StatusBarNotification)
+ * Returns true for notifications that have an intent and are not headers for grouped
+ * notifications and should be shown in the notification popup.
*/
- private List<StatusBarNotification> filterNotifications(
- StatusBarNotification[] notifications) {
- if (notifications == null) return null;
- IntSet removedNotifications = new IntSet();
- for (int i = 0; i < notifications.length; i++) {
- if (shouldBeFilteredOut(notifications[i])) {
- removedNotifications.add(i);
- }
- }
- List<StatusBarNotification> filteredNotifications = new ArrayList<>(
- notifications.length - removedNotifications.size());
- for (int i = 0; i < notifications.length; i++) {
- if (!removedNotifications.contains(i)) {
- filteredNotifications.add(notifications[i]);
- }
- }
- return filteredNotifications;
- }
-
- private boolean shouldBeFilteredOut(StatusBarNotification sbn) {
+ @WorkerThread
+ private boolean notificationIsValidForUI(StatusBarNotification sbn) {
Notification notification = sbn.getNotification();
-
updateGroupKeyIfNecessary(sbn);
getCurrentRanking().getRanking(sbn.getKey(), mTempRanking);
if (!mTempRanking.canShowBadge()) {
- return true;
+ return false;
}
if (mTempRanking.getChannel().getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
// Special filtering for the default, legacy "Miscellaneous" channel.
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
- return true;
+ return false;
}
}
@@ -382,19 +328,19 @@
CharSequence text = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
boolean missingTitleAndText = TextUtils.isEmpty(title) && TextUtils.isEmpty(text);
boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
- return (isGroupHeader || missingTitleAndText);
+ return !isGroupHeader && !missingTitleAndText;
+ }
+
+ private static Pair<PackageUserKey, NotificationKeyData> toKeyPair(StatusBarNotification sbn) {
+ return Pair.create(PackageUserKey.fromNotification(sbn),
+ NotificationKeyData.fromNotification(sbn));
}
public interface NotificationsChangedListener {
void onNotificationPosted(PackageUserKey postedPackageUserKey,
- NotificationKeyData notificationKey, boolean shouldBeFilteredOut);
+ NotificationKeyData notificationKey);
void onNotificationRemoved(PackageUserKey removedPackageUserKey,
NotificationKeyData notificationKey);
void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications);
}
-
- public interface StatusBarNotificationsChangedListener {
- void onNotificationPosted(StatusBarNotification sbn);
- void onNotificationRemoved(StatusBarNotification sbn);
- }
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b67adbb..b193ffd 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -51,7 +51,7 @@
@TargetApi(Build.VERSION_CODES.N)
public class NotificationMainView extends FrameLayout implements SingleAxisSwipeDetector.Listener {
- private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
+ private static final FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
new FloatProperty<NotificationMainView>("contentTranslation") {
@Override
public void setValue(NotificationMainView view, float v) {
@@ -176,7 +176,7 @@
// SingleAxisSwipeDetector.Listener's
@Override
- public void onDragStart(boolean start) { }
+ public void onDragStart(boolean start, float startDisplacement) { }
@Override
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 654e593..0f2ca72 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -123,7 +123,7 @@
mLauncher = Launcher.getLauncher(context);
mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
- boolean darkText = WallpaperColorInfo.getInstance(context).supportsDarkText();
+ boolean darkText = WallpaperColorInfo.INSTANCE.get(context).supportsDarkText();
mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
new file mode 100644
index 0000000..976d7ba
--- /dev/null
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2014 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.pm;
+
+import static com.android.launcher3.Utilities.getPrefs;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.SessionCommitReceiver;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Utility class to tracking install sessions
+ */
+public class InstallSessionHelper {
+
+ // Set<String> of session ids of promise icons that have been added to the home screen
+ // as FLAG_PROMISE_NEW_INSTALLS.
+ protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
+ public static final String KEY_INSTALL_SESSION_CREATED_TIMESTAMP =
+ "key_install_session_created_timestamp";
+
+ private static final boolean DEBUG = false;
+
+ public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE =
+ new MainThreadInitializedObject<>(InstallSessionHelper::new);
+
+ private final LauncherApps mLauncherApps;
+ private final Context mAppContext;
+ private final IntSet mPromiseIconIds;
+
+ private final PackageInstaller mInstaller;
+ private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>();
+
+ public InstallSessionHelper(Context context) {
+ mInstaller = context.getPackageManager().getPackageInstaller();
+ mAppContext = context.getApplicationContext();
+ mLauncherApps = context.getSystemService(LauncherApps.class);
+
+ mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
+ getPrefs(context).getString(PROMISE_ICON_IDS, "")));
+
+ cleanUpPromiseIconIds();
+ }
+
+ public static UserHandle getUserHandle(SessionInfo info) {
+ return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
+ }
+
+ protected void cleanUpPromiseIconIds() {
+ IntArray existingIds = new IntArray();
+ for (SessionInfo info : getActiveSessions().values()) {
+ existingIds.add(info.getSessionId());
+ }
+ IntArray idsToRemove = new IntArray();
+
+ for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) {
+ if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) {
+ idsToRemove.add(mPromiseIconIds.getArray().get(i));
+ }
+ }
+ for (int i = idsToRemove.size() - 1; i >= 0; --i) {
+ mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
+ }
+ }
+
+ public HashMap<PackageUserKey, SessionInfo> getActiveSessions() {
+ HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
+ for (SessionInfo info : getAllVerifiedSessions()) {
+ activePackages.put(new PackageUserKey(info.getAppPackageName(), getUserHandle(info)),
+ info);
+ }
+ return activePackages;
+ }
+
+ public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
+ for (SessionInfo info : getAllVerifiedSessions()) {
+ boolean match = pkg.equals(info.getAppPackageName());
+ if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) {
+ match = false;
+ }
+ if (match) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ private void updatePromiseIconPrefs() {
+ getPrefs(mAppContext).edit()
+ .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString())
+ .apply();
+ }
+
+ SessionInfo getVerifiedSessionInfo(int sessionId) {
+ return verify(mInstaller.getSessionInfo(sessionId));
+ }
+
+ private SessionInfo verify(SessionInfo sessionInfo) {
+ if (sessionInfo == null
+ || sessionInfo.getInstallerPackageName() == null
+ || TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
+ return null;
+ }
+ String pkg = sessionInfo.getInstallerPackageName();
+ synchronized (mSessionVerifiedMap) {
+ if (!mSessionVerifiedMap.containsKey(pkg)) {
+ boolean hasSystemFlag = new PackageManagerHelper(mAppContext).getApplicationInfo(
+ pkg, getUserHandle(sessionInfo), ApplicationInfo.FLAG_SYSTEM) != null;
+ mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
+ }
+ }
+ return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
+ }
+
+ public List<SessionInfo> getAllVerifiedSessions() {
+ List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q
+ ? mLauncherApps.getAllPackageInstallerSessions()
+ : mInstaller.getAllSessions());
+ Iterator<SessionInfo> it = list.iterator();
+ while (it.hasNext()) {
+ if (verify(it.next()) == null) {
+ it.remove();
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Attempt to restore workspace layout if the session is triggered due to device restore and it
+ * has a newer timestamp.
+ */
+ public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) {
+ if (!Utilities.ATLEAST_OREO || !FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
+ return false;
+ }
+ if (isRestore(info) && hasNewerTimestamp(mAppContext, info)) {
+ LauncherSettings.Settings.call(mAppContext.getContentResolver(),
+ LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE);
+ return true;
+ }
+ return false;
+ }
+
+ @RequiresApi(26)
+ private static boolean isRestore(@NonNull final SessionInfo info) {
+ return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+ }
+
+ private static boolean hasNewerTimestamp(
+ @NonNull final Context context, @NonNull final SessionInfo info) {
+ return PackageManagerHelper.getSessionCreatedTimeInMillis(info)
+ > Utilities.getDevicePrefs(context).getLong(
+ KEY_INSTALL_SESSION_CREATED_TIMESTAMP, 0);
+ }
+
+ public boolean promiseIconAddedForId(int sessionId) {
+ return mPromiseIconIds.contains(sessionId);
+ }
+
+ public void removePromiseIconId(int sessionId) {
+ if (mPromiseIconIds.contains(sessionId)) {
+ mPromiseIconIds.getArray().removeValue(sessionId);
+ updatePromiseIconPrefs();
+ }
+ }
+
+ /**
+ * Add a promise app icon to the workspace iff:
+ * - The settings for it are enabled
+ * - The user installed the app
+ * - There is an app icon and label (For apps with no launching activity, no icon is provided).
+ * - The app is not already installed
+ * - A promise icon for the session has not already been created
+ */
+ void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
+ if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
+ && SessionCommitReceiver.isEnabled(mAppContext)
+ && verify(sessionInfo) != null
+ && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
+ && sessionInfo.getAppIcon() != null
+ && !TextUtils.isEmpty(sessionInfo.getAppLabel())
+ && !mPromiseIconIds.contains(sessionInfo.getSessionId())
+ && new PackageManagerHelper(mAppContext).getApplicationInfo(
+ sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
+ SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
+ mPromiseIconIds.add(sessionInfo.getSessionId());
+ updatePromiseIconPrefs();
+ }
+ }
+
+ public InstallSessionTracker registerInstallTracker(
+ InstallSessionTracker.Callback callback, LooperExecutor executor) {
+ InstallSessionTracker tracker = new InstallSessionTracker(this, callback);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ mInstaller.registerSessionCallback(tracker, executor.getHandler());
+ } else {
+ mLauncherApps.registerPackageInstallerSessionCallback(executor, tracker);
+ }
+ return tracker;
+ }
+
+ void unregister(InstallSessionTracker tracker) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ mInstaller.unregisterSessionCallback(tracker);
+ } else {
+ mLauncherApps.unregisterPackageInstallerSessionCallback(tracker);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
new file mode 100644
index 0000000..eb3ca73
--- /dev/null
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.pm;
+
+import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
+import static com.android.launcher3.pm.PackageInstallInfo.STATUS_FAILED;
+import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED;
+
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.launcher3.util.PackageUserKey;
+
+public class InstallSessionTracker extends PackageInstaller.SessionCallback {
+
+ // Lazily initialized
+ private SparseArray<PackageUserKey> mActiveSessions = null;
+
+ private final InstallSessionHelper mInstallerCompat;
+ private final Callback mCallback;
+
+ InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback) {
+ mInstallerCompat = installerCompat;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onCreated(int sessionId) {
+ SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+ if (sessionInfo != null) {
+ mCallback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
+ }
+
+ mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+ }
+
+ @Override
+ public void onFinished(int sessionId, boolean success) {
+ // For a finished session, we can't get the session info. So use the
+ // packageName from our local cache.
+ SparseArray<PackageUserKey> activeSessions = getActiveSessionMap();
+ PackageUserKey key = activeSessions.get(sessionId);
+ activeSessions.remove(sessionId);
+
+ if (key != null && key.mPackageName != null) {
+ String packageName = key.mPackageName;
+ PackageInstallInfo info = PackageInstallInfo.fromState(
+ success ? STATUS_INSTALLED : STATUS_FAILED,
+ packageName, key.mUser);
+ mCallback.onPackageStateChanged(info);
+
+ if (!success && mInstallerCompat.promiseIconAddedForId(sessionId)) {
+ mCallback.onSessionFailure(packageName, key.mUser);
+ // If it is successful, the id is removed in the the package added flow.
+ mInstallerCompat.removePromiseIconId(sessionId);
+ }
+ }
+ }
+
+ @Override
+ public void onProgressChanged(int sessionId, float progress) {
+ SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+ if (session != null && session.getAppPackageName() != null) {
+ mCallback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session));
+ }
+ }
+
+ @Override
+ public void onActiveChanged(int sessionId, boolean active) { }
+
+ @Override
+ public void onBadgingChanged(int sessionId) {
+ SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+ if (sessionInfo != null) {
+ mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+ }
+ }
+
+ private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
+ SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+ if (session != null && session.getAppPackageName() != null) {
+ PackageUserKey key =
+ new PackageUserKey(session.getAppPackageName(), getUserHandle(session));
+ getActiveSessionMap().put(session.getSessionId(), key);
+ mCallback.onUpdateSessionDisplay(key, session);
+ return session;
+ }
+ return null;
+ }
+
+ private SparseArray<PackageUserKey> getActiveSessionMap() {
+ if (mActiveSessions == null) {
+ mActiveSessions = new SparseArray<>();
+ mInstallerCompat.getActiveSessions().forEach(
+ (key, si) -> mActiveSessions.put(si.getSessionId(), key));
+ }
+ return mActiveSessions;
+ }
+
+ public void unregister() {
+ mInstallerCompat.unregister(this);
+ }
+
+ public interface Callback {
+
+ void onSessionFailure(String packageName, UserHandle user);
+
+ void onUpdateSessionDisplay(PackageUserKey key, SessionInfo info);
+
+ void onPackageStateChanged(PackageInstallInfo info);
+
+ void onInstallSessionCreated(PackageInstallInfo info);
+ }
+}
diff --git a/src/com/android/launcher3/pm/PackageInstallInfo.java b/src/com/android/launcher3/pm/PackageInstallInfo.java
new file mode 100644
index 0000000..7997d16
--- /dev/null
+++ b/src/com/android/launcher3/pm/PackageInstallInfo.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.pm;
+
+import android.content.ComponentName;
+import android.content.pm.PackageInstaller;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+public final class PackageInstallInfo {
+
+ public static final int STATUS_INSTALLED = 0;
+ public static final int STATUS_INSTALLING = 1;
+ public static final int STATUS_FAILED = 2;
+
+ public final ComponentName componentName;
+ public final String packageName;
+ public final int state;
+ public final int progress;
+ public final UserHandle user;
+
+ private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) {
+ this.state = STATUS_INSTALLING;
+ this.packageName = info.getAppPackageName();
+ this.componentName = new ComponentName(packageName, "");
+ this.progress = (int) (info.getProgress() * 100f);
+ this.user = InstallSessionHelper.getUserHandle(info);
+ }
+
+ public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) {
+ this.state = state;
+ this.packageName = packageName;
+ this.componentName = new ComponentName(packageName, "");
+ this.progress = progress;
+ this.user = user;
+ }
+
+ public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) {
+ return new PackageInstallInfo(info);
+ }
+
+ public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) {
+ return new PackageInstallInfo(packageName, state, 0 /* progress */, user);
+ }
+
+}
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
new file mode 100644
index 0000000..74a5a31
--- /dev/null
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 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.pm;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.PinItemRequest;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+import android.os.Parcelable;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.ShortcutCachingLogic;
+
+public class PinRequestHelper {
+
+ /**
+ * request.accept() will initiate the following flow:
+ * -> go-to-system-process for actual processing (a)
+ * -> callback-to-launcher on UI thread (b)
+ * -> post callback on the worker thread (c)
+ * -> Update model and unpin (in system) any shortcut not in out model. (d)
+ *
+ * Note that (b) will take at-least one frame as it involves posting callback from binder
+ * thread to UI thread.
+ * If (d) happens before we add this shortcut to our model, we will end up unpinning
+ * the shortcut in the system.
+ * Here its the caller's responsibility to add the newly created WorkspaceItemInfo immediately
+ * to the model (which may involves a single post-to-worker-thread). That will guarantee
+ * that (d) happens after model is updated.
+ */
+ @Nullable
+ @TargetApi(Build.VERSION_CODES.O)
+ public static WorkspaceItemInfo createWorkspaceItemFromPinItemRequest(
+ Context context, final PinItemRequest request, final long acceptDelay) {
+ if (request != null && request.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT
+ && request.isValid()) {
+
+ if (acceptDelay <= 0) {
+ if (!request.accept()) {
+ return null;
+ }
+ } else {
+ // Block the worker thread until the accept() is called.
+ MODEL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(acceptDelay);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ if (request.isValid()) {
+ request.accept();
+ }
+ }
+ });
+ }
+
+ ShortcutInfo si = request.getShortcutInfo();
+ WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
+ // Apply the unbadged icon synchronously using the caching logic directly and
+ // fetch the actual icon asynchronously.
+ info.bitmap = new ShortcutCachingLogic().loadIcon(context, si);
+ LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si);
+ return info;
+ } else {
+ return null;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.O)
+ public static PinItemRequest getPinItemRequest(Intent intent) {
+ Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);
+ return extra instanceof PinItemRequest ? (PinItemRequest) extra : null;
+ }
+}
diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
similarity index 66%
rename from src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
rename to src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index ace5691..ac0e065 100644
--- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -14,34 +14,44 @@
* limitations under the License.
*/
-package com.android.launcher3.compat;
+package com.android.launcher3.pm;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
import android.widget.Toast;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.IconCache;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Wrapper class for representing a shortcut configure activity.
*/
-public abstract class ShortcutConfigActivityInfo implements ComponentWithLabel {
+public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAndIcon {
private static final String TAG = "SCActivityInfo";
@@ -67,6 +77,7 @@
return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
}
+ @Override
public abstract Drawable getFullResIcon(IconCache cache);
/**
@@ -87,9 +98,9 @@
Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Launcher does not have the permission to launch " + intent +
- ". Make sure to create a MAIN intent-filter for the corresponding activity " +
- "or use the exported attribute for this activity.", e);
+ Log.e(TAG, "Launcher does not have the permission to launch " + intent
+ + ". Make sure to create a MAIN intent-filter for the corresponding activity "
+ + "or use the exported attribute for this activity.", e);
}
return false;
}
@@ -106,7 +117,7 @@
private final ActivityInfo mInfo;
- public ShortcutConfigActivityInfoVL(ActivityInfo info) {
+ ShortcutConfigActivityInfoVL(ActivityInfo info) {
super(new ComponentName(info.packageName, info.name), Process.myUserHandle());
mInfo = info;
}
@@ -158,4 +169,46 @@
}
}
}
+
+ public static List<ShortcutConfigActivityInfo> queryList(
+ Context context, @Nullable PackageUserKey packageUser) {
+ List<ShortcutConfigActivityInfo> result = new ArrayList<>();
+ UserHandle myUser = Process.myUserHandle();
+
+ if (Utilities.ATLEAST_OREO) {
+ final List<UserHandle> users;
+ final String packageName;
+ if (packageUser == null) {
+ users = UserCache.INSTANCE.get(context).getUserProfiles();
+ packageName = null;
+ } else {
+ users = new ArrayList<>(1);
+ users.add(packageUser.mUser);
+ packageName = packageUser.mPackageName;
+ }
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ for (UserHandle user : users) {
+ boolean ignoreTargetSdk = myUser.equals(user);
+ for (LauncherActivityInfo activityInfo :
+ launcherApps.getShortcutConfigActivityList(packageName, user)) {
+ if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.O) {
+ result.add(new ShortcutConfigActivityInfoVO(activityInfo));
+ }
+ }
+ }
+ } else {
+ if (packageUser == null || packageUser.mUser.equals(myUser)) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ if (packageUser != null) {
+ intent.setPackage(packageUser.mPackageName);
+ }
+ for (ResolveInfo info :
+ context.getPackageManager().queryIntentActivities(intent, 0)) {
+ result.add(new ShortcutConfigActivityInfoVL(info.activityInfo));
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
new file mode 100644
index 0000000..678b647
--- /dev/null
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 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.pm;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class which manages a local cache of user handles to avoid system rpc
+ */
+public class UserCache {
+
+ public static final MainThreadInitializedObject<UserCache> INSTANCE =
+ new MainThreadInitializedObject<>(UserCache::new);
+
+ private final Context mContext;
+ private final UserManager mUserManager;
+ private final ArrayList<Runnable> mUserChangeListeners = new ArrayList<>();
+ private final SimpleBroadcastReceiver mUserChangeReceiver =
+ new SimpleBroadcastReceiver(this::onUsersChanged);
+
+ private LongSparseArray<UserHandle> mUsers;
+ // Create a separate reverse map as LongSparseArray.indexOfValue checks if objects are same
+ // and not {@link Object#equals}
+ private ArrayMap<UserHandle, Long> mUserToSerialMap;
+
+ private UserCache(Context context) {
+ mContext = context;
+ mUserManager = context.getSystemService(UserManager.class);
+ }
+
+ private void onUsersChanged(Intent intent) {
+ enableAndResetCache();
+ mUserChangeListeners.forEach(Runnable::run);
+ }
+
+ /**
+ * Adds a listener for user additions and removals
+ */
+ public SafeCloseable addUserChangeListener(Runnable command) {
+ synchronized (this) {
+ if (mUserChangeListeners.isEmpty()) {
+ // Enable caching and start listening for user broadcast
+ mUserChangeReceiver.register(mContext,
+ Intent.ACTION_MANAGED_PROFILE_ADDED,
+ Intent.ACTION_MANAGED_PROFILE_REMOVED);
+ enableAndResetCache();
+ }
+ mUserChangeListeners.add(command);
+ return () -> removeUserChangeListener(command);
+ }
+ }
+
+ private void enableAndResetCache() {
+ synchronized (this) {
+ mUsers = new LongSparseArray<>();
+ mUserToSerialMap = new ArrayMap<>();
+ List<UserHandle> users = mUserManager.getUserProfiles();
+ if (users != null) {
+ for (UserHandle user : users) {
+ long serial = mUserManager.getSerialNumberForUser(user);
+ mUsers.put(serial, user);
+ mUserToSerialMap.put(user, serial);
+ }
+ }
+ }
+ }
+
+ private void removeUserChangeListener(Runnable command) {
+ synchronized (this) {
+ mUserChangeListeners.add(command);
+ if (mUserChangeListeners.isEmpty()) {
+ // Disable cache and stop listening
+ mContext.unregisterReceiver(mUserChangeReceiver);
+
+ mUsers = null;
+ mUserToSerialMap = null;
+ }
+ }
+ }
+
+ /**
+ * Returns true if any user profile has quiet mode enabled.
+ */
+ public boolean isAnyProfileQuietModeEnabled() {
+ List<UserHandle> userProfiles = getUserProfiles();
+ for (UserHandle userProfile : userProfiles) {
+ if (Process.myUserHandle().equals(userProfile)) {
+ continue;
+ }
+ if (mUserManager.isQuietModeEnabled(userProfile)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @see UserManager#getSerialNumberForUser(UserHandle)
+ */
+ public long getSerialNumberForUser(UserHandle user) {
+ synchronized (this) {
+ if (mUserToSerialMap != null) {
+ Long serial = mUserToSerialMap.get(user);
+ return serial == null ? 0 : serial;
+ }
+ }
+ return mUserManager.getSerialNumberForUser(user);
+ }
+
+ /**
+ * @see UserManager#getUserForSerialNumber(long)
+ */
+ public UserHandle getUserForSerialNumber(long serialNumber) {
+ synchronized (this) {
+ if (mUsers != null) {
+ return mUsers.get(serialNumber);
+ }
+ }
+ return mUserManager.getUserForSerialNumber(serialNumber);
+ }
+
+ /**
+ * @see UserManager#getUserProfiles()
+ */
+ public List<UserHandle> getUserProfiles() {
+ synchronized (this) {
+ if (mUsers != null) {
+ return new ArrayList<>(mUserToSerialMap.keySet());
+ }
+ }
+
+ List<UserHandle> users = mUserManager.getUserProfiles();
+ return users == null ? Collections.emptyList() : users;
+ }
+
+ /**
+ * Returns true is there is at least one user profile enabled
+ */
+ public boolean hasWorkProfile() {
+ synchronized (this) {
+ if (mUsers != null) {
+ return mUsers.size() > 1;
+ }
+ }
+ return getUserProfiles().size() > 1;
+ }
+}
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 98f7fd8..18bc55a 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -32,6 +32,7 @@
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -40,8 +41,8 @@
import android.widget.FrameLayout;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -57,14 +58,16 @@
/**
* A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
*/
-public abstract class ArrowPopup extends AbstractFloatingView {
+public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
private final Rect mTempRect = new Rect();
protected final LayoutInflater mInflater;
private final float mOutlineRadius;
- protected final Launcher mLauncher;
+ protected final T mLauncher;
protected final boolean mIsRtl;
private final int mArrowOffset;
@@ -83,7 +86,7 @@
super(context, attrs, defStyleAttr);
mInflater = LayoutInflater.from(context);
mOutlineRadius = Themes.getDialogCornerRadius(context);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mIsRtl = Utilities.isRtl(getResources());
setClipToOutline(true);
@@ -120,16 +123,22 @@
}
}
- public <T extends View> T inflateAndAdd(int resId, ViewGroup container) {
+ /**
+ * Utility method for inflating and adding a view
+ */
+ public <R extends View> R inflateAndAdd(int resId, ViewGroup container) {
View view = mInflater.inflate(resId, container, false);
container.addView(view);
- return (T) view;
+ return (R) view;
}
- public <T extends View> T inflateAndAdd(int resId, ViewGroup container, int index) {
+ /**
+ * Utility method for inflating and adding a view
+ */
+ public <R extends View> R inflateAndAdd(int resId, ViewGroup container, int index) {
View view = mInflater.inflate(resId, container, false);
container.addView(view, index);
- return (T) view;
+ return (R) view;
}
/**
@@ -231,6 +240,17 @@
* and align above if there is enough vertical space.
*/
protected void orientAboutObject() {
+ orientAboutObject(true /* allowAlignLeft */, true /* allowAlignRight */);
+ }
+
+ /**
+ * @see #orientAboutObject()
+ *
+ * @param allowAlignLeft Set to false if we already tried aligning left and didn't have room.
+ * @param allowAlignRight Set to false if we already tried aligning right and didn't have room.
+ * TODO: Can we test this with all permutations of widths/heights and icon locations + RTL?
+ */
+ private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width = getMeasuredWidth();
int extraVerticalSpace = mArrow.getLayoutParams().height + mArrowOffset
@@ -244,14 +264,8 @@
// Align left (right in RTL) if there is room.
int leftAlignedX = mTempRect.left;
int rightAlignedX = mTempRect.right - width;
- int x = leftAlignedX;
- boolean canBeLeftAligned = leftAlignedX + width + insets.left
- < dragLayer.getRight() - insets.right;
- boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
- if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
- x = rightAlignedX;
- }
- mIsLeftAligned = x == leftAlignedX;
+ mIsLeftAligned = !mIsRtl ? allowAlignLeft : !allowAlignRight;
+ int x = mIsLeftAligned ? leftAlignedX : rightAlignedX;
// Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
int iconWidth = mTempRect.width();
@@ -273,6 +287,24 @@
}
x += mIsLeftAligned ? xOffset : -xOffset;
+ // Check whether we can still align as we originally wanted, now that we've calculated x.
+ if (!allowAlignLeft && !allowAlignRight) {
+ // We've already tried both ways and couldn't make it fit. onLayout() will set the
+ // gravity to CENTER_HORIZONTAL, but continue below to update y.
+ } else {
+ boolean canBeLeftAligned = x + width + insets.left
+ < dragLayer.getRight() - insets.right;
+ boolean canBeRightAligned = x > dragLayer.getLeft() + insets.left;
+ boolean alignmentStillValid = mIsLeftAligned && canBeLeftAligned
+ || !mIsLeftAligned && canBeRightAligned;
+ if (!alignmentStillValid) {
+ // Try again, but don't allow this alignment we already know won't work.
+ orientAboutObject(allowAlignLeft && !mIsLeftAligned /* allowAlignLeft */,
+ allowAlignRight && mIsLeftAligned /* allowAlignRight */);
+ return;
+ }
+ }
+
// Open above icon if there is room.
int iconHeight = mTempRect.height();
int y = mTempRect.top - height;
@@ -350,6 +382,11 @@
}
}
+ @Override
+ protected Pair<View, String> getAccessibilityTarget() {
+ return Pair.create(this, "");
+ }
+
private void animateOpen() {
setVisibility(View.VISIBLE);
@@ -405,6 +442,8 @@
}
if (getOutlineProvider() instanceof RevealOutlineAnimation) {
((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
+ } else {
+ mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
if (mOpenCloseAnimator != null) {
mOpenCloseAnimator.cancel();
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index e8ac1d4..5af5ebb 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
@@ -37,14 +38,13 @@
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -59,15 +59,12 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
@@ -76,21 +73,22 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
*/
-public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
- DragController.DragListener, View.OnLongClickListener,
- View.OnTouchListener, PopupDataChangeListener {
+public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends ArrowPopup<T>
+ implements DragSource, DragController.DragListener {
private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
private final PointF mInterceptTouchDown = new PointF();
- protected final Point mIconLastTouchPos = new Point();
private final int mStartDragThreshold;
- private final LauncherAccessibilityDelegate mAccessibilityDelegate;
private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
@@ -98,11 +96,13 @@
private ViewGroup mSystemShortcutContainer;
+ protected PopupItemDragHandler mPopupItemDragHandler;
+ protected LauncherAccessibilityDelegate mAccessibilityDelegate;
+
public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
- mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
}
public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -118,18 +118,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLauncher.getPopupDataProvider().setChangeListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mLauncher.getPopupDataProvider().setChangeListener(null);
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mInterceptTouchDown.set(ev.getX(), ev.getY());
@@ -169,18 +157,22 @@
public OnClickListener getItemClickListener() {
return (view) -> {
- ItemClickHandler.INSTANCE.onClick(view);
+ mLauncher.getItemOnClickListener().onClick(view);
close(true);
};
}
+ public PopupItemDragHandler getItemDragHandler() {
+ return mPopupItemDragHandler;
+ }
+
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
BaseDragLayer dl = getPopupContainer();
if (!dl.isEventOverView(this, ev)) {
mLauncher.getUserEventDispatcher().logActionTapOutside(
- LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
+ newContainerTarget(ContainerType.DEEPSHORTCUTS));
close(true);
// We let touches on the original icon go through so that users can launch
@@ -196,27 +188,41 @@
* @return the container if shown or null.
*/
public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_CONTEXT_MENU, "showForIcon");
- }
Launcher launcher = Launcher.getLauncher(icon.getContext());
if (getOpen(launcher) != null) {
// There is already an items container open, so don't open this one.
icon.clearFocus();
return null;
}
- ItemInfo itemInfo = (ItemInfo) icon.getTag();
- if (!ShortcutUtil.supportsShortcuts(itemInfo)) {
+ ItemInfo item = (ItemInfo) icon.getTag();
+ if (!ShortcutUtil.supportsShortcuts(item)) {
return null;
}
final PopupContainerWithArrow container =
(PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
- container.populateAndShow(icon, itemInfo, SystemShortcutFactory.INSTANCE.get(launcher));
+ container.configureForLauncher(launcher);
+
+ PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
+ container.populateAndShow(icon,
+ popupDataProvider.getShortcutCountForItem(item),
+ popupDataProvider.getNotificationKeysForItem(item),
+ launcher.getSupportedShortcuts()
+ .map(s -> s.getShortcut(launcher, item))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
return container;
}
+ private void configureForLauncher(Launcher launcher) {
+ addOnAttachStateChangeListener(new LiveUpdateHandler(launcher));
+ mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+ mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
+ launcher.getDragController().addDragListener(this);
+ }
+
@Override
protected void onInflationComplete(boolean isReversed) {
if (isReversed && mNotificationItemView != null) {
@@ -238,28 +244,22 @@
}
}
- protected void populateAndShow(
- BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow");
- }
- PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
- populateAndShow(icon,
- popupDataProvider.getShortcutCountForItem(item),
- popupDataProvider.getNotificationKeysForItem(item),
- factory.getEnabledShortcuts(mLauncher, item));
- }
-
- public ViewGroup getSystemShortcutContainerForTesting() {
- return mSystemShortcutContainer;
- }
-
@TargetApi(Build.VERSION_CODES.P)
- protected void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
+ public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
mOriginalIcon = originalIcon;
+ boolean hasDeepShortcuts = shortcutCount > 0;
+ int containerWidth = (int) getResources().getDimension(R.dimen.bg_popup_item_width);
+
+ // if there are deep shortcuts, we might want to increase the width of shortcuts to fit
+ // horizontally laid out system shortcuts.
+ if (hasDeepShortcuts) {
+ containerWidth = (int) Math.max(containerWidth,
+ systemShortcuts.size() * getResources().getDimension(
+ R.dimen.system_shortcut_header_icon_touch_size));
+ }
// Add views
if (mNumNotifications > 0) {
// Add notification entries
@@ -268,18 +268,22 @@
if (mNumNotifications == 1) {
mNotificationItemView.removeFooter();
}
+ else {
+ mNotificationItemView.setFooterWidth(containerWidth);
+ }
updateNotificationHeader();
}
int viewsToFlip = getChildCount();
mSystemShortcutContainer = this;
-
- if (shortcutCount > 0) {
+ if (hasDeepShortcuts) {
if (mNotificationItemView != null) {
mNotificationItemView.addGutter();
}
for (int i = shortcutCount; i > 0; i--) {
- mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut, this));
+ DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, this);
+ v.getLayoutParams().width = containerWidth;
+ mShortcuts.add(v);
}
updateHiddenShortcuts();
@@ -307,7 +311,6 @@
setAccessibilityPaneTitle(getTitleForAccessibility());
}
- mLauncher.getDragController().addDragListener(this);
mOriginalIcon.setForceHideDot(true);
// All views are added. Animate layout from now on.
@@ -326,11 +329,6 @@
}
@Override
- protected Pair<View, String> getAccessibilityTarget() {
- return Pair.create(this, "");
- }
-
- @Override
protected void getTargetObjectLocation(Rect outPos) {
getPopupContainer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos);
outPos.top += mOriginalIcon.getPaddingTop();
@@ -342,7 +340,9 @@
}
public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
- mNotificationItemView.applyNotificationInfos(notificationInfos);
+ if (mNotificationItemView != null) {
+ mNotificationItemView.applyNotificationInfos(notificationInfos);
+ }
}
private void updateHiddenShortcuts() {
@@ -379,45 +379,6 @@
}
}
- @Override
- public void onWidgetsBound() {
- ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
- SystemShortcut widgetInfo = new SystemShortcut.Widgets();
- View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
- View widgetsView = null;
- int count = mSystemShortcutContainer.getChildCount();
- for (int i = 0; i < count; i++) {
- View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
- if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
- widgetsView = systemShortcutView;
- break;
- }
- }
-
- if (onClickListener != null && widgetsView == null) {
- // We didn't have any widgets cached but now there are some, so enable the shortcut.
- if (mSystemShortcutContainer != this) {
- initializeSystemShortcut(
- R.layout.system_shortcut_icon_only, mSystemShortcutContainer, widgetInfo);
- } else {
- // If using the expanded system shortcut (as opposed to just the icon), we need to
- // reopen the container to ensure measurements etc. all work out. While this could
- // be quite janky, in practice the user would typically see a small flicker as the
- // animation restarts partway through, and this is a very rare edge case anyway.
- close(false);
- PopupContainerWithArrow.showForIcon(mOriginalIcon);
- }
- } else if (onClickListener == null && widgetsView != null) {
- // No widgets exist, but we previously added the shortcut so remove it.
- if (mSystemShortcutContainer != this) {
- mSystemShortcutContainer.removeView(widgetsView);
- } else {
- close(false);
- PopupContainerWithArrow.showForIcon(mOriginalIcon);
- }
- }
- }
-
private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
View view = inflateAndAdd(
resId, container, getInsertIndexForSystemShortcut(container, info));
@@ -430,8 +391,7 @@
info.setIconAndContentDescriptionFor((ImageView) view);
}
view.setTag(info);
- view.setOnClickListener(info.getOnClickListener(mLauncher,
- (ItemInfo) mOriginalIcon.getTag()));
+ view.setOnClickListener(info);
}
/**
@@ -489,43 +449,12 @@
};
}
- /**
- * Updates the notification header if the original icon's dot updated.
- */
- @Override
- public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
- ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
- PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
- if (updatedDots.test(packageUser)) {
- updateNotificationHeader();
- }
- }
-
private void updateNotificationHeader() {
ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo);
if (mNotificationItemView != null && dotInfo != null) {
mNotificationItemView.updateHeader(
- dotInfo.getNotificationCount(), itemInfo.iconColor);
- }
- }
-
- @Override
- public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
- if (mNotificationItemView == null) {
- return;
- }
- ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
- DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
- if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
- // No more notifications, remove the notification views and expand all shortcuts.
- mNotificationItemView.removeAllViews();
- mNotificationItemView = null;
- updateHiddenShortcuts();
- updateDividers();
- } else {
- mNotificationItemView.trimNotifications(
- NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
+ dotInfo.getNotificationCount(), itemInfo.bitmap.color);
}
}
@@ -556,14 +485,15 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- if (info == NOTIFICATION_ITEM_INFO) {
- target.itemType = ItemType.NOTIFICATION;
+ public void fillInLogContainerData(ItemInfo childInfo, Target child,
+ ArrayList<Target> parents) {
+ if (childInfo == NOTIFICATION_ITEM_INFO) {
+ child.itemType = ItemType.NOTIFICATION;
} else {
- target.itemType = ItemType.DEEPSHORTCUT;
- target.rank = info.rank;
+ child.itemType = ItemType.DEEPSHORTCUT;
+ child.rank = childInfo.rank;
}
- targetParent.containerType = ContainerType.DEEPSHORTCUTS;
+ parents.add(newContainerTarget(ContainerType.DEEPSHORTCUTS));
}
@Override
@@ -583,47 +513,164 @@
super.closeComplete();
}
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- // Touched a shortcut, update where it was touched so we can drag from there on long click.
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
- break;
- }
- return false;
- }
-
- @Override
- public boolean onLongClick(View v) {
- if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
- // Return early if not the correct view
- if (!(v.getParent() instanceof DeepShortcutView)) return false;
-
- // Long clicked on a shortcut.
- DeepShortcutView sv = (DeepShortcutView) v.getParent();
- sv.setWillDrawIcon(false);
-
- // Move the icon to align with the center-top of the touch point
- Point iconShift = new Point();
- iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
- iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
- DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
- this, sv.getFinalInfo(),
- new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
- dv.animateShift(-iconShift.x, -iconShift.y);
-
- // TODO: support dragging from within folder without having to close it
- AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
- return false;
- }
-
/**
* Returns a PopupContainerWithArrow which is already open or null
*/
- public static PopupContainerWithArrow getOpen(Launcher launcher) {
+ public static PopupContainerWithArrow getOpen(BaseDraggingActivity launcher) {
return getOpenView(launcher, TYPE_ACTION_POPUP);
}
+
+ /**
+ * Utility class to handle updates while the popup is visible (like widgets and
+ * notification changes)
+ */
+ private class LiveUpdateHandler implements
+ PopupDataChangeListener, View.OnAttachStateChangeListener {
+
+ private final Launcher mLauncher;
+
+ LiveUpdateHandler(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mLauncher.getPopupDataProvider().setChangeListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ mLauncher.getPopupDataProvider().setChangeListener(null);
+ }
+
+ @Override
+ public void onWidgetsBound() {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
+ View widgetsView = null;
+ int count = mSystemShortcutContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+ if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+ widgetsView = systemShortcutView;
+ break;
+ }
+ }
+
+ if (widgetInfo != null && widgetsView == null) {
+ // We didn't have any widgets cached but now there are some, so enable the shortcut.
+ if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+ initializeSystemShortcut(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, widgetInfo);
+ } else {
+ // If using the expanded system shortcut (as opposed to just the icon), we need
+ // to reopen the container to ensure measurements etc. all work out. While this
+ // could be quite janky, in practice the user would typically see a small
+ // flicker as the animation restarts partway through, and this is a very rare
+ // edge case anyway.
+ close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ } else if (widgetInfo == null && widgetsView != null) {
+ // No widgets exist, but we previously added the shortcut so remove it.
+ if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+ mSystemShortcutContainer.removeView(widgetsView);
+ } else {
+ close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ }
+ }
+
+ /**
+ * Updates the notification header if the original icon's dot updated.
+ */
+ @Override
+ public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
+ if (updatedDots.test(packageUser)) {
+ updateNotificationHeader();
+ }
+ }
+
+
+ @Override
+ public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
+ if (mNotificationItemView == null) {
+ return;
+ }
+ ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
+ DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
+ if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
+ // No more notifications, remove the notification views and expand all shortcuts.
+ mNotificationItemView.removeAllViews();
+ mNotificationItemView = null;
+ updateHiddenShortcuts();
+ updateDividers();
+ } else {
+ mNotificationItemView.trimNotifications(
+ NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
+ }
+ }
+ }
+
+ /**
+ * Handler to control drag-and-drop for popup items
+ */
+ public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { }
+
+ /**
+ * Drag and drop handler for popup items in Launcher activity
+ */
+ public static class LauncherPopupItemDragHandler implements PopupItemDragHandler {
+
+ protected final Point mIconLastTouchPos = new Point();
+ private final Launcher mLauncher;
+ private final PopupContainerWithArrow mContainer;
+
+ LauncherPopupItemDragHandler(Launcher launcher, PopupContainerWithArrow container) {
+ mLauncher = launcher;
+ mContainer = container;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on
+ // long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+ // Return early if not the correct view
+ if (!(v.getParent() instanceof DeepShortcutView)) return false;
+
+ // Long clicked on a shortcut.
+ DeepShortcutView sv = (DeepShortcutView) v.getParent();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ Point iconShift = new Point();
+ iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ mContainer, sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
+ new DragOptions());
+ dv.animateShift(-iconShift.x, -iconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+ }
}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 4612b2a..1092c7b 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -20,13 +20,14 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
@@ -39,13 +40,10 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
* Provides data for the popup menu that appears after long-clicking on apps.
*/
@@ -54,7 +52,7 @@
private static final boolean LOGD = false;
private static final String TAG = "PopupDataProvider";
- private final Launcher mLauncher;
+ private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
/** Maps launcher activity components to a count of how many shortcuts they have. */
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
@@ -65,39 +63,25 @@
private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
- public PopupDataProvider(Launcher launcher) {
- mLauncher = launcher;
+ public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
+ mNotificationDotsChangeListener = notificationDotsChangeListener;
}
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mLauncher.updateNotificationDots(updatedDots);
+ mNotificationDotsChangeListener.accept(updatedDots);
mChangeListener.onNotificationDotsUpdated(updatedDots);
}
@Override
public void onNotificationPosted(PackageUserKey postedPackageUserKey,
- NotificationKeyData notificationKey, boolean shouldBeFilteredOut) {
+ NotificationKeyData notificationKey) {
DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey);
- boolean dotShouldBeRefreshed;
if (dotInfo == null) {
- if (!shouldBeFilteredOut) {
- DotInfo newDotInfo = new DotInfo();
- newDotInfo.addOrUpdateNotificationKey(notificationKey);
- mPackageUserToDotInfos.put(postedPackageUserKey, newDotInfo);
- dotShouldBeRefreshed = true;
- } else {
- dotShouldBeRefreshed = false;
- }
- } else {
- dotShouldBeRefreshed = shouldBeFilteredOut
- ? dotInfo.removeNotificationKey(notificationKey)
- : dotInfo.addOrUpdateNotificationKey(notificationKey);
- if (dotInfo.getNotificationKeys().size() == 0) {
- mPackageUserToDotInfos.remove(postedPackageUserKey);
- }
+ dotInfo = new DotInfo();
+ mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo);
}
- if (dotShouldBeRefreshed) {
- updateNotificationDots(t -> postedPackageUserKey.equals(t));
+ if (dotInfo.addOrUpdateNotificationKey(notificationKey)) {
+ updateNotificationDots(postedPackageUserKey::equals);
}
}
@@ -109,7 +93,7 @@
if (oldDotInfo.getNotificationKeys().size() == 0) {
mPackageUserToDotInfos.remove(removedPackageUserKey);
}
- updateNotificationDots(t -> removedPackageUserKey.equals(t));
+ updateNotificationDots(removedPackageUserKey::equals);
trimNotifications(mPackageUserToDotInfos);
}
}
@@ -195,14 +179,6 @@
: getNotificationsForItem(info, dotInfo.getNotificationKeys());
}
- /** This makes a potentially expensive binder call and should be run on a background thread. */
- public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys(
- List<NotificationKeyData> notificationKeys) {
- NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
- return notificationListener == null ? Collections.EMPTY_LIST
- : notificationListener.getNotificationsForKeys(notificationKeys);
- }
-
public void cancelNotification(String notificationKey) {
NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
if (notificationListener == null) {
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index dbfe988..fdcf04f 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -20,26 +20,27 @@
import android.content.pm.ShortcutInfo;
import android.os.Handler;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationKeyData;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
+import java.util.stream.Collectors;
/**
* Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular,
@@ -122,7 +123,11 @@
return filteredShortcuts;
}
- public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
+ /**
+ * Returns a runnable to update the provided shortcuts and notifications
+ */
+ public static Runnable createUpdateRunnable(final BaseDraggingActivity launcher,
+ final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
final List<DeepShortcutView> shortcutViews,
final List<NotificationKeyData> notificationKeys) {
@@ -130,38 +135,35 @@
final UserHandle user = originalInfo.user;
return () -> {
if (!notificationKeys.isEmpty()) {
- List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
- .getStatusBarNotificationsForKeys(notificationKeys);
- List<NotificationInfo> infos = new ArrayList<>(notifications.size());
- for (int i = 0; i < notifications.size(); i++) {
- StatusBarNotification notification = notifications.get(i);
- infos.add(new NotificationInfo(launcher, notification));
+ NotificationListener notificationListener =
+ NotificationListener.getInstanceIfConnected();
+ final List<NotificationInfo> infos;
+ if (notificationListener == null) {
+ infos = Collections.emptyList();
+ } else {
+ infos = notificationListener.getNotificationsForKeys(notificationKeys).stream()
+ .map(sbn -> new NotificationInfo(launcher, sbn))
+ .collect(Collectors.toList());
}
uiHandler.post(() -> container.applyNotificationInfos(infos));
}
- List<ShortcutInfo> shortcuts = DeepShortcutManager.getInstance(launcher)
- .queryForShortcutsContainer(activity, user);
+ List<ShortcutInfo> shortcuts = new ShortcutRequest(launcher, user)
+ .withContainer(activity)
+ .query(ShortcutRequest.PUBLISHED);
String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
: notificationKeys.get(0).shortcutId;
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
+ IconCache cache = LauncherAppState.getInstance(launcher).getIconCache();
for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
final ShortcutInfo shortcut = shortcuts.get(i);
final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher);
- // Use unbadged icon for the menu.
- LauncherIcons li = LauncherIcons.obtain(launcher);
- si.applyFrom(li.createShortcutIcon(shortcut, false /* badged */));
- li.recycle();
+ cache.getUnbadgedShortcutIcon(si, shortcut);
si.rank = i;
final DeepShortcutView view = shortcutViews.get(i);
uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
}
-
- // This ensures that mLauncher.getWidgetsForPackageUser()
- // doesn't return null (it puts all the widgets in memory).
- uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
- PackageUserKey.fromItemInfo(originalInfo)));
};
}
}
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 5a5fbab..8751202 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -16,13 +16,19 @@
package com.android.launcher3.popup;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.app.RemoteAction;
+import android.content.Context;
import android.content.Intent;
-import android.os.Handler;
-import android.os.Looper;
+import android.os.Build;
import android.util.Log;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.ImageView;
+import android.widget.TextView;
import android.widget.Toast;
import com.android.launcher3.AbstractFloatingView;
@@ -32,55 +38,75 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto;
+@TargetApi(Build.VERSION_CODES.Q)
public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
private static final String TAG = "RemoteActionShortcut";
private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE;
private final RemoteAction mAction;
- public RemoteActionShortcut(RemoteAction action) {
- super(action.getIcon(), action.getTitle(), action.getContentDescription(),
- R.id.action_remote_action_shortcut);
+ public RemoteActionShortcut(RemoteAction action,
+ BaseDraggingActivity activity, ItemInfo itemInfo) {
+ super(0, R.id.action_remote_action_shortcut, activity, itemInfo);
mAction = action;
}
@Override
- public View.OnClickListener getOnClickListener(
- final BaseDraggingActivity activity, final ItemInfo itemInfo) {
- return view -> {
- AbstractFloatingView.closeAllOpenViews(activity);
+ public void setIconAndLabelFor(View iconView, TextView labelView) {
+ mAction.getIcon().loadDrawableAsync(iconView.getContext(),
+ iconView::setBackground,
+ MAIN_EXECUTOR.getHandler());
+ labelView.setText(mAction.getTitle());
+ }
- final String actionIdentity = mAction.getTitle() + ", " +
- itemInfo.getTargetComponent().getPackageName();
- try {
- if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity);
- mAction.getActionIntent().send(
- activity,
- 0,
- new Intent().putExtra(
- Intent.EXTRA_PACKAGE_NAME,
- itemInfo.getTargetComponent().getPackageName()),
- (pendingIntent, intent, resultCode, resultData, resultExtras) -> {
- if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity);
- if (resultData != null && !resultData.isEmpty()) {
- Log.e(TAG, "Remote action returned result: " + actionIdentity
- + " : " + resultData);
- Toast.makeText(activity, resultData, Toast.LENGTH_SHORT).show();
- }
- },
- new Handler(Looper.getMainLooper()));
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Remote action canceled: " + actionIdentity, e);
- Toast.makeText(activity, activity.getString(
- R.string.remote_action_failed,
- mAction.getTitle()),
- Toast.LENGTH_SHORT)
- .show();
- }
+ @Override
+ public void setIconAndContentDescriptionFor(ImageView view) {
+ mAction.getIcon().loadDrawableAsync(view.getContext(),
+ view::setImageDrawable,
+ MAIN_EXECUTOR.getHandler());
+ view.setContentDescription(mAction.getContentDescription());
+ }
- activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
- LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
- };
+ @Override
+ public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
+ return new AccessibilityNodeInfo.AccessibilityAction(
+ R.id.action_remote_action_shortcut, mAction.getContentDescription());
+ }
+
+ @Override
+ public void onClick(View view) {
+ AbstractFloatingView.closeAllOpenViews(mTarget);
+
+ final String actionIdentity = mAction.getTitle() + ", "
+ + mItemInfo.getTargetComponent().getPackageName();
+ try {
+ if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity);
+ mAction.getActionIntent().send(
+ mTarget,
+ 0,
+ new Intent().putExtra(
+ Intent.EXTRA_PACKAGE_NAME,
+ mItemInfo.getTargetComponent().getPackageName()),
+ (pendingIntent, intent, resultCode, resultData, resultExtras) -> {
+ if (DEBUG) Log.d(TAG, "Action is complete: " + actionIdentity);
+ if (resultData != null && !resultData.isEmpty()) {
+ Log.e(TAG, "Remote action returned result: " + actionIdentity
+ + " : " + resultData);
+ Toast.makeText(mTarget, resultData, Toast.LENGTH_SHORT).show();
+ }
+ },
+ MAIN_EXECUTOR.getHandler());
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Remote action canceled: " + actionIdentity, e);
+ Toast.makeText(mTarget, mTarget.getString(
+ R.string.remote_action_failed,
+ mAction.getTitle()),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+
+ mTarget.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+ LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
}
@Override
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index a87b7b8..21c5ac5 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -5,26 +5,21 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Looper;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
@@ -39,41 +34,30 @@
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
* @param <T>
*/
-public abstract class SystemShortcut<T extends BaseDraggingActivity>
- extends ItemInfo {
+public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo
+ implements View.OnClickListener {
+
private final int mIconResId;
private final int mLabelResId;
- private final Icon mIcon;
- private final CharSequence mLabel;
- private final CharSequence mContentDescription;
private final int mAccessibilityActionId;
- public SystemShortcut(int iconResId, int labelResId) {
+ protected final T mTarget;
+ protected final ItemInfo mItemInfo;
+
+ public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
mIconResId = iconResId;
mLabelResId = labelResId;
mAccessibilityActionId = labelResId;
- mIcon = null;
- mLabel = null;
- mContentDescription = null;
+ mTarget = target;
+ mItemInfo = itemInfo;
}
- public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription,
- int accessibilityActionId) {
- mIcon = icon;
- mLabel = label;
- mContentDescription = contentDescription;
- mAccessibilityActionId = accessibilityActionId;
- mIconResId = 0;
- mLabelResId = 0;
- }
-
- public SystemShortcut(SystemShortcut other) {
+ public SystemShortcut(SystemShortcut<T> other) {
mIconResId = other.mIconResId;
mLabelResId = other.mLabelResId;
- mIcon = other.mIcon;
- mLabel = other.mLabel;
- mContentDescription = other.mContentDescription;
mAccessibilityActionId = other.mAccessibilityActionId;
+ mTarget = other.mTarget;
+ mItemInfo = other.mItemInfo;
}
/**
@@ -84,150 +68,111 @@
}
public void setIconAndLabelFor(View iconView, TextView labelView) {
- if (mIcon != null) {
- mIcon.loadDrawableAsync(iconView.getContext(),
- iconView::setBackground,
- new Handler(Looper.getMainLooper()));
- } else {
- iconView.setBackgroundResource(mIconResId);
- }
-
- if (mLabel != null) {
- labelView.setText(mLabel);
- } else {
- labelView.setText(mLabelResId);
- }
+ iconView.setBackgroundResource(mIconResId);
+ labelView.setText(mLabelResId);
}
public void setIconAndContentDescriptionFor(ImageView view) {
- if (mIcon != null) {
- mIcon.loadDrawableAsync(view.getContext(),
- view::setImageDrawable,
- new Handler(Looper.getMainLooper()));
- } else {
- view.setImageResource(mIconResId);
- }
-
- view.setContentDescription(getContentDescription(view.getContext()));
- }
-
- private CharSequence getContentDescription(Context context) {
- return mContentDescription != null ? mContentDescription : context.getText(mLabelResId);
+ view.setImageResource(mIconResId);
+ view.setContentDescription(view.getContext().getText(mLabelResId));
}
public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
- return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
- getContentDescription(context));
+ return new AccessibilityNodeInfo.AccessibilityAction(
+ mAccessibilityActionId, context.getText(mLabelResId));
}
public boolean hasHandlerForAction(int action) {
return mAccessibilityActionId == action;
}
- public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo);
+ public interface Factory<T extends BaseDraggingActivity> {
+
+ @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
+ }
+
+ public static final Factory<Launcher> WIDGETS = (launcher, itemInfo) -> {
+ if (itemInfo.getTargetComponent() == null) return null;
+ final List<WidgetItem> widgets =
+ launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
+ itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
+ if (widgets == null) {
+ return null;
+ }
+ return new Widgets(launcher, itemInfo);
+ };
public static class Widgets extends SystemShortcut<Launcher> {
- public Widgets() {
- super(R.drawable.ic_widget, R.string.widget_button_text);
+ public Widgets(Launcher target, ItemInfo itemInfo) {
+ super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo);
}
@Override
- public View.OnClickListener getOnClickListener(final Launcher launcher,
- final ItemInfo itemInfo) {
- if (itemInfo.getTargetComponent() == null) return null;
- final List<WidgetItem> widgets =
- launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
- itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
- if (widgets == null) {
- return null;
- }
- return (view) -> {
- AbstractFloatingView.closeAllOpenViews(launcher);
- WidgetsBottomSheet widgetsBottomSheet =
- (WidgetsBottomSheet) launcher.getLayoutInflater().inflate(
- R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false);
- widgetsBottomSheet.populateAndShow(itemInfo);
- launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
- ControlType.WIDGETS_BUTTON, view);
- };
+ public void onClick(View view) {
+ AbstractFloatingView.closeAllOpenViews(mTarget);
+ WidgetsBottomSheet widgetsBottomSheet =
+ (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate(
+ R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false);
+ widgetsBottomSheet.populateAndShow(mItemInfo);
+ mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+ ControlType.WIDGETS_BUTTON, view);
}
}
+ public static final Factory<BaseDraggingActivity> APP_INFO = AppInfo::new;
+
public static class AppInfo extends SystemShortcut {
- public AppInfo() {
- super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label);
+
+ public AppInfo(BaseDraggingActivity target, ItemInfo itemInfo) {
+ super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
+ itemInfo);
}
@Override
- public View.OnClickListener getOnClickListener(
- BaseDraggingActivity activity, ItemInfo itemInfo) {
- return (view) -> {
- dismissTaskMenuView(activity);
- Rect sourceBounds = activity.getViewBounds(view);
- new PackageManagerHelper(activity).startDetailsActivityForInfo(
- itemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
- activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
- ControlType.APPINFO_TARGET, view);
- };
+ public void onClick(View view) {
+ dismissTaskMenuView(mTarget);
+ Rect sourceBounds = mTarget.getViewBounds(view);
+ new PackageManagerHelper(mTarget).startDetailsActivityForInfo(
+ mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
+ mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+ ControlType.APPINFO_TARGET, view);
}
}
+ public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
+ boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)
+ && ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
+ WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
+ boolean isInstantApp = false;
+ if (itemInfo instanceof com.android.launcher3.AppInfo) {
+ com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
+ isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
+ }
+ boolean enabled = supportsWebUI || isInstantApp;
+ if (!enabled) {
+ return null;
+ }
+ return new Install(activity, itemInfo);
+ };
+
public static class Install extends SystemShortcut {
- public Install() {
- super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label);
+
+ public Install(BaseDraggingActivity target, ItemInfo itemInfo) {
+ super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
+ target, itemInfo);
}
@Override
- public View.OnClickListener getOnClickListener(
- BaseDraggingActivity activity, ItemInfo itemInfo) {
- boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo) &&
- ((WorkspaceItemInfo) itemInfo).hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
- boolean isInstantApp = false;
- if (itemInfo instanceof com.android.launcher3.AppInfo) {
- com.android.launcher3.AppInfo appInfo = (com.android.launcher3.AppInfo) itemInfo;
- isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
- }
- boolean enabled = supportsWebUI || isInstantApp;
- if (!enabled) {
- return null;
- }
- return createOnClickListener(activity, itemInfo);
- }
-
- public View.OnClickListener createOnClickListener(
- BaseDraggingActivity activity, ItemInfo itemInfo) {
- return view -> {
- Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
- itemInfo.getTargetComponent().getPackageName());
- activity.startActivitySafely(view, intent, itemInfo, null);
- AbstractFloatingView.closeAllOpenViews(activity);
- };
+ public void onClick(View view) {
+ Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
+ mItemInfo.getTargetComponent().getPackageName());
+ mTarget.startActivitySafely(view, intent, mItemInfo, null);
+ AbstractFloatingView.closeAllOpenViews(mTarget);
}
}
- public static class DismissPrediction extends SystemShortcut<Launcher> {
- public DismissPrediction() {
- super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label);
- }
-
- @Override
- public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) {
- if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
- if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
- return (view) -> {
- PopupContainerWithArrow.closeAllOpenViews(activity);
- activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
- ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
- AppLaunchTracker.INSTANCE.get(view.getContext())
- .onDismissApp(itemInfo.getTargetComponent(),
- itemInfo.user,
- AppLaunchTracker.CONTAINER_PREDICTIONS);
- };
- }
- }
-
- protected static void dismissTaskMenuView(BaseDraggingActivity activity) {
+ public static void dismissTaskMenuView(BaseDraggingActivity activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
}
diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java
deleted file mode 100644
index dfcc2f8..0000000
--- a/src/com/android/launcher3/popup/SystemShortcutFactory.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.popup;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SystemShortcutFactory implements ResourceBasedOverride {
-
- public static final MainThreadInitializedObject<SystemShortcutFactory> INSTANCE =
- forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class);
-
- /** Note that these are in order of priority. */
- private final SystemShortcut[] mAllShortcuts;
-
- @SuppressWarnings("unused")
- public SystemShortcutFactory() {
- this(new SystemShortcut.AppInfo(),
- new SystemShortcut.Widgets(),
- new SystemShortcut.Install(),
- new SystemShortcut.DismissPrediction());
- }
-
- protected SystemShortcutFactory(SystemShortcut... shortcuts) {
- mAllShortcuts = shortcuts;
- }
-
- public @NonNull List<SystemShortcut> getEnabledShortcuts(Launcher launcher, ItemInfo info) {
- List<SystemShortcut> systemShortcuts = new ArrayList<>();
- for (SystemShortcut systemShortcut : mAllShortcuts) {
- if (systemShortcut.getOnClickListener(launcher, info) != null) {
- systemShortcuts.add(systemShortcut);
- }
- }
-
- return systemShortcuts;
- }
-}
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 970a03e..732fb0b 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -43,10 +43,10 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.Workspace;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.PackageManagerHelper;
@@ -100,7 +100,7 @@
* 3) In the end fills any holes in hotseat with items from default hotseat layout.
*/
private void importWorkspaceItems() throws Exception {
- String profileId = Long.toString(UserManagerCompat.getInstance(mContext)
+ String profileId = Long.toString(UserCache.INSTANCE.get(mContext)
.getSerialNumberForUser(Process.myUserHandle()));
boolean createEmptyRowOnFirstScreen;
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 2c843f9..f7ecc3f 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -22,10 +22,12 @@
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.os.Binder;
+import android.os.Process;
import android.util.Log;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.IntArray;
import java.util.Locale;
@@ -116,6 +118,15 @@
db.execSQL("DROP TABLE IF EXISTS " + tableName);
}
+ /** Copy from table to the to table. */
+ public static void copyTable(SQLiteDatabase db, String from, String to, Context context) {
+ long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser(
+ Process.myUserHandle());
+ dropTable(db, to);
+ Favorites.addTableToDb(db, userSerial, false, to);
+ db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from);
+ }
+
/**
* Utility class to simplify managing sqlite transactions
*/
diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
index 6d839f3..c0111b9 100644
--- a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
+++ b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
@@ -18,7 +18,6 @@
import android.content.ContentValues;
import android.content.Context;
-import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
@@ -43,7 +42,7 @@
protected LossyScreenMigrationTask(
Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
// Decrease the rows count by 1
- super(context, db, getValidPackages(context),
+ super(context, db, getValidPackages(context), false /* usePreviewTable */,
new Point(idp.numColumns, idp.numRows + 1),
new Point(idp.numColumns, idp.numRows));
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index fb33551..0fe3673 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,6 +16,7 @@
package com.android.launcher3.provider;
+import static com.android.launcher3.pm.InstallSessionHelper.KEY_INSTALL_SESSION_CREATED_TIMESTAMP;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import android.app.backup.BackupManager;
@@ -25,23 +26,28 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.LongSparseArray;
import android.util.SparseLongArray;
import androidx.annotation.NonNull;
import com.android.launcher3.AppWidgetsRestoredReceiver;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.GridBackupTable;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
import java.io.InvalidObjectException;
+import java.util.Arrays;
/**
* Utility class to update DB schema after it has been restored.
@@ -65,6 +71,7 @@
SQLiteDatabase db = helper.getWritableDatabase();
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
RestoreDbTask task = new RestoreDbTask();
+ task.backupWorkspace(context, db);
task.sanitizeDB(helper, db, backupManager);
task.restoreAppWidgetIdsIfExists(context);
t.commit();
@@ -76,6 +83,49 @@
}
/**
+ * Restore the workspace if backup is available.
+ */
+ public static boolean restoreIfPossible(@NonNull Context context,
+ @NonNull DatabaseHelper helper, @NonNull BackupManager backupManager) {
+ Utilities.getDevicePrefs(context).edit().putLong(
+ KEY_INSTALL_SESSION_CREATED_TIMESTAMP, System.currentTimeMillis()).apply();
+ final SQLiteDatabase db = helper.getWritableDatabase();
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ RestoreDbTask task = new RestoreDbTask();
+ task.restoreWorkspace(context, db, helper, backupManager);
+ task.restoreAppWidgetIdsIfExists(context);
+ t.commit();
+ return true;
+ } catch (Exception e) {
+ FileLog.e(TAG, "Failed to restore db", e);
+ return false;
+ }
+ }
+
+ /**
+ * Backup the workspace so that if things go south in restore, we can recover these entries.
+ */
+ private void backupWorkspace(Context context, SQLiteDatabase db) throws Exception {
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+ // TODO(pinyaoting): Support backing up workspace with multiple grid options.
+ new GridBackupTable(context, db, idp.numHotseatIcons, idp.numColumns, idp.numRows)
+ .doBackup(getDefaultProfileId(db), GridBackupTable.OPTION_REQUIRES_SANITIZATION);
+ }
+
+ private void restoreWorkspace(@NonNull Context context, @NonNull SQLiteDatabase db,
+ @NonNull DatabaseHelper helper, @NonNull BackupManager backupManager)
+ throws Exception {
+ // TODO(pinyaoting): Support restoring workspace with multiple grid options.
+ final InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+ GridBackupTable backupTable = new GridBackupTable(context, db, idp.numHotseatIcons,
+ idp.numColumns, idp.numRows);
+ if (backupTable.restoreFromRawBackupIfAvailable(getDefaultProfileId(db))) {
+ sanitizeDB(helper, db, backupManager);
+ LauncherAppState.getInstance(context).getModel().forceReload();
+ }
+ }
+
+ /**
* Makes the following changes in the provider DB.
* 1. Removes all entries belonging to any profiles that were not restored.
* 2. Marks all entries as restored. The flags are updated during first load or as
@@ -107,15 +157,14 @@
int numProfiles = profileMapping.size();
String[] profileIds = new String[numProfiles];
profileIds[0] = Long.toString(oldProfileId);
- StringBuilder whereClause = new StringBuilder("profileId != ?");
- for (int i = profileMapping.size() - 1; i >= 1; --i) {
- whereClause.append(" AND profileId != ?");
+ for (int i = numProfiles - 1; i >= 1; --i) {
profileIds[i] = Long.toString(profileMapping.keyAt(i));
}
- int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
- if (itemsDeleted > 0) {
- FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
- }
+ final String[] args = new String[profileIds.length];
+ Arrays.fill(args, "?");
+ final String where = "profileId NOT IN (" + TextUtils.join(", ", Arrays.asList(args)) + ")";
+ int itemsDeleted = db.delete(Favorites.TABLE_NAME, where, profileIds);
+ FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
// Mark all items as restored.
boolean keepAllIcons = Utilities.isPropertyEnabled(LogConfig.KEEP_ALL_ICONS);
@@ -125,15 +174,16 @@
db.update(Favorites.TABLE_NAME, values, null, null);
// Mark widgets with appropriate restore flag.
- values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
- LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
- LauncherAppWidgetInfo.FLAG_UI_NOT_READY |
- (keepAllIcons ? LauncherAppWidgetInfo.FLAG_RESTORE_STARTED : 0));
+ values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
+ | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
+ | LauncherAppWidgetInfo.FLAG_UI_NOT_READY
+ | (keepAllIcons ? LauncherAppWidgetInfo.FLAG_RESTORE_STARTED : 0));
db.update(Favorites.TABLE_NAME, values, "itemType = ?",
new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)});
- // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp location.
- // Using Long.MIN_VALUE since profile ids can not be negative, so there will be no overlap.
+ // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp
+ // location. Using Long.MIN_VALUE since profile ids can not be negative, so there will
+ // be no overlap.
final long tempLocationOffset = Long.MIN_VALUE;
SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size());
int numTempMigrations = 0;
@@ -191,10 +241,10 @@
private LongSparseArray<Long> getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) {
LongSparseArray<Long> ids = new LongSparseArray<>();
try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? "
- + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})){
- while (c.moveToNext()) {
- ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null);
- }
+ + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})) {
+ while (c.moveToNext()) {
+ ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null);
+ }
}
return ids;
}
@@ -215,7 +265,7 @@
* Returns the profile id used in the favorites table of the provided db.
*/
protected long getDefaultProfileId(SQLiteDatabase db) throws Exception {
- try (Cursor c = db.rawQuery("PRAGMA table_info (favorites)", null)){
+ try (Cursor c = db.rawQuery("PRAGMA table_info (favorites)", null)) {
int nameIndex = c.getColumnIndex(INFO_COLUMN_NAME);
while (c.moveToNext()) {
if (Favorites.PROFILE_ID.equals(c.getString(nameIndex))) {
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 0eb4285..289e0d8 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -54,7 +54,7 @@
* A frame layout which contains a QSB. This internally uses fragment to bind the view, which
* allows it to contain the logic for {@link Fragment#startActivityForResult(Intent, int)}.
*
- * Note: AppWidgetManagerCompat can be disabled using FeatureFlags. In QSB, we should use
+ * Note: WidgetManagerHelper can be disabled using FeatureFlags. In QSB, we should use
* AppWidgetManager directly, so that it keeps working in that case.
*/
public class QsbContainerView extends FrameLayout {
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
new file mode 100644
index 0000000..54b7fb9
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.secondarydisplay;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.content.ComponentName;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Process;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.allapps.AppInfoComparator;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Executors;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Adapter to manage pinned apps and show then in a grid.
+ */
+public class PinnedAppsAdapter extends BaseAdapter implements OnSharedPreferenceChangeListener {
+
+ private static final String PINNED_APPS_KEY = "pinned_apps";
+
+ private final SecondaryDisplayLauncher mLauncher;
+ private final OnClickListener mOnClickListener;
+ private final OnLongClickListener mOnLongClickListener;
+ private final SharedPreferences mPrefs;
+ private final AllAppsStore mAllAppsList;
+ private final AppInfoComparator mAppNameComparator;
+
+ private final Set<ComponentKey> mPinnedApps = new HashSet<>();
+ private final ArrayList<AppInfo> mItems = new ArrayList<>();
+
+ public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+ OnLongClickListener onLongClickListener) {
+ mLauncher = launcher;
+ mOnClickListener = launcher.getItemOnClickListener();
+ mOnLongClickListener = onLongClickListener;
+ mAllAppsList = allAppsStore;
+ mPrefs = launcher.getSharedPreferences(PINNED_APPS_KEY, MODE_PRIVATE);
+ mAppNameComparator = new AppInfoComparator(launcher);
+
+ mAllAppsList.addUpdateListener(this::createFilteredAppsList);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (PINNED_APPS_KEY.equals(key)) {
+ Executors.MODEL_EXECUTOR.submit(() -> {
+ Set<ComponentKey> apps = prefs.getStringSet(key, Collections.emptySet())
+ .stream()
+ .map(this::parseComponentKey)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Executors.MAIN_EXECUTOR.submit(() -> {
+ mPinnedApps.clear();
+ mPinnedApps.addAll(apps);
+ createFilteredAppsList();
+ });
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AppInfo getItem(int position) {
+ return mItems.get(position);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ BubbleTextView icon;
+ if (view instanceof BubbleTextView) {
+ icon = (BubbleTextView) view;
+ } else {
+ icon = (BubbleTextView) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.app_icon, parent, false);
+ icon.setOnClickListener(mOnClickListener);
+ icon.setOnLongClickListener(mOnLongClickListener);
+ icon.setLongPressTimeoutFactor(1f);
+ int padding = mLauncher.getDeviceProfile().edgeMarginPx;
+ icon.setPadding(padding, padding, padding, padding);
+ }
+
+ icon.applyFromApplicationInfo(mItems.get(position));
+ return icon;
+ }
+
+ private void createFilteredAppsList() {
+ mItems.clear();
+ mPinnedApps.stream().map(mAllAppsList::getApp)
+ .filter(Objects::nonNull).forEach(mItems::add);
+ mItems.sort(mAppNameComparator);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Initialized the pinned apps list and starts listening for changes
+ */
+ public void init() {
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ onSharedPreferenceChanged(mPrefs, PINNED_APPS_KEY);
+ }
+
+ /**
+ * Stops listening for any pinned apps changes
+ */
+ public void destroy() {
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ private void update(ItemInfo info, Function<ComponentKey, Boolean> op) {
+ ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user);
+ if (op.apply(key)) {
+ createFilteredAppsList();
+ Set<ComponentKey> copy = new HashSet<>(mPinnedApps);
+ Executors.MODEL_EXECUTOR.submit(() ->
+ mPrefs.edit().putStringSet(PINNED_APPS_KEY,
+ copy.stream().map(this::encode).collect(Collectors.toSet()))
+ .apply());
+ }
+ }
+
+ private ComponentKey parseComponentKey(String string) {
+ try {
+ String[] parts = string.split("#");
+ UserHandle user;
+ if (parts.length > 2) {
+ user = UserCache.INSTANCE.get(mLauncher)
+ .getUserForSerialNumber(Long.parseLong(parts[2]));
+ } else {
+ user = Process.myUserHandle();
+ }
+ ComponentName cn = ComponentName.unflattenFromString(parts[0]);
+ return new ComponentKey(cn, user);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private String encode(ComponentKey key) {
+ return key.componentName.flattenToShortString() + "#"
+ + UserCache.INSTANCE.get(mLauncher).getSerialNumberForUser(key.user);
+ }
+
+ /**
+ * Returns a system shortcut to pin/unpin a shortcut
+ */
+ public SystemShortcut getSystemShortcut(ItemInfo info) {
+ return new PinUnPinShortcut(mLauncher, info,
+ mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user)));
+ }
+
+ private class PinUnPinShortcut extends SystemShortcut<SecondaryDisplayLauncher> {
+
+ private final boolean mIsPinned;
+
+ PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, boolean isPinned) {
+ super(isPinned ? R.drawable.ic_remove_no_shadow : R.drawable.ic_pin,
+ isPinned ? R.string.remove_drop_target_label : R.string.action_add_to_workspace,
+ target, info);
+ mIsPinned = isPinned;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mIsPinned) {
+ update(mItemInfo, mPinnedApps::remove);
+ } else {
+ update(mItemInfo, mPinnedApps::add);
+ }
+ AbstractFloatingView.closeAllOpenViews(mLauncher);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
new file mode 100644
index 0000000..1cc01f4
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.secondarydisplay;
+
+import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewAnimationUtils;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Launcher activity for secondary displays
+ */
+public class SecondaryDisplayLauncher extends BaseDraggingActivity
+ implements BgDataModel.Callbacks {
+
+ private LauncherModel mModel;
+
+ private BaseDragLayer mDragLayer;
+ private AllAppsContainerView mAppsView;
+ private View mAppsButton;
+
+ private PopupDataProvider mPopupDataProvider;
+
+ private boolean mAppDrawerShown = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mModel = LauncherAppState.getInstance(this).getModel();
+ if (getWindow().getDecorView().isAttachedToWindow()) {
+ initUi();
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ initUi();
+ }
+
+ private void initUi() {
+ if (mDragLayer != null) {
+ return;
+ }
+ InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this);
+ InvariantDeviceProfile currentDisplayIdp =
+ new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
+
+ // Pick the device profile with the smaller icon size so that the cached icons are
+ // shown properly
+ if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) {
+ mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this);
+ } else {
+ mDeviceProfile = currentDisplayIdp.getDeviceProfile(this);
+ }
+
+ setContentView(R.layout.secondary_launcher);
+ mDragLayer = findViewById(R.id.drag_layer);
+ mAppsView = findViewById(R.id.apps_view);
+ mAppsButton = findViewById(R.id.all_apps_button);
+
+ mPopupDataProvider = new PopupDataProvider(
+ mAppsView.getAppsStore()::updateNotificationDots);
+
+ mModel.addCallbacksAndLoad(this);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+
+ if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ // Hide keyboard.
+ final View v = getWindow().peekDecorView();
+ if (v != null && v.getWindowToken() != null) {
+ getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
+ v.getWindowToken(), 0);
+ }
+ }
+
+ // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
+ showAppDrawer(false);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (finishAutoCancelActionMode()) {
+ return;
+ }
+
+ // Note: There should be at most one log per method call. This is enforced implicitly
+ // by using if-else statements.
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+ if (topView != null && topView.onBackPressed()) {
+ // Handled by the floating view.
+ } else {
+ showAppDrawer(false);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mModel.removeCallbacks(this);
+ }
+
+ public boolean isAppDrawerShown() {
+ return mAppDrawerShown;
+ }
+
+ public AllAppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
+ @Override
+ public <T extends View> T getOverviewPanel() {
+ return null;
+ }
+
+ @Override
+ public View getRootView() {
+ return mDragLayer;
+ }
+
+ @Override
+ public ActivityOptions getActivityLaunchOptions(View v) {
+ return null;
+ }
+
+ @Override
+ protected void reapplyUi() { }
+
+ @Override
+ public BaseDragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ @Override
+ public int getPageToBindSynchronously() {
+ return 0;
+ }
+
+ @Override
+ public void clearPendingBinds() { }
+
+ @Override
+ public void startBinding() { }
+
+ @Override
+ public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+
+ @Override
+ public void bindScreens(IntArray orderedScreenIds) { }
+
+ @Override
+ public void finishFirstPageBind(ViewOnDrawExecutor executor) {
+ if (executor != null) {
+ executor.onLoadAnimationCompleted();
+ }
+ }
+
+ @Override
+ public void finishBindingItems(int pageBoundFirst) { }
+
+ @Override
+ public void preAddApps() { }
+
+ @Override
+ public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
+ ArrayList<ItemInfo> addAnimated) { }
+
+ @Override
+ public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
+ mAppsView.getAppsStore().updatePromiseAppProgress(app);
+ }
+
+ @Override
+ public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
+
+ @Override
+ public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+
+ @Override
+ public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+ @Override
+ public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+
+ @Override
+ public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets) { }
+
+ @Override
+ public void onPageBoundSynchronously(int page) { }
+
+ @Override
+ public void executeOnNextDraw(ViewOnDrawExecutor executor) {
+ executor.attachTo(getDragLayer(), false, null);
+ }
+
+ /**
+ * Called when apps-button is clicked
+ */
+ public void onAppsButtonClicked(View v) {
+ showAppDrawer(true);
+ }
+
+ /**
+ * Show/hide app drawer card with animation.
+ */
+ public void showAppDrawer(boolean show) {
+ if (show == mAppDrawerShown) {
+ return;
+ }
+
+ float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
+ float closeR = Themes.getDialogCornerRadius(this);
+ float startR = mAppsButton.getWidth() / 2f;
+
+ float[] buttonPos = new float[] { startR, startR};
+ mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
+ mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
+ final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
+ (int) buttonPos[0], (int) buttonPos[1],
+ show ? closeR : openR, show ? openR : closeR);
+
+ if (show) {
+ mAppDrawerShown = true;
+ mAppsView.setVisibility(View.VISIBLE);
+ mAppsButton.setVisibility(View.INVISIBLE);
+ } else {
+ mAppDrawerShown = false;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAppsView.setVisibility(View.INVISIBLE);
+ mAppsButton.setVisibility(View.VISIBLE);
+ mAppsView.getSearchUiManager().resetSearch();
+ }
+ });
+ }
+ animator.start();
+ }
+
+ @Override
+ public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
+ mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
+ }
+
+ @Override
+ public void bindAllApplications(AppInfo[] apps) {
+ mAppsView.getAppsStore().setApps(apps);
+ }
+
+ public PopupDataProvider getPopupDataProvider() {
+ return mPopupDataProvider;
+ }
+
+ @Override
+ public OnClickListener getItemOnClickListener() {
+ return this::onIconClicked;
+ }
+
+ private void onIconClicked(View v) {
+ // Make sure that rogue clicks don't get through while allapps is launching, or after the
+ // view has detached (it's possible for this to happen if the view is removed mid touch).
+ if (v.getWindowToken() == null) return;
+
+ Object tag = v.getTag();
+ if (tag instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) tag;
+ Intent intent;
+ if (item instanceof PromiseAppInfo) {
+ PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+ intent = promiseAppInfo.getMarketIntent(this);
+ } else {
+ intent = item.getIntent();
+ }
+ if (intent == null) {
+ throw new IllegalArgumentException("Input must have a valid intent");
+ }
+ startActivitySafely(v, intent, item, CONTAINER_ALL_APPS);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
new file mode 100644
index 0000000..936d377
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.secondarydisplay;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.GridView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * DragLayer for Secondary launcher
+ */
+public class SecondaryDragLayer extends BaseDragLayer<SecondaryDisplayLauncher> {
+
+ private View mAllAppsButton;
+ private AllAppsContainerView mAppsView;
+
+ private GridView mWorkspace;
+ private PinnedAppsAdapter mPinnedAppsAdapter;
+
+ public SecondaryDragLayer(Context context, AttributeSet attrs) {
+ super(context, attrs, 1 /* alphaChannelCount */);
+ recreateControllers();
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[] {new CloseAllAppsTouchController()};
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAllAppsButton = findViewById(R.id.all_apps_button);
+
+ mAppsView = findViewById(R.id.apps_view);
+ mAppsView.setOnIconLongClickListener(this::onIconLongClicked);
+
+ // Setup workspace
+ mWorkspace = findViewById(R.id.workspace_grid);
+ mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(),
+ this::onIconLongClicked);
+ mWorkspace.setAdapter(mPinnedAppsAdapter);
+ mWorkspace.setNumColumns(mActivity.getDeviceProfile().inv.numColumns);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mPinnedAppsAdapter.init();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPinnedAppsAdapter.destroy();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(width, height);
+
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ InvariantDeviceProfile idp = grid.inv;
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child == mAppsView) {
+ int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
+ + grid.cellLayoutPaddingLeftRightPx);
+ int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
+
+ int appsWidth = Math.min(width, maxWidth);
+ int appsHeight = Math.round(appsWidth * (float) height / (float) width);
+
+ mAppsView.measure(
+ makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
+
+ } else if (child == mAllAppsButton) {
+ int appsButtonSpec = makeMeasureSpec(grid.iconSizePx, EXACTLY);
+ mAllAppsButton.measure(appsButtonSpec, appsButtonSpec);
+
+ } else if (child == mWorkspace) {
+ measureChildWithMargins(mWorkspace, widthMeasureSpec, 0, heightMeasureSpec,
+ grid.iconSizePx + grid.edgeMarginPx);
+
+ } else {
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ }
+ }
+ }
+
+ private class CloseAllAppsTouchController implements TouchController {
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (!mActivity.isAppDrawerShown()) {
+ return false;
+ }
+
+ if (AbstractFloatingView.getTopOpenView(mActivity) != null) {
+ return false;
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN
+ && !isEventOverView(mActivity.getAppsView(), ev)) {
+ mActivity.showAppDrawer(false);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private boolean onIconLongClicked(View v) {
+ if (!(v instanceof BubbleTextView)) {
+ return false;
+ }
+ if (PopupContainerWithArrow.getOpen(mActivity) != null) {
+ // There is already an items container open, so don't open this one.
+ v.clearFocus();
+ return false;
+ }
+ ItemInfo item = (ItemInfo) v.getTag();
+ if (!ShortcutUtil.supportsShortcuts(item)) {
+ return false;
+ }
+ final PopupContainerWithArrow container =
+ (PopupContainerWithArrow) mActivity.getLayoutInflater().inflate(
+ R.layout.popup_container, mActivity.getDragLayer(), false);
+
+ container.populateAndShow((BubbleTextView) v,
+ mActivity.getPopupDataProvider().getShortcutCountForItem(item),
+ Collections.emptyList(),
+ Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item),
+ APP_INFO.getShortcut(mActivity, item)));
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index a9242f9..8dc2e61 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.settings;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -24,42 +26,45 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.ArrayMap;
-import android.util.ArraySet;
+import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceDataStore;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.FlagTogglerPrefUi;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceDataStore;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
+import java.util.stream.Collectors;
/**
* Dev-build only UI allowing developers to toggle flag settings and plugins.
* See {@link FeatureFlags}.
*/
@TargetApi(Build.VERSION_CODES.O)
-public class DeveloperOptionsFragment extends PreferenceFragment {
+public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
@@ -91,6 +96,7 @@
initFlags();
loadPluginPrefs();
+ maybeAddSandboxCategory();
}
@Override
@@ -154,44 +160,78 @@
PackageManager pm = getContext().getPackageManager();
Set<String> pluginActions = manager.getPluginActions();
- ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
+
+ ArrayMap<Pair<String, String>, ArrayList<Pair<String, ServiceInfo>>> plugins =
+ new ArrayMap<>();
+
+ Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
+ new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
+ .stream()
+ .map(pi -> pi.packageName)
+ .collect(Collectors.toSet());
+
for (String action : pluginActions) {
String name = toName(action);
List<ResolveInfo> result = pm.queryIntentServices(
- new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
+ new Intent(action), MATCH_DISABLED_COMPONENTS);
for (ResolveInfo info : result) {
String packageName = info.serviceInfo.packageName;
- if (!plugins.containsKey(packageName)) {
- plugins.put(packageName, new ArraySet<>());
+ if (!pluginPermissionApps.contains(packageName)) {
+ continue;
}
- plugins.get(packageName).add(name);
+
+ Pair<String, String> key = Pair.create(packageName, info.serviceInfo.processName);
+ if (!plugins.containsKey(key)) {
+ plugins.put(key, new ArrayList<>());
+ }
+ plugins.get(key).add(Pair.create(name, info.serviceInfo));
}
}
- List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION},
- PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
- PreferenceDataStore enabled = manager.getPluginEnabler();
- apps.forEach(app -> {
- if (!plugins.containsKey(app.packageName)) return;
- SwitchPreference pref = new PluginPreference(prefContext, app, enabled);
- pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
- mPluginsCategory.addPreference(pref);
+ PreferenceDataStore enabler = manager.getPluginEnabler();
+ plugins.forEach((key, si) -> {
+ String packageName = key.first;
+ List<ComponentName> componentNames = si.stream()
+ .map(p -> new ComponentName(packageName, p.second.name))
+ .collect(Collectors.toList());
+ if (!componentNames.isEmpty()) {
+ SwitchPreference pref = new PluginPreference(
+ prefContext, si.get(0).second.applicationInfo, enabler, componentNames);
+ pref.setSummary("Plugins: "
+ + si.stream().map(p -> p.first).collect(Collectors.joining(", ")));
+ mPluginsCategory.addPreference(pref);
+ }
});
}
- private String toString(ArraySet<String> plugins) {
- StringBuilder b = new StringBuilder();
- for (String string : plugins) {
- if (b.length() != 0) {
- b.append(", ");
- }
- b.append(string);
+ private void maybeAddSandboxCategory() {
+ Context context = getContext();
+ if (context == null) {
+ return;
}
- return b.toString();
+ Intent launchSandboxIntent =
+ new Intent("com.android.quickstep.action.GESTURE_SANDBOX")
+ .setPackage(context.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (launchSandboxIntent.resolveActivity(context.getPackageManager()) == null) {
+ return;
+ }
+ PreferenceCategory sandboxCategory = newCategory("Sandbox");
+ Preference launchSandboxPreference = new Preference(context);
+ launchSandboxPreference.setKey("launchSandbox");
+ launchSandboxPreference.setTitle("Launch Gesture Navigation Sandbox");
+ launchSandboxPreference.setSummary(
+ "This provides tutorials and a place to practice navigation gestures.");
+ launchSandboxPreference.setOnPreferenceClickListener(preference -> {
+ startActivity(launchSandboxIntent);
+ return true;
+ });
+ sandboxCategory.addPreference(launchSandboxPreference);
}
private String toName(String action) {
- String str = action.replace("com.android.systemui.action.PLUGIN_", "");
+ String str = action.replace("com.android.systemui.action.PLUGIN_", "")
+ .replace("com.android.launcher3.action.PLUGIN_", "");
StringBuilder b = new StringBuilder();
for (String s : str.split("_")) {
if (b.length() != 0) {
@@ -205,18 +245,20 @@
private static class PluginPreference extends SwitchPreference {
private final boolean mHasSettings;
- private final PackageInfo mInfo;
private final PreferenceDataStore mPluginEnabler;
+ private final String mPackageName;
+ private final List<ComponentName> mComponentNames;
- public PluginPreference(Context prefContext, PackageInfo info,
- PreferenceDataStore pluginEnabler) {
+ PluginPreference(Context prefContext, ApplicationInfo info,
+ PreferenceDataStore pluginEnabler, List<ComponentName> componentNames) {
super(prefContext);
PackageManager pm = prefContext.getPackageManager();
mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
.setPackage(info.packageName), 0) != null;
- mInfo = info;
+ mPackageName = info.packageName;
+ mComponentNames = componentNames;
mPluginEnabler = pluginEnabler;
- setTitle(info.applicationInfo.loadLabel(pm));
+ setTitle(info.loadLabel(pm));
setChecked(isPluginEnabled());
setWidgetLayoutResource(R.layout.switch_preference_with_settings);
}
@@ -227,9 +269,7 @@
}
private boolean isPluginEnabled() {
- for (int i = 0; i < mInfo.services.length; i++) {
- ComponentName componentName = new ComponentName(mInfo.packageName,
- mInfo.services[i].name);
+ for (ComponentName componentName : mComponentNames) {
if (!isEnabled(componentName)) {
return false;
}
@@ -240,17 +280,14 @@
@Override
protected boolean persistBoolean(boolean isEnabled) {
boolean shouldSendBroadcast = false;
- for (int i = 0; i < mInfo.services.length; i++) {
- ComponentName componentName = new ComponentName(mInfo.packageName,
- mInfo.services[i].name);
-
+ for (ComponentName componentName : mComponentNames) {
if (isEnabled(componentName) != isEnabled) {
mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled);
shouldSendBroadcast = true;
}
}
if (shouldSendBroadcast) {
- final String pkg = mInfo.packageName;
+ final String pkg = mPackageName;
final Intent intent = new Intent(PLUGIN_CHANGED,
pkg != null ? Uri.fromParts("package", pkg, null) : null);
getContext().sendBroadcast(intent);
@@ -268,8 +305,7 @@
: View.GONE);
holder.findViewById(R.id.settings).setOnClickListener(v -> {
ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
- new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
- mInfo.packageName), 0);
+ new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName), 0);
if (result != null) {
v.getContext().startActivity(new Intent().setComponent(
new ComponentName(result.activityInfo.packageName,
@@ -278,7 +314,7 @@
});
holder.itemView.setOnLongClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- intent.setData(Uri.fromParts("package", mInfo.packageName, null));
+ intent.setData(Uri.fromParts("package", mPackageName, null));
getContext().startActivity(intent);
return true;
});
diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java
index f30470a..a91303a 100644
--- a/src/com/android/launcher3/settings/NotificationDotsPreference.java
+++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java
@@ -20,7 +20,6 @@
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.DialogFragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -30,13 +29,14 @@
import android.util.AttributeSet;
import android.view.View;
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
import com.android.launcher3.R;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.SecureSettingsObserver;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
-
/**
* A {@link Preference} for indicating notification dots status.
* Also has utility methods for updating UI based on dots status changes.
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 8d2db78..12085c8 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -23,9 +23,6 @@
import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
-import android.app.Activity;
-import android.app.DialogFragment;
-import android.app.Fragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -35,10 +32,14 @@
import android.text.TextUtils;
import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
-import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
import androidx.preference.PreferenceGroup.PreferencePositionCallback;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
@@ -54,7 +55,7 @@
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
*/
-public class SettingsActivity extends Activity
+public class SettingsActivity extends FragmentActivity
implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
SharedPreferences.OnSharedPreferenceChangeListener{
@@ -83,15 +84,16 @@
args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey);
}
- Fragment f = Fragment.instantiate(
- this, getString(R.string.settings_fragment_name), args);
+ final FragmentManager fm = getSupportFragmentManager();
+ final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(),
+ getString(R.string.settings_fragment_name));
+ f.setArguments(args);
// Display the fragment as the main content.
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, f)
- .commit();
+ fm.beginTransaction().replace(android.R.id.content, f).commit();
}
Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
}
+
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (GRID_OPTIONS_PREFERENCE_KEY.equals(key)) {
@@ -115,41 +117,39 @@
}
private boolean startFragment(String fragment, Bundle args, String key) {
- if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) {
+ if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) {
// Sometimes onClick can come after onPause because of being posted on the handler.
// Skip starting new fragments in that case.
return false;
}
- Fragment f = Fragment.instantiate(this, fragment, args);
+ final FragmentManager fm = getSupportFragmentManager();
+ final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment);
+ f.setArguments(args);
if (f instanceof DialogFragment) {
- ((DialogFragment) f).show(getFragmentManager(), key);
+ ((DialogFragment) f).show(getSupportFragmentManager(), key);
} else {
- getFragmentManager()
- .beginTransaction()
- .replace(android.R.id.content, f)
- .addToBackStack(key)
- .commit();
+ fm.beginTransaction().replace(android.R.id.content, f).addToBackStack(key).commit();
}
return true;
}
@Override
public boolean onPreferenceStartFragment(
- PreferenceFragment preferenceFragment, Preference pref) {
+ PreferenceFragmentCompat preferenceFragment, Preference pref) {
return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
}
@Override
- public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+ public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
Bundle args = new Bundle();
- args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+ args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey());
return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey());
}
/**
* This fragment shows the launcher preferences.
*/
- public static class LauncherSettingsFragment extends PreferenceFragment {
+ public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
private SecureSettingsObserver mNotificationDotsObserver;
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 9274d44..9cc7d8f 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -27,8 +27,8 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
/**
@@ -111,8 +111,8 @@
// TODO: Add the click handler to this view directly and not the child view.
mBubbleText.setOnClickListener(container.getItemClickListener());
- mBubbleText.setOnLongClickListener(container);
- mBubbleText.setOnTouchListener(container);
+ mBubbleText.setOnLongClickListener(container.getItemDragHandler());
+ mBubbleText.setOnTouchListener(container.getItemDragHandler());
}
/**
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index ee97641..3e59b61 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -25,7 +25,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.BitmapRenderer;
/**
* Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size.
@@ -39,16 +41,27 @@
mPositionShift = shift;
}
+ @Override
public Bitmap createDragBitmap() {
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
+ return BitmapRenderer.createHardwareBitmap(
+ size + blurSizeOutline,
+ size + blurSizeOutline,
+ (c) -> drawDragViewOnBackground(c, size));
+ } else {
+ return createDragBitmapLegacy();
+ }
+ }
+
+ private Bitmap createDragBitmapLegacy() {
Drawable d = mView.getBackground();
Rect bounds = getDrawableBounds(d);
-
int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
final Bitmap b = Bitmap.createBitmap(
size + blurSizeOutline,
size + blurSizeOutline,
Bitmap.Config.ARGB_8888);
-
Canvas canvas = new Canvas(b);
canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
@@ -57,6 +70,16 @@
return b;
}
+ private void drawDragViewOnBackground(Canvas canvas, float size) {
+ Drawable d = mView.getBackground();
+ Rect bounds = getDrawableBounds(d);
+
+ canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
+ canvas.scale(size / bounds.width(), size / bounds.height(), 0, 0);
+ canvas.translate(bounds.left, bounds.top);
+ d.draw(canvas);
+ }
+
@Override
public float getScaleAndPosition(Bitmap preview, int[] outPos) {
Launcher launcher = Launcher.getLauncher(mView.getContext());
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 70665ca..fa1a85f 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -1,6 +1,7 @@
package com.android.launcher3.shortcuts;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
@@ -29,6 +30,14 @@
return componentName.getClassName();
}
+ /**
+ * Creates a {@link ShortcutRequest} for this key
+ */
+ public ShortcutRequest buildRequest(Context context) {
+ return new ShortcutRequest(context, user)
+ .forPackage(componentName.getPackageName(), getId());
+ }
+
public static ShortcutKey fromInfo(ShortcutInfo shortcutInfo) {
return new ShortcutKey(shortcutInfo.getPackage(), shortcutInfo.getUserHandle(),
shortcutInfo.getId());
diff --git a/src/com/android/launcher3/shortcuts/ShortcutRequest.java b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
new file mode 100644
index 0000000..5291ce4
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutRequest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shortcuts;
+
+import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class to streamline Shortcut query
+ */
+public class ShortcutRequest {
+
+ private static final String TAG = "ShortcutRequest";
+
+ public static final int ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
+ public static final int PUBLISHED = ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_MANIFEST;
+ public static final int PINNED = ShortcutQuery.FLAG_MATCH_PINNED;
+
+ private final ShortcutQuery mQuery = GO_DISABLE_WIDGETS ? null : new ShortcutQuery();
+
+ private final Context mContext;
+ private final UserHandle mUserHandle;
+
+ boolean mFailed = false;
+
+ public ShortcutRequest(Context context, UserHandle userHandle) {
+ mContext = context;
+ mUserHandle = userHandle;
+ }
+
+ /** @see #forPackage(String, List) */
+ public ShortcutRequest forPackage(String packageName) {
+ return forPackage(packageName, (List<String>) null);
+ }
+
+ /** @see #forPackage(String, List) */
+ public ShortcutRequest forPackage(String packageName, String... shortcutIds) {
+ return forPackage(packageName, Arrays.asList(shortcutIds));
+ }
+
+ /**
+ * @param shortcutIds If null, match all shortcuts, otherwise only match the given id's.
+ * @return A list of ShortcutInfo's associated with the given package.
+ */
+ public ShortcutRequest forPackage(String packageName, @Nullable List<String> shortcutIds) {
+ if (!GO_DISABLE_WIDGETS && packageName != null) {
+ mQuery.setPackage(packageName);
+ mQuery.setShortcutIds(shortcutIds);
+ }
+ return this;
+ }
+
+ public ShortcutRequest withContainer(@Nullable ComponentName activity) {
+ if (!GO_DISABLE_WIDGETS) {
+ if (activity == null) {
+ mFailed = true;
+ } else {
+ mQuery.setActivity(activity);
+ }
+ }
+ return this;
+ }
+
+ public QueryResult query(int flags) {
+ if (GO_DISABLE_WIDGETS || mFailed) {
+ return QueryResult.DEFAULT;
+ }
+ mQuery.setQueryFlags(flags);
+
+ try {
+ return new QueryResult(mContext.getSystemService(LauncherApps.class)
+ .getShortcuts(mQuery, mUserHandle));
+ } catch (SecurityException | IllegalStateException e) {
+ Log.e(TAG, "Failed to query for shortcuts", e);
+ return QueryResult.DEFAULT;
+ }
+ }
+
+ public static class QueryResult extends ArrayList<ShortcutInfo> {
+
+ static final QueryResult DEFAULT = new QueryResult(GO_DISABLE_WIDGETS);
+
+ private final boolean mWasSuccess;
+
+ QueryResult(List<ShortcutInfo> result) {
+ super(result == null ? Collections.emptyList() : result);
+ mWasSuccess = true;
+ }
+
+ QueryResult(boolean wasSuccess) {
+ mWasSuccess = wasSuccess;
+ }
+
+
+ public boolean wasSuccess() {
+ return mWasSuccess;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
new file mode 100644
index 0000000..290dbb6
--- /dev/null
+++ b/src/com/android/launcher3/states/HintState.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.states;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen.
+ */
+public class HintState extends LauncherState {
+
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE
+ | FLAG_HAS_SYS_UI_SCRIM;
+
+ public HintState(int id) {
+ super(id, ContainerType.DEFAULT_CONTAINERTYPE, STATE_FLAGS);
+ }
+
+ @Override
+ public int getTransitionDuration(Launcher launcher) {
+ return 80;
+ }
+
+ @Override
+ public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
+ return new ScaleAndTranslation(0.9f, 0, 0);
+ }
+
+ @Override
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ // Treat the QSB as part of the hotseat so they move together.
+ return getHotseatScaleAndTranslation(launcher);
+ }
+
+ @Override
+ public void onStateTransitionEnd(Launcher launcher) {
+ launcher.getStateManager().goToState(NORMAL);
+ Workspace workspace = launcher.getWorkspace();
+ workspace.post(workspace::moveToDefaultScreen);
+ }
+}
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
deleted file mode 100644
index a23cd6d..0000000
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.states;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Utility class to sending state handling logic to Launcher from within the same process.
- *
- * Extending {@link Binder} ensures that the platform maintains a single instance of each object
- * which allows this object to safely navigate the system process.
- */
-public abstract class InternalStateHandler extends Binder {
-
- public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
-
- private static final Scheduler sScheduler = new Scheduler();
-
- /**
- * Initializes the handler when the launcher is ready.
- * @return true if the handler wants to stay alive.
- */
- protected abstract boolean init(Launcher launcher, boolean alreadyOnHome);
-
- public final Intent addToIntent(Intent intent) {
- Bundle extras = new Bundle();
- extras.putBinder(EXTRA_STATE_HANDLER, this);
- intent.putExtras(extras);
- return intent;
- }
-
- public final void initWhenReady() {
- sScheduler.schedule(this);
- }
-
- public boolean clearReference() {
- return sScheduler.clearReference(this);
- }
-
- public static boolean hasPending() {
- return sScheduler.hasPending();
- }
-
- public static boolean handleCreate(Launcher launcher, Intent intent) {
- return handleIntent(launcher, intent, false, false);
- }
-
- public static boolean handleNewIntent(Launcher launcher, Intent intent, boolean alreadyOnHome) {
- return handleIntent(launcher, intent, alreadyOnHome, true);
- }
-
- private static boolean handleIntent(
- Launcher launcher, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
- boolean result = false;
- if (intent != null && intent.getExtras() != null) {
- IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
- if (stateBinder instanceof InternalStateHandler) {
- InternalStateHandler handler = (InternalStateHandler) stateBinder;
- if (!handler.init(launcher, alreadyOnHome)) {
- intent.getExtras().remove(EXTRA_STATE_HANDLER);
- }
- result = true;
- }
- }
- if (!result && !explicitIntent) {
- result = sScheduler.initIfPending(launcher, alreadyOnHome);
- }
- return result;
- }
-
- private static class Scheduler implements Runnable {
-
- private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null);
-
- public void schedule(InternalStateHandler handler) {
- synchronized (this) {
- mPendingHandler = new WeakReference<>(handler);
- }
- MAIN_EXECUTOR.execute(this);
- }
-
- @Override
- public void run() {
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app == null) {
- return;
- }
- Callbacks cb = app.getModel().getCallback();
- if (!(cb instanceof Launcher)) {
- return;
- }
- Launcher launcher = (Launcher) cb;
- initIfPending(launcher, launcher.isStarted());
- }
-
- public boolean initIfPending(Launcher launcher, boolean alreadyOnHome) {
- InternalStateHandler pendingHandler = mPendingHandler.get();
- if (pendingHandler != null) {
- if (!pendingHandler.init(launcher, alreadyOnHome)) {
- clearReference(pendingHandler);
- }
- return true;
- }
- return false;
- }
-
- public boolean clearReference(InternalStateHandler handler) {
- synchronized (this) {
- if (mPendingHandler.get() == handler) {
- mPendingHandler.clear();
- return true;
- }
- return false;
- }
- }
-
- public boolean hasPending() {
- return mPendingHandler.get() != null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index abf90e2..fae0fe2 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -17,21 +17,35 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
+import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.provider.Settings;
+import android.view.MotionEvent;
+import android.view.Surface;
import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.UiThreadHelper;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Utility class to manage launcher rotation
*/
@@ -39,9 +53,19 @@
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
+ public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
+ private final ContentResolver mContentResolver;
+
+ /**
+ * Listener to receive changes when {@link #FIXED_ROTATION_TRANSFORM_SETTING_NAME} flag changes.
+ */
+ public interface ForcedRotationChangedListener {
+ void onForcedRotationChanged(boolean isForcedRotation);
+ }
+
public static boolean getAllowRotationDefaultValue() {
- // If the device was scaled, used the original dimensions to determine if rotation
- // is allowed of not.
+ // If the device's pixel density was scaled (usually via settings for A11y), use the
+ // original dimensions to determine if rotation is allowed of not.
Resources res = Resources.getSystem();
int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
* res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE;
@@ -53,13 +77,18 @@
public static final int REQUEST_LOCK = 2;
private final Launcher mLauncher;
- private final SharedPreferences mPrefs;
+ private final SharedPreferences mSharedPrefs;
+ private final SharedPreferences mFeatureFlagsPrefs;
private boolean mIgnoreAutoRotateSettings;
private boolean mAutoRotateEnabled;
+ private boolean mForcedRotation;
+ private List<ForcedRotationChangedListener> mForcedRotationChangedListeners = new ArrayList<>();
/**
- * Rotation request made by {@link InternalStateHandler}. This supersedes any other request.
+ * Rotation request made by
+ * {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}.
+ * This supersedes any other request.
*/
private int mStateHandlerRequest = REQUEST_NONE;
/**
@@ -84,13 +113,52 @@
// On large devices we do not handle auto-rotate differently.
mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
if (!mIgnoreAutoRotateSettings) {
- mPrefs = Utilities.getPrefs(mLauncher);
- mPrefs.registerOnSharedPreferenceChangeListener(this);
- mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+ mSharedPrefs = Utilities.getPrefs(mLauncher);
+ mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+ mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
} else {
- mPrefs = null;
+ mSharedPrefs = null;
}
+
+ mContentResolver = launcher.getContentResolver();
+ mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mLauncher);
+ mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
+ updateForcedRotation(true);
+ }
+
+ /**
+ * @param setValueFromPrefs If true, then {@link #mForcedRotation} will get set to the value
+ * from the home developer settings. Otherwise it will not.
+ * This is primarily to allow tests to set their own conditions.
+ */
+ private void updateForcedRotation(boolean setValueFromPrefs) {
+ boolean isForcedRotation = mFeatureFlagsPrefs
+ .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
+ && !getAllowRotationDefaultValue();
+ if (mForcedRotation == isForcedRotation) {
+ return;
+ }
+ if (setValueFromPrefs) {
+ mForcedRotation = isForcedRotation;
+ }
+ UI_HELPER_EXECUTOR.execute(
+ () -> Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
+ mForcedRotation ? 1 : 0));
+ for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
+ listener.onForcedRotationChanged(mForcedRotation);
+ }
+ }
+
+ /**
+ * will not be called when first registering the listener.
+ */
+ public void addForcedRotationCallback(ForcedRotationChangedListener listener) {
+ mForcedRotationChangedListeners.add(listener);
+ }
+
+ public void removeForcedRotationCallback(ForcedRotationChangedListener listener) {
+ mForcedRotationChangedListeners.remove(listener);
}
public void setRotationHadDifferentUI(boolean rotationHasDifferentUI) {
@@ -118,8 +186,13 @@
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
+ updateForcedRotation(true);
+ return;
+ }
+
boolean wasRotationEnabled = mAutoRotateEnabled;
- mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+ mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue());
if (mAutoRotateEnabled != wasRotationEnabled) {
@@ -155,6 +228,10 @@
public void forceAllowRotationForTesting(boolean allowRotation) {
mIgnoreAutoRotateSettings =
allowRotation || mLauncher.getResources().getBoolean(R.bool.allow_rotation);
+ // TODO(b/150214193) Tests currently expect launcher to be able to be rotated
+ // Modify tests for this new behavior
+ mForcedRotation = !allowRotation;
+ updateForcedRotation(false);
notifyChange();
}
@@ -169,9 +246,11 @@
public void destroy() {
if (!mDestroyed) {
mDestroyed = true;
- if (mPrefs != null) {
- mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ if (mSharedPrefs != null) {
+ mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
+ mForcedRotationChangedListeners.clear();
+ mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
}
@@ -181,7 +260,10 @@
}
final int activityFlags;
- if (mStateHandlerRequest != REQUEST_NONE) {
+ if (mForcedRotation) {
+ // TODO(b/150214193) Properly address this
+ activityFlags = SCREEN_ORIENTATION_PORTRAIT;
+ } else if (mStateHandlerRequest != REQUEST_NONE) {
activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
} else if (mCurrentTransitionRequest != REQUEST_NONE) {
@@ -203,6 +285,138 @@
}
}
+ public static int getDegreesFromRotation(int rotation) {
+ int degrees;
+ switch (rotation) {
+ case Surface.ROTATION_90:
+ degrees = 90;
+ break;
+ case Surface.ROTATION_180:
+ degrees = 180;
+ break;
+ case Surface.ROTATION_270:
+ degrees = 270;
+ break;
+ case Surface.ROTATION_0:
+ default:
+ degrees = 0;
+ break;
+ }
+ return degrees;
+ }
+
+ public static int getRotationFromDegrees(float degrees) {
+ int threshold = 70;
+ if (degrees >= (360 - threshold) || degrees < (threshold)) {
+ return Surface.ROTATION_0;
+ } else if (degrees < (90 + threshold)) {
+ return Surface.ROTATION_270;
+ } else if (degrees < 180 + threshold) {
+ return Surface.ROTATION_180;
+ } else {
+ return Surface.ROTATION_90;
+ }
+ }
+
+ /**
+ * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
+ * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
+ * A value of 0 means no rotation has been applied
+ */
+ public static int deltaRotation(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
+ /**
+ * For landscape, since the navbar is already in a vertical position, we don't have to do any
+ * rotations as the change in Y coordinate is what is read. We only flip the sign of the
+ * y coordinate to make it match existing behavior of swipe to the top to go previous
+ */
+ public static void transformEventForNavBar(MotionEvent ev, boolean inverse) {
+ // TODO(b/151269990): Use a temp matrix
+ Matrix m = new Matrix();
+ m.setScale(1, -1);
+ if (inverse) {
+ Matrix inv = new Matrix();
+ m.invert(inv);
+ ev.transform(inv);
+ } else {
+ ev.transform(m);
+ }
+ }
+
+ /**
+ * Creates a matrix to transform the given motion event specified by degrees.
+ * If {@param inverse} is {@code true}, the inverse of that matrix will be applied
+ */
+ public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
+ Matrix transform = new Matrix();
+ // TODO(b/151269990): Use a temp matrix
+ transform.setRotate(degrees);
+ if (inverse) {
+ Matrix inv = new Matrix();
+ transform.invert(inv);
+ ev.transform(inv);
+ } else {
+ ev.transform(transform);
+ }
+ // TODO: Add scaling back in based on degrees
+// if (getWidth() > 0 && getHeight() > 0) {
+// float scale = ((float) getWidth()) / getHeight();
+// transform.postScale(scale, 1 / scale);
+// }
+ }
+
+ /**
+ * TODO(b/149658423): Have {@link com.android.quickstep.OrientationTouchTransformer
+ * also use this}
+ */
+ public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
+ Matrix m = new Matrix();
+ // TODO(b/151269990): Use a temp matrix
+ switch (displayRotation) {
+ case Surface.ROTATION_0:
+ return m;
+ case Surface.ROTATION_90:
+ m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
+ m.postTranslate(0, screenWidth);
+ break;
+ case Surface.ROTATION_270:
+ m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
+ m.postTranslate(screenHeight, 0);
+ break;
+ }
+ return m;
+ }
+
+ public static void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight,
+ int displayRotation) {
+ Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
+ m.mapRect(src);
+ }
+
+ public static void mapInverseRectFromNormalOrientation(RectF src, int screenWidth,
+ int screenHeight, int displayRotation) {
+ Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
+ Matrix inverse = new Matrix();
+ m.invert(inverse);
+ inverse.mapRect(src);
+ }
+
+ public static void getTargetRectForRotation(Rect srcOut, int screenWidth, int screenHeight,
+ int displayRotation) {
+ RectF wrapped = new RectF(srcOut);
+ Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
+ m.mapRect(wrapped);
+ wrapped.round(srcOut);
+ }
+
+ public static boolean isRotationLandscape(int rotation) {
+ return rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90;
+ }
+
@Override
public String toString() {
return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index be3e6c9..97c67c5 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.states;
-import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import android.graphics.Rect;
@@ -37,7 +36,12 @@
FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS | FLAG_HIDE_BACK_BUTTON;
public SpringLoadedState(int id) {
- super(id, ContainerType.OVERVIEW, SPRING_LOADED_TRANSITION_MS, STATE_FLAGS);
+ super(id, ContainerType.OVERVIEW, STATE_FLAGS);
+ }
+
+ @Override
+ public int getTransitionDuration(Launcher launcher) {
+ return 150;
}
@Override
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index c6de9ca..4e49c6e 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -17,27 +17,44 @@
import static android.graphics.Bitmap.Config.ARGB_8888;
+import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import android.annotation.TargetApi;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.Insets;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
+import android.system.Os;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.annotation.Keep;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.util.ResourceBasedOverride;
import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+/**
+ * Class to handle requests from tests
+ */
+@TargetApi(Build.VERSION_CODES.Q)
public class TestInformationHandler implements ResourceBasedOverride {
public static TestInformationHandler newInstance(Context context) {
@@ -48,7 +65,6 @@
protected Context mContext;
protected DeviceProfile mDeviceProfile;
protected LauncherAppState mLauncherAppState;
- protected Launcher mLauncher;
private static LinkedList mLeaks;
public void init(Context context) {
@@ -56,36 +72,31 @@
mDeviceProfile = InvariantDeviceProfile.INSTANCE.
get(context).getDeviceProfile(context);
mLauncherAppState = LauncherAppState.getInstanceNoCreate();
- mLauncher = mLauncherAppState != null ?
- (Launcher) mLauncherAppState.getModel().getCallback() : null;
}
public Bundle call(String method) {
final Bundle response = new Bundle();
switch (method) {
case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
- if (mLauncher == null) return null;
-
- final float progress = LauncherState.OVERVIEW.getVerticalProgress(mLauncher)
- - LauncherState.ALL_APPS.getVerticalProgress(mLauncher);
- final float distance = mLauncher.getAllAppsController().getShiftRange() * progress;
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
- break;
+ return getLauncherUIProperty(Bundle::putInt, l -> {
+ final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
+ - LauncherState.ALL_APPS.getVerticalProgress(l);
+ final float distance = l.getAllAppsController().getShiftRange() * progress;
+ return (int) distance;
+ });
}
case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
- if (mLauncher == null) return null;
-
- final float progress = LauncherState.NORMAL.getVerticalProgress(mLauncher)
- - LauncherState.ALL_APPS.getVerticalProgress(mLauncher);
- final float distance = mLauncher.getAllAppsController().getShiftRange() * progress;
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
- break;
+ return getLauncherUIProperty(Bundle::putInt, l -> {
+ final float progress = LauncherState.NORMAL.getVerticalProgress(l)
+ - LauncherState.ALL_APPS.getVerticalProgress(l);
+ final float distance = l.getAllAppsController().getShiftRange() * progress;
+ return (int) distance;
+ });
}
case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
- response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized());
- break;
+ return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
}
case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
@@ -97,32 +108,42 @@
break;
case TestProtocol.REQUEST_FREEZE_APP_LIST:
- MAIN_EXECUTOR.execute(() ->
- mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
- AllAppsStore.DEFER_UPDATES_TEST));
- break;
-
+ return getLauncherUIProperty(Bundle::putBoolean, l -> {
+ l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+ return true;
+ });
case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
- MAIN_EXECUTOR.execute(() ->
- mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
- AllAppsStore.DEFER_UPDATES_TEST));
- break;
+ return getLauncherUIProperty(Bundle::putBoolean, l -> {
+ l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+ return true;
+ });
case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
- try {
- final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() ->
- mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- deferUpdatesFlags);
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ return getLauncherUIProperty(Bundle::putInt,
+ l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
+ }
+
+ case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
+ return getLauncherUIProperty(Bundle::putInt,
+ l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
+ }
+
+ case TestProtocol.REQUEST_WINDOW_INSETS: {
+ return getUIProperty(Bundle::putParcelable, a -> {
+ WindowInsets insets = a.getWindow()
+ .getDecorView().getRootWindowInsets();
+ return Insets.max(
+ insets.getSystemGestureInsets(), insets.getSystemWindowInsets());
+ }, this::getCurrentActivity);
+ }
+
+ case TestProtocol.REQUEST_PID: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
break;
}
case TestProtocol.REQUEST_TOTAL_PSS_KB: {
+ runGcAndFinalizersSync();
Debug.MemoryInfo mem = new Debug.MemoryInfo();
Debug.getMemoryInfo(mem);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
@@ -152,6 +173,12 @@
break;
}
+ case TestProtocol.REQUEST_VIEW_LEAK: {
+ if (mLeaks == null) mLeaks = new LinkedList();
+ mLeaks.add(new View(mContext));
+ break;
+ }
+
case TestProtocol.REQUEST_ICON_HEIGHT: {
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
mDeviceProfile.allAppsCellHeightPx);
@@ -162,8 +189,85 @@
}
protected boolean isLauncherInitialized() {
- final LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
- return model.getCallback() == null || model.isModelLoaded();
+ return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
+ || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
+ }
+
+ protected Activity getCurrentActivity() {
+ return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+ }
+
+ private static void runGcAndFinalizersSync() {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+
+ final CountDownLatch fence = new CountDownLatch(1);
+ createFinalizationObserver(fence);
+ try {
+ do {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ } while (!fence.await(100, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Returns the result by getting a Launcher property on UI thread
+ */
+ public static <T> Bundle getLauncherUIProperty(
+ BundleSetter<T> bundleSetter, Function<Launcher, T> provider) {
+ return getUIProperty(bundleSetter, provider, Launcher.ACTIVITY_TRACKER::getCreatedActivity);
+ }
+
+ /**
+ * Returns the result by getting a generic property on UI thread
+ */
+ private static <S, T> Bundle getUIProperty(
+ BundleSetter<T> bundleSetter, Function<S, T> provider, Supplier<S> targetSupplier) {
+ try {
+ return MAIN_EXECUTOR.submit(() -> {
+ S target = targetSupplier.get();
+ if (target == null) {
+ return null;
+ }
+ T value = provider.apply(target);
+ Bundle response = new Bundle();
+ bundleSetter.set(response, TestProtocol.TEST_INFO_RESPONSE_FIELD, value);
+ return response;
+ }).get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Generic interface for setting a fiend in bundle
+ * @param <T> the type of value being set
+ */
+ public interface BundleSetter<T> {
+
+ /**
+ * Sets any generic property to the bundle
+ */
+ void set(Bundle b, String key, T value);
+ }
+
+ // Create the observer in the scope of a method to minimize the chance that
+ // it remains live in a DEX/machine register at the point of the fence guard.
+ // This must be kept to avoid R8 inlining it.
+ @Keep
+ private static void createFinalizationObserver(CountDownLatch fence) {
+ new Object() {
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ fence.countDown();
+ } finally {
+ super.finalize();
+ }
+ }
+ };
}
}
-
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
new file mode 100644
index 0000000..d522d81
--- /dev/null
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.testing;
+
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Utilities;
+
+public final class TestLogging {
+ private static void recordEventSlow(String sequence, String event) {
+ Log.d(TestProtocol.TAPL_EVENTS_TAG, sequence + " / " + event);
+ }
+
+ public static void recordEvent(String sequence, String event) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ recordEventSlow(sequence, event);
+ }
+ }
+
+ public static void recordEvent(String sequence, String message, Object parameter) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ recordEventSlow(sequence, message + ": " + parameter);
+ }
+ }
+
+ public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS && event.getAction() != MotionEvent.ACTION_MOVE) {
+ recordEventSlow(sequence, message + ": " + event);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 07ddbdc..97ce65e 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -20,13 +20,10 @@
* Protocol for custom accessibility events for communication with UI Automation tests.
*/
public final class TestProtocol {
- public static final String GET_SCROLL_MESSAGE = "TAPL_GET_SCROLL";
- public static final String SCROLL_Y_FIELD = "scrollY";
public static final String STATE_FIELD = "state";
public static final String SWITCHED_TO_STATE_MESSAGE = "TAPL_SWITCHED_TO_STATE";
public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED";
public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED";
- public static final String RESPONSE_MESSAGE_POSTFIX = "_RESPONSE";
public static final int NORMAL_STATE_ORDINAL = 0;
public static final int SPRING_LOADED_STATE_ORDINAL = 1;
public static final int OVERVIEW_STATE_ORDINAL = 2;
@@ -34,6 +31,10 @@
public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
public static final int ALL_APPS_STATE_ORDINAL = 5;
public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
+ public static final int HINT_STATE_ORDINAL = 7;
+ public static final String TAPL_EVENTS_TAG = "TaplEvents";
+ public static final String SEQUENCE_MAIN = "Main";
+ public static final String SEQUENCE_TIS = "TIS";
public static String stateOrdinalToString(int ordinal) {
switch (ordinal) {
@@ -51,6 +52,8 @@
return "AllApps";
case BACKGROUND_APP_STATE_ORDINAL:
return "Background";
+ case HINT_STATE_ORDINAL:
+ return "Hint";
default:
return null;
}
@@ -73,18 +76,23 @@
public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
- public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
- public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
+ public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
+ public static final String REQUEST_WINDOW_INSETS = "window-insets";
+ public static final String REQUEST_PID = "pid";
public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
public static final String REQUEST_JAVA_LEAK = "java-leak";
public static final String REQUEST_NATIVE_LEAK = "native-leak";
+ public static final String REQUEST_VIEW_LEAK = "view-leak";
+ public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list";
public static boolean sDebugTracing = false;
public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
+ public static final String REQUEST_OVERVIEW_ACTIONS_ENABLED = "overview-actions-enabled";
+
+ public static final String PERMANENT_DIAG_TAG = "TaplTarget";
+
public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
- public static final String NO_DRAG_TO_WORKSPACE = "b/138729456";
public static final String APP_NOT_DISABLED = "b/139891609";
- public static final String NO_CONTEXT_MENU = "b/141770616";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index f40f976..c3664c3 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -19,11 +19,11 @@
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.LauncherStateManager.ANIM_ALL;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
-import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.LauncherStateManager.PLAY_NON_ATOMIC;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
@@ -37,16 +37,16 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.FlingBlockCheck;
-import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.TouchController;
/**
@@ -176,7 +176,7 @@
protected abstract LauncherState getTargetState(LauncherState fromState,
boolean isDragTowardPositive);
- protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
+ protected abstract float initCurrentAnimation(@AnimationFlags int animComponents);
/**
* Returns the container that the touch started from when leaving NORMAL state.
@@ -201,10 +201,10 @@
mCurrentAnimation.setOnCancelRunnable(null);
}
int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
- ? NON_ATOMIC_COMPONENT : ANIM_ALL;
+ ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS;
mScheduleResumeAtomicComponent = false;
if (mAtomicAnim != null) {
- animComponents = NON_ATOMIC_COMPONENT;
+ animComponents = PLAY_NON_ATOMIC;
// Control the non-atomic components until the atomic animation finishes, then control
// the atomic components as well.
mScheduleResumeAtomicComponent = true;
@@ -215,7 +215,7 @@
}
if (mAtomicComponentsController != null) {
- animComponents &= ~ATOMIC_OVERVIEW_SCALE_COMPONENT;
+ animComponents &= ~PLAY_ATOMIC_OVERVIEW_SCALE;
}
mProgressMultiplier = initCurrentAnimation(animComponents);
mCurrentAnimation.dispatchOnStart();
@@ -230,7 +230,7 @@
}
@Override
- public void onDragStart(boolean start) {
+ public void onDragStart(boolean start, float startDisplacement) {
mStartState = mLauncher.getStateManager().getState();
mIsLogContainerSet = false;
if (mCurrentAnimation == null) {
@@ -360,7 +360,7 @@
long duration) {
AnimatorSetBuilder builder = getAnimatorSetBuilderForStates(fromState, targetState);
return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, builder,
- ATOMIC_OVERVIEW_SCALE_COMPONENT, duration);
+ PLAY_ATOMIC_OVERVIEW_SCALE, duration);
}
protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
@@ -380,6 +380,7 @@
final LauncherState targetState;
final float progress = mCurrentAnimation.getProgressFraction();
+ final float progressVelocity = velocity * mProgressMultiplier;
final float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
targetState =
@@ -406,7 +407,7 @@
startProgress = 1;
} else {
startProgress = Utilities.boundToRange(progress
- + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+ + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f);
duration = BaseSwipeDetector.calculateDuration(velocity,
endProgress - Math.max(progress, 0)) * durationMultiplier;
}
@@ -421,7 +422,7 @@
startProgress = 0;
} else {
startProgress = Utilities.boundToRange(progress
- + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+ + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f);
duration = BaseSwipeDetector.calculateDuration(velocity,
Math.min(progress, 1) - endProgress) * durationMultiplier;
}
@@ -433,8 +434,8 @@
maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
targetState, velocity, fling);
- mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity);
- if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
+ mCurrentAnimation.dispatchOnStart();
+ if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
}
anim.start();
@@ -508,7 +509,7 @@
mAtomicComponentsController.getAnimationPlayer().end();
mAtomicComponentsController = null;
}
- cancelAnimationControllers();
+ clearState();
boolean shouldGoToTargetState = true;
if (mPendingAnimation != null) {
boolean reachedTarget = mToState == targetState;
@@ -525,7 +526,11 @@
if (targetState != mStartState) {
logReachedState(logAction, targetState);
}
- mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ if (!mLauncher.isInState(targetState)) {
+ // If we're already in the target state, don't jump to it at the end of the animation in
+ // case the user started interacting with it before the animation finished.
+ mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ }
mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
}
@@ -546,13 +551,13 @@
mAtomicAnim = null;
}
mScheduleResumeAtomicComponent = false;
+ mDetector.finishedScrolling();
+ mDetector.setDetectableScrollConditions(0, false);
}
private void cancelAnimationControllers() {
mCurrentAnimation = null;
cancelAtomicComponentsController();
- mDetector.finishedScrolling();
- mDetector.setDetectableScrollConditions(0, false);
}
private void cancelAtomicComponentsController() {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
similarity index 73%
rename from src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
rename to src/com/android/launcher3/touch/AllAppsSwipeController.java
index 23f21a1..8d5f33d 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -1,4 +1,19 @@
-package com.android.launcher3.uioverrides;
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.touch;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -8,9 +23,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationComponents;
-import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.LauncherStateManager.AnimationFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
@@ -58,12 +71,12 @@
@Override
protected int getLogContainerTypeForNormalState(MotionEvent ev) {
- return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent) ?
- ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+ return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent)
+ ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
}
@Override
- protected float initCurrentAnimation(@AnimationComponents int animComponents) {
+ protected float initCurrentAnimation(@AnimationFlags int animComponents) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager()
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 12ca5ee..1276ece 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -24,6 +24,10 @@
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.LinkedList;
+import java.util.Queue;
/**
* Scroll/drag/swipe gesture detector.
@@ -49,13 +53,15 @@
protected final boolean mIsRtl;
protected final float mTouchSlop;
protected final float mMaxVelocity;
+ private final Queue<Runnable> mSetStateQueue = new LinkedList<>();
private int mActivePointerId = INVALID_POINTER_ID;
private VelocityTracker mVelocityTracker;
private PointF mLastDisplacement = new PointF();
private PointF mDisplacement = new PointF();
protected PointF mSubtractDisplacement = new PointF();
- private ScrollState mState = ScrollState.IDLE;
+ @VisibleForTesting ScrollState mState = ScrollState.IDLE;
+ private boolean mIsSettingState;
protected boolean mIgnoreSlopWhenSettling;
@@ -195,6 +201,12 @@
// SETTLING -> (View settled) -> IDLE
private void setState(ScrollState newState) {
+ if (mIsSettingState) {
+ mSetStateQueue.add(() -> setState(newState));
+ return;
+ }
+ mIsSettingState = true;
+
if (DBG) {
Log.d(TAG, "setState:" + mState + "->" + newState);
}
@@ -212,6 +224,10 @@
}
mState = newState;
+ mIsSettingState = false;
+ if (!mSetStateQueue.isEmpty()) {
+ mSetStateQueue.remove().run();
+ }
}
private void initializeDragging() {
@@ -220,7 +236,7 @@
} else {
mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
- }
+ }
}
protected abstract boolean shouldScrollStart(PointF displacement);
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 03493a5..f7ce160 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -25,8 +25,6 @@
import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
import android.app.AlertDialog;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller.SessionInfo;
@@ -51,14 +49,14 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
+import com.android.launcher3.widget.WidgetManagerHelper;
/**
* Class for handling clicks on workspace and all-apps items
@@ -125,8 +123,8 @@
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
if (v.isReadyForClickSetup()) {
- LauncherAppWidgetProviderInfo appWidgetInfo = AppWidgetManagerCompat
- .getInstance(launcher).findProvider(info.providerName, info.user);
+ LauncherAppWidgetProviderInfo appWidgetInfo = new WidgetManagerHelper(launcher)
+ .findProvider(info.providerName, info.user);
if (appWidgetInfo == null) {
return;
}
@@ -171,8 +169,8 @@
private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
ItemInfo item = (ItemInfo) v.getTag();
if (Utilities.ATLEAST_Q) {
- PackageInstallerCompat pkgInstaller = PackageInstallerCompat.getInstance(launcher);
- SessionInfo sessionInfo = pkgInstaller.getActiveSessionInfo(item.user, packageName);
+ SessionInfo sessionInfo = InstallSessionHelper.INSTANCE.get(launcher)
+ .getActiveSessionInfo(item.user, packageName);
if (sessionInfo != null) {
LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
try {
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 86d2b39..ba1bfa5 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import android.util.Log;
import android.view.View;
import android.view.View.OnLongClickListener;
@@ -34,19 +33,16 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.testing.TestProtocol;
-
-import java.util.Arrays;
/**
* Class to handle long-clicks on workspace items and start drag as a result.
*/
public class ItemLongClickListener {
- public static OnLongClickListener INSTANCE_WORKSPACE =
+ public static final OnLongClickListener INSTANCE_WORKSPACE =
ItemLongClickListener::onWorkspaceItemLongClick;
- public static OnLongClickListener INSTANCE_ALL_APPS =
+ public static final OnLongClickListener INSTANCE_ALL_APPS =
ItemLongClickListener::onAllAppsItemLongClick;
private static boolean onWorkspaceItemLongClick(View v) {
@@ -79,19 +75,10 @@
}
private static boolean onAllAppsItemLongClick(View v) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick1");
- }
Launcher launcher = Launcher.getLauncher(v.getContext());
if (!canStartDrag(launcher)) return false;
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick2");
- }
// When we have exited all apps or are in transition, disregard long clicks
if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick3");
- }
if (launcher.getWorkspace().isSwitchingState()) return false;
// Start the drag
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
new file mode 100644
index 0000000..1db65b9
--- /dev/null
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.util.OverScroller;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+
+public class LandscapePagedViewHandler implements PagedOrientationHandler {
+
+ @Override
+ public int getPrimaryValue(int x, int y) {
+ return y;
+ }
+
+ @Override
+ public int getSecondaryValue(int x, int y) {
+ return x;
+ }
+
+ @Override
+ public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int minMaxScroll) {
+ pagedView.superScrollTo(secondaryScroll, minMaxScroll);
+ }
+
+ @Override
+ public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) {
+ pagedView.scrollTo(pagedView.getScrollX() + x, unboundedScroll + y);
+ }
+
+ @Override
+ public void scrollerStartScroll(OverScroller scroller, int newPosition) {
+ scroller.startScroll(scroller.getCurrPos(), newPosition - scroller.getCurrPos());
+ }
+
+ @Override
+ public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) {
+ int scroll = pagedView.getScrollY();
+ final int halfPageSize = pagedView.getNormalChildHeight() / 2;
+ final int screenCenter = mInsets.top + pagedView.getPaddingTop() + scroll + halfPageSize;
+ final int halfScreenSize = pagedView.getMeasuredHeight() / 2;
+ return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize);
+ }
+
+ @Override
+ public boolean isGoingUp(float displacement) {
+ return displacement > 0;
+ }
+
+ @Override
+ public void adjustFloatingIconStartVelocity(PointF velocity) {
+ float oldX = velocity.x;
+ float oldY = velocity.y;
+ velocity.set(-oldY, oldX);
+ }
+
+ @Override
+ public void delegateScrollTo(PagedView pagedView, int primaryScroll) {
+ pagedView.superScrollTo(pagedView.getScrollX(), primaryScroll);
+ }
+
+ @Override
+ public <T> void set(T target, Int2DAction<T> action, int param) {
+ action.call(target, 0, param);
+ }
+
+ @Override
+ public <T> void set(T target, Float2DAction<T> action, float param) {
+ action.call(target, 0, param);
+ }
+
+ @Override
+ public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
+ return event.getY(pointerIndex);
+ }
+
+ @Override
+ public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
+ return velocityTracker.getYVelocity(pointerId);
+ }
+
+ @Override
+ public int getMeasuredSize(View view) {
+ return view.getMeasuredHeight();
+ }
+
+ @Override
+ public int getPrimarySize(Rect rect) {
+ return rect.height();
+ }
+
+ @Override
+ public float getPrimarySize(RectF rect) {
+ return rect.height();
+ }
+
+ @Override
+ public int getSecondaryDimension(View view) {
+ return view.getWidth();
+ }
+
+ @Override
+ public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
+ float offscreenTranslationY = dp.heightPx - view.getPaddingTop();
+ return new ScaleAndTranslation(1f, 0f, offscreenTranslationY);
+ }
+
+ @Override
+ public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
+ return scaleAndTranslation.translationY;
+ }
+
+ @Override
+ public FloatProperty<View> getPrimaryViewTranslate() {
+ return VIEW_TRANSLATE_Y;
+ }
+
+ @Override
+ public FloatProperty<View> getSecondaryViewTranslate() {
+ return VIEW_TRANSLATE_X;
+ }
+
+ @Override
+ public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
+ view.setTranslationX(0);
+ view.setTranslationY(translation);
+ }
+
+ @Override
+ public float getViewCenterPosition(View view) {
+ return view.getTop() + view.getTranslationY();
+ }
+
+ @Override
+ public int getPrimaryScroll(View view) {
+ return view.getScrollY();
+ }
+
+ @Override
+ public float getPrimaryScale(View view) {
+ return view.getScaleY();
+ }
+
+ @Override
+ public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
+ event.setMaxScrollY(maxScroll);
+ }
+
+ @Override
+ public boolean getRecentsRtlSetting(Resources resources) {
+ return !Utilities.isRtl(resources);
+ }
+
+ @Override
+ public float getDegreesRotated() {
+ return 90;
+ }
+
+ @Override
+ public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+ if (displayRotation == Surface.ROTATION_0) {
+ rect.offset(0, value);
+ } else if (displayRotation == Surface.ROTATION_90) {
+ rect.offset(value, 0);
+ } else if (displayRotation == Surface.ROTATION_180) {
+ rect.offset(0, -value);
+ } else {
+ rect.offset(-value, 0);
+ }
+ }
+
+ @Override
+ public int getChildStart(View view) {
+ return view.getTop();
+ }
+
+ @Override
+ public int getCenterForPage(View view, Rect insets) {
+ return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left
+ - insets.right - view.getPaddingRight()) / 2;
+ }
+
+ @Override
+ public int getScrollOffsetStart(View view, Rect insets) {
+ return insets.top + view.getPaddingTop();
+ }
+
+ @Override
+ public int getScrollOffsetEnd(View view, Rect insets) {
+ return view.getHeight() - view.getPaddingBottom() - insets.bottom;
+ }
+
+ @Override
+ public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() {
+ return HORIZONTAL;
+ }
+
+ @Override
+ public int getShortEdgeLength(DeviceProfile dp) {
+ return dp.heightPx;
+ }
+
+ @Override
+ public int getTaskDismissDirectionFactor() {
+ return 1;
+ }
+
+ @Override
+ public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
+ boolean layoutChild) {
+ final int childHeight = child.getMeasuredHeight();
+ final int childBottom = childStart + childHeight;
+ final int childWidth = child.getMeasuredWidth();
+ final int childLeft = pageCenter - childWidth/ 2;
+ if (layoutChild) {
+ child.layout(childLeft, childStart, childLeft + childWidth, childBottom);
+ }
+ return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
+ }
+}
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
new file mode 100644
index 0000000..b4802cd
--- /dev/null
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.util.OverScroller;
+
+/**
+ * Abstraction layer to separate horizontal and vertical specific implementations
+ * for {@link com.android.launcher3.PagedView}. Majority of these implementations are (should be) as
+ * simple as choosing the correct X and Y analogous methods.
+ */
+public interface PagedOrientationHandler {
+
+ interface Int2DAction<T> {
+ void call(T target, int x, int y);
+ }
+ interface Float2DAction<T> {
+ void call(T target, float x, float y);
+ }
+ Int2DAction<View> VIEW_SCROLL_BY = View::scrollBy;
+ Int2DAction<View> VIEW_SCROLL_TO = View::scrollTo;
+ Float2DAction<Canvas> CANVAS_TRANSLATE = Canvas::translate;
+ <T> void set(T target, Int2DAction<T> action, int param);
+ <T> void set(T target, Float2DAction<T> action, float param);
+ float getPrimaryDirection(MotionEvent event, int pointerIndex);
+ float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
+ int getMeasuredSize(View view);
+ int getPrimarySize(Rect rect);
+ float getPrimarySize(RectF rect);
+ int getSecondaryDimension(View view);
+ LauncherState.ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view);
+ float getTranslationValue(LauncherState.ScaleAndTranslation scaleAndTranslation);
+ FloatProperty<View> getPrimaryViewTranslate();
+ FloatProperty<View> getSecondaryViewTranslate();
+ void setPrimaryAndResetSecondaryTranslate(View view, float translation);
+ float getViewCenterPosition(View view);
+ int getPrimaryScroll(View view);
+ float getPrimaryScale(View view);
+ int getChildStart(View view);
+ int getCenterForPage(View view, Rect insets);
+ int getScrollOffsetStart(View view, Rect insets);
+ int getScrollOffsetEnd(View view, Rect insets);
+ SingleAxisSwipeDetector.Direction getOppositeSwipeDirection();
+ int getShortEdgeLength(DeviceProfile dp);
+ int getTaskDismissDirectionFactor();
+ ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
+ void setMaxScroll(AccessibilityEvent event, int maxScroll);
+ boolean getRecentsRtlSetting(Resources resources);
+ float getDegreesRotated();
+ void offsetTaskRect(RectF rect, float value, int delta);
+ int getPrimaryValue(int x, int y);
+ int getSecondaryValue(int x, int y);
+ void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
+ /** Uses {@params pagedView}.getScroll[X|Y]() method for the secondary amount*/
+ void delegateScrollTo(PagedView pagedView, int primaryScroll);
+ void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y);
+ void scrollerStartScroll(OverScroller scroller, int newPosition);
+ CurveProperties getCurveProperties(PagedView pagedView, Rect insets);
+ boolean isGoingUp(float displacement);
+
+ /**
+ * Maps the velocity from the coordinate plane of the foreground app to that
+ * of Launcher's (which now will always be portrait)
+ */
+ void adjustFloatingIconStartVelocity(PointF velocity);
+
+ class CurveProperties {
+ public final int scroll;
+ public final int halfPageSize;
+ public final int screenCenter;
+ public final int halfScreenSize;
+
+ public CurveProperties(int scroll, int halfPageSize, int screenCenter, int halfScreenSize) {
+ this.scroll = scroll;
+ this.halfPageSize = halfPageSize;
+ this.screenCenter = screenCenter;
+ this.halfScreenSize = halfScreenSize;
+ }
+ }
+
+ class ChildBounds {
+
+ public final int primaryDimension;
+ public final int secondaryDimension;
+ public final int childPrimaryEnd;
+ public final int childSecondaryEnd;
+
+ ChildBounds(int primaryDimension, int secondaryDimension, int childPrimaryEnd,
+ int childSecondaryEnd) {
+ this.primaryDimension = primaryDimension;
+ this.secondaryDimension = secondaryDimension;
+ this.childPrimaryEnd = childPrimaryEnd;
+ this.childSecondaryEnd = childSecondaryEnd;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
new file mode 100644
index 0000000..22eee49
--- /dev/null
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.util.OverScroller;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+
+public class PortraitPagedViewHandler implements PagedOrientationHandler {
+
+ @Override
+ public int getPrimaryValue(int x, int y) {
+ return x;
+ }
+
+ @Override
+ public int getSecondaryValue(int x, int y) {
+ return y;
+ }
+
+ @Override
+ public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll) {
+ pagedView.superScrollTo(primaryScroll, secondaryScroll);
+ }
+
+ @Override
+ public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) {
+ pagedView.scrollTo(unboundedScroll + x, pagedView.getScrollY() + y);
+ }
+
+ @Override
+ public void scrollerStartScroll(OverScroller scroller, int newPosition) {
+ scroller.startScroll(newPosition - scroller.getCurrPos(), scroller.getCurrPos());
+ }
+
+ @Override
+ public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) {
+ int scroll = pagedView.getScrollX();
+ final int halfPageSize = pagedView.getNormalChildWidth() / 2;
+ final int screenCenter = mInsets.left + pagedView.getPaddingLeft() + scroll + halfPageSize;
+ final int halfScreenSize = pagedView.getMeasuredWidth() / 2;
+ return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize);
+ }
+
+ @Override
+ public boolean isGoingUp(float displacement) {
+ return displacement < 0;
+ }
+
+ @Override
+ public void adjustFloatingIconStartVelocity(PointF velocity) {
+ //no-op
+ }
+
+ @Override
+ public void delegateScrollTo(PagedView pagedView, int primaryScroll) {
+ pagedView.superScrollTo(primaryScroll, pagedView.getScrollY());
+ }
+
+ @Override
+ public <T> void set(T target, Int2DAction<T> action, int param) {
+ action.call(target, param, 0);
+ }
+
+ @Override
+ public <T> void set(T target, Float2DAction<T> action, float param) {
+ action.call(target, param, 0);
+ }
+
+ @Override
+ public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
+ return event.getX(pointerIndex);
+ }
+
+ @Override
+ public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
+ return velocityTracker.getXVelocity(pointerId);
+ }
+
+ @Override
+ public int getMeasuredSize(View view) {
+ return view.getMeasuredWidth();
+ }
+
+ @Override
+ public int getPrimarySize(Rect rect) {
+ return rect.width();
+ }
+
+ @Override
+ public float getPrimarySize(RectF rect) {
+ return rect.width();
+ }
+
+ @Override
+ public int getSecondaryDimension(View view) {
+ return view.getHeight();
+ }
+
+ @Override
+ public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
+ float offscreenTranslationX = dp.widthPx - view.getPaddingStart();
+ return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
+ }
+
+ @Override
+ public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
+ return scaleAndTranslation.translationX;
+ }
+
+ @Override
+ public FloatProperty<View> getPrimaryViewTranslate() {
+ return VIEW_TRANSLATE_X;
+ }
+
+ @Override
+ public FloatProperty<View> getSecondaryViewTranslate() {
+ return VIEW_TRANSLATE_Y;
+ }
+
+ @Override
+ public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
+ view.setTranslationX(translation);
+ view.setTranslationY(0);
+ }
+
+ @Override
+ public float getViewCenterPosition(View view) {
+ return view.getLeft() + view.getTranslationX();
+ }
+
+ @Override
+ public int getPrimaryScroll(View view) {
+ return view.getScrollX();
+ }
+
+ @Override
+ public float getPrimaryScale(View view) {
+ return view.getScaleX();
+ }
+
+ @Override
+ public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
+ event.setMaxScrollX(maxScroll);
+ }
+
+ @Override
+ public boolean getRecentsRtlSetting(Resources resources) {
+ return !Utilities.isRtl(resources);
+ }
+
+ @Override
+ public float getDegreesRotated() {
+ return 0;
+ }
+
+ @Override
+ public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+ if (displayRotation == Surface.ROTATION_0) {
+ rect.offset(value, 0);
+ } else if (displayRotation == Surface.ROTATION_90) {
+ rect.offset(0, -value);
+ } else if (displayRotation == Surface.ROTATION_180) {
+ rect.offset(-value, 0);
+ } else {
+ rect.offset(0, value);
+ }
+ }
+
+ @Override
+ public int getChildStart(View view) {
+ return view.getLeft();
+ }
+
+ @Override
+ public int getCenterForPage(View view, Rect insets) {
+ return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
+ - insets.bottom - view.getPaddingBottom()) / 2;
+ }
+
+ @Override
+ public int getScrollOffsetStart(View view, Rect insets) {
+ return insets.left + view.getPaddingLeft();
+ }
+
+ @Override
+ public int getScrollOffsetEnd(View view, Rect insets) {
+ return view.getWidth() - view.getPaddingRight() - insets.right;
+ }
+
+ @Override
+ public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() {
+ return VERTICAL;
+ }
+
+ @Override
+ public int getShortEdgeLength(DeviceProfile dp) {
+ return dp.widthPx;
+ }
+
+ @Override
+ public int getTaskDismissDirectionFactor() {
+ return -1;
+ }
+
+ @Override
+ public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
+ boolean layoutChild) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childRight = childStart + childWidth;
+ final int childHeight = child.getMeasuredHeight();
+ final int childTop = pageCenter - childHeight / 2;
+ if (layoutChild) {
+ child.layout(childStart, childTop, childRight, childTop + childHeight);
+ }
+ return new ChildBounds(childWidth, childHeight, childRight, childTop);
+ }
+}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
new file mode 100644
index 0000000..eebd87f
--- /dev/null
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.android.launcher3.Utilities;
+
+public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
+
+ @Override
+ public int getTaskDismissDirectionFactor() {
+ return -1;
+ }
+
+ @Override
+ public boolean getRecentsRtlSetting(Resources resources) {
+ return Utilities.isRtl(resources);
+ }
+
+ @Override
+ public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+ if (displayRotation == Surface.ROTATION_0) {
+ rect.offset(0, value);
+ } else if (displayRotation == Surface.ROTATION_90) {
+ rect.offset(value, 0);
+ } else if (displayRotation == Surface.ROTATION_180) {
+ rect.offset(0, -value);
+ } else {
+ rect.offset(-value, 0);
+ }
+ }
+
+ @Override
+ public float getDegreesRotated() {
+ return 270;
+ }
+
+ @Override
+ public boolean isGoingUp(float displacement) {
+ return displacement < 0;
+ }
+
+ @Override
+ public void adjustFloatingIconStartVelocity(PointF velocity) {
+ float oldX = velocity.x;
+ float oldY = velocity.y;
+ velocity.set(oldY, -oldX);
+ }
+}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index f2ebc45..d725486 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -54,8 +54,8 @@
}
@Override
- boolean canScrollStart(PointF displacement, float touchSlop) {
- return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop);
+ float extractOrthogonalDirection(PointF direction) {
+ return direction.x;
}
};
@@ -80,9 +80,10 @@
}
@Override
- boolean canScrollStart(PointF displacement, float touchSlop) {
- return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop);
+ float extractOrthogonalDirection(PointF direction) {
+ return direction.y;
}
+
};
private final Direction mDir;
@@ -126,7 +127,9 @@
@Override
protected boolean shouldScrollStart(PointF displacement) {
// Reject cases where the angle or slop condition is not met.
- if (!mDir.canScrollStart(displacement, mTouchSlop)) {
+ float minDisplacement = Math.max(mTouchSlop,
+ Math.abs(mDir.extractOrthogonalDirection(displacement)));
+ if (Math.abs(mDir.extractDirection(displacement)) < minDisplacement) {
return false;
}
@@ -145,12 +148,14 @@
@Override
protected void reportDragStartInternal(boolean recatch) {
- mListener.onDragStart(!recatch);
+ float startDisplacement = mDir.extractDirection(mSubtractDisplacement);
+ mListener.onDragStart(!recatch, startDisplacement);
}
@Override
protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
- mListener.onDrag(mDir.extractDirection(displacement), event);
+ mListener.onDrag(mDir.extractDirection(displacement),
+ mDir.extractOrthogonalDirection(displacement), event);
}
@Override
@@ -161,16 +166,24 @@
/** Listener to receive updates on the swipe. */
public interface Listener {
- /** @param start whether this was the original drag start, as opposed to a recatch. */
- void onDragStart(boolean start);
+ /**
+ * TODO(b/150256055) consolidate all the different onDrag() methods into one
+ * @param start whether this was the original drag start, as opposed to a recatch.
+ * @param startDisplacement the initial touch displacement for the primary direction as
+ * given by by {@link Direction#extractDirection(PointF)}
+ */
+ void onDragStart(boolean start, float startDisplacement);
- // TODO remove
boolean onDrag(float displacement);
default boolean onDrag(float displacement, MotionEvent event) {
return onDrag(displacement);
}
+ default boolean onDrag(float displacement, float orthogonalDisplacement, MotionEvent ev) {
+ return onDrag(displacement, ev);
+ }
+
void onDragEnd(float velocity);
}
@@ -183,8 +196,7 @@
/** Returns the part of the given {@link PointF} that is relevant to this direction. */
abstract float extractDirection(PointF point);
- /** Reject cases where the angle or slop condition is not met. */
- abstract boolean canScrollStart(PointF displacement, float touchSlop);
+ abstract float extractOrthogonalDirection(PointF point);
}
}
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
new file mode 100644
index 0000000..499f655
--- /dev/null
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Helper class to statically track activity creation
+ */
+public final class ActivityTracker<T extends BaseActivity> implements Runnable {
+
+ private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
+ private WeakReference<SchedulerCallback<T>> mPendingCallback = new WeakReference<>(null);
+
+ private static final String EXTRA_SCHEDULER_CALLBACK = "launcher.scheduler_callback";
+
+ @Nullable
+ public <R extends T> R getCreatedActivity() {
+ return (R) mCurrentActivity.get();
+ }
+
+ public void onActivityDestroyed(T activity) {
+ if (mCurrentActivity.get() == activity) {
+ mCurrentActivity.clear();
+ }
+ }
+
+ /**
+ * Schedules the callback to be notified when the activity is created.
+ * @return true if the activity is already created, false otherwise
+ */
+ public boolean schedule(SchedulerCallback<? extends T> callback) {
+ synchronized (this) {
+ mPendingCallback = new WeakReference<>((SchedulerCallback<T>) callback);
+ }
+ if (!notifyInitIfPending()) {
+ // If the activity doesn't already exist, then post and wait for the activity to start
+ MAIN_EXECUTOR.execute(this);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void run() {
+ notifyInitIfPending();
+ }
+
+ /**
+ * Notifies the pending callback if the activity is now created.
+ * @return true if the activity is now created.
+ */
+ private boolean notifyInitIfPending() {
+ T activity = mCurrentActivity.get();
+ if (activity != null) {
+ notifyInitIfPending(activity, activity.isStarted());
+ return true;
+ }
+ return false;
+ }
+
+ public boolean notifyInitIfPending(T activity, boolean alreadyOnHome) {
+ SchedulerCallback<T> pendingCallback = mPendingCallback.get();
+ if (pendingCallback != null) {
+ if (!pendingCallback.init(activity, alreadyOnHome)) {
+ clearReference(pendingCallback);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean clearReference(SchedulerCallback<? extends T> handler) {
+ synchronized (this) {
+ if (mPendingCallback.get() == handler) {
+ mPendingCallback.clear();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public boolean hasPending() {
+ return mPendingCallback.get() != null;
+ }
+
+ public boolean handleCreate(T activity) {
+ mCurrentActivity = new WeakReference<>(activity);
+ return handleIntent(activity, activity.getIntent(), false, false);
+ }
+
+ public boolean handleNewIntent(T activity, Intent intent) {
+ return handleIntent(activity, intent, activity.isStarted(), true);
+ }
+
+ private boolean handleIntent(
+ T activity, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
+ boolean result = false;
+ if (intent != null && intent.getExtras() != null) {
+ IBinder stateBinder = intent.getExtras().getBinder(EXTRA_SCHEDULER_CALLBACK);
+ if (stateBinder instanceof ObjectWrapper) {
+ SchedulerCallback<T> handler =
+ ((ObjectWrapper<SchedulerCallback>) stateBinder).get();
+ if (!handler.init(activity, alreadyOnHome)) {
+ intent.getExtras().remove(EXTRA_SCHEDULER_CALLBACK);
+ }
+ result = true;
+ }
+ }
+ if (!result && !explicitIntent) {
+ result = notifyInitIfPending(activity, alreadyOnHome);
+ }
+ return result;
+ }
+
+ public interface SchedulerCallback<T extends BaseActivity> {
+
+ boolean init(T activity, boolean alreadyOnHome);
+
+ default Intent addToIntent(Intent intent) {
+ Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_SCHEDULER_CALLBACK, ObjectWrapper.wrap(this));
+ intent.putExtras(extras);
+ return intent;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
index 00adf10..30c9ff9 100644
--- a/src/com/android/launcher3/util/ContentWriter.java
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -19,14 +19,14 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
import android.net.Uri;
import android.os.UserHandle;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
+import com.android.launcher3.pm.UserCache;
/**
* A wrapper around {@link ContentValues} with some utility methods.
@@ -37,7 +37,7 @@
private final Context mContext;
private CommitParams mCommitParams;
- private Bitmap mIcon;
+ private BitmapInfo mIcon;
private UserHandle mUser;
public ContentWriter(Context context, CommitParams commitParams) {
@@ -79,14 +79,14 @@
return this;
}
- public ContentWriter putIcon(Bitmap value, UserHandle user) {
+ public ContentWriter putIcon(BitmapInfo value, UserHandle user) {
mIcon = value;
mUser = user;
return this;
}
public ContentWriter put(String key, UserHandle user) {
- return put(key, UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user));
+ return put(key, UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user));
}
/**
@@ -97,7 +97,7 @@
Preconditions.assertNonUiThread();
if (mIcon != null && !LauncherAppState.getInstance(context).getIconCache()
.isDefaultIcon(mIcon, mUser)) {
- mValues.put(LauncherSettings.Favorites.ICON, GraphicsUtils.flattenBitmap(mIcon));
+ mValues.put(LauncherSettings.Favorites.ICON, GraphicsUtils.flattenBitmap(mIcon.icon));
mIcon = null;
}
return mValues;
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index 8529d50..f18e411 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -28,6 +28,8 @@
import android.view.Display;
import android.view.WindowManager;
+import androidx.annotation.VisibleForTesting;
+
import java.util.ArrayList;
/**
@@ -127,9 +129,23 @@
public final DisplayMetrics metrics;
- private Info(Context context) {
- Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+ @VisibleForTesting
+ public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
+ Point largestSize, DisplayMetrics metrics) {
+ this.id = id;
+ this.rotation = rotation;
+ this.singleFrameMs = singleFrameMs;
+ this.realSize = realSize;
+ this.smallestSize = smallestSize;
+ this.largestSize = largestSize;
+ this.metrics = metrics;
+ }
+ private Info(Context context) {
+ this(context.getSystemService(WindowManager.class).getDefaultDisplay());
+ }
+
+ public Info(Display display) {
id = display.getDisplayId();
rotation = display.getRotation();
diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java
new file mode 100644
index 0000000..e6ee186
--- /dev/null
+++ b/src/com/android/launcher3/util/DynamicResource.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.Context;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.DimenRes;
+import androidx.annotation.FractionRes;
+import androidx.annotation.IntegerRes;
+
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.ResourceProvider;
+
+/**
+ * Utility class to support customizing resource values using plugins
+ *
+ * To load resources, call
+ * DynamicResource.provider(context).getInt(resId) or any other supported methods
+ *
+ * To allow customization for a particular resource, add them to dynamic_resources.xml
+ */
+public class DynamicResource implements ResourceProvider, PluginListener<ResourceProvider> {
+
+ private static final MainThreadInitializedObject<DynamicResource> INSTANCE =
+ new MainThreadInitializedObject<>(DynamicResource::new);
+
+ private final Context mContext;
+ private ResourceProvider mPlugin;
+
+ private DynamicResource(Context context) {
+ mContext = context;
+ PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this,
+ ResourceProvider.class, false /* allowedMultiple */);
+ }
+
+ @Override
+ public int getInt(@IntegerRes int resId) {
+ return mContext.getResources().getInteger(resId);
+ }
+
+ @Override
+ public float getFraction(@FractionRes int resId) {
+ return mContext.getResources().getFraction(resId, 1, 1);
+ }
+
+ @Override
+ public float getDimension(@DimenRes int resId) {
+ return mContext.getResources().getDimension(resId);
+ }
+
+ @Override
+ public int getColor(@ColorRes int resId) {
+ return mContext.getResources().getColor(resId, null);
+ }
+
+ @Override
+ public float getFloat(@DimenRes int resId) {
+ return mContext.getResources().getFloat(resId);
+ }
+
+ @Override
+ public void onPluginConnected(ResourceProvider plugin, Context context) {
+ mPlugin = plugin;
+ }
+
+ @Override
+ public void onPluginDisconnected(ResourceProvider plugin) {
+ mPlugin = null;
+ }
+
+ /**
+ * Returns the currently active or default provider
+ */
+ public static ResourceProvider provider(Context context) {
+ DynamicResource dr = DynamicResource.INSTANCE.get(context);
+ ResourceProvider plugin = dr.mPlugin;
+ return plugin == null ? dr : plugin;
+ }
+}
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index 4d5ee49..0a32734 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -19,7 +19,6 @@
import android.os.Looper;
import android.os.Process;
-import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -36,9 +35,9 @@
private static final int KEEP_ALIVE = 1;
/**
- * An {@link Executor} to be used with async task with no limit on the queue size.
+ * An {@link ThreadPoolExecutor} to be used with async task with no limit on the queue size.
*/
- public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
+ public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, new LinkedBlockingQueue<>());
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index 4a4a5ca..fcb96d7 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.util.Log;
-import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import java.io.ByteArrayOutputStream;
@@ -67,7 +66,7 @@
* Utility method to debug binary data
*/
public static String createTempFile(Context context, byte[] data) {
- if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (!FeatureFlags.IS_STUDIO_BUILD) {
throw new IllegalStateException("Method only allowed in development mode");
}
@@ -87,7 +86,7 @@
try {
c.close();
} catch (IOException e) {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
Log.d(TAG, "Error closing", e);
}
}
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index 4acdb5c..b54074e 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -28,4 +28,9 @@
* When turned on, icon cache is only fetched from memory and not disk.
*/
public static final String MEMORY_ONLY_ICON_CACHE = "MemoryOnlyIconCache";
+
+ /**
+ * When turned on, we enable doodle related logging.
+ */
+ public static final String DOODLE_LOGGING = "DoodleLogging";
}
diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java
index 8ac600f..3a8a13c 100644
--- a/src/com/android/launcher3/util/LooperExecutor.java
+++ b/src/com/android/launcher3/util/LooperExecutor.java
@@ -41,10 +41,10 @@
@Override
public void execute(Runnable runnable) {
- if (mHandler.getLooper() == Looper.myLooper()) {
+ if (getHandler().getLooper() == Looper.myLooper()) {
runnable.run();
} else {
- mHandler.post(runnable);
+ getHandler().post(runnable);
}
}
@@ -52,7 +52,7 @@
* Same as execute, but never runs the action inline.
*/
public void post(Runnable runnable) {
- mHandler.post(runnable);
+ getHandler().post(runnable);
}
/**
@@ -96,14 +96,14 @@
* Returns the thread for this executor
*/
public Thread getThread() {
- return mHandler.getLooper().getThread();
+ return getHandler().getLooper().getThread();
}
/**
* Returns the looper for this executor
*/
public Looper getLooper() {
- return mHandler.getLooper();
+ return getHandler().getLooper();
}
/**
diff --git a/src/com/android/launcher3/util/LooperIdleLock.java b/src/com/android/launcher3/util/LooperIdleLock.java
index 2896535..f4ccf42 100644
--- a/src/com/android/launcher3/util/LooperIdleLock.java
+++ b/src/com/android/launcher3/util/LooperIdleLock.java
@@ -22,29 +22,30 @@
/**
* Utility class to block execution until the UI looper is idle.
*/
-public class LooperIdleLock implements MessageQueue.IdleHandler, Runnable {
+public class LooperIdleLock implements MessageQueue.IdleHandler {
private final Object mLock;
private boolean mIsLocked;
+ private Looper mLooper;
public LooperIdleLock(Object lock, Looper looper) {
mLock = lock;
+ mLooper = looper;
mIsLocked = true;
looper.getQueue().addIdleHandler(this);
}
@Override
- public void run() {
- Looper.myQueue().addIdleHandler(this);
- }
-
- @Override
public boolean queueIdle() {
synchronized (mLock) {
mIsLocked = false;
mLock.notify();
}
+ // Manually remove from the list in case we're calling this outside of the idle callbacks
+ // (this is Ok in the normal flow as well because MessageQueue makes a copy of all handlers
+ // before calling back)
+ mLooper.getQueue().removeIdleHandler(this);
return false;
}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index fe9c2c4..fc9f8f7 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -22,6 +22,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import java.util.concurrent.ExecutionException;
@@ -39,9 +40,14 @@
}
public T get(Context context) {
+ if (context instanceof PreviewContext) {
+ return ((PreviewContext) context).getObject(this, mProvider);
+ }
+
if (mValue == null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
- mValue = mProvider.get(context.getApplicationContext());
+ mValue = TraceHelper.whitelistIpcs("main.thread.object",
+ () -> mProvider.get(context.getApplicationContext()));
} else {
try {
return MAIN_EXECUTOR.submit(() -> get(context)).get();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java b/src/com/android/launcher3/util/ObjectWrapper.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
rename to src/com/android/launcher3/util/ObjectWrapper.java
index abfe3ad..e5b4707 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
+++ b/src/com/android/launcher3/util/ObjectWrapper.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep.util;
+package com.android.launcher3.util;
import android.os.Binder;
import android.os.IBinder;
@@ -25,7 +25,7 @@
*/
public class ObjectWrapper<T> extends Binder {
- private final T mObject;
+ private T mObject;
public ObjectWrapper(T object) {
mObject = object;
@@ -35,6 +35,10 @@
return mObject;
}
+ public void clear() {
+ mObject = null;
+ }
+
public static IBinder wrap(Object obj) {
return new ObjectWrapper<>(obj);
}
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
index fc8a138..3c398b8 100644
--- a/src/com/android/launcher3/util/OverScroller.java
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -26,11 +26,13 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.launcher3.R;
+import com.android.systemui.plugins.ResourceProvider;
+
/**
* Based on {@link android.widget.OverScroller} supporting only 1-d scrolling and with more
* customization options.
@@ -425,6 +427,7 @@
// Current state of the animation.
private int mState = SPLINE;
+ private Context mContext;
private SpringAnimation mSpring;
// Constant gravity value, used in the deceleration phase.
@@ -500,6 +503,7 @@
}
SplineOverScroller(Context context) {
+ mContext = context;
mFinished = true;
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
@@ -560,9 +564,12 @@
}
mSpring = new SpringAnimation(this, SPRING_PROPERTY);
+ ResourceProvider rp = DynamicResource.provider(mContext);
+ float stiffness = rp.getFloat(R.dimen.horizontal_spring_stiffness);
+ float damping = rp.getFloat(R.dimen.horizontal_spring_damping_ratio);
mSpring.setSpring(new SpringForce(mFinal)
- .setStiffness(SpringForce.STIFFNESS_LOW)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+ .setStiffness(stiffness)
+ .setDampingRatio(damping));
mSpring.setStartVelocity(velocity);
mSpring.animateToFinalPosition(mFinal);
mSpring.addEndListener((animation, canceled, value, velocity1) -> {
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index e97adb5..6c18747 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,6 +16,9 @@
package com.android.launcher3.util;
+import static android.content.pm.PackageInstaller.SessionInfo;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+
import android.app.AppOpsManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -24,6 +27,7 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -34,20 +38,23 @@
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
+import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.AppInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
import java.net.URISyntaxException;
import java.util.List;
@@ -61,12 +68,12 @@
private final Context mContext;
private final PackageManager mPm;
- private final LauncherAppsCompat mLauncherApps;
+ private final LauncherApps mLauncherApps;
public PackageManagerHelper(Context context) {
mContext = context;
mPm = context.getPackageManager();
- mLauncherApps = LauncherAppsCompat.getInstance(context);
+ mLauncherApps = context.getSystemService(LauncherApps.class);
}
/**
@@ -74,8 +81,8 @@
* guarantee that the app is on SD card.
*/
public boolean isAppOnSdcard(String packageName, UserHandle user) {
- ApplicationInfo info = mLauncherApps.getApplicationInfo(
- packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
+ ApplicationInfo info = getApplicationInfo(
+ packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
@@ -84,10 +91,47 @@
* {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
*/
public boolean isAppSuspended(String packageName, UserHandle user) {
- ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, 0, user);
+ ApplicationInfo info = getApplicationInfo(packageName, user, 0);
return info != null && isAppSuspended(info);
}
+ /**
+ * Returns the application info for the provided package or null
+ */
+ public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
+ if (Utilities.ATLEAST_OREO) {
+ try {
+ ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+ return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+ ? null : info;
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ } else {
+ final boolean isPrimaryUser = Process.myUserHandle().equals(user);
+ if (!isPrimaryUser && (flags == 0)) {
+ // We are looking for an installed app on a secondary profile. Prior to O, the only
+ // entry point for work profiles is through the LauncherActivity.
+ List<LauncherActivityInfo> activityList =
+ mLauncherApps.getActivityList(packageName, user);
+ return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
+ }
+ try {
+ ApplicationInfo info = mPm.getApplicationInfo(packageName, flags);
+ // There is no way to check if the app is installed for managed profile. But for
+ // primary profile, we can still have this check.
+ if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
+ || !info.enabled) {
+ return null;
+ }
+ return info;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package not found
+ return null;
+ }
+ }
+ }
+
public boolean isSafeMode() {
return mContext.getPackageManager().isSafeMode();
}
@@ -202,8 +246,7 @@
}
if (componentName != null) {
try {
- mLauncherApps.showAppDetailsForProfile(
- componentName, info.user, sourceBounds, opts);
+ mLauncherApps.startAppDetailsActivity(componentName, info.user, sourceBounds, opts);
} catch (SecurityException | ActivityNotFoundException e) {
Toast.makeText(mContext, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch settings", e);
@@ -259,16 +302,13 @@
*/
public static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
final Intent intent = new Intent(action);
- for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
- if (info.activityInfo != null &&
- (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- final String packageName = info.activityInfo.packageName;
- try {
- final Resources res = pm.getResourcesForApplication(packageName);
- return Pair.create(packageName, res);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to find resources for " + packageName);
- }
+ for (ResolveInfo info : pm.queryBroadcastReceivers(intent, MATCH_SYSTEM_ONLY)) {
+ final String packageName = info.activityInfo.packageName;
+ try {
+ final Resources res = pm.getResourcesForApplication(packageName);
+ return Pair.create(packageName, res);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to find resources for " + packageName);
}
}
return null;
@@ -295,4 +335,28 @@
}
return false;
}
+
+ /**
+ * Returns true if Launcher has the permission to access shortcuts.
+ * @see LauncherApps#hasShortcutHostPermission()
+ */
+ public static boolean hasShortcutsPermission(Context context) {
+ try {
+ return context.getSystemService(LauncherApps.class).hasShortcutHostPermission();
+ } catch (SecurityException | IllegalStateException e) {
+ Log.e(TAG, "Failed to make shortcut manager call", e);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the created time in millis of given session info. Returns 0 if not available.
+ */
+ public static long getSessionCreatedTimeInMillis(@NonNull final SessionInfo info) {
+ try {
+ return (long) SessionInfo.class.getDeclaredMethod("getCreatedMillis").invoke(info);
+ } catch (Exception e) {
+ return 0;
+ }
+ }
}
diff --git a/src/com/android/launcher3/util/PendingAnimation.java b/src/com/android/launcher3/util/PendingAnimation.java
deleted file mode 100644
index 617a38b..0000000
--- a/src/com/android/launcher3/util/PendingAnimation.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import android.animation.AnimatorSet;
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Utility class to keep track of a running animation.
- *
- * This class allows attaching end callbacks to an animation is intended to be used with
- * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
- * AnimationListeners are not properly dispatched.
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class PendingAnimation {
-
- private final ArrayList<Consumer<OnEndListener>> mEndListeners = new ArrayList<>();
-
- public final AnimatorSet anim;
-
- public PendingAnimation(AnimatorSet anim) {
- this.anim = anim;
- }
-
- public void finish(boolean isSuccess, int logAction) {
- for (Consumer<OnEndListener> listeners : mEndListeners) {
- listeners.accept(new OnEndListener(isSuccess, logAction));
- }
- mEndListeners.clear();
- }
-
- public void addEndListener(Consumer<OnEndListener> listener) {
- mEndListeners.add(listener);
- }
-
- public static class OnEndListener {
- public boolean isSuccess;
- public int logAction;
-
- public OnEndListener(boolean isSuccess, int logAction) {
- this.isSuccess = isSuccess;
- this.logAction = logAction;
- }
- }
-}
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index ed66422..63d56e4 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -28,25 +28,25 @@
public class Preconditions {
public static void assertNotNull(Object o) {
- if (FeatureFlags.IS_DOGFOOD_BUILD && o == null) {
+ if (FeatureFlags.IS_STUDIO_BUILD && o == null) {
throw new IllegalStateException();
}
}
public static void assertWorkerThread() {
- if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
+ if (FeatureFlags.IS_STUDIO_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
throw new IllegalStateException();
}
}
public static void assertUIThread() {
- if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(Looper.getMainLooper())) {
+ if (FeatureFlags.IS_STUDIO_BUILD && !isSameLooper(Looper.getMainLooper())) {
throw new IllegalStateException();
}
}
public static void assertNonUiThread() {
- if (FeatureFlags.IS_DOGFOOD_BUILD && isSameLooper(Looper.getMainLooper())) {
+ if (FeatureFlags.IS_STUDIO_BUILD && isSameLooper(Looper.getMainLooper())) {
throw new IllegalStateException();
}
}
diff --git a/src/com/android/launcher3/util/RaceConditionTracker.java b/src/com/android/launcher3/util/RaceConditionTracker.java
deleted file mode 100644
index 6954d0e..0000000
--- a/src/com/android/launcher3/util/RaceConditionTracker.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-/**
- * Event tracker for reliably reproducing race conditions in tests.
- * The app should call onEvent() for events that the test will try to reproduce in all possible
- * orders.
- */
-public class RaceConditionTracker {
- public final static boolean ENTER = true;
- public final static boolean EXIT = false;
- static final String ENTER_POSTFIX = "enter";
- static final String EXIT_POSTFIX = "exit";
-
- public interface EventProcessor {
- void onEvent(String eventName);
- }
-
- private static EventProcessor sEventProcessor;
-
- static void setEventProcessor(EventProcessor eventProcessor) {
- sEventProcessor = eventProcessor;
- }
-
- public static void onEvent(String eventName) {
- if (sEventProcessor != null) sEventProcessor.onEvent(eventName);
- }
-
- public static void onEvent(String eventName, boolean isEnter) {
- if (sEventProcessor != null) {
- sEventProcessor.onEvent(enterExitEvt(eventName, isEnter));
- }
- }
-
- public static String enterExitEvt(String eventName, boolean isEnter) {
- return eventName + ":" + (isEnter ? ENTER_POSTFIX : EXIT_POSTFIX);
- }
-
- public static String enterEvt(String eventName) {
- return enterExitEvt(eventName, ENTER);
- }
-
- public static String exitEvt(String eventName) {
- return enterExitEvt(eventName, EXIT);
- }
-}
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index af99713..49c97da 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -19,7 +19,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.shortcuts.ShortcutKey;
public class ShortcutUtil {
@@ -64,7 +64,7 @@
private static boolean isActive(ItemInfo info) {
boolean isLoading = info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi();
- return !isLoading && !info.isDisabled() && !FeatureFlags.GO_DISABLE_WIDGETS;
+ return !isLoading && !info.isDisabled() && !WidgetsModel.GO_DISABLE_WIDGETS;
}
private static boolean isApp(ItemInfo info) {
@@ -76,4 +76,4 @@
&& info.container != ItemInfo.NO_ID
&& info instanceof WorkspaceItemInfo;
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
new file mode 100644
index 0000000..465a0e8
--- /dev/null
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import java.util.function.Consumer;
+
+public class SimpleBroadcastReceiver extends BroadcastReceiver {
+
+ private final Consumer<Intent> mIntentConsumer;
+
+ public SimpleBroadcastReceiver(Consumer<Intent> intentConsumer) {
+ mIntentConsumer = intentConsumer;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIntentConsumer.accept(intent);
+ }
+
+ /**
+ * Helper method to register multiple actions
+ */
+ public void register(Context context, String... actions) {
+ IntentFilter filter = new IntentFilter();
+ for (String action : actions) {
+ filter.addAction(action);
+ }
+ context.registerReceiver(this, filter);
+ }
+}
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 0d02715..da59afe 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -36,7 +36,7 @@
public class Themes {
public static int getActivityThemeRes(Context context) {
- WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(context);
+ WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
boolean darkTheme;
if (Utilities.ATLEAST_Q) {
Configuration configuration = context.getResources().getConfiguration();
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
index c24bb67..168227d 100644
--- a/src/com/android/launcher3/util/TraceHelper.java
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -15,19 +15,14 @@
*/
package com.android.launcher3.util;
-import static android.util.Log.VERBOSE;
-import static android.util.Log.isLoggable;
-
-import android.os.SystemClock;
import android.os.Trace;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.MutableLong;
-import com.android.launcher3.config.FeatureFlags;
+import androidx.annotation.MainThread;
+
+import java.util.function.Supplier;
/**
- * A wrapper around {@link Trace} with some utility information.
+ * A wrapper around {@link Trace} to allow better testing.
*
* To enable any tracing log, execute the following command:
* $ adb shell setprop log.tag.LAUNCHER_TRACE VERBOSE
@@ -35,65 +30,60 @@
*/
public class TraceHelper {
- private static final boolean ENABLED = isLoggable("LAUNCHER_TRACE", VERBOSE);
+ // Track binder class for this trace
+ public static final int FLAG_ALLOW_BINDER_TRACKING = 1 << 0;
- private static final boolean SYSTEM_TRACE = ENABLED;
- private static final ArrayMap<String, MutableLong> sUpTimes = ENABLED ? new ArrayMap<>() : null;
+ // Temporarily ignore blocking binder calls for this trace.
+ public static final int FLAG_IGNORE_BINDERS = 1 << 1;
- public static void beginSection(String sectionName) {
- if (ENABLED) {
- synchronized (sUpTimes) {
- MutableLong time = sUpTimes.get(sectionName);
- if (time == null) {
- time = new MutableLong(isLoggable(sectionName, VERBOSE) ? 0 : -1);
- sUpTimes.put(sectionName, time);
- }
- if (time.value >= 0) {
- if (SYSTEM_TRACE) {
- Trace.beginSection(sectionName);
- }
- time.value = SystemClock.uptimeMillis();
- }
- }
- }
+ public static final int FLAG_CHECK_FOR_RACE_CONDITIONS = 1 << 2;
+
+ public static final int FLAG_UI_EVENT =
+ FLAG_ALLOW_BINDER_TRACKING | FLAG_CHECK_FOR_RACE_CONDITIONS;
+
+ /**
+ * Static instance of Trace helper, overridden in tests.
+ */
+ public static TraceHelper INSTANCE = new TraceHelper();
+
+ /**
+ * @return a token to pass into {@link #endSection(Object)}.
+ */
+ public Object beginSection(String sectionName) {
+ return beginSection(sectionName, 0);
}
- public static void partitionSection(String sectionName, String partition) {
- if (ENABLED) {
- synchronized (sUpTimes) {
- MutableLong time = sUpTimes.get(sectionName);
- if (time != null && time.value >= 0) {
-
- if (SYSTEM_TRACE) {
- Trace.endSection();
- Trace.beginSection(sectionName);
- }
-
- long now = SystemClock.uptimeMillis();
- Log.d(sectionName, partition + " : " + (now - time.value));
- time.value = now;
- }
- }
- }
+ public Object beginSection(String sectionName, int flags) {
+ Trace.beginSection(sectionName);
+ return null;
}
- public static void endSection(String sectionName) {
- if (ENABLED) {
- endSection(sectionName, "End");
- }
+ /**
+ * @param token the token returned from {@link #beginSection(String, int)}
+ */
+ public void endSection(Object token) {
+ Trace.endSection();
}
- public static void endSection(String sectionName, String msg) {
- if (ENABLED) {
- synchronized (sUpTimes) {
- MutableLong time = sUpTimes.get(sectionName);
- if (time != null && time.value >= 0) {
- if (SYSTEM_TRACE) {
- Trace.endSection();
- }
- Log.d(sectionName, msg + " : " + (SystemClock.uptimeMillis() - time.value));
- }
- }
+ /**
+ * Similar to {@link #beginSection} but doesn't add a trace section.
+ */
+ public Object beginFlagsOverride(int flags) {
+ return null;
+ }
+
+ public void endFlagsOverride(Object token) { }
+
+ /**
+ * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
+ */
+ @MainThread
+ public static <T> T whitelistIpcs(String rpcName, Supplier<T> supplier) {
+ Object traceToken = INSTANCE.beginSection(rpcName, FLAG_IGNORE_BINDERS);
+ try {
+ return supplier.get();
+ } finally {
+ INSTANCE.endSection(traceToken);
}
}
}
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index f8d1632..0498052 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -29,38 +29,39 @@
*/
public class UiThreadHelper {
- private static Handler sHandler;
+ private static final MainThreadInitializedObject<Handler> HANDLER =
+ new MainThreadInitializedObject<>(
+ c -> new Handler(UI_HELPER_EXECUTOR.getLooper(), new UiCallbacks(c)));
private static final int MSG_HIDE_KEYBOARD = 1;
private static final int MSG_SET_ORIENTATION = 2;
private static final int MSG_RUN_COMMAND = 3;
- private static Handler getHandler(Context context) {
- if (sHandler == null) {
- sHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(),
- new UiCallbacks(context.getApplicationContext()));
- }
- return sHandler;
- }
-
public static void hideKeyboardAsync(Context context, IBinder token) {
- Message.obtain(getHandler(context), MSG_HIDE_KEYBOARD, token).sendToTarget();
+ Message.obtain(HANDLER.get(context), MSG_HIDE_KEYBOARD, token).sendToTarget();
}
public static void setOrientationAsync(Activity activity, int orientation) {
- Message.obtain(getHandler(activity), MSG_SET_ORIENTATION, orientation, 0, activity)
+ Message.obtain(HANDLER.get(activity), MSG_SET_ORIENTATION, orientation, 0, activity)
.sendToTarget();
}
+ public static void setBackButtonAlphaAsync(Context context, AsyncCommand command, float alpha,
+ boolean animate) {
+ runAsyncCommand(context, command, Float.floatToIntBits(alpha), animate ? 1 : 0);
+ }
+
public static void runAsyncCommand(Context context, AsyncCommand command, int arg1, int arg2) {
- Message.obtain(getHandler(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
+ Message.obtain(HANDLER.get(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
}
private static class UiCallbacks implements Handler.Callback {
+ private final Context mContext;
private final InputMethodManager mIMM;
UiCallbacks(Context context) {
+ mContext = context;
mIMM = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
}
@@ -74,7 +75,7 @@
((Activity) message.obj).setRequestedOrientation(message.arg1);
return true;
case MSG_RUN_COMMAND:
- ((AsyncCommand) message.obj).execute(message.arg1, message.arg2);
+ ((AsyncCommand) message.obj).execute(mContext, message.arg1, message.arg2);
return true;
}
return false;
@@ -82,7 +83,6 @@
}
public interface AsyncCommand {
-
- void execute(int arg1, int arg2);
+ void execute(Context proxy, int arg1, int arg2);
}
}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 5a131c8..82e24c2 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -23,10 +23,13 @@
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewTreeObserver.OnDrawListener;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.Launcher;
import java.util.ArrayList;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* An executor which runs all the tasks after the first onDraw is called on the target view.
@@ -36,7 +39,7 @@
private final ArrayList<Runnable> mTasks = new ArrayList<>();
- private Launcher mLauncher;
+ private Consumer<ViewOnDrawExecutor> mOnClearCallback;
private View mAttachedView;
private boolean mCompleted;
@@ -44,11 +47,16 @@
private boolean mFirstDrawCompleted;
public void attachTo(Launcher launcher) {
- attachTo(launcher, launcher.getWorkspace(), true /* waitForLoadAnimation */);
+ attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
+ launcher::clearPendingExecutor);
}
- public void attachTo(Launcher launcher, View attachedView, boolean waitForLoadAnimation) {
- mLauncher = launcher;
+ /**
+ * Attached the executor to the existence of the view
+ */
+ public void attachTo(View attachedView, boolean waitForLoadAnimation,
+ Consumer<ViewOnDrawExecutor> onClearCallback) {
+ mOnClearCallback = onClearCallback;
mAttachedView = attachedView;
mAttachedView.addOnAttachStateChangeListener(this);
if (!waitForLoadAnimation) {
@@ -108,8 +116,8 @@
mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
mAttachedView.removeOnAttachStateChangeListener(this);
}
- if (mLauncher != null) {
- mLauncher.clearPendingExecutor(this);
+ if (mOnClearCallback != null) {
+ mOnClearCallback.accept(this);
}
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
@@ -118,7 +126,11 @@
return mCompleted;
}
- protected void runAllTasks() {
+ /**
+ * Executes all tasks immediately
+ */
+ @VisibleForTesting
+ public void runAllTasks() {
for (final Runnable r : mTasks) {
r.run();
}
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 195a77a..11c1029 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.views;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import android.animation.Animator;
@@ -41,7 +43,7 @@
public abstract class AbstractSlideInView extends AbstractFloatingView
implements SingleAxisSwipeDetector.Listener {
- protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
+ protected static final Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
@Override
@@ -62,6 +64,7 @@
protected final ObjectAnimator mOpenCloseAnimator;
protected View mContent;
+ private final View mColorScrim;
protected Interpolator mScrollInterpolator;
// range [0, 1], 0=> completely open, 1=> completely closed
@@ -85,11 +88,30 @@
announceAccessibilityChanges();
}
});
+ int scrimColor = getScrimColor(mLauncher);
+ mColorScrim = scrimColor != -1 ? createColorScrim(mLauncher, scrimColor) : null;
+ }
+
+ protected void attachToContainer() {
+ if (mColorScrim != null) {
+ getPopupContainer().addView(mColorScrim);
+ }
+ getPopupContainer().addView(this);
+ }
+
+ /**
+ * Returns a scrim color for a sliding view. if returned value is -1, no scrim is added.
+ */
+ protected int getScrimColor(Context context) {
+ return -1;
}
protected void setTranslationShift(float translationShift) {
mTranslationShift = translationShift;
mContent.setTranslationY(mTranslationShift * mContent.getHeight());
+ if (mColorScrim != null) {
+ mColorScrim.setAlpha(1 - mTranslationShift);
+ }
}
@Override
@@ -127,7 +149,7 @@
/* SingleAxisSwipeDetector.Listener */
@Override
- public void onDragStart(boolean start) { }
+ public void onDragStart(boolean start, float startDisplacement) { }
@Override
public boolean onDrag(float displacement) {
@@ -185,9 +207,25 @@
protected void onCloseComplete() {
mIsOpen = false;
getPopupContainer().removeView(this);
+ if (mColorScrim != null) {
+ getPopupContainer().removeView(mColorScrim);
+ }
}
protected BaseDragLayer getPopupContainer() {
return mLauncher.getDragLayer();
}
+
+
+ protected static View createColorScrim(Context context, int bgColor) {
+ View view = new View(context);
+ view.forceHasOverlappingRendering(false);
+ view.setBackgroundColor(bgColor);
+
+ BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ lp.ignoreInsets = true;
+ view.setLayoutParams(lp);
+
+ return view;
+ }
}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index e43fc8a..25748ae 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -29,7 +29,6 @@
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
@@ -42,7 +41,6 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.TouchController;
@@ -117,6 +115,11 @@
}
/**
+ * Called to reinitialize touch controllers.
+ */
+ public abstract void recreateControllers();
+
+ /**
* Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer.
*/
public boolean isEventOverView(View view, MotionEvent ev) {
@@ -150,10 +153,20 @@
return findActiveController(ev);
}
+ private boolean isEventInLauncher(MotionEvent ev) {
+ final float x = ev.getX();
+ final float y = ev.getY();
+
+ return x >= mSystemGestureRegion.left && x < getWidth() - mSystemGestureRegion.right
+ && y >= mSystemGestureRegion.top && y < getHeight() - mSystemGestureRegion.bottom;
+ }
+
private TouchController findControllerToHandleTouch(MotionEvent ev) {
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
- if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
- return topView;
+ if (isEventInLauncher(ev)) {
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
+ return topView;
+ }
}
for (TouchController controller : mControllers) {
@@ -245,26 +258,17 @@
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case ACTION_DOWN: {
- float x = ev.getX();
- float y = ev.getY();
mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
- if ((y < mSystemGestureRegion.top
- || x < mSystemGestureRegion.left
- || x > (getWidth() - mSystemGestureRegion.right)
- || y > (getHeight() - mSystemGestureRegion.bottom))) {
- mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
- } else {
+ if (isEventInLauncher(ev)) {
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+ } else {
+ mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
}
break;
}
case ACTION_CANCEL:
case ACTION_UP:
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE,
- "BaseDragLayer.ACTION_UP/CANCEL " + ev);
- }
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
break;
diff --git a/src/com/android/launcher3/views/BottomUserEducationView.java b/src/com/android/launcher3/views/BottomUserEducationView.java
deleted file mode 100644
index bdc69af..0000000
--- a/src/com/android/launcher3/views/BottomUserEducationView.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-
-import android.animation.PropertyValuesHolder;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-
-public class BottomUserEducationView extends AbstractSlideInView implements Insettable {
-
- private static final String KEY_SHOWED_BOTTOM_USER_EDUCATION = "showed_bottom_user_education";
-
- private static final int DEFAULT_CLOSE_DURATION = 200;
-
- private final Rect mInsets = new Rect();
-
- private View mCloseButton;
-
- public BottomUserEducationView(Context context, AttributeSet attr) {
- this(context, attr, 0);
- }
-
- public BottomUserEducationView(Context context, AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mContent = this;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mCloseButton = findViewById(R.id.close_bottom_user_tip);
- mCloseButton.setOnClickListener(view -> handleClose(true));
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- setTranslationShift(mTranslationShift);
- expandTouchAreaOfCloseButton();
- }
-
- @Override
- public void logActionCommand(int command) {
- // Since this is on-boarding popup, it is not a user controlled action.
- }
-
- @Override
- public int getLogContainerType() {
- return ContainerType.TIP;
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_ON_BOARD_POPUP) != 0;
- }
-
- @Override
- public void setInsets(Rect insets) {
- // Extend behind left, right, and bottom insets.
- int leftInset = insets.left - mInsets.left;
- int rightInset = insets.right - mInsets.right;
- int bottomInset = insets.bottom - mInsets.bottom;
- mInsets.set(insets);
- setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
- getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
- }
-
- @Override
- protected void handleClose(boolean animate) {
- handleClose(animate, DEFAULT_CLOSE_DURATION);
- if (animate) {
- // We animate only when the user is visible, which is a proxy for an explicit
- // close action.
- mLauncher.getSharedPrefs().edit()
- .putBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, true).apply();
- sendCustomAccessibilityEvent(
- BottomUserEducationView.this,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- getContext().getString(R.string.bottom_work_tab_user_education_closed));
- }
- }
-
- private void open(boolean animate) {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
- return;
- }
- mIsOpen = true;
- if (animate) {
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.start();
- } else {
- setTranslationShift(TRANSLATION_SHIFT_OPENED);
- }
- }
-
- public static void showIfNeeded(Launcher launcher) {
- if (launcher.getSharedPrefs().getBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, false)) {
- return;
- }
-
- LayoutInflater layoutInflater = LayoutInflater.from(launcher);
- BottomUserEducationView bottomUserEducationView =
- (BottomUserEducationView) layoutInflater.inflate(
- R.layout.work_tab_bottom_user_education_view, launcher.getDragLayer(),
- false);
- launcher.getDragLayer().addView(bottomUserEducationView);
- bottomUserEducationView.open(true);
- }
-
- private void expandTouchAreaOfCloseButton() {
- Rect hitRect = new Rect();
- mCloseButton.getHitRect(hitRect);
- hitRect.left -= mCloseButton.getWidth();
- hitRect.top -= mCloseButton.getHeight();
- hitRect.right += mCloseButton.getWidth();
- hitRect.bottom += mCloseButton.getHeight();
- View parent = (View) mCloseButton.getParent();
- parent.setTouchDelegate(new TouchDelegate(hitRect, mCloseButton));
- }
-}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 49d94f0..fa625ed 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -399,7 +399,8 @@
Drawable drawable = null;
Drawable badge = null;
boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
- && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ && !info.isDisabled(); // Use original icon for disabled icons.
Drawable btvIcon = originalView instanceof BubbleTextView
? ((BubbleTextView) originalView).getIcon() : null;
if (info instanceof SystemShortcut) {
@@ -415,7 +416,7 @@
int width = isFolderIcon ? originalView.getWidth() : (int) pos.width();
int height = isFolderIcon ? originalView.getHeight() : (int) pos.height();
if (supportsAdaptiveIcons) {
- drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray);
+ drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
if (drawable instanceof AdaptiveIconDrawable) {
badge = getBadge(l, info, sTmpObjArray[0]);
} else {
@@ -428,7 +429,7 @@
// Similar to DragView, we simply use the BubbleTextView icon here.
drawable = btvIcon;
} else {
- drawable = getFullDrawable(l, info, width, height, false, sTmpObjArray);
+ drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
}
}
}
@@ -581,6 +582,7 @@
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
+
setVisibility(VISIBLE);
hideOriginalView(originalView);
};
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 465df44..880f123 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -37,7 +37,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -47,7 +47,6 @@
import java.util.ArrayList;
import java.util.List;
-
/**
* Popup shown on long pressing an empty space in launcher
*/
@@ -127,7 +126,8 @@
popup.mTargetRect = targetRect;
for (OptionItem item : items) {
- DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
+ DeepShortcutView view =
+ (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
view.getIconView().setBackgroundResource(item.mIconRes);
view.getBubbleText().setText(item.mLabelRes);
view.setDividerVisibility(View.INVISIBLE);
@@ -158,7 +158,7 @@
R.drawable.ic_palette : R.drawable.ic_wallpaper;
options.add(new OptionItem(resString, resDrawable,
ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
- if (!FeatureFlags.GO_DISABLE_WIDGETS) {
+ if (!WidgetsModel.GO_DISABLE_WIDGETS) {
options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked));
}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 9f59d78..6d204f6 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -40,7 +40,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
-import android.util.Property;
+import android.util.IntProperty;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -77,11 +77,11 @@
/**
* Simple scrim which draws a flat color
*/
-public class ScrimView extends View implements Insettable, OnChangeListener,
+public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener,
AccessibilityStateChangeListener, StateListener {
- public static final Property<ScrimView, Integer> DRAG_HANDLE_ALPHA =
- new Property<ScrimView, Integer>(Integer.TYPE, "dragHandleAlpha") {
+ public static final IntProperty<ScrimView> DRAG_HANDLE_ALPHA =
+ new IntProperty<ScrimView>("dragHandleAlpha") {
@Override
public Integer get(ScrimView scrimView) {
@@ -89,7 +89,7 @@
}
@Override
- public void set(ScrimView scrimView, Integer value) {
+ public void setValue(ScrimView scrimView, int value) {
scrimView.setDragHandleAlpha(value);
}
};
@@ -101,7 +101,7 @@
private final Rect mTempRect = new Rect();
private final int[] mTempPos = new int[2];
- protected final Launcher mLauncher;
+ protected final T mLauncher;
private final WallpaperColorInfo mWallpaperColorInfo;
private final AccessibilityManager mAM;
protected final int mEndScrim;
@@ -130,8 +130,8 @@
public ScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
- mLauncher = Launcher.getLauncher(context);
- mWallpaperColorInfo = WallpaperColorInfo.getInstance(context);
+ mLauncher = Launcher.cast(Launcher.getLauncher(context));
+ mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mMaxScrimAlpha = 0.7f;
@@ -336,7 +336,7 @@
}
private void updateDragHandleVisibility(Drawable recycle) {
- boolean visible = mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
+ boolean visible = shouldDragHandleBeVisible();
boolean wasVisible = mDragHandle != null;
if (visible != wasVisible) {
if (visible) {
@@ -352,6 +352,10 @@
}
}
+ protected boolean shouldDragHandleBeVisible() {
+ return mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
+ }
+
@Override
public boolean dispatchHoverEvent(MotionEvent event) {
return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
new file mode 100644
index 0000000..d849138
--- /dev/null
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * On boarding flow for users right after setting up work profile
+ */
+public class WorkEduView extends AbstractSlideInView implements Insettable {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+ public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+ public static final String KEY_LEGACY_WORK_EDU_SEEN = "showed_bottom_user_education";
+
+ private static final int WORK_EDU_NOT_STARTED = 0;
+ private static final int WORK_EDU_PERSONAL_APPS = 1;
+ private static final int WORK_EDU_WORK_APPS = 2;
+
+ protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
+
+
+ private Rect mInsets = new Rect();
+ private View mViewWrapper;
+ private Button mProceedButton;
+ private TextView mContentText;
+ private AllAppsPagedView mAllAppsPagedView;
+
+ private int mNextWorkEduStep = WORK_EDU_PERSONAL_APPS;
+
+
+ public WorkEduView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public WorkEduView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContent = this;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mLauncher.getSharedPrefs().edit().putInt(KEY_WORK_EDU_STEP, mNextWorkEduStep).apply();
+ handleClose(true, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // Since this is on-boarding popup, it is not a user controlled action.
+ }
+
+ @Override
+ public int getLogContainerType() {
+ return LauncherLogProto.ContainerType.TIP;
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ON_BOARD_POPUP) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mViewWrapper = findViewById(R.id.view_wrapper);
+ mProceedButton = findViewById(R.id.proceed);
+ mContentText = findViewById(R.id.content_text);
+
+ // make sure layout does not shrink when we change the text
+ mContentText.post(() -> mContentText.setMinLines(mContentText.getLineCount()));
+ if (mLauncher.getAppsView().getContentView() instanceof AllAppsPagedView) {
+ mAllAppsPagedView = (AllAppsPagedView) mLauncher.getAppsView().getContentView();
+ }
+
+ mProceedButton.setOnClickListener(view -> {
+ if (mAllAppsPagedView != null) {
+ mAllAppsPagedView.snapToPage(AllAppsContainerView.AdapterHolder.WORK);
+ }
+ goToWorkTab(true);
+ });
+ }
+
+ private void goToWorkTab(boolean animate) {
+ mProceedButton.setText(R.string.work_profile_edu_accept);
+ if (animate) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mContentText, ALPHA, 0);
+ animator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mContentText.setText(mLauncher.getString(R.string.work_profile_edu_work_apps));
+ ObjectAnimator.ofFloat(mContentText, ALPHA, 1).start();
+ }
+ });
+ animator.start();
+ } else {
+ mContentText.setText(mLauncher.getString(R.string.work_profile_edu_work_apps));
+ }
+ mNextWorkEduStep = WORK_EDU_WORK_APPS;
+ mProceedButton.setOnClickListener(v -> handleClose(true));
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mViewWrapper.setPaddingRelative(mViewWrapper.getPaddingStart(),
+ mViewWrapper.getPaddingTop(), mViewWrapper.getPaddingEnd(), bottomInset);
+ }
+
+ private void show() {
+ attachToContainer();
+ animateOpen();
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return FINAL_SCRIM_BG_COLOR;
+ }
+
+ private void goToFirstPage() {
+ if (mAllAppsPagedView != null) {
+ mAllAppsPagedView.snapToPageImmediately(AllAppsContainerView.AdapterHolder.MAIN);
+ }
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+
+ /**
+ * Checks if user has not seen onboarding UI yet and shows it when user navigates to all apps
+ */
+ public static LauncherStateManager.StateListener showEduFlowIfNeeded(Launcher launcher,
+ @Nullable LauncherStateManager.StateListener oldListener) {
+ if (oldListener != null) {
+ launcher.getStateManager().removeStateListener(oldListener);
+ }
+ if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+ WORK_EDU_NOT_STARTED) != WORK_EDU_NOT_STARTED) {
+ return null;
+ }
+
+ LauncherStateManager.StateListener listener = new LauncherStateManager.StateListener() {
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState != LauncherState.ALL_APPS) return;
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ WorkEduView v = (WorkEduView) layoutInflater.inflate(
+ R.layout.work_profile_edu, launcher.getDragLayer(),
+ false);
+ v.show();
+ v.goToFirstPage();
+ launcher.getStateManager().removeStateListener(this);
+ }
+ };
+ launcher.getStateManager().addStateListener(listener);
+ return listener;
+ }
+
+ /**
+ * Shows work apps edu if user had dismissed full edu flow
+ */
+ public static void showWorkEduIfNeeded(Launcher launcher) {
+ if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+ WORK_EDU_NOT_STARTED) != WORK_EDU_PERSONAL_APPS) {
+ return;
+ }
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ WorkEduView v = (WorkEduView) layoutInflater.inflate(
+ R.layout.work_profile_edu, launcher.getDragLayer(), false);
+ v.show();
+ v.goToWorkTab(false);
+ }
+
+ private static boolean hasSeenLegacyEdu(Launcher launcher) {
+ return launcher.getSharedPrefs().getBoolean(KEY_LEGACY_WORK_EDU_SEEN, false);
+ }
+}
diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
index fb17b4f..9ac8230 100644
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ b/src/com/android/launcher3/views/WorkFooterContainer.java
@@ -15,32 +15,61 @@
*/
package com.android.launcher3.views;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.WorkModeSwitch;
+import com.android.launcher3.pm.UserCache;
/**
* Container to show work footer in all-apps.
*/
-public class WorkFooterContainer extends RelativeLayout {
+public class WorkFooterContainer extends LinearLayout implements Insettable {
+ private Rect mInsets = new Rect();
+
+ private WorkModeSwitch mWorkModeSwitch;
+ private TextView mWorkModeLabel;
+
+ protected final ObjectAnimator mOpenCloseAnimator;
public WorkFooterContainer(Context context) {
- super(context);
+ this(context, null, 0);
}
public WorkFooterContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
public WorkFooterContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
updateTranslation();
+ this.setVisibility(shouldShowWorkFooter() ? VISIBLE : GONE);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mWorkModeSwitch = findViewById(R.id.work_mode_toggle);
+ mWorkModeLabel = findViewById(R.id.work_mode_label);
}
@Override
@@ -56,4 +85,51 @@
setTranslationY(Math.max(0, availableBot - getBottom()));
}
}
+
+ @Override
+ public void setInsets(Rect insets) {
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+ getPaddingBottom() + bottomInset);
+ }
+
+ /**
+ * Animates in/out work profile toggle panel based on the tab user is on
+ */
+ public void setWorkTabVisible(boolean workTabVisible) {
+ if (!shouldShowWorkFooter()) return;
+
+ mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
+ mOpenCloseAnimator.start();
+ }
+
+ /**
+ * Refreshes views based on current work profile enabled status
+ */
+ public void refresh() {
+ if (!shouldShowWorkFooter()) return;
+ boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
+ getContext()).isAnyProfileQuietModeEnabled();
+
+ mWorkModeLabel.setText(anyProfileQuietModeEnabled
+ ? R.string.work_mode_off_label : R.string.work_mode_on_label);
+ mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds(
+ anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
+ mWorkModeSwitch.refresh();
+ }
+
+ /**
+ * Returns work mode switch
+ */
+ public WorkModeSwitch getWorkModeSwitch() {
+ return mWorkModeSwitch;
+ }
+
+ private boolean shouldShowWorkFooter() {
+ Launcher launcher = Launcher.getLauncher(getContext());
+ return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
+ || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+ == PackageManager.PERMISSION_GRANTED);
+ }
}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 72cddc7..73a0615 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.widget;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
@@ -42,7 +40,8 @@
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.AbstractSlideInView;
-import com.android.launcher3.views.BaseDragLayer;
+
+import java.util.ArrayList;
/**
* Base class for various widgets popup
@@ -55,11 +54,14 @@
/* Touch handling related member variables. */
private Toast mWidgetInstructionToast;
- protected final View mColorScrim;
-
public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mColorScrim = createColorScrim(context);
+ }
+
+ protected int getScrimColor(Context context) {
+ WallpaperColorInfo colors = WallpaperColorInfo.INSTANCE.get(context);
+ int alpha = context.getResources().getInteger(R.integer.extracted_color_gradient_alpha);
+ return setColorAlphaBound(colors.getSecondaryColor(), alpha);
}
@Override
@@ -98,16 +100,6 @@
return true;
}
- protected void attachToContainer() {
- getPopupContainer().addView(mColorScrim);
- getPopupContainer().addView(this);
- }
-
- protected void setTranslationShift(float translationShift) {
- super.setTranslationShift(translationShift);
- mColorScrim.setAlpha(1 - mTranslationShift);
- }
-
private boolean beginDraggingWidget(WidgetCell v) {
// Get the widget preview as the drag representation
WidgetImageView image = v.getWidgetView();
@@ -138,7 +130,6 @@
protected void onCloseComplete() {
super.onCloseComplete();
- getPopupContainer().removeView(mColorScrim);
clearNavBarColor();
}
@@ -155,9 +146,11 @@
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- targetParent.containerType = ContainerType.WIDGETS;
- targetParent.cardinality = getElementsRowCount();
+ public void fillInLogContainerData(ItemInfo childInfo, Target child,
+ ArrayList<Target> parents) {
+ Target target = newContainerTarget(ContainerType.WIDGETS);
+ target.cardinality = getElementsRowCount();
+ parents.add(target);
}
@Override
@@ -177,19 +170,4 @@
protected SystemUiController getSystemUiController() {
return mLauncher.getSystemUiController();
}
-
- private static View createColorScrim(Context context) {
- View view = new View(context);
- view.forceHasOverlappingRendering(false);
-
- WallpaperColorInfo colors = WallpaperColorInfo.getInstance(context);
- int alpha = context.getResources().getInteger(R.integer.extracted_color_gradient_alpha);
- view.setBackgroundColor(setColorAlphaBound(colors.getSecondaryColor(), alpha));
-
- BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(MATCH_PARENT, MATCH_PARENT);
- lp.ignoreInsets = true;
- view.setLayoutParams(lp);
-
- return view;
- }
}
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index 62b6903..6e21a41 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -16,7 +16,7 @@
package com.android.launcher3.widget;
import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
/**
* Meta data used for late binding of the short cuts.
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 50db40f..895f8de 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -16,6 +16,9 @@
package com.android.launcher3.widget;
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
+
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -30,15 +33,15 @@
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.View.OnClickListener;
+import android.widget.RemoteViews;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.R;
-import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Themes;
@@ -92,6 +95,15 @@
}
@Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ super.updateAppWidget(remoteViews);
+ WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(getContext());
+ if (widgetManagerHelper.isAppWidgetRestored(mInfo.appWidgetId)) {
+ reInflate();
+ }
+ }
+
+ @Override
public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
int maxHeight) {
// No-op
@@ -128,24 +140,22 @@
mCenterDrawable.setCallback(null);
mCenterDrawable = null;
}
- if (info.iconBitmap != null) {
+ if (info.bitmap.icon != null) {
// The view displays three modes,
// 1) App icon in the center
// 2) Preload icon in the center
// 3) Setup icon in the center and app icon in the top right corner.
- DrawableFactory drawableFactory = DrawableFactory.INSTANCE.get(getContext());
if (mDisabledForSafeMode) {
- FastBitmapDrawable disabledIcon = drawableFactory.newIcon(getContext(), info);
+ FastBitmapDrawable disabledIcon = newIcon(getContext(), info);
disabledIcon.setIsDisabled(true);
mCenterDrawable = disabledIcon;
mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
- mCenterDrawable = drawableFactory.newIcon(getContext(), info);
+ mCenterDrawable = newIcon(getContext(), info);
mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
- updateSettingColor(info.iconColor);
+ updateSettingColor(info.bitmap.color);
} else {
- mCenterDrawable = DrawableFactory.INSTANCE.get(getContext())
- .newPendingIcon(getContext(), info);
+ mCenterDrawable = newPendingIcon(getContext(), info);
mSettingIconDrawable = null;
applyState();
}
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 8ea9bd4..662e627 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -91,8 +91,8 @@
createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
}
if (preview == null) {
- preview = app.getWidgetCache().generateWidgetPreview(
- launcher, createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
+ preview = app.getWidgetCache().generateWidgetPreview(launcher,
+ createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale).first;
}
if (previewSizeBeforeScale[0] < previewBitmapWidth) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 6944879..f055adf 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -36,7 +36,6 @@
import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.model.WidgetItem;
@@ -105,7 +104,7 @@
}
private void setContainerWidth() {
- mCellSize = (int) (mDeviceProfile.allAppsCellWidthPx * WIDTH_SCALE);
+ mCellSize = (int) (mDeviceProfile.cellWidthPx * WIDTH_SCALE);
mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
}
@@ -182,10 +181,8 @@
return;
}
if (bitmap != null) {
- mWidgetImage.setBitmap(bitmap,
- DrawableFactory.INSTANCE.get(getContext()).getBadgeForUser(mItem.user,
- getContext(), BaseIconFactory.getBadgeSizeForIconSize(
- mDeviceProfile.allAppsIconSizePx)));
+ mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+ BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
if (mAnimatePreview) {
mWidgetImage.setAlpha(0f);
ViewPropertyAnimator anim = mWidgetImage.animate();
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 8dcdd44..c022374 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -13,7 +13,6 @@
import com.android.launcher3.DropTarget;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -103,7 +102,7 @@
if (LOGD) {
Log.d(TAG, "Binding widget, id: " + mWidgetLoadingId);
}
- if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
+ if (new WidgetManagerHelper(mLauncher).bindAppWidgetIdIfAllowed(
mWidgetLoadingId, pInfo, options)) {
// Widget id bound. Inflate the widget.
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
new file mode 100644
index 0000000..f3c7822
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.annotation.TargetApi;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Utility class to working with {@link AppWidgetManager}
+ */
+public class WidgetManagerHelper {
+
+ //TODO: replace this with OPTION_APPWIDGET_RESTORE_COMPLETED b/63667276
+ public static final String WIDGET_OPTION_RESTORE_COMPLETED = "appWidgetRestoreCompleted";
+
+ final AppWidgetManager mAppWidgetManager;
+ final Context mContext;
+
+ public WidgetManagerHelper(Context context) {
+ mContext = context;
+ mAppWidgetManager = AppWidgetManager.getInstance(context);
+ }
+
+ /**
+ * @see AppWidgetManager#getAppWidgetInfo(int)
+ */
+ public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) {
+ if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+ return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId);
+ }
+ AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
+ }
+
+ /**
+ * @see AppWidgetManager#getInstalledProvidersForPackage(String, UserHandle)
+ */
+ @TargetApi(Build.VERSION_CODES.O)
+ public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ return Collections.emptyList();
+ }
+
+ if (packageUser == null) {
+ return allWidgetsSteam(mContext).collect(Collectors.toList());
+ }
+
+ if (Utilities.ATLEAST_OREO) {
+ return mAppWidgetManager.getInstalledProvidersForPackage(
+ packageUser.mPackageName, packageUser.mUser);
+ }
+
+ String pkg = packageUser.mPackageName;
+ return Stream.concat(
+ // Only get providers for the given package/user.
+ mAppWidgetManager.getInstalledProvidersForProfile(packageUser.mUser)
+ .stream()
+ .filter(w -> w.provider.equals(pkg)),
+ Process.myUserHandle().equals(packageUser.mUser)
+ && mContext.getPackageName().equals(pkg)
+ ? CustomWidgetManager.INSTANCE.get(mContext).stream()
+ : Stream.empty())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * @see AppWidgetManager#bindAppWidgetIdIfAllowed(int, UserHandle, ComponentName, Bundle)
+ */
+ public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
+ Bundle options) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ return false;
+ }
+ if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+ return true;
+ }
+ return mAppWidgetManager.bindAppWidgetIdIfAllowed(
+ appWidgetId, info.getProfile(), info.provider, options);
+ }
+
+ public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandle user) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ return null;
+ }
+ for (AppWidgetProviderInfo info :
+ getAllProviders(new PackageUserKey(provider.getPackageName(), user))) {
+ if (info.provider.equals(provider)) {
+ return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns if a AppWidgetProvider has marked a widget restored
+ */
+ public boolean isAppWidgetRestored(int appWidgetId) {
+ return !WidgetsModel.GO_DISABLE_WIDGETS && mAppWidgetManager.getAppWidgetOptions(
+ appWidgetId).getBoolean(WIDGET_OPTION_RESTORE_COMPLETED);
+ }
+
+ public static Map<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap(Context context) {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ return Collections.emptyMap();
+ }
+ return allWidgetsSteam(context).collect(
+ Collectors.toMap(info -> new ComponentKey(info.provider, info.getProfile()),
+ Function.identity()));
+ }
+
+ private static Stream<AppWidgetProviderInfo> allWidgetsSteam(Context context) {
+ AppWidgetManager awm = context.getSystemService(AppWidgetManager.class);
+ return Stream.concat(
+ UserCache.INSTANCE.get(context)
+ .getUserProfiles()
+ .stream()
+ .flatMap(u -> awm.getInstalledProvidersForProfile(u).stream()),
+ CustomWidgetManager.INSTANCE.get(context).stream());
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index 435125b..f3b325d 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -18,6 +18,8 @@
import android.util.Log;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
@@ -25,8 +27,6 @@
import java.util.ArrayList;
import java.util.Iterator;
-import androidx.recyclerview.widget.RecyclerView;
-
/**
* Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
* methods accordingly.
@@ -137,7 +137,7 @@
}
private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
- return curInfo.iconBitmap.equals(newInfo.iconBitmap) &&
- !mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user);
+ return curInfo.bitmap.icon.equals(newInfo.bitmap.icon)
+ && !mIconCache.isDefaultIcon(curInfo.bitmap, curInfo.user);
}
}
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 521f511..2a102d2 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.widget;
+import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -37,6 +39,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.R;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.TopRoundedCornerView;
@@ -247,4 +250,10 @@
anim.play(ObjectAnimator.ofFloat(mRecyclerView, ALPHA, 0.5f));
return anim;
}
+
+ @Override
+ protected void onCloseComplete() {
+ super.onCloseComplete();
+ AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
+ }
}
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index f20c83d..0ea7d85 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -38,10 +38,10 @@
import com.android.systemui.plugins.CustomWidgetPlugin;
import com.android.systemui.plugins.PluginListener;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Stream;
/**
* CustomWidgetManager handles custom widgets implemented as a plugin.
@@ -51,30 +51,33 @@
public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
new MainThreadInitializedObject<>(CustomWidgetManager::new);
+ private final Context mContext;
/**
* auto provider Id is an ever-increasing number that serves as the providerId whenever a new
* custom widget has been connected.
*/
private int mAutoProviderId = 0;
private final SparseArray<CustomWidgetPlugin> mPlugins;
- private final SparseArray<WeakReference<Context>> mContexts;
private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
private final SparseArray<ComponentName> mWidgetsIdMap;
private Consumer<PackageUserKey> mWidgetRefreshCallback;
private CustomWidgetManager(Context context) {
+ mContext = context;
mPlugins = new SparseArray<>();
- mContexts = new SparseArray<>();
mCustomWidgets = new ArrayList<>();
mWidgetsIdMap = new SparseArray<>();
PluginManagerWrapper.INSTANCE.get(context)
.addPluginListener(this, CustomWidgetPlugin.class, true);
}
+ public void onDestroy() {
+ PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this);
+ }
+
@Override
public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
mPlugins.put(mAutoProviderId, plugin);
- mContexts.put(mAutoProviderId, new WeakReference<>(context));
List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
.getInstalledProvidersForProfile(Process.myUserHandle());
if (providers.isEmpty()) return;
@@ -94,7 +97,6 @@
int providerId = findProviderId(plugin);
if (providerId == -1) return;
mPlugins.remove(providerId);
- mContexts.remove(providerId);
mCustomWidgets.remove(getWidgetProvider(providerId));
mWidgetsIdMap.remove(providerId);
}
@@ -112,17 +114,16 @@
public void onViewCreated(LauncherAppWidgetHostView view) {
CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo();
CustomWidgetPlugin plugin = mPlugins.get(info.providerId);
- WeakReference<Context> context = mContexts.get(info.providerId);
if (plugin == null) return;
- plugin.onViewCreated(context == null ? null : context.get(), view);
+ plugin.onViewCreated(view);
}
/**
- * Returns the list of custom widgets.
+ * Returns the stream of custom widgets.
*/
@NonNull
- public List<CustomAppWidgetProviderInfo> getCustomWidgets() {
- return mCustomWidgets;
+ public Stream<CustomAppWidgetProviderInfo> stream() {
+ return mCustomWidgets.stream();
}
/**
@@ -156,13 +157,13 @@
info.provider = new ComponentName(
context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
- info.label = plugin.getLabel(context);
- info.resizeMode = plugin.getResizeMode(context);
+ info.label = plugin.getLabel();
+ info.resizeMode = plugin.getResizeMode();
- info.spanX = plugin.getSpanX(context);
- info.spanY = plugin.getSpanY(context);
- info.minSpanX = plugin.getMinSpanX(context);
- info.minSpanY = plugin.getMinSpanY(context);
+ info.spanX = plugin.getSpanX();
+ info.spanY = plugin.getSpanY();
+ info.minSpanX = plugin.getMinSpanX();
+ info.minSpanY = plugin.getMinSpanY();
return info;
}
diff --git a/src_build_config/BuildConfig.java b/src_build_config/BuildConfig.java
index 36d7f4b..49aadf6 100644
--- a/src_build_config/BuildConfig.java
+++ b/src_build_config/BuildConfig.java
@@ -18,4 +18,5 @@
public final class BuildConfig {
public static final String APPLICATION_ID = "com.android.launcher3";
+ public static final boolean DEBUG = false;
}
diff --git a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
index 47aa94b..56ebcc5 100644
--- a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
@@ -17,7 +17,6 @@
package com.android.systemui.plugins;
import android.appwidget.AppWidgetHostView;
-import android.content.Context;
import com.android.systemui.plugins.annotations.ProvidesInterface;
@@ -33,40 +32,40 @@
/**
* The label to display to the user in the AppWidget picker.
*/
- String getLabel(Context context);
+ String getLabel();
/**
* The default width of the widget when added to a host, in dp. The widget will get
* at least this width, and will often be given more, depending on the host.
*/
- int getSpanX(Context context);
+ int getSpanX();
/**
* The default height of the widget when added to a host, in dp. The widget will get
* at least this height, and will often be given more, depending on the host.
*/
- int getSpanY(Context context);
+ int getSpanY();
/**
* Minimum width (in dp) which the widget can be resized to. This field has no effect if it
* is greater than minWidth or if horizontal resizing isn't enabled.
*/
- int getMinSpanX(Context context);
+ int getMinSpanX();
/**
* Minimum height (in dp) which the widget can be resized to. This field has no effect if it
* is greater than minHeight or if vertical resizing isn't enabled.
*/
- int getMinSpanY(Context context);
+ int getMinSpanY();
/**
* The rules by which a widget can be resized.
*/
- int getResizeMode(Context context);
+ int getResizeMode();
/**
* Notify the plugin that container of the widget has been rendered, where the custom widget
* can be attached to.
*/
- void onViewCreated(Context context, AppWidgetHostView parent);
+ void onViewCreated(AppWidgetHostView parent);
}
diff --git a/src_plugins/com/android/systemui/plugins/OverlayPlugin.java b/src_plugins/com/android/systemui/plugins/OverlayPlugin.java
new file mode 100644
index 0000000..1edb692
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OverlayPlugin.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.plugins;
+
+import android.app.Activity;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.shared.LauncherExterns;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+
+/**
+ * Implement this interface to add a -1 content on the home screen.
+ */
+@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION)
+public interface OverlayPlugin extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERLAY";
+ int VERSION = 1;
+
+ LauncherOverlayManager createOverlayManager(Activity activity, LauncherExterns externs);
+
+}
diff --git a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
new file mode 100644
index 0000000..28a9193
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to receive a callback when the user swipes right
+ * to left on the gesture area. It won't fire if the user has quick switched to a previous app
+ * (swiped right) and the current app isn't yet the active one (i.e., if swiping left would take
+ * the user to a more recent app).
+ */
+@ProvidesInterface(action = com.android.systemui.plugins.OverscrollPlugin.ACTION,
+ version = com.android.systemui.plugins.OverscrollPlugin.VERSION)
+public interface OverscrollPlugin extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL";
+ int VERSION = 3;
+
+ String DEVICE_STATE_LOCKED = "Locked";
+ String DEVICE_STATE_LAUNCHER = "Launcher";
+ String DEVICE_STATE_APP = "App";
+ String DEVICE_STATE_UNKNOWN = "Unknown";
+
+ /**
+ * @return true if the plugin is active and will accept overscroll gestures
+ */
+ boolean isActive();
+
+ /**
+ * Called when a touch is down and has been recognized as an overscroll gesture.
+ * A call of this method will always result in `onTouchUp` being called, and possibly
+ * `onFling` as well.
+ *
+ * @param deviceState String representing the current device state
+ * @param underlyingActivity String representing the currently active Activity
+ */
+ void onTouchStart(String deviceState, String underlyingActivity);
+
+ /**
+ * Called when a touch that was previously recognized has moved.
+ *
+ * @param px distance between the position of touch on this update and the position of the
+ * touch when it was initially recognized.
+ */
+ void onTouchTraveled(int px);
+
+ /**
+ * Called when a touch that was previously recognized has ended.
+ *
+ * @param px distance between the position of touch on this update and the position of the
+ * touch when it was initially recognized.
+ */
+ void onTouchEnd(int px);
+
+ /**
+ * Called when the user starts Compose with a fling. `onTouchUp` will also be called.
+ */
+ void onFling(float velocity);
+}
diff --git a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
index 0ebea3d..cd9f33d 100644
--- a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
+++ b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
@@ -34,9 +34,9 @@
/**
* Sets up the recents overview extra card and fills in data.
*
- * @param context Plugin context
+ * @param context Plugin context
* @param frameLayout PlaceholderView
- * @param activity Recents activity to hold extra view
+ * @param activity Recents activity to hold extra view
*/
void setupView(Context context, FrameLayout frameLayout, Activity activity);
}
diff --git a/src_plugins/com/android/systemui/plugins/ResourceProvider.java b/src_plugins/com/android/systemui/plugins/ResourceProvider.java
new file mode 100644
index 0000000..d1767a0
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/ResourceProvider.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Plugin to support customizing resource
+ */
+@ProvidesInterface(action = ResourceProvider.ACTION, version = ResourceProvider.VERSION)
+public interface ResourceProvider extends Plugin {
+ String ACTION = "com.android.launcher3.action.PLUGIN_DYNAMIC_RESOURCE";
+ int VERSION = 1;
+
+ /**
+ * @see android.content.res.Resources#getInteger(int)
+ */
+ int getInt(int resId);
+
+ /**
+ * @see android.content.res.Resources#getFraction(int, int, int)
+ */
+ float getFraction(int resId);
+
+ /**
+ * @see android.content.res.Resources#getDimension(int)
+ */
+ float getDimension(int resId);
+
+ /**
+ * @see android.content.res.Resources#getColor(int)
+ */
+ int getColor(int resId);
+
+ /**
+ * @see android.content.res.Resources#getFloat(int)
+ */
+ float getFloat(int resId);
+}
diff --git a/src/com/android/launcher3/LauncherExterns.java b/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
similarity index 63%
rename from src/com/android/launcher3/LauncherExterns.java
rename to src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
index 272bbf6..13e4999 100644
--- a/src/com/android/launcher3/LauncherExterns.java
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
@@ -14,19 +14,36 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.systemui.plugins.shared;
import android.content.SharedPreferences;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
+
/**
* This interface defines the set of methods that the Launcher activity exposes. Methods
* here should be safe to call from classes outside of com.android.launcher3.*
*/
public interface LauncherExterns {
- boolean setLauncherCallbacks(LauncherCallbacks callbacks);
-
+ /**
+ * Returns the shared main preference
+ */
SharedPreferences getSharedPrefs();
- void setLauncherOverlay(Launcher.LauncherOverlay overlay);
+ /**
+ * Returns the device specific preference
+ */
+ SharedPreferences getDevicePrefs();
+
+ /**
+ * Sets the overlay on the target activity
+ */
+ void setLauncherOverlay(LauncherOverlay overlay);
+
+ /**
+ * Executes the command, next time the overlay is hidden
+ */
+ void runOnOverlayHidden(Runnable runnable);
+
}
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
new file mode 100644
index 0000000..ac02ba4
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.plugins.shared;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface to control the overlay on Launcher
+ */
+public interface LauncherOverlayManager extends Application.ActivityLifecycleCallbacks {
+
+ default void onDeviceProvideChanged() { }
+
+ default void onAttachedToWindow() { }
+ default void onDetachedFromWindow() { }
+
+ default void dump(String prefix, PrintWriter w) { }
+
+ default void openOverlay() { }
+
+ default void hideOverlay(boolean animate) {
+ hideOverlay(animate ? 200 : 0);
+ }
+
+ default void hideOverlay(int duration) { }
+
+ default boolean startSearch(byte[] config, Bundle extras) {
+ return false;
+ }
+
+ @Override
+ default void onActivityCreated(Activity activity, Bundle bundle) { }
+
+ @Override
+ default void onActivityStarted(Activity activity) { }
+
+ @Override
+ default void onActivityResumed(Activity activity) { }
+
+ @Override
+ default void onActivityPaused(Activity activity) { }
+
+ @Override
+ default void onActivityStopped(Activity activity) { }
+
+ @Override
+ default void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+ @Override
+ default void onActivityDestroyed(Activity activity) { }
+
+ interface LauncherOverlay {
+
+ /**
+ * Touch interaction leading to overscroll has begun
+ */
+ void onScrollInteractionBegin();
+
+ /**
+ * Touch interaction related to overscroll has ended
+ */
+ void onScrollInteractionEnd();
+
+ /**
+ * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
+ * screen (or in the case of RTL, the rightmost screen).
+ */
+ void onScrollChange(float progress, boolean rtl);
+
+ /**
+ * Called when the launcher is ready to use the overlay
+ * @param callbacks A set of callbacks provided by Launcher in relation to the overlay
+ */
+ void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
+ }
+
+ interface LauncherOverlayCallbacks {
+
+ void onScrollChanged(float progress);
+ }
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 789bfd8..dcb4636 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -16,12 +16,14 @@
package com.android.launcher3.model;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.widget.WidgetListRowEntry;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -31,8 +33,13 @@
public class LoaderResults extends BaseLoaderResults {
public LoaderResults(LauncherAppState app, BgDataModel dataModel,
- AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
- super(app, dataModel, allAppsList, pageToBindFirst, callbacks);
+ AllAppsList allAppsList, Callbacks[] callbacks) {
+ this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
+ }
+
+ public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+ AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
+ super(app, dataModel, allAppsList, callbacks, executor);
}
@Override
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 7a7f828..0b99e7a 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -3,30 +3,34 @@
import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
+import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
+
import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AppFilter;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.widget.WidgetItemComparator;
import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -37,8 +41,6 @@
import java.util.Map.Entry;
import java.util.Set;
-import androidx.annotation.Nullable;
-
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
*
@@ -46,6 +48,9 @@
*/
public class WidgetsModel {
+ // True is the widget support is disabled.
+ public static final boolean GO_DISABLE_WIDGETS = false;
+
private static final String TAG = "WidgetsModel";
private static final boolean DEBUG = false;
@@ -81,18 +86,19 @@
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
*/
- public List<ComponentWithLabel> update(LauncherAppState app, @Nullable PackageUserKey packageUser) {
+ public List<ComponentWithLabelAndIcon> update(
+ LauncherAppState app, @Nullable PackageUserKey packageUser) {
Preconditions.assertWorkerThread();
Context context = app.getContext();
final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
- List<ComponentWithLabel> updatedItems = new ArrayList<>();
+ List<ComponentWithLabelAndIcon> updatedItems = new ArrayList<>();
try {
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
PackageManager pm = app.getContext().getPackageManager();
// Widgets
- AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context);
+ WidgetManagerHelper widgetManager = new WidgetManagerHelper(context);
for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
LauncherAppWidgetProviderInfo launcherWidgetInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
@@ -103,14 +109,14 @@
}
// Shortcuts
- for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context)
- .getCustomShortcutActivityList(packageUser)) {
+ for (ShortcutConfigActivityInfo info :
+ queryList(context, packageUser)) {
widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
updatedItems.add(info);
}
setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
} catch (Exception e) {
- if (!FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
+ if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) {
// the returned value may be incomplete and will not be refreshed until the next
// time Launcher starts.
// TODO: after figuring out a repro step, introduce a dirty bit to check when
@@ -238,4 +244,20 @@
}
}
}
+
+ public WidgetItem getWidgetProviderInfoByProviderName(
+ ComponentName providerName) {
+ ArrayList<WidgetItem> widgetsList = mWidgetsList.get(
+ new PackageItemInfo(providerName.getPackageName()));
+ if (widgetsList == null) {
+ return null;
+ }
+
+ for (WidgetItem item : widgetsList) {
+ if (item.componentName.equals(providerName)) {
+ return item;
+ }
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
deleted file mode 100644
index 57f4164..0000000
--- a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shortcuts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutQuery;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
- */
-public class DeepShortcutManager {
- private static final String TAG = "DeepShortcutManager";
-
- private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
- | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
-
- private static DeepShortcutManager sInstance;
-
- public static DeepShortcutManager getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new DeepShortcutManager(context.getApplicationContext());
- }
- return sInstance;
- }
-
- private final LauncherApps mLauncherApps;
-
- private DeepShortcutManager(Context context) {
- mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
- }
-
- /**
- * Queries for the shortcuts with the package name and provided ids.
- *
- * This method is intended to get the full details for shortcuts when they are added or updated,
- * because we only get "key" fields in onShortcutsChanged().
- */
- public QueryResult queryForFullDetails(String packageName,
- List<String> shortcutIds, UserHandle user) {
- return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
- }
-
- /**
- * Gets all the manifest and dynamic shortcuts associated with the given package and user,
- * to be displayed in the shortcuts container on long press.
- */
- public QueryResult queryForShortcutsContainer(@Nullable ComponentName activity,
- UserHandle user) {
- if (activity == null) return QueryResult.FAILURE;
- return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
- activity.getPackageName(), activity, null, user);
- }
-
- /**
- * Removes the given shortcut from the current list of pinned shortcuts.
- * (Runs on background thread)
- */
- public void unpinShortcut(final ShortcutKey key) {
- String packageName = key.componentName.getPackageName();
- String id = key.getId();
- UserHandle user = key.user;
- List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
- pinnedIds.remove(id);
- try {
- mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
- } catch (SecurityException|IllegalStateException e) {
- Log.w(TAG, "Failed to unpin shortcut", e);
- }
- }
-
- /**
- * Adds the given shortcut to the current list of pinned shortcuts.
- * (Runs on background thread)
- */
- public void pinShortcut(final ShortcutKey key) {
- String packageName = key.componentName.getPackageName();
- String id = key.getId();
- UserHandle user = key.user;
- List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
- pinnedIds.add(id);
- try {
- mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
- } catch (SecurityException|IllegalStateException e) {
- Log.w(TAG, "Failed to pin shortcut", e);
- }
- }
-
- public void startShortcut(String packageName, String id, Rect sourceBounds,
- Bundle startActivityOptions, UserHandle user) {
- try {
- mLauncherApps.startShortcut(packageName, id, sourceBounds,
- startActivityOptions, user);
- } catch (SecurityException|IllegalStateException e) {
- Log.e(TAG, "Failed to start shortcut", e);
- }
- }
-
- public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
- try {
- return mLauncherApps.getShortcutIconDrawable(shortcutInfo, density);
- } catch (SecurityException|IllegalStateException e) {
- Log.e(TAG, "Failed to get shortcut icon", e);
- return null;
- }
- }
-
- /**
- * Returns the id's of pinned shortcuts associated with the given package and user.
- *
- * If packageName is null, returns all pinned shortcuts regardless of package.
- */
- public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
- return queryForPinnedShortcuts(packageName, null, user);
- }
-
- public QueryResult queryForPinnedShortcuts(String packageName, List<String> shortcutIds,
- UserHandle user) {
- return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, shortcutIds, user);
- }
-
- public QueryResult queryForAllShortcuts(UserHandle user) {
- return query(FLAG_GET_ALL, null, null, null, user);
- }
-
- private static List<String> extractIds(List<ShortcutInfo> shortcuts) {
- List<String> shortcutIds = new ArrayList<>(shortcuts.size());
- for (ShortcutInfo shortcut : shortcuts) {
- shortcutIds.add(shortcut.getId());
- }
- return shortcutIds;
- }
-
- /**
- * Query the system server for all the shortcuts matching the given parameters.
- * If packageName == null, we query for all shortcuts with the passed flags, regardless of app.
- *
- * TODO: Use the cache to optimize this so we don't make an RPC every time.
- */
- private QueryResult query(int flags, String packageName, ComponentName activity,
- List<String> shortcutIds, UserHandle user) {
- ShortcutQuery q = new ShortcutQuery();
- q.setQueryFlags(flags);
- if (packageName != null) {
- q.setPackage(packageName);
- q.setActivity(activity);
- q.setShortcutIds(shortcutIds);
- }
- try {
- return new QueryResult(mLauncherApps.getShortcuts(q, user));
- } catch (SecurityException|IllegalStateException e) {
- Log.e(TAG, "Failed to query for shortcuts", e);
- return QueryResult.FAILURE;
- }
- }
-
- public boolean hasHostPermission() {
- try {
- return mLauncherApps.hasShortcutHostPermission();
- } catch (SecurityException|IllegalStateException e) {
- Log.e(TAG, "Failed to make shortcut manager call", e);
- }
- return false;
- }
-
- public static class QueryResult extends ArrayList<ShortcutInfo> {
-
- static QueryResult FAILURE = new QueryResult();
-
- private final boolean mWasSuccess;
-
- QueryResult(List<ShortcutInfo> result) {
- super(result == null ? Collections.emptyList() : result);
- mWasSuccess = true;
- }
-
- QueryResult() {
- mWasSuccess = false;
- }
-
-
- public boolean wasSuccess() {
- return mWasSuccess;
- }
- }
-}
diff --git a/src_flags/com/android/launcher3/config/FeatureFlags.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
similarity index 62%
copy from src_flags/com/android/launcher3/config/FeatureFlags.java
copy to src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index 73c6996..6fd147a 100644
--- a/src_flags/com/android/launcher3/config/FeatureFlags.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-package com.android.launcher3.config;
+package com.android.launcher3.uioverrides;
-import android.content.Context;
+import android.app.Person;
+import android.content.pm.ShortcutInfo;
-/**
- * Defines a set of flags used to control various launcher behaviors
- */
-public final class FeatureFlags extends BaseFlags {
- private FeatureFlags() {
- // Prevent instantiation
+import com.android.launcher3.Utilities;
+
+public class ApiWrapper {
+
+ public static Person[] getPersons(ShortcutInfo si) {
+ return Utilities.EMPTY_PERSON_ARRAY;
}
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java b/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
new file mode 100644
index 0000000..232bad3
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides;
+
+
+import android.util.IntProperty;
+import android.view.View;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+
+/**
+ * Controls the blur, for the Launcher surface only.
+ */
+public class BackgroundBlurController implements LauncherStateManager.StateHandler {
+
+ public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
+ new IntProperty<BackgroundBlurController>("backgroundBlur") {
+ @Override
+ public void setValue(BackgroundBlurController blurController, int blurRadius) {}
+
+ @Override
+ public Integer get(BackgroundBlurController blurController) {
+ return 0;
+ }
+ };
+
+ public BackgroundBlurController(Launcher l) {}
+
+ public int getFolderBackgroundBlurAdjustment() {
+ return 0;
+ }
+
+ public void setSurfaceToLauncher(View v) {}
+
+ @Override
+ public void setState(LauncherState toState) {}
+
+ @Override
+ public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
+ LauncherStateManager.AnimationConfig config) {}
+}
diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/src_ui_overrides/com/android/launcher3/uioverrides/DeviceFlag.java
similarity index 64%
rename from go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/DeviceFlag.java
index fb89013..5c1ac28 100644
--- a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -13,19 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep.util;
-import com.android.launcher3.Launcher;
+package com.android.launcher3.uioverrides;
-/** Empty class, only exists so that l3goWithQuickstepIconRecentsDebug compiles. */
-public class ShelfPeekAnim {
- public ShelfPeekAnim(Launcher launcher) {
- }
+import com.android.launcher3.config.FeatureFlags.DebugFlag;
- public enum ShelfAnimState {
- }
+public class DeviceFlag extends DebugFlag {
- public boolean isPeeking() {
- return false;
+ public DeviceFlag(String key, boolean defaultValue, String description) {
+ super(key, defaultValue, description);
}
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java
deleted file mode 100644
index b1a67e9..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import android.content.Context;
-import android.view.OrientationEventListener;
-
-/**
- * Utility class for listening for rotation changes
- */
-public class DisplayRotationListener extends OrientationEventListener {
-
- private final Runnable mCallback;
-
- public DisplayRotationListener(Context context, Runnable callback) {
- super(context);
- mCallback = callback;
- }
-
- @Override
- public void onOrientationChanged(int i) {
- mCallback.run();
- }
-}
diff --git a/src_flags/com/android/launcher3/config/FeatureFlags.java b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
similarity index 64%
rename from src_flags/com/android/launcher3/config/FeatureFlags.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
index 73c6996..4913cad 100644
--- a/src_flags/com/android/launcher3/config/FeatureFlags.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package com.android.launcher3.config;
+package com.android.launcher3.uioverrides;
import android.content.Context;
+import android.os.Bundle;
-/**
- * Defines a set of flags used to control various launcher behaviors
- */
-public final class FeatureFlags extends BaseFlags {
- private FeatureFlags() {
- // Prevent instantiation
- }
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer {
+
+ /** Handle a received surface view request. */
+ public static void render(Context context, Bundle bundle) { }
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
deleted file mode 100644
index 60f12d8..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import android.content.Context;
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
-
-public class TogglableFlag extends BaseTogglableFlag {
-
- public TogglableFlag(String key, boolean defaultValue, String description) {
- super(key, defaultValue, description);
- }
-
- @Override
- public boolean getOverridenDefaultValue(boolean value) {
- return value;
- }
-
- @Override
- public void addChangeListener(Context context, Runnable r) { }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
deleted file mode 100644
index f2b5ed2..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import android.app.Activity;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ShortcutInfo;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.util.TouchController;
-
-import java.io.PrintWriter;
-
-public class UiFactory {
-
- public static TouchController[] createTouchControllers(Launcher launcher) {
- return new TouchController[] {
- launcher.getDragController(), new AllAppsSwipeController(launcher)};
- }
-
- public static Runnable enableLiveUIChanges(Launcher l) {
- return null;
- }
-
- public static StateHandler[] getStateHandler(Launcher launcher) {
- return new StateHandler[] {
- launcher.getAllAppsController(), launcher.getWorkspace() };
- }
-
- public static void resetOverview(Launcher launcher) { }
-
- public static void onLauncherStateOrFocusChanged(Launcher launcher) { }
-
- public static void onCreate(Launcher launcher) { }
-
- public static void onStart(Launcher launcher) { }
-
- public static void onEnterAnimationComplete(Context context) {}
-
- public static void onLauncherStateOrResumeChanged(Launcher launcher) { }
-
- public static void onTrimMemory(Launcher launcher, int level) { }
-
- public static void useFadeOutAnimationForLauncherStart(Launcher launcher,
- CancellationSignal cancellationSignal) { }
-
- public static boolean dumpActivity(Activity activity, PrintWriter writer) {
- return false;
- }
-
- public static void setBackButtonAlpha(Launcher launcher, float alpha, boolean animate) { }
-
-
- public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) {
- return new ScaleAndTranslation(1.1f, 0f, 0f);
- }
-
- public static RotationMode getRotationMode(DeviceProfile dp) {
- return RotationMode.NORMAL;
- }
-
- public static boolean startIntentSenderForResult(Activity activity, IntentSender intent,
- int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
- Bundle options) {
- return false;
- }
-
- public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode,
- Bundle options) {
- return false;
- }
-
- public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
-
- public static void clearSwipeSharedState(boolean finishAnimation) {}
-
- public static Person[] getPersons(ShortcutInfo si) {
- return Utilities.EMPTY_PERSON_ARRAY;
- }
-
- public static void closeSystemWindows() {}
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java b/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java
index b05e125..b3aa365 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/WallpaperColorInfo.java
@@ -16,18 +16,19 @@
package com.android.launcher3.uioverrides;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
import android.content.Context;
import android.graphics.Color;
import android.util.Pair;
+import com.android.launcher3.uioverrides.dynamicui.ColorExtractionAlgorithm;
import com.android.launcher3.uioverrides.dynamicui.WallpaperColorsCompat;
import com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompat;
-import com.android.launcher3.uioverrides.dynamicui.ColorExtractionAlgorithm;
+import com.android.launcher3.util.MainThreadInitializedObject;
import java.util.ArrayList;
-import static android.app.WallpaperManager.FLAG_SYSTEM;
-
public class WallpaperColorInfo implements WallpaperManagerCompat.OnColorsChangedListenerCompat {
private static final int MAIN_COLOR_LIGHT = 0xffdadce0;
@@ -35,17 +36,9 @@
private static final int MAIN_COLOR_REGULAR = 0xff000000;
private static final int FALLBACK_COLOR = Color.WHITE;
- private static final Object sInstanceLock = new Object();
- private static WallpaperColorInfo sInstance;
- public static WallpaperColorInfo getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- sInstance = new WallpaperColorInfo(context.getApplicationContext());
- }
- return sInstance;
- }
- }
+ public static final MainThreadInitializedObject<WallpaperColorInfo> INSTANCE =
+ new MainThreadInitializedObject<>(WallpaperColorInfo::new);
private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
private final WallpaperManagerCompat mWallpaperManager;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index 7006d77..a56801f 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
@@ -42,7 +41,12 @@
};
public AllAppsState(int id) {
- super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, STATE_FLAGS);
+ super(id, ContainerType.ALLAPPS, STATE_FLAGS);
+ }
+
+ @Override
+ public int getTransitionDuration(Launcher context) {
+ return 320;
}
@Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index aeba788..e20b2ca 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,8 +15,7 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -26,7 +25,12 @@
public class OverviewState extends LauncherState {
public OverviewState(int id) {
- super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, FLAG_DISABLE_RESTORE);
+ super(id, ContainerType.WORKSPACE, FLAG_DISABLE_RESTORE);
+ }
+
+ @Override
+ public int getTransitionDuration(Launcher context) {
+ return 250;
}
public static OverviewState newBackgroundState(int id) {
diff --git a/tests/Android.mk b/tests/Android.mk
index 02ead4e..a9fff8e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -36,7 +36,7 @@
endif
LOCAL_MODULE := ub-launcher-aosp-tapl
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := system_current
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -50,13 +50,14 @@
androidx.test.runner \
androidx.test.rules \
androidx.test.uiautomator_uiautomator \
- mockito-target-minus-junit4
+ mockito-target-minus-junit4 \
+ launcher_log_protos_lite
ifneq (,$(wildcard frameworks/base))
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_STATIC_JAVA_LIBRARIES += launcher-aosp-tapl
else
- LOCAL_SDK_VERSION := 28
+ LOCAL_SDK_VERSION := system_28
LOCAL_MIN_SDK_VERSION := 21
LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
endif
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index ffa90b9..1c8f095 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
@@ -79,8 +80,12 @@
</intent-filter>
</activity>
+ <service
+ android:name="com.android.launcher3.testcomponent.ListViewService"
+ android:permission="android.permission.BIND_REMOTEVIEWS" />
+
<provider
- android:name="com.android.launcher3.testcomponent.TestCommandReceiver"
+ android:name="com.android.launcher3.testcomponent.TestCommandProvider"
android:authorities="${packageName}.commands"
android:exported="true"/>
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 24b5b02..5cf96c8 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3.tests">
- <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"
+ <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"
tools:overrideLibrary="android.support.test.uiautomator.v18"/>
<application android:debuggable="true">
diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml
index 9d0a74a..f00138c 100644
--- a/tests/dummy_app/AndroidManifest.xml
+++ b/tests/dummy_app/AndroidManifest.xml
@@ -21,7 +21,7 @@
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.aardwolf">
- <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="21"/>
+ <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="21"/>
<application android:label="Aardwolf">
<activity
android:name="Activity1"
diff --git a/tests/res/layout/test_layout_widget_list.xml b/tests/res/layout/test_layout_widget_list.xml
new file mode 100644
index 0000000..0152040
--- /dev/null
+++ b/tests/res/layout/test_layout_widget_list.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#FFFFFF">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF0000FF"
+ android:id="@android:id/text1"
+ android:padding="10dp" />
+
+ <ListView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:id="@android:id/list" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index efbd9c9..33066e4 100644
--- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -49,7 +49,7 @@
super.setUp();
mDevice.pressHome();
waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
- waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
mSessionId = -1;
}
diff --git a/tests/src/com/android/launcher3/testcomponent/ListViewService.java b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
new file mode 100644
index 0000000..3da20e0
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+public class ListViewService extends RemoteViewsService {
+
+ public static IBinder sBinderForTest;
+
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return new SimpleViewsFactory();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return sBinderForTest != null ? sBinderForTest : super.onBind(intent);
+ }
+
+ public static class SimpleViewsFactory implements RemoteViewsFactory {
+
+ public int viewCount = 0;
+
+ @Override
+ public void onCreate() { }
+
+ @Override
+ public void onDataSetChanged() { }
+
+ @Override
+ public void onDestroy() { }
+
+ @Override
+ public int getCount() {
+ return viewCount;
+ }
+
+ @Override
+ public RemoteViews getViewAt(int i) {
+ RemoteViews views = new RemoteViews("android", android.R.layout.simple_list_item_1);
+ views.setTextViewText(android.R.id.text1, getLabel(i));
+ return views;
+ }
+
+ public String getLabel(int i) {
+ return "Item " + i;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return null;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ public IBinder toBinder() {
+ return new RemoteViewsService() {
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return SimpleViewsFactory.this;
+ }
+ }.onBind(new Intent("dummy_intent"));
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandProvider.java b/tests/src/com/android/launcher3/testcomponent/TestCommandProvider.java
new file mode 100644
index 0000000..f9981a9
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandProvider.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+
+import static com.android.launcher3.testcomponent.TestCommandReceiver.DISABLE_TEST_LAUNCHER;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.ENABLE_TEST_LAUNCHER;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.KILL_PROCESS;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Base64;
+
+import com.android.launcher3.tapl.TestHelpers;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class TestCommandProvider extends ContentProvider {
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ switch (method) {
+ case ENABLE_TEST_LAUNCHER: {
+ getContext().getPackageManager().setComponentEnabledSetting(
+ new ComponentName(getContext(), TestLauncherActivity.class),
+ COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+ return null;
+ }
+ case DISABLE_TEST_LAUNCHER: {
+ getContext().getPackageManager().setComponentEnabledSetting(
+ new ComponentName(getContext(), TestLauncherActivity.class),
+ COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
+ return null;
+ }
+ case KILL_PROCESS: {
+ ((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE))
+ .killBackgroundProcesses(arg);
+ return null;
+ }
+
+ case GET_SYSTEM_HEALTH_MESSAGE: {
+ final Bundle response = new Bundle();
+ response.putString("result",
+ TestHelpers.getSystemHealthMessage(getContext(), Long.parseLong(arg)));
+ return response;
+ }
+
+ case SET_LIST_VIEW_SERVICE_BINDER: {
+ ListViewService.sBinderForTest = extras.getBinder(EXTRA_VALUE);
+ return null;
+ }
+ }
+ return super.call(method, arg, extras);
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ String path = Base64.encodeToString(uri.getPath().getBytes(),
+ Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
+ File file = new File(getContext().getCacheDir(), path);
+ if (!file.exists()) {
+ // Create an empty file so that we can pass its descriptor
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ }
+ }
+
+ return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index 4246096..eb6c3ed 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -15,126 +15,36 @@
*/
package com.android.launcher3.testcomponent;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.DONT_KILL_APP;
-import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
-
-import android.app.Activity;
-import android.app.ActivityManager;
import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.util.Base64;
import androidx.test.InstrumentationRegistry;
-import com.android.launcher3.tapl.TestHelpers;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
/**
* Content provider to receive commands from tests
*/
-public class TestCommandReceiver extends ContentProvider {
+public class TestCommandReceiver {
public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
public static final String KILL_PROCESS = "kill-process";
public static final String GET_SYSTEM_HEALTH_MESSAGE = "get-system-health-message";
+ public static final String SET_LIST_VIEW_SERVICE_BINDER = "set-list-view-service-binder";
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public String getType(Uri uri) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
- switch (method) {
- case ENABLE_TEST_LAUNCHER: {
- getContext().getPackageManager().setComponentEnabledSetting(
- new ComponentName(getContext(), TestLauncherActivity.class),
- COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
- return null;
- }
- case DISABLE_TEST_LAUNCHER: {
- getContext().getPackageManager().setComponentEnabledSetting(
- new ComponentName(getContext(), TestLauncherActivity.class),
- COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
- return null;
- }
- case KILL_PROCESS: {
- ((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE)).
- killBackgroundProcesses(arg);
- return null;
- }
-
- case GET_SYSTEM_HEALTH_MESSAGE: {
- final Bundle response = new Bundle();
- response.putString("result",
- TestHelpers.getSystemHealthMessage(getContext(), Long.parseLong(arg)));
- return response;
- }
- }
- return super.call(method, arg, extras);
- }
+ public static final String EXTRA_VALUE = "value";
public static Bundle callCommand(String command) {
return callCommand(command, null);
}
public static Bundle callCommand(String command, String arg) {
- Instrumentation inst = InstrumentationRegistry.getInstrumentation();
- Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
- return inst.getTargetContext().getContentResolver().call(uri, command, arg, null);
+ return callCommand(command, arg, null);
}
- @Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- String path = Base64.encodeToString(uri.getPath().getBytes(),
- Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
- File file = new File(getContext().getCacheDir(), path);
- if (!file.exists()) {
- // Create an empty file so that we can pass its descriptor
- try {
- file.createNewFile();
- } catch (IOException e) {
- }
- }
-
- return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+ public static Bundle callCommand(String command, String arg, Bundle extras) {
+ Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
+ return inst.getTargetContext().getContentResolver().call(uri, command, arg, extras);
}
}
diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index 5174e4d..472e1a1 100644
--- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -21,14 +21,17 @@
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.test.InstrumentationRegistry;
@@ -86,7 +89,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 - mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragStart(anyBoolean());
+ verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
}
@Test
@@ -96,7 +99,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragStart(anyBoolean());
+ verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
}
@Test
@@ -104,7 +107,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener, never()).onDragStart(anyBoolean());
+ verify(mMockListener, never()).onDragStart(anyBoolean(), anyFloat());
}
@Test
@@ -115,7 +118,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragStart(anyBoolean());
+ verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
}
@Test
@@ -126,7 +129,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 - mTouchSlop, 100);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragStart(anyBoolean());
+ verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
}
@Test
@@ -137,7 +140,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 - mTouchSlop, 100);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragStart(anyBoolean());
+ verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
}
@Test
@@ -148,7 +151,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragStart(anyBoolean());
+ verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
}
@Test
@@ -156,7 +159,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDrag(anyFloat(), anyObject());
+ verify(mMockListener).onDrag(anyFloat(), anyFloat(), any(MotionEvent.class));
}
@Test
@@ -168,4 +171,21 @@
// TODO: actually calculate the following parameters and do exact value checks.
verify(mMockListener).onDragEnd(anyFloat());
}
+
+ @Test
+ public void testInterleavedSetState() {
+ doAnswer(invocationOnMock -> {
+ // Sets state to IDLE. (Normally onDragEnd() will have state SETTLING.)
+ mDetector.finishedScrolling();
+ return null;
+ }).when(mMockListener).onDragEnd(anyFloat());
+
+ mGenerator.put(0, 100, 100);
+ mGenerator.move(0, 100, 100 + mTouchSlop);
+ mGenerator.move(0, 100, 100 + mTouchSlop * 2);
+ mGenerator.lift(0);
+ verify(mMockListener).onDragEnd(anyFloat());
+ assertTrue("SwipeDetector should be IDLE but was " + mDetector.mState,
+ mDetector.isIdleState());
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 9a894b1..3d12248 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -15,25 +15,31 @@
*/
package com.android.launcher3.ui;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static java.lang.System.exit;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.os.Debug;
import android.os.Process;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -42,28 +48,30 @@
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
+import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.FailureRewriterRule;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.launcher3.util.rule.LauncherActivityRule;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.RuleChain;
@@ -79,6 +87,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
/**
* Base class for all instrumentation tests providing various utility methods.
@@ -88,16 +97,52 @@
public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
- public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866
+ public static final long DEFAULT_UI_TIMEOUT = 10000;
private static final String TAG = "AbstractLauncherUiTest";
+ private static String sDetectedActivityLeak;
+ private static boolean sActivityLeakReported;
+
protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
protected Context mTargetContext;
protected String mTargetPackage;
+ private int mLauncherPid;
+
+ static {
+ if (TestHelpers.isInLauncherProcess()) {
+ StrictMode.VmPolicy.Builder builder =
+ new StrictMode.VmPolicy.Builder()
+ .detectActivityLeaks()
+ .penaltyLog()
+ .penaltyListener(Runnable::run, violation -> {
+ // Runs in the main thread. We can't dumpheap in the main thread,
+ // so let's just mark the fact that the leak has happened.
+ if (sDetectedActivityLeak == null) {
+ sDetectedActivityLeak = violation.toString();
+ try {
+ Debug.dumpHprofData(
+ getInstrumentation().getTargetContext()
+ .getFilesDir().getPath()
+ + "/ActivityLeakHeapDump.hprof");
+ } catch (Throwable e) {
+ Log.e(TAG, "dumpHprofData failed", e);
+ }
+ }
+ });
+ StrictMode.setVmPolicy(builder.build());
+ }
+ }
+
+ public static void checkDetectedLeaks() {
+ if (sDetectedActivityLeak != null && !sActivityLeakReported) {
+ sActivityLeakReported = true;
+ }
+ }
protected AbstractLauncherUiTest() {
+ mLauncher.enableCheckEventsForSuccessfulGestures();
try {
mDevice.setOrientationNatural();
} catch (RemoteException e) {
@@ -114,15 +159,13 @@
checkLauncherIntegrity(launcher, containerType)));
}
mLauncher.enableDebugTracing();
+ // Avoid double-reporting of Launcher crashes.
+ mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0);
}
protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@Rule
- public ShellCommandRule mDefaultLauncherRule =
- TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null;
-
- @Rule
public ShellCommandRule mDisableHeadsUpNotification =
ShellCommandRule.disableHeadsUpNotification();
@@ -150,16 +193,21 @@
}
protected TestRule getRulesInsideActivityMonitor() {
- return RuleChain.
- outerRule(new PortraitLandscapeRunner(this)).
- around(new FailureWatcher(mDevice));
+ final RuleChain inner = RuleChain.outerRule(new PortraitLandscapeRunner(this))
+ .around(new FailureWatcher(mDevice));
+
+ return TestHelpers.isInLauncherProcess()
+ ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher())
+ .around(inner) :
+ inner;
}
@Rule
public TestRule mOrderSensitiveRules = RuleChain.
- outerRule(new TestStabilityRule()).
- around(mActivityMonitor).
- around(getRulesInsideActivityMonitor());
+ outerRule(new FailureRewriterRule())
+ .around(new TestStabilityRule())
+ .around(mActivityMonitor)
+ .around(getRulesInsideActivityMonitor());
public UiDevice getDevice() {
return mDevice;
@@ -167,25 +215,38 @@
@Before
public void setUp() throws Exception {
+ final String launcherPackageName = mDevice.getLauncherPackageName();
+ try {
+ final Context context = InstrumentationRegistry.getContext();
+ final PackageManager pm = context.getPackageManager();
+ final PackageInfo launcherPackage = pm.getPackageInfo(launcherPackageName, 0);
+
+ if (!launcherPackage.versionName.equals("BuildFromAndroidStudio")) {
+ Assert.assertEquals("Launcher version doesn't match tests version",
+ pm.getPackageInfo(context.getPackageName(), 0).getLongVersionCode(),
+ launcherPackage.getLongVersionCode());
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ mLauncherPid = 0;
// Disable app tracker
AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
mTargetContext = InstrumentationRegistry.getTargetContext();
mTargetPackage = mTargetContext.getPackageName();
+ mLauncherPid = mLauncher.getPid();
}
@After
public void verifyLauncherState() {
- try {
- // Limits UI tests affecting tests running after them.
- waitForModelLoaded();
- } catch (Throwable t) {
- Log.e(TAG,
- "Couldn't deinit after a test, exiting tests, see logs for failures that "
- + "could have caused this",
- t);
- exit(1);
+ // Limits UI tests affecting tests running after them.
+ mLauncher.waitForLauncherInitialized();
+ if (mLauncherPid != 0) {
+ assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
}
+ checkDetectedLeaks();
}
protected void clearLauncherData() throws IOException, InterruptedException {
@@ -196,6 +257,7 @@
} else {
clearPackageData(mDevice.getLauncherPackageName());
mLauncher.enableDebugTracing();
+ mLauncherPid = mLauncher.getPid();
}
}
@@ -217,14 +279,36 @@
} catch (Throwable t) {
throw new IllegalArgumentException(t);
}
- waitForModelLoaded();
+ mLauncher.waitForLauncherInitialized();
}
- protected void waitForModelLoaded() {
- waitForLauncherCondition("Launcher model didn't load", launcher -> {
- final LauncherModel model = LauncherAppState.getInstance(mTargetContext).getModel();
- return model.getCallback() == null || model.isModelLoaded();
- });
+ /**
+ * Adds {@param item} on the homescreen on the 0th screen
+ */
+ protected void addItemToScreen(ItemInfo item) {
+ ContentResolver resolver = mTargetContext.getContentResolver();
+ int screenId = FIRST_SCREEN_ID;
+ // Update the screen id counter for the provider.
+ LauncherSettings.Settings.call(resolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+ if (screenId > FIRST_SCREEN_ID) {
+ screenId = FIRST_SCREEN_ID;
+ }
+
+ // Insert the item
+ ContentWriter writer = new ContentWriter(mTargetContext);
+ item.id = LauncherSettings.Settings.call(
+ resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ item.screenId = screenId;
+ item.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites._ID, item.id);
+ resolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
+ resetLoaderState();
+
+ // Launch the home activity
+ mDevice.pressHome();
+ mLauncher.waitForLauncherInitialized();
}
/**
@@ -233,7 +317,7 @@
protected <T> T getOnUiThread(final Callable<T> callback) {
try {
return mMainThreadExecutor.submit(callback).get();
- } catch (Exception e) {
+ } catch (Throwable e) {
throw new RuntimeException(e);
}
}
@@ -252,9 +336,9 @@
// Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
// the results of that gesture because the wait can hide flakeness.
- protected void waitForState(String message, LauncherState state) {
+ protected void waitForState(String message, Supplier<LauncherState> state) {
waitForLauncherCondition(message,
- launcher -> launcher.getStateManager().getCurrentStableState() == state);
+ launcher -> launcher.getStateManager().getCurrentStableState() == state.get());
}
protected void waitForResumed(String message) {
@@ -278,7 +362,7 @@
protected void waitForLauncherCondition(
String message, Function<Launcher, Boolean> condition, long timeout) {
if (!TestHelpers.isInLauncherProcess()) return;
- Wait.atMost(message, () -> getFromLauncher(condition), timeout);
+ Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
@@ -291,7 +375,7 @@
final Object fromLauncher = getFromLauncher(f);
output[0] = fromLauncher;
return fromLauncher != null;
- }, timeout);
+ }, timeout, mLauncher);
return (T) output[0];
}
@@ -305,13 +389,12 @@
Wait.atMost(message, () -> {
testThreadAction.run();
return getFromLauncher(condition);
- }, timeout);
+ }, timeout, mLauncher);
}
protected LauncherActivityInfo getSettingsApp() {
- return LauncherAppsCompat.getInstance(mTargetContext)
- .getActivityList("com.android.settings",
- Process.myUserHandle()).get(0);
+ return mTargetContext.getSystemService(LauncherApps.class)
+ .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
}
/**
@@ -344,7 +427,7 @@
}
}
- protected void startAppFast(String packageName) {
+ public static void startAppFast(String packageName) {
startIntent(
getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
packageName),
@@ -352,7 +435,7 @@
true /* newTask */);
}
- protected void startTestActivity(int activityNumber) {
+ public static void startTestActivity(int activityNumber) {
final String packageName = getAppPackageName();
final Intent intent = getInstrumentation().getContext().getPackageManager().
getLaunchIntentForPackage(packageName);
@@ -362,7 +445,7 @@
false /* newTask */);
}
- private void startIntent(Intent intent, BySelector selector, boolean newTask) {
+ private static void startIntent(Intent intent, BySelector selector, boolean newTask) {
intent.addCategory(Intent.CATEGORY_LAUNCHER);
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -371,14 +454,20 @@
}
getInstrumentation().getTargetContext().startActivity(intent);
assertTrue("App didn't start: " + selector,
- mDevice.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
+ UiDevice.getInstance(getInstrumentation())
+ .wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
}
- public static String resolveSystemApp(String category) {
+ public static ActivityInfo resolveSystemAppInfo(String category) {
return getInstrumentation().getContext().getPackageManager().resolveActivity(
new Intent(Intent.ACTION_MAIN).addCategory(category),
PackageManager.MATCH_SYSTEM_ONLY).
- activityInfo.packageName;
+ activityInfo;
+ }
+
+
+ public static String resolveSystemApp(String category) {
+ return resolveSystemAppInfo(category).packageName;
}
protected void closeLauncherActivity() {
@@ -396,9 +485,9 @@
return !launcher.hasBeenResumed();
}
- protected boolean isInState(LauncherState state) {
+ protected boolean isInState(Supplier<LauncherState> state) {
if (!TestHelpers.isInLauncherProcess()) return true;
- return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
+ return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get());
}
protected int getAllAppsScroll(Launcher launcher) {
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
deleted file mode 100644
index ff21880..0000000
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.android.launcher3.ui;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.testcomponent.TestCommandReceiver;
-import com.android.launcher3.util.LauncherLayoutBuilder;
-import com.android.launcher3.util.rule.ShellCommandRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.OutputStreamWriter;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class DefaultLayoutProviderTest extends AbstractLauncherUiTest {
-
- @Rule
- public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
-
- private static final String SETTINGS_APP = "com.android.settings";
-
- private Context mContext;
- private String mAuthority;
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- mContext = InstrumentationRegistry.getContext();
-
- PackageManager pm = mTargetContext.getPackageManager();
- ProviderInfo pi = pm.getProviderInfo(new ComponentName(mContext,
- TestCommandReceiver.class), 0);
- mAuthority = pi.authority;
- }
-
- @Test
- public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
- writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
-
- // Launch the home activity
- mDevice.pressHome();
-
- mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
- }
-
- @Test
- public void testCustomProfileLoaded_with_widget() throws Exception {
- // A non-restored widget with no config screen gets restored automatically.
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
-
- writeLayout(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
- .putWidget(info.getComponent().getPackageName(),
- info.getComponent().getClassName(), 2, 2));
-
- // Launch the home activity
- mDevice.pressHome();
-
- // Verify widget present
- assertTrue("Widget is not present",
- mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
- }
-
- @Test
- public void testCustomProfileLoaded_with_folder() throws Exception {
- writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
- .addApp(SETTINGS_APP, SETTINGS_APP)
- .addApp(SETTINGS_APP, SETTINGS_APP)
- .addApp(SETTINGS_APP, SETTINGS_APP)
- .build());
-
- // Launch the home activity
- mDevice.pressHome();
-
- mLauncher.getWorkspace().getHotseatFolder("Folder: Copy");
- }
-
- @After
- public void cleanup() throws Exception {
- mDevice.executeShellCommand("settings delete secure launcher3.layout.provider");
- }
-
- private void writeLayout(LauncherLayoutBuilder builder) throws Exception {
- mDevice.executeShellCommand("settings put secure launcher3.layout.provider " + mAuthority);
- ParcelFileDescriptor pfd = mTargetContext.getContentResolver().openFileDescriptor(
- Uri.parse("content://" + mAuthority + "/launcher_layout"), "w");
-
- try (OutputStreamWriter writer = new OutputStreamWriter(new AutoCloseOutputStream(pfd))) {
- builder.build(writer);
- }
- clearLauncherData();
- }
-}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 80bb3ed..38f50c1 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -38,8 +38,8 @@
evaluateInPortrait();
evaluateInLandscape();
- } catch (Exception e) {
- Log.e(TAG, "Exception", e);
+ } catch (Throwable e) {
+ Log.e(TAG, "Error", e);
throw e;
} finally {
mTest.mDevice.setOrientationNatural();
@@ -56,6 +56,7 @@
private void evaluateInPortrait() throws Throwable {
mTest.mDevice.setOrientationNatural();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
+ AbstractLauncherUiTest.checkDetectedLeaks();
base.evaluate();
mTest.getDevice().pressHome();
}
@@ -63,6 +64,7 @@
private void evaluateInLandscape() throws Throwable {
mTest.mDevice.setOrientationLeft();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
+ AbstractLauncherUiTest.checkDetectedLeaks();
base.evaluate();
mTest.getDevice().pressHome();
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 2cf6c2b..f8bbf21 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -24,8 +24,6 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.util.Log;
-
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -38,13 +36,11 @@
import com.android.launcher3.tapl.AppIconMenuItem;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.widget.WidgetsFullSheet;
import com.android.launcher3.widget.WidgetsRecyclerView;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,10 +59,12 @@
test.clearLauncherData();
test.mDevice.pressHome();
test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
- test.waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ test.waitForState("Launcher internal state didn't switch to Home",
+ () -> LauncherState.NORMAL);
test.waitForResumed("Launcher internal state is still Background");
// Check that we switched to home.
test.mLauncher.getWorkspace();
+ AbstractLauncherUiTest.checkDetectedLeaks();
}
// Please don't add negative test cases for methods that fail only after a long wait.
@@ -104,7 +102,14 @@
}
@Test
- @Ignore
+ public void testOpenHomeSettingsFromWorkspace() {
+ mDevice.pressMenu();
+ mDevice.waitForIdle();
+ mLauncher.getOptionsPopupMenu().getMenuItem("Home settings")
+ .launch(mDevice.getLauncherPackageName());
+ }
+
+ @Test
public void testPressHomeOnAllAppsContextMenu() throws Exception {
final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
allApps.freeze();
@@ -123,7 +128,7 @@
assertTrue(
"Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
// Test flinging forward and backward.
test.executeOnLauncher(launcher -> assertEquals(
@@ -132,7 +137,7 @@
allApps.flingForward();
assertTrue("Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
final Integer flingForwardY = test.getFromLauncher(
launcher -> test.getAllAppsScroll(launcher));
test.executeOnLauncher(
@@ -142,7 +147,7 @@
allApps.flingBackward();
assertTrue(
"Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
final Integer flingBackwardY = test.getFromLauncher(
launcher -> test.getAllAppsScroll(launcher));
test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
@@ -159,7 +164,7 @@
assertTrue(
"Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
} finally {
allApps.unfreeze();
}
@@ -170,7 +175,8 @@
public void testWorkspaceSwitchToAllApps() {
assertNotNull("switchToAllApps() returned null",
mLauncher.getWorkspace().switchToAllApps());
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
}
@Test
@@ -196,7 +202,7 @@
// Test flinging workspace.
workspace.flingBackward();
- assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+ assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
executeOnLauncher(
launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
0, getCurrentWorkspacePage(launcher)));
@@ -205,7 +211,7 @@
executeOnLauncher(
launcher -> assertEquals("Flinging forward didn't switch workspace to page #1",
1, getCurrentWorkspacePage(launcher)));
- assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+ assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
// Test starting a workspace app.
final AppIcon app = workspace.getWorkspaceAppIcon("Chrome");
@@ -231,7 +237,8 @@
@PortraitLandscape
public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
runIconLaunchFromAllAppsTest(this, allApps);
}
@@ -345,10 +352,4 @@
public static String getAppPackageName() {
return getInstrumentation().getContext().getPackageName();
}
-
- @Test
- @Stability
- public void testTestStabilityAttribute() {
- Log.d("TestStabilityRule", "Hello world!");
- }
}
diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
index d0df664..eceff34 100644
--- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java
+++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
@@ -24,12 +24,10 @@
import android.view.View;
import android.view.ViewGroup;
-import androidx.test.uiautomator.UiDevice;
-
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.concurrent.Callable;
import java.util.function.Function;
@@ -53,7 +51,7 @@
hasConfigureScreen ? AppWidgetWithConfig.class
: AppWidgetNoConfig.class);
Log.d(TAG, "findWidgetProvider componentName=" + cn.flattenToString());
- return AppWidgetManagerCompat.getInstance(getTargetContext())
+ return new WidgetManagerHelper(getTargetContext())
.findProvider(cn, Process.myUserHandle());
}
});
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index d9edc35..db2d974 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -17,22 +17,40 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.widget.TextView;
+
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.WorkEduView;
+import com.android.launcher3.views.WorkFooterContainer;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+import java.util.Objects;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
public class WorkTabTest extends AbstractLauncherUiTest {
private int mProfileUserId;
+ private static final int WORK_PAGE = AllAppsContainerView.AdapterHolder.WORK;
+
@Before
public void createWorkProfile() throws Exception {
String output =
@@ -54,14 +72,117 @@
@Test
public void workTabExists() {
mDevice.pressHome();
- waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
+ waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+ executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+ waitForLauncherCondition("Personal tab is missing",
+ launcher -> launcher.getAppsView().isPersonalTabVisible(), 60000);
+ waitForLauncherCondition("Work tab is missing",
+ launcher -> launcher.getAppsView().isWorkTabVisible(), 60000);
+ }
+
+ @Test
+ public void toggleWorks() {
+ mDevice.pressHome();
+ waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+ executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+ waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
+ getOnceNotNull("Apps view did not bind",
+ launcher -> launcher.getAppsView().getWorkFooterContainer(), 60000);
+
+ UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
+ assertEquals(2, userManager.getUserProfiles().size());
+ UserHandle workProfile = getFromLauncher(l -> {
+ UserHandle myHandle = Process.myUserHandle();
+ List<UserHandle> userProfiles = userManager.getUserProfiles();
+ return userProfiles.get(0) == myHandle ? userProfiles.get(1) : userProfiles.get(0);
+ });
+
+ waitForLauncherCondition("work profile can't be turned off",
+ l -> userManager.requestQuietModeEnabled(true, workProfile));
+
+ assertTrue(userManager.isQuietModeEnabled(workProfile));
+ executeOnLauncher(launcher -> {
+ WorkFooterContainer wf = launcher.getAppsView().getWorkFooterContainer();
+ ((AllAppsPagedView) launcher.getAppsView().getContentView()).snapToPageImmediately(
+ AllAppsContainerView.AdapterHolder.WORK);
+ wf.getWorkModeSwitch().toggle();
+ });
+ waitForLauncherCondition("Work toggle did not work",
+ l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
+ }
+
+ @Test
+ public void testWorkEduFlow() {
+ mDevice.pressHome();
+ waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+ executeOnLauncher(launcher -> launcher.getSharedPrefs().edit().remove(
+ WorkEduView.KEY_WORK_EDU_STEP).remove(
+ WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
+
+ waitForLauncherCondition("Work tab not setup",
+ launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
+ 60000);
+
+ executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+ WorkEduView workEduView = getEduView();
+ // verify personal app edu is seen first and click "next"
+ executeOnLauncher(l -> {
+ assertEquals(((TextView) workEduView.findViewById(R.id.content_text)).getText(),
+ l.getResources().getString(R.string.work_profile_edu_personal_apps));
+ workEduView.findViewById(R.id.proceed).callOnClick();
+ });
+ // verify work edu is seen next
+ waitForLauncherCondition("Launcher did not show the next edu screen", l ->
+ ((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage() == WORK_PAGE
+ && ((TextView) workEduView.findViewById(
+ R.id.content_text)).getText().equals(
+ l.getResources().getString(R.string.work_profile_edu_work_apps)));
+ }
+
+ @Test
+ public void testWorkEduIntermittent() {
+ mDevice.pressHome();
+ waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+ executeOnLauncher(launcher -> launcher.getSharedPrefs().edit().remove(
+ WorkEduView.KEY_WORK_EDU_STEP).remove(
+ WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
+
+
+ waitForLauncherCondition("Work tab not setup",
+ launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
+ 60000);
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
- /*
- assertTrue("Personal tab is missing", waitForLauncherCondition(
- launcher -> launcher.getAppsView().isPersonalTabVisible()));
- assertTrue("Work tab is missing", waitForLauncherCondition(
- launcher -> launcher.getAppsView().isWorkTabVisible()));
- */
+ // verify personal app edu is seen
+ getEduView();
+
+ // dismiss personal edu
+ mDevice.pressHome();
+
+ // open work tab
+ executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+ executeOnLauncher(launcher -> {
+ AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
+ pagedView.setCurrentPage(WORK_PAGE);
+ });
+
+ WorkEduView workEduView = getEduView();
+
+ // verify work tab edu is shown
+ waitForLauncherCondition("Launcher did not show the next edu screen",
+ l -> ((TextView) workEduView.findViewById(R.id.content_text)).getText().equals(
+ l.getResources().getString(R.string.work_profile_edu_work_apps)));
}
+
+
+ private WorkEduView getEduView() {
+ waitForLauncherCondition("Edu did not show", l -> {
+ DragLayer dragLayer = l.getDragLayer();
+ return dragLayer.getChildCount() > 0 && dragLayer.getChildAt(
+ dragLayer.getChildCount() - 1) instanceof WorkEduView;
+ });
+ return getFromLauncher(launcher -> (WorkEduView) launcher.getDragLayer().getChildAt(
+ launcher.getDragLayer().getChildCount() - 1));
+ }
+
}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index e1b3ede..de9757f 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -36,8 +36,8 @@
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Before;
@@ -103,12 +103,12 @@
setResult(acceptConfig);
if (acceptConfig) {
- Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT);
+ Wait.atMost("", new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
} else {
// Verify that the widget id is deleted.
- Wait.atMost(null, () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
- DEFAULT_ACTIVITY_TIMEOUT);
+ Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
+ DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
}
}
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index f42bf1f..793af48 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,21 +15,26 @@
*/
package com.android.launcher3.ui.widget;
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
+import android.widget.RemoteViews;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -38,16 +43,14 @@
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.R;
+import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.ContentWriter;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.WidgetHostViewLoader;
+import com.android.launcher3.widget.WidgetManagerHelper;
import org.junit.After;
import org.junit.Before;
@@ -57,7 +60,6 @@
import java.util.HashSet;
import java.util.Set;
-import java.util.function.Consumer;
/**
* Tests for bind widget flow.
@@ -72,7 +74,6 @@
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
private ContentResolver mResolver;
- private AppWidgetManagerCompat mWidgetManager;
// Objects created during test, which should be cleaned up in the end.
private Cursor mCursor;
@@ -85,11 +86,11 @@
super.setUp();
mResolver = mTargetContext.getContentResolver();
- mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext);
// Clear all existing data
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+ LauncherSettings.Settings.call(mResolver,
+ LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
}
@After
@@ -108,7 +109,7 @@
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
LauncherAppWidgetInfo item = createWidgetInfo(info, true);
- setupContents(item);
+ addItemToScreen(item);
verifyWidgetPresent(info);
}
@@ -117,7 +118,7 @@
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
LauncherAppWidgetInfo item = createWidgetInfo(info, true);
- setupContents(item);
+ addItemToScreen(item);
verifyWidgetPresent(info);
}
@@ -127,7 +128,7 @@
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.appWidgetId = -33;
- setupContents(item);
+ addItemToScreen(item);
final Workspace workspace = mLauncher.getWorkspace();
// Item deleted from db
@@ -148,7 +149,7 @@
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
- setupContents(item);
+ addItemToScreen(item);
verifyWidgetPresent(info);
}
@@ -161,7 +162,7 @@
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
- setupContents(item);
+ addItemToScreen(item);
verifyPendingWidgetPresent();
// Item deleted from db
@@ -175,6 +176,26 @@
assertNotNull(AppWidgetManager.getInstance(mTargetContext)
.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
LauncherSettings.Favorites.APPWIDGET_ID))));
+
+ // send OPTION_APPWIDGET_RESTORE_COMPLETED
+ int appWidgetId = mCursor.getInt(
+ mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID));
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext);
+
+ Bundle b = new Bundle();
+ b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true);
+ RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready);
+ appWidgetManager.updateAppWidgetOptions(appWidgetId, b);
+ appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
+
+
+ // verify changes are reflected
+ waitForLauncherCondition("App widget options did not update",
+ l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean(
+ WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED));
+ executeOnLauncher(l -> l.getAppWidgetHost().startListening());
+ verifyWidgetPresent(info);
+ assertNull(mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT));
}
@Test
@@ -183,7 +204,7 @@
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
- setupContents(item);
+ addItemToScreen(item);
assertTrue("Pending widget exists",
mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
@@ -202,7 +223,7 @@
| LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
- setupContents(item);
+ addItemToScreen(item);
verifyPendingWidgetPresent();
// Verify item still exists in db
@@ -230,7 +251,7 @@
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
mSessionId = installer.createSession(params);
- setupContents(item);
+ addItemToScreen(item);
verifyPendingWidgetPresent();
// Verify item still exists in db
@@ -245,34 +266,6 @@
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
}
- /**
- * Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the
- * widget class is displayed on the homescreen.
- */
- private void setupContents(LauncherAppWidgetInfo item) {
- int screenId = FIRST_SCREEN_ID;
- // Update the screen id counter for the provider.
- LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
- if (screenId > FIRST_SCREEN_ID) {
- screenId = FIRST_SCREEN_ID;
- }
-
- // Insert the item
- ContentWriter writer = new ContentWriter(mTargetContext);
- item.id = LauncherSettings.Settings.call(
- mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
- item.screenId = screenId;
- item.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites._ID, item.id);
- mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
- resetLoaderState();
-
- // Launch the home activity
- mDevice.pressHome();
- }
-
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
assertTrue("Widget is not present",
mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
@@ -285,11 +278,14 @@
/**
* Creates a LauncherAppWidgetInfo corresponding to {@param info}
+ *
* @param bindWidget if true the info is bound and a valid widgetId is assigned to
* the LauncherAppWidgetInfo
*/
- private LauncherAppWidgetInfo createWidgetInfo(
+ public static LauncherAppWidgetInfo createWidgetInfo(
LauncherAppWidgetProviderInfo info, boolean bindWidget) {
+ Context targetContext = getTargetContext();
+
LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
LauncherAppWidgetInfo.NO_ID, info.provider);
item.spanX = info.minSpanX;
@@ -307,11 +303,12 @@
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
- Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo);
+ Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo);
- AppWidgetHost host = new LauncherAppWidgetHost(mTargetContext);
+ AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
int widgetId = host.allocateAppWidgetId();
- if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) {
+ if (!new WidgetManagerHelper(targetContext)
+ .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
host.deleteAppWidgetId(widgetId);
throw new IllegalArgumentException("Unable to bind widget id");
}
@@ -330,11 +327,11 @@
Set<String> activePackage = getOnUiThread(() -> {
Set<String> packages = new HashSet<>();
- PackageInstallerCompat.getInstance(mTargetContext).updateAndGetActiveSessionCache()
+ InstallSessionHelper.INSTANCE.get(mTargetContext).getActiveSessions()
.keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName));
return packages;
});
- while(true) {
+ while (true) {
try {
mTargetContext.getPackageManager().getPackageInfo(
pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
@@ -344,7 +341,7 @@
}
}
pkg = invalidPackage + count;
- count ++;
+ count++;
}
LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10,
new ComponentName(pkg, "com.test.widgetprovider"));
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 07129dd..0246f95 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -41,8 +41,8 @@
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.testcomponent.RequestPinItemActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Before;
@@ -59,7 +59,8 @@
@RunWith(AndroidJUnit4.class)
public class RequestPinItemTest extends AbstractLauncherUiTest {
- @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+ @Rule
+ public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
private String mCallbackAction;
private String mShortcutId;
@@ -84,10 +85,10 @@
.equals(AppWidgetNoConfig.class.getName()));
}
- @Test
+ @Test
public void testPinWidgetNoConfig_customPreview() throws Throwable {
// Command to set custom preview
- Intent command = RequestPinItemActivity.getCommandIntent(
+ Intent command = RequestPinItemActivity.getCommandIntent(
RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
@@ -169,7 +170,8 @@
// Go back to home
mLauncher.pressHome();
- Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT);
+ Wait.atMost("", new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT,
+ mLauncher);
}
/**
diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java
deleted file mode 100644
index d85dd3a..0000000
--- a/tests/src/com/android/launcher3/util/Condition.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.launcher3.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import androidx.test.uiautomator.UiObject2;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public interface Condition {
-
- boolean isTrue() throws Throwable;
-
- /**
- * Converts the condition to be run on UI thread.
- */
- static Condition runOnUiThread(final Condition condition) {
- final LooperExecutor executor = MAIN_EXECUTOR;
- return () -> {
- final AtomicBoolean value = new AtomicBoolean(false);
- final Throwable[] exceptions = new Throwable[1];
- final CountDownLatch latch = new CountDownLatch(1);
- executor.execute(() -> {
- try {
- value.set(condition.isTrue());
- } catch (Throwable e) {
- exceptions[0] = e;
- }
-
- });
- latch.await(1, TimeUnit.SECONDS);
- if (exceptions[0] != null) {
- throw exceptions[0];
- }
- return value.get();
- };
- }
-
- static Condition minChildCount(final UiObject2 obj, final int childCount) {
- return () -> obj.getChildCount() >= childCount;
- }
-}
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
index 8f89173..ed2ec7b 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
@@ -17,8 +17,6 @@
package com.android.launcher3.util;
import static com.android.launcher3.util.Executors.createAndStartNewLooper;
-import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX;
-import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -64,15 +62,30 @@
*
* When we register event XXX:enter, we hold all other events until we register XXX:exit.
*/
-public class RaceConditionReproducer implements RaceConditionTracker.EventProcessor {
+public class RaceConditionReproducer {
private static final String TAG = "RaceConditionReproducer";
+
+ private static final boolean ENTER = true;
+ private static final boolean EXIT = false;
+ private static final String ENTER_POSTFIX = "enter";
+ private static final String EXIT_POSTFIX = "exit";
+
private static final long SHORT_TIMEOUT_MS = 2000;
private static final long LONG_TIMEOUT_MS = 60000;
// Handler used to resume postponed events.
- private static final Handler POSTPONED_EVENT_RESUME_HANDLER = createEventResumeHandler();
+ private static final Handler POSTPONED_EVENT_RESUME_HANDLER =
+ new Handler(createAndStartNewLooper("RaceConditionEventResumer"));
- private static Handler createEventResumeHandler() {
- return new Handler(createAndStartNewLooper("RaceConditionEventResumer"));
+ public static String enterExitEvt(String eventName, boolean isEnter) {
+ return eventName + ":" + (isEnter ? ENTER_POSTFIX : EXIT_POSTFIX);
+ }
+
+ public static String enterEvt(String eventName) {
+ return enterExitEvt(eventName, ENTER);
+ }
+
+ public static String exitEvt(String eventName) {
+ return enterExitEvt(eventName, EXIT);
}
/**
@@ -209,7 +222,8 @@
parseReproString(mReproString) : generateSequenceToFollowLocked();
Log.e(TAG, "---- Start of iteration; state:\n" + dumpStateLocked());
checkIfCompletedSequenceToFollowLocked();
- RaceConditionTracker.setEventProcessor(this);
+
+ TraceHelperForTest.setRaceConditionReproducer(this);
}
/**
@@ -218,7 +232,8 @@
* Returns whether we need more iterations.
*/
public synchronized boolean finishIteration() {
- RaceConditionTracker.setEventProcessor(null);
+ TraceHelperForTest.setRaceConditionReproducer(null);
+
runResumeAllEventsCallbackLocked();
assertTrue("Non-empty postponed events", mPostponedEvents.isEmpty());
assertTrue("Last registered event is :enter", lastEventAsEnter() == null);
@@ -243,7 +258,6 @@
/**
* Called when the app issues an event.
*/
- @Override
public void onEvent(String event) {
final Semaphore waitObject = tryRegisterEvent(event);
if (waitObject != null) {
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
index 3fc268e..59f2173 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
@@ -22,6 +22,8 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,17 +39,29 @@
return res;
}
- private static void run3_3_TestAction() throws InterruptedException {
+ RaceConditionReproducer eventProcessor;
+
+ @Before
+ public void setup() {
+ eventProcessor = new RaceConditionReproducer();
+ }
+
+ @After
+ public void tearDown() {
+ TraceHelperForTest.cleanup();
+ }
+
+ private void run3_3_TestAction() throws InterruptedException {
Thread tb = new Thread(() -> {
- RaceConditionTracker.onEvent("B1");
- RaceConditionTracker.onEvent("B2");
- RaceConditionTracker.onEvent("B3");
+ eventProcessor.onEvent("B1");
+ eventProcessor.onEvent("B2");
+ eventProcessor.onEvent("B3");
});
tb.start();
- RaceConditionTracker.onEvent("A1");
- RaceConditionTracker.onEvent("A2");
- RaceConditionTracker.onEvent("A3");
+ eventProcessor.onEvent("A1");
+ eventProcessor.onEvent("A2");
+ eventProcessor.onEvent("A3");
tb.join();
}
@@ -56,7 +70,6 @@
@Ignore // The test is too long for continuous testing.
// 2 threads, 3 events each.
public void test3_3() throws Exception {
- final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
boolean sawTheValidSequence = false;
for (; ; ) {
@@ -80,25 +93,24 @@
@Ignore // The test is too long for continuous testing.
// 2 threads, 3 events, including enter-exit pairs each.
public void test3_3_enter_exit() throws Exception {
- final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
boolean sawTheValidSequence = false;
for (; ; ) {
eventProcessor.startIteration();
Thread tb = new Thread(() -> {
- RaceConditionTracker.onEvent("B1:enter");
- RaceConditionTracker.onEvent("B1:exit");
- RaceConditionTracker.onEvent("B2");
- RaceConditionTracker.onEvent("B3:enter");
- RaceConditionTracker.onEvent("B3:exit");
+ eventProcessor.onEvent("B1:enter");
+ eventProcessor.onEvent("B1:exit");
+ eventProcessor.onEvent("B2");
+ eventProcessor.onEvent("B3:enter");
+ eventProcessor.onEvent("B3:exit");
});
tb.start();
- RaceConditionTracker.onEvent("A1");
- RaceConditionTracker.onEvent("A2:enter");
- RaceConditionTracker.onEvent("A2:exit");
- RaceConditionTracker.onEvent("A3:enter");
- RaceConditionTracker.onEvent("A3:exit");
+ eventProcessor.onEvent("A1");
+ eventProcessor.onEvent("A2:enter");
+ eventProcessor.onEvent("A2:exit");
+ eventProcessor.onEvent("A3:enter");
+ eventProcessor.onEvent("A3:exit");
tb.join();
final boolean needMoreIterations = eventProcessor.finishIteration();
@@ -119,9 +131,7 @@
@Test
// 2 threads, 3 events each; reproducing a particular event sequence.
public void test3_3_ReproMode() throws Exception {
- final RaceConditionReproducer eventProcessor = new RaceConditionReproducer(
- SOME_VALID_SEQUENCE_3_3);
-
+ eventProcessor = new RaceConditionReproducer(SOME_VALID_SEQUENCE_3_3);
eventProcessor.startIteration();
run3_3_TestAction();
assertTrue(!eventProcessor.finishIteration());
@@ -134,23 +144,21 @@
@Ignore // The test is too long for continuous testing.
// 2 threads with 2 events; 1 thread with 1 event.
public void test2_1_2() throws Exception {
- final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
-
for (; ; ) {
eventProcessor.startIteration();
Thread tb = new Thread(() -> {
- RaceConditionTracker.onEvent("B1");
- RaceConditionTracker.onEvent("B2");
+ eventProcessor.onEvent("B1");
+ eventProcessor.onEvent("B2");
});
tb.start();
Thread tc = new Thread(() -> {
- RaceConditionTracker.onEvent("C1");
+ eventProcessor.onEvent("C1");
});
tc.start();
- RaceConditionTracker.onEvent("A1");
- RaceConditionTracker.onEvent("A2");
+ eventProcessor.onEvent("A1");
+ eventProcessor.onEvent("A2");
tb.join();
tc.join();
@@ -167,28 +175,26 @@
@Ignore // The test is too long for continuous testing.
// 2 threads with 2 events; 1 thread with 1 event. Includes enter-exit pairs.
public void test2_1_2_enter_exit() throws Exception {
- final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
-
for (; ; ) {
eventProcessor.startIteration();
Thread tb = new Thread(() -> {
- RaceConditionTracker.onEvent("B1:enter");
- RaceConditionTracker.onEvent("B1:exit");
- RaceConditionTracker.onEvent("B2:enter");
- RaceConditionTracker.onEvent("B2:exit");
+ eventProcessor.onEvent("B1:enter");
+ eventProcessor.onEvent("B1:exit");
+ eventProcessor.onEvent("B2:enter");
+ eventProcessor.onEvent("B2:exit");
});
tb.start();
Thread tc = new Thread(() -> {
- RaceConditionTracker.onEvent("C1:enter");
- RaceConditionTracker.onEvent("C1:exit");
+ eventProcessor.onEvent("C1:enter");
+ eventProcessor.onEvent("C1:exit");
});
tc.start();
- RaceConditionTracker.onEvent("A1:enter");
- RaceConditionTracker.onEvent("A1:exit");
- RaceConditionTracker.onEvent("A2:enter");
- RaceConditionTracker.onEvent("A2:exit");
+ eventProcessor.onEvent("A1:enter");
+ eventProcessor.onEvent("A1:exit");
+ eventProcessor.onEvent("A2:enter");
+ eventProcessor.onEvent("A2:exit");
tb.join();
tc.join();
diff --git a/tests/src/com/android/launcher3/util/TraceHelperForTest.java b/tests/src/com/android/launcher3/util/TraceHelperForTest.java
new file mode 100644
index 0000000..f1c8a67
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/TraceHelperForTest.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import java.util.LinkedList;
+import java.util.function.IntConsumer;
+
+public class TraceHelperForTest extends TraceHelper {
+
+ private static final TraceHelperForTest INSTANCE_FOR_TEST = new TraceHelperForTest();
+
+ private final ThreadLocal<LinkedList<TraceInfo>> mStack =
+ ThreadLocal.withInitial(LinkedList::new);
+
+ private RaceConditionReproducer mRaceConditionReproducer;
+ private IntConsumer mFlagsChangeListener;
+
+ public static void setRaceConditionReproducer(RaceConditionReproducer reproducer) {
+ TraceHelper.INSTANCE = INSTANCE_FOR_TEST;
+ INSTANCE_FOR_TEST.mRaceConditionReproducer = reproducer;
+ }
+
+ public static void cleanup() {
+ INSTANCE_FOR_TEST.mRaceConditionReproducer = null;
+ INSTANCE_FOR_TEST.mFlagsChangeListener = null;
+ }
+
+ public static void setFlagsChangeListener(IntConsumer listener) {
+ TraceHelper.INSTANCE = INSTANCE_FOR_TEST;
+ INSTANCE_FOR_TEST.mFlagsChangeListener = listener;
+ }
+
+ private TraceHelperForTest() { }
+
+ @Override
+ public Object beginSection(String sectionName, int flags) {
+ LinkedList<TraceInfo> stack = mStack.get();
+ TraceInfo info = new TraceInfo(sectionName, flags);
+ stack.add(info);
+
+ if ((flags & TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS) != 0
+ && mRaceConditionReproducer != null) {
+ mRaceConditionReproducer.onEvent(RaceConditionReproducer.enterEvt(sectionName));
+ }
+ updateBinderTracking(stack);
+
+ super.beginSection(sectionName, flags);
+ return info;
+ }
+
+ @Override
+ public void endSection(Object token) {
+ LinkedList<TraceInfo> stack = mStack.get();
+ if (stack.size() == 0) {
+ new Throwable().printStackTrace();
+ }
+ TraceInfo info = (TraceInfo) token;
+ stack.remove(info);
+ if ((info.flags & TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS) != 0
+ && mRaceConditionReproducer != null) {
+ mRaceConditionReproducer.onEvent(RaceConditionReproducer.exitEvt(info.sectionName));
+ }
+ updateBinderTracking(stack);
+
+ super.endSection(token);
+ }
+
+ @Override
+ public Object beginFlagsOverride(int flags) {
+ LinkedList<TraceInfo> stack = mStack.get();
+ TraceInfo info = new TraceInfo(null, flags);
+ stack.add(info);
+ updateBinderTracking(stack);
+ super.beginFlagsOverride(flags);
+ return info;
+ }
+
+ @Override
+ public void endFlagsOverride(Object token) {
+ super.endFlagsOverride(token);
+ LinkedList<TraceInfo> stack = mStack.get();
+ TraceInfo info = (TraceInfo) token;
+ stack.remove(info);
+ updateBinderTracking(stack);
+ }
+
+ private void updateBinderTracking(LinkedList<TraceInfo> stack) {
+ if (mFlagsChangeListener != null) {
+ mFlagsChangeListener.accept(stack.stream()
+ .mapToInt(info -> info.flags).reduce(0, (a, b) -> a | b));
+ }
+ }
+
+ private static class TraceInfo {
+ public final String sectionName;
+ public final int flags;
+
+ TraceInfo(String sectionName, int flags) {
+ this.sectionName = sectionName;
+ this.flags = flags;
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 899686b..fe6143c 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -3,8 +3,12 @@
import android.os.SystemClock;
import android.util.Log;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+
import org.junit.Assert;
+import java.util.function.Supplier;
+
/**
* A utility class for waiting for a condition to be true.
*/
@@ -12,11 +16,19 @@
private static final long DEFAULT_SLEEP_MS = 200;
- public static void atMost(String message, Condition condition, long timeout) {
- atMost(message, condition, timeout, DEFAULT_SLEEP_MS);
+ public static void atMost(String message, Condition condition, long timeout,
+ LauncherInstrumentation launcher) {
+ atMost(() -> message, condition, timeout, DEFAULT_SLEEP_MS, launcher);
}
- public static void atMost(String message, Condition condition, long timeout, long sleepMillis) {
+ public static void atMost(Supplier<String> message, Condition condition, long timeout,
+ LauncherInstrumentation launcher) {
+ atMost(message, condition, timeout, DEFAULT_SLEEP_MS, launcher);
+ }
+
+ public static void atMost(Supplier<String> message, Condition condition, long timeout,
+ long sleepMillis,
+ LauncherInstrumentation launcher) {
final long startTime = SystemClock.uptimeMillis();
long endTime = startTime + timeout;
Log.d("Wait", "atMost: " + startTime + " - " + endTime);
@@ -40,6 +52,15 @@
throw new RuntimeException(t);
}
Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis());
- Assert.fail(message);
+ launcher.checkForAnomaly();
+ Assert.fail(message.get());
+ }
+
+ /**
+ * Interface representing a generic condition
+ */
+ public interface Condition {
+
+ boolean isTrue() throws Throwable;
}
}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
new file mode 100644
index 0000000..e4f520f
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import android.os.SystemClock;
+
+import androidx.test.uiautomator.UiDevice;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+class FailureInvestigator {
+ private static boolean matches(String regex, CharSequence string) {
+ return Pattern.compile(regex).matcher(string).find();
+ }
+
+ static class LogcatMatch {
+ String logcatPattern;
+ int bug;
+
+ LogcatMatch(String logcatPattern, int bug) {
+ this.logcatPattern = logcatPattern;
+ this.bug = bug;
+ }
+ }
+
+ static class ExceptionMatch {
+ String exceptionPattern;
+ LogcatMatch[] logcatMatches;
+
+ ExceptionMatch(String exceptionPattern, LogcatMatch[] logcatMatches) {
+ this.exceptionPattern = exceptionPattern;
+ this.logcatMatches = logcatMatches;
+ }
+ }
+
+ private static final ExceptionMatch[] EXCEPTION_MATCHES = {
+ new ExceptionMatch(
+ "java.lang.AssertionError: http://go/tapl : Tests are broken by a "
+ + "non-Launcher system error: (Phone is locked|Screen is empty)",
+ new LogcatMatch[]{
+ new LogcatMatch(
+ "BroadcastQueue: Can't deliver broadcast to com.android"
+ + ".systemui.*Crashing it",
+ 147845913),
+ new LogcatMatch(
+ "Attempt to invoke virtual method 'boolean android\\"
+ + ".graphics\\.Bitmap\\.isRecycled\\(\\)' on a null "
+ + "object reference",
+ 148424291),
+ new LogcatMatch(
+ "java\\.lang\\.IllegalArgumentException\\: Ranking map "
+ + "doesn't contain key",
+ 148570537),
+ }),
+ new ExceptionMatch("Launcher didn't initialize",
+ new LogcatMatch[]{
+ new LogcatMatch(
+ "ActivityManager: Reason: executing service com.google"
+ + ".android.apps.nexuslauncher/com.android.launcher3"
+ + ".notification.NotificationListener",
+ 148238677),
+ }),
+ };
+
+ static int getBugForFailure(CharSequence exception) {
+ if ("com.google.android.setupwizard".equals(
+ UiDevice.getInstance(getInstrumentation()).getLauncherPackageName())) {
+ return 145935261;
+ }
+
+ if (matches("java\\.lang\\.AssertionError\\: http\\:\\/\\/go\\/tapl \\: want to get "
+ + "workspace object; Presence of recents button doesn't match the interaction "
+ + "mode, mode\\=ZERO_BUTTON, hasRecents\\=true", exception)) {
+ return 148422894;
+ }
+
+ final String logSinceBoot;
+ try {
+ final String systemBootTime =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(
+ new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime()));
+
+ logSinceBoot =
+ UiDevice.getInstance(getInstrumentation())
+ .executeShellCommand("logcat -d -t " + systemBootTime.replace(" ", ""));
+ } catch (IOException | OutOfMemoryError e) {
+ return 0;
+ }
+
+ if (matches("android\\:\\:uirenderer\\:\\:renderthread\\:\\:EglManager\\:\\:swapBuffers",
+ logSinceBoot)) {
+ return 148529608;
+ }
+
+ for (ExceptionMatch exceptionMatch : EXCEPTION_MATCHES) {
+ if (matches(exceptionMatch.exceptionPattern, exception)) {
+ for (LogcatMatch logcatMatch : exceptionMatch.logcatMatches) {
+ if (matches(logcatMatch.logcatPattern, logSinceBoot)) {
+ return logcatMatch.bug;
+ }
+ }
+ break;
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
new file mode 100644
index 0000000..99ddee4
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule;
+
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class FailureRewriterRule implements TestRule {
+ private static final String TAG = "FailureRewriter";
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } catch (Throwable e) {
+ final int bug = FailureInvestigator.getBugForFailure(e.toString());
+ if (bug == 0) throw e;
+
+ Log.e(TAG, "Known bug found for the original failure "
+ + android.util.Log.getStackTraceString(e));
+ throw new AssertionError(
+ "Detected a failure that matches a known bug b/" + bug);
+ }
+ }
+ };
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 62fe26d..6a6ec3e 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -16,16 +16,10 @@
package com.android.launcher3.util.rule;
import android.app.Activity;
-import android.app.Application;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.os.Bundle;
-
-import androidx.test.InstrumentationRegistry;
import com.android.launcher3.Launcher;
import com.android.launcher3.Workspace.ItemOperator;
-import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -34,17 +28,23 @@
/**
* Test rule to get the current Launcher activity.
*/
-public class LauncherActivityRule implements TestRule {
+public class LauncherActivityRule extends SimpleActivityRule<Launcher> {
- private Launcher mActivity;
+ public LauncherActivityRule() {
+ super(Launcher.class);
+ }
@Override
public Statement apply(Statement base, Description description) {
- return new MyStatement(base);
- }
- public Launcher getActivity() {
- return mActivity;
+ return new MyStatement(base) {
+ @Override
+ public void onActivityStarted(Activity activity) {
+ if (activity instanceof Launcher) {
+ ((Launcher) activity).getRotationHelper().forceAllowRotationForTesting(true);
+ }
+ }
+ };
}
public Callable<Boolean> itemExists(final ItemOperator op) {
@@ -56,62 +56,4 @@
return launcher.getWorkspace().getFirstMatch(op) != null;
};
}
-
- private class MyStatement extends Statement implements ActivityLifecycleCallbacks {
-
- private final Statement mBase;
-
- public MyStatement(Statement base) {
- mBase = base;
- }
-
- @Override
- public void evaluate() throws Throwable {
- Application app = (Application)
- InstrumentationRegistry.getTargetContext().getApplicationContext();
- app.registerActivityLifecycleCallbacks(this);
- try {
- mBase.evaluate();
- } finally {
- app.unregisterActivityLifecycleCallbacks(this);
- }
- }
-
- @Override
- public void onActivityCreated(Activity activity, Bundle bundle) {
- if (activity instanceof Launcher) {
- mActivity = (Launcher) activity;
- }
- }
-
- @Override
- public void onActivityStarted(Activity activity) {
- if (activity instanceof Launcher) {
- mActivity.getRotationHelper().forceAllowRotationForTesting(true);
- }
- }
-
- @Override
- public void onActivityResumed(Activity activity) {
- }
-
- @Override
- public void onActivityPaused(Activity activity) {
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- if (activity == mActivity) {
- mActivity = null;
- }
- }
- }
}
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
new file mode 100644
index 0000000..1dbba6a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.util.rule;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Test rule to get the current activity.
+ */
+public class SimpleActivityRule<T extends Activity> implements TestRule {
+
+ private final Class<T> mClass;
+ private T mActivity;
+
+ public SimpleActivityRule(Class<T> clazz) {
+ mClass = clazz;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new MyStatement(base);
+ }
+
+ public T getActivity() {
+ return mActivity;
+ }
+
+ protected class MyStatement extends Statement implements ActivityLifecycleCallbacks {
+
+ private final Statement mBase;
+
+ public MyStatement(Statement base) {
+ mBase = base;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ Application app = (Application)
+ InstrumentationRegistry.getTargetContext().getApplicationContext();
+ app.registerActivityLifecycleCallbacks(this);
+ try {
+ mBase.evaluate();
+ } finally {
+ app.unregisterActivityLifecycleCallbacks(this);
+ mActivity = null;
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ if (activity != null && mClass.isInstance(activity)) {
+ mActivity = (T) activity;
+ }
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ if (activity == mActivity) {
+ mActivity = null;
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index d7f41bf..61f5e05 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -17,9 +17,11 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
import org.junit.rules.TestRule;
@@ -50,20 +52,33 @@
+ "(?<postsubmit>[0-9]+)"
+ ")$");
+ public static final int LOCAL = 0x1;
+ public static final int UNBUNDLED_PRESUBMIT = 0x2;
+ public static final int UNBUNDLED_POSTSUBMIT = 0x4;
+ public static final int PLATFORM_PRESUBMIT = 0x8;
+ public static final int PLATFORM_POSTSUBMIT = 0x10;
+
+ private static int sRunFlavor;
+
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Stability {
+ int flavors();
}
@Override
public Statement apply(Statement base, Description description) {
- if (description.getAnnotation(Stability.class) != null) {
+ final Stability stability = description.getAnnotation(Stability.class);
+ if (stability != null) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
- getRunFlavor();
-
- base.evaluate();
+ if ((stability.flavors() & getRunFlavor()) != 0) {
+ Log.d(TAG, "Running " + description.getDisplayName());
+ base.evaluate();
+ } else {
+ Log.d(TAG, "Skipping " + description.getDisplayName());
+ }
}
};
} else {
@@ -71,49 +86,77 @@
}
}
- private static void getRunFlavor() throws Exception {
- final String launcherVersion = getInstrumentation().
- getContext().
- getPackageManager().
- getPackageInfo(
- UiDevice.getInstance(getInstrumentation()).
- getLauncherPackageName(),
- 0).
- versionName;
+ public static int getRunFlavor() {
+ if (sRunFlavor != 0) return sRunFlavor;
- final Matcher launcherBuildMatcher = LAUNCHER_BUILD.matcher(launcherVersion);
+ final String flavorOverride = InstrumentationRegistry.getArguments().getString("flavor");
- if (!launcherBuildMatcher.find()) {
- Log.e(TAG, "Match not found");
+ if (flavorOverride != null) {
+ Log.d(TAG, "Flavor override: " + flavorOverride);
+ try {
+ return (int) TestStabilityRule.class.getField(flavorOverride).get(null);
+ } catch (NoSuchFieldException e) {
+ throw new AssertionError("Unrecognized run flavor override: " + flavorOverride);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ final String launcherVersion;
+ try {
+ final String launcherPackageName = UiDevice.getInstance(getInstrumentation())
+ .getLauncherPackageName();
+ Log.d(TAG, "Launcher package: " + launcherPackageName);
+
+ launcherVersion = getInstrumentation().
+ getContext().
+ getPackageManager().
+ getPackageInfo(launcherPackageName, 0)
+ .versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
}
final String platformVersion = Build.VERSION.INCREMENTAL;
- final Matcher platformBuildMatcher = PLATFORM_BUILD.matcher(platformVersion);
-
- if (!platformBuildMatcher.find()) {
- Log.e(TAG, "Match not found");
- }
Log.d(TAG, "Launcher: " + launcherVersion + ", platform: " + platformVersion);
+ final Matcher launcherBuildMatcher = LAUNCHER_BUILD.matcher(launcherVersion);
+ if (!launcherBuildMatcher.find()) {
+ throw new AssertionError("Launcher build match not found");
+ }
+
+ final Matcher platformBuildMatcher = PLATFORM_BUILD.matcher(platformVersion);
+ if (!platformBuildMatcher.find()) {
+ throw new AssertionError("Platform build match not found");
+ }
+
if (launcherBuildMatcher.group("local") != null && (
platformBuildMatcher.group("commandLine") != null ||
platformBuildMatcher.group("postsubmit") != null)) {
Log.d(TAG, "LOCAL RUN");
+ sRunFlavor = LOCAL;
} else if (launcherBuildMatcher.group("presubmit") != null
&& platformBuildMatcher.group("postsubmit") != null) {
Log.d(TAG, "UNBUNDLED PRESUBMIT");
+ sRunFlavor = UNBUNDLED_PRESUBMIT;
} else if (launcherBuildMatcher.group("postsubmit") != null
&& platformBuildMatcher.group("postsubmit") != null) {
Log.d(TAG, "UNBUNDLED POSTSUBMIT");
+ sRunFlavor = UNBUNDLED_POSTSUBMIT;
} else if (launcherBuildMatcher.group("platform") != null
&& platformBuildMatcher.group("presubmit") != null) {
Log.d(TAG, "PLATFORM PRESUBMIT");
+ sRunFlavor = PLATFORM_PRESUBMIT;
} else if (launcherBuildMatcher.group("platform") != null
- && platformBuildMatcher.group("postsubmit") != null) {
+ && (platformBuildMatcher.group("postsubmit") != null
+ || platformBuildMatcher.group("commandLine") != null)) {
Log.d(TAG, "PLATFORM POSTSUBMIT");
+ sRunFlavor = PLATFORM_POSTSUBMIT;
} else {
- Log.e(TAG, "ERROR3");
+ throw new AssertionError("Unrecognized run flavor");
}
+
+ return sRunFlavor;
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
index 03d1600..5daac39 100644
--- a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -21,6 +21,8 @@
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.testing.TestProtocol;
+
import java.util.regex.Pattern;
public class AddToHomeScreenPrompt {
@@ -37,8 +39,20 @@
}
public void addAutomatically() {
- mLauncher.waitForObjectInContainer(
- mWidgetCell.getParent().getParent().getParent().getParent(),
- By.text(ADD_AUTOMATICALLY)).click();
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ if (mLauncher.getNavigationModel()
+ != LauncherInstrumentation.NavigationModel.THREE_BUTTON) {
+ if (!mLauncher.isLauncher3()) {
+ mLauncher.expectEvent(
+ TestProtocol.SEQUENCE_TIS,
+ LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
+ mLauncher.expectEvent(
+ TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
+ }
+ }
+ mLauncher.waitForObjectInContainer(
+ mWidgetCell.getParent().getParent().getParent().getParent(),
+ By.text(ADD_AUTOMATICALLY)).click();
+ }
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 96e4b8c..4a2d699 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -29,6 +29,8 @@
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
+import java.util.stream.Collectors;
+
/**
* Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
*/
@@ -48,9 +50,8 @@
mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
verifyNotFrozen("All apps freeze flags upon opening all apps");
mIconHeight = mLauncher.getTestInfo(
- TestProtocol.REQUEST_ICON_HEIGHT)
- .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-
+ TestProtocol.REQUEST_ICON_HEIGHT).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
@Override
@@ -58,8 +59,8 @@
return LauncherInstrumentation.ContainerType.ALL_APPS;
}
- private boolean hasClickableIcon(
- UiObject2 allAppsContainer, UiObject2 appListRecycler, BySelector appIconSelector) {
+ private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler,
+ BySelector appIconSelector, int displayBottom) {
final UiObject2 icon = appListRecycler.findObject(appIconSelector);
if (icon == null) {
LauncherInstrumentation.log("hasClickableIcon: icon not visible");
@@ -75,6 +76,10 @@
LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
return false;
}
+ if (iconBounds.bottom > displayBottom) {
+ LauncherInstrumentation.log("hasClickableIcon: icon bottom below bottom offset");
+ return false;
+ }
LauncherInstrumentation.log("hasClickableIcon: icon is clickable");
return true;
}
@@ -94,31 +99,41 @@
*/
@NonNull
public AppIcon getAppIcon(String appName) {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "getting app icon " + appName + " on all apps")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "getting app icon " + appName + " on all apps")) {
final UiObject2 allAppsContainer = verifyActiveContainer();
final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
"apps_list_view");
final UiObject2 searchBox = getSearchBox(allAppsContainer);
- allAppsContainer.setGestureMargins(
- 0,
- getSearchBox(allAppsContainer).getVisibleBounds().bottom + 1,
- 0,
- ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
- mLauncher.getResources()) + 1);
+
+ int bottomGestureMargin = ResourceUtils.getNavbarSize(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
+ int deviceHeight = mLauncher.getDevice().getDisplayHeight();
+ int displayBottom = deviceHeight - bottomGestureMargin;
final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
- if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)) {
+ if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
+ displayBottom)) {
scrollBackToBeginning();
int attempts = 0;
- int scroll = getScroll(allAppsContainer);
+ int scroll = getAllAppsScroll();
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
- while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)) {
+ while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
+ displayBottom)) {
mLauncher.scrollToLastVisibleRow(
allAppsContainer,
- mLauncher.getObjectsInContainer(allAppsContainer, "icon"),
- searchBox.getVisibleBounds().bottom -
- allAppsContainer.getVisibleBounds().top);
- final int newScroll = getScroll(allAppsContainer);
+ mLauncher.getObjectsInContainer(allAppsContainer, "icon")
+ .stream()
+ .filter(icon ->
+ icon.getVisibleBounds().bottom
+ <= displayBottom)
+ .collect(Collectors.toList()),
+ searchBox.getVisibleBounds().bottom
+ - allAppsContainer.getVisibleBounds().top);
+ final int newScroll = getAllAppsScroll();
+ mLauncher.assertTrue(
+ "Scrolled in a wrong direction in AllApps: from " + scroll + " to "
+ + newScroll, newScroll >= scroll);
if (newScroll == scroll) break;
mLauncher.assertTrue(
@@ -131,8 +146,11 @@
verifyActiveContainer();
}
+ // Ignore bottom offset selection here as there might not be any scroll more scroll
+ // region available.
mLauncher.assertTrue("Unable to scroll to a clickable icon: " + appName,
- hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector));
+ hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
+ deviceHeight));
final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
appIconSelector);
@@ -150,16 +168,17 @@
int attempts = 0;
final Rect margins = new Rect(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
- for (int scroll = getScroll(allAppsContainer);
+ for (int scroll = getAllAppsScroll();
scroll != 0;
- scroll = getScroll(allAppsContainer)) {
+ scroll = getAllAppsScroll()) {
mLauncher.assertTrue("Negative scroll position", scroll > 0);
mLauncher.assertTrue(
"Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
++attempts <= MAX_SCROLL_ATTEMPTS);
- mLauncher.scroll(allAppsContainer, Direction.UP, margins, 50);
+ mLauncher.scroll(
+ allAppsContainer, Direction.UP, margins, 12, false);
}
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
@@ -168,9 +187,10 @@
}
}
- private int getScroll(UiObject2 allAppsContainer) {
- return mLauncher.getAnswerFromLauncher(allAppsContainer, TestProtocol.GET_SCROLL_MESSAGE).
- getInt(TestProtocol.SCROLL_Y_FIELD, -1);
+ private int getAllAppsScroll() {
+ return mLauncher.getTestInfo(
+ TestProtocol.REQUEST_APPS_LIST_SCROLL_Y)
+ .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
private UiObject2 getSearchBox(UiObject2 allAppsContainer) {
@@ -181,12 +201,13 @@
* Flings forward (down) and waits the fling's end.
*/
public void flingForward() {
- try (LauncherInstrumentation.Closable c =
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to fling forward in all apps")) {
final UiObject2 allAppsContainer = verifyActiveContainer();
// Start the gesture in the center to avoid starting at elements near the top.
mLauncher.scroll(
- allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10);
+ allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10, false);
verifyActiveContainer();
}
}
@@ -195,12 +216,13 @@
* Flings backward (up) and waits the fling's end.
*/
public void flingBackward() {
- try (LauncherInstrumentation.Closable c =
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to fling backward in all apps")) {
final UiObject2 allAppsContainer = verifyActiveContainer();
// Start the gesture in the center, for symmetry with forward.
mLauncher.scroll(
- allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10);
+ allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10, false);
verifyActiveContainer();
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
index f48d4dd..835790d 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -42,8 +42,9 @@
*/
@NonNull
public Overview switchBackToOverview() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to switch back from all apps to overview")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to switch back from all apps to overview")) {
final UiObject2 allAppsContainer = verifyActiveContainer();
// Swipe from the search box to the bottom.
final UiObject2 qsb = mLauncher.waitForObjectInContainer(
@@ -55,7 +56,8 @@
final int endY = start.y + swipeHeight;
LauncherInstrumentation.log("AllAppsFromOverview.switchBackToOverview before swipe");
- mLauncher.swipeToState(start.x, start.y, start.x, endY, 60, OVERVIEW_STATE_ORDINAL);
+ mLauncher.swipeToState(start.x, start.y, start.x, endY, 60, OVERVIEW_STATE_ORDINAL,
+ LauncherInstrumentation.GestureScope.INSIDE);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("swiped down")) {
return new Overview(mLauncher);
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 44fc3f7..8932291 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,19 +16,23 @@
package com.android.launcher3.tapl;
-import android.graphics.Point;
-import android.os.SystemClock;
-import android.view.MotionEvent;
import android.widget.TextView;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.regex.Pattern;
+
/**
* App icon, whether in all apps or in workspace/
*/
public final class AppIcon extends Launchable {
+
+ private static final Pattern START_EVENT = Pattern.compile("start:");
+
AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
super(launcher, icon);
}
@@ -41,18 +45,19 @@
* Long-clicks the icon to open its menu.
*/
public AppIconMenu openMenu() {
- final Point iconCenter = mObject.getVisibleCenter();
- final long downTime = SystemClock.uptimeMillis();
- mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, iconCenter);
- final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
- "deep_shortcuts_container");
- mLauncher.sendPointer(
- downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, iconCenter);
- return new AppIconMenu(mLauncher, deepShortcutsContainer);
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ return new AppIconMenu(mLauncher, mLauncher.clickAndGet(
+ mObject, "deep_shortcuts_container"));
+ }
}
@Override
protected String getLongPressIndicator() {
return "deep_shortcuts_container";
}
+
+ @Override
+ protected void expectActivityStartEvents() {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_EVENT);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index ba9c10e..f8dd89c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -18,10 +18,17 @@
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.regex.Pattern;
+
/**
* Menu item in an app icon menu.
*/
public class AppIconMenuItem extends Launchable {
+
+ private static final Pattern START_SHORTCUT_EVENT = Pattern.compile("start: shortcut:");
+
AppIconMenuItem(LauncherInstrumentation launcher, UiObject2 shortcut) {
super(launcher, shortcut);
}
@@ -37,4 +44,9 @@
protected String getLongPressIndicator() {
return "drop_target_bar";
}
+
+ @Override
+ protected void expectActivityStartEvents() {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_SHORTCUT_EVENT);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index db3d846..2acab97 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -16,23 +16,29 @@
package com.android.launcher3.tapl;
+import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import android.graphics.Point;
import android.os.SystemClock;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
import com.android.launcher3.testing.TestProtocol;
+import java.util.regex.Pattern;
+
/**
* Indicates the base state with a UI other than Overview running as foreground. It can also
* indicate Launcher as long as Launcher is not in Overview state.
*/
public class Background extends LauncherInstrumentation.VisibleContainer {
private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
+ private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
Background(LauncherInstrumentation launcher) {
super(launcher);
@@ -51,16 +57,21 @@
*/
@NonNull
public BaseOverview switchToOverview() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to switch from background to overview")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to switch from background to overview")) {
verifyActiveContainer();
- goToOverviewUnchecked(BACKGROUND_APP_STATE_ORDINAL);
+ goToOverviewUnchecked();
return mLauncher.isFallbackOverview() ?
new BaseOverview(mLauncher) : new Overview(mLauncher);
}
}
- protected void goToOverviewUnchecked(int expectedState) {
+ protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
+ return false;
+ }
+
+ protected void goToOverviewUnchecked() {
switch (mLauncher.getNavigationModel()) {
case ZERO_BUTTON: {
final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -72,18 +83,27 @@
new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
final long downTime = SystemClock.uptimeMillis();
- mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
+ final LauncherInstrumentation.GestureScope gestureScope =
+ zeroButtonToOverviewGestureStartsInLauncher()
+ ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
+ : LauncherInstrumentation.GestureScope.OUTSIDE;
+ mLauncher.sendPointer(
+ downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
mLauncher.executeAndWaitForEvent(
() -> mLauncher.movePointer(
downTime,
downTime,
ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
start,
- end),
+ end,
+ gestureScope),
event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(event.getClassName()),
- "Pause wasn't detected");
- mLauncher.sendPointer(
- downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end);
+ () -> "Pause wasn't detected");
+ mLauncher.runToState(
+ () -> mLauncher.sendPointer(
+ downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end,
+ gestureScope),
+ OVERVIEW_STATE_ORDINAL);
break;
}
@@ -105,12 +125,16 @@
startY = endY = mLauncher.getDevice().getDisplayHeight() / 2;
}
- mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState);
+ mLauncher.swipeToState(startX, startY, endX, endY, 10, OVERVIEW_STATE_ORDINAL,
+ LauncherInstrumentation.GestureScope.OUTSIDE);
break;
}
case THREE_BUTTON:
- mLauncher.waitForSystemUiObject("recent_apps").click();
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+ mLauncher.runToState(
+ () -> mLauncher.waitForSystemUiObject("recent_apps").click(),
+ OVERVIEW_STATE_ORDINAL);
break;
}
}
@@ -119,8 +143,9 @@
* Swipes right or double presses the square button to switch to the previous app.
*/
public Background quickSwitchToPreviousApp() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to quick switch to the previous app")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to quick switch to the previous app")) {
verifyActiveContainer();
quickSwitchToPreviousApp(getExpectedStateForQuickSwitch());
return new Background(mLauncher);
@@ -155,18 +180,29 @@
endX = startX;
endY = 0;
}
- mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState);
+ final boolean launcherIsVisible =
+ mLauncher.hasLauncherObject(By.textStartsWith(""));
+ final boolean isZeroButton = mLauncher.getNavigationModel()
+ == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
+ mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
+ launcherIsVisible && isZeroButton
+ ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
+ : LauncherInstrumentation.GestureScope.OUTSIDE
+ );
break;
}
case THREE_BUTTON:
// Double press the recents button.
UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
- recentsButton.click();
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+ mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
mLauncher.getOverview();
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
recentsButton.click();
break;
}
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
}
protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 49c0c89..a769acf 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -23,8 +23,6 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
-import com.android.launcher3.testing.TestProtocol;
-
import java.util.Collections;
import java.util.List;
@@ -48,14 +46,19 @@
* Flings forward (left) and waits the fling's end.
*/
public void flingForward() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ flingForwardImpl();
+ }
+ }
+
+ private void flingForwardImpl() {
try (LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to fling forward in overview")) {
LauncherInstrumentation.log("Overview.flingForward before fling");
final UiObject2 overview = verifyActiveContainer();
- final int leftMargin = mLauncher.getTestInfo(
- TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
- getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
- mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin, 0, 0, 0), 20);
+ final int leftMargin = mLauncher.getTargetInsets().left;
+ mLauncher.scroll(
+ overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, false);
verifyActiveContainer();
}
}
@@ -63,22 +66,20 @@
/**
* Dismissed all tasks by scrolling to Clear-all button and pressing it.
*/
- public Workspace dismissAllTasks() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "dismissing all tasks")) {
- final BySelector clearAllSelector = mLauncher.getLauncherObjectSelector("clear_all");
+ public void dismissAllTasks() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "dismissing all tasks")) {
+ final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
for (int i = 0;
i < FLINGS_FOR_DISMISS_LIMIT
&& !verifyActiveContainer().hasObject(clearAllSelector);
++i) {
- flingForward();
+ flingForwardImpl();
}
- mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
- try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "dismissed all tasks")) {
- return new Workspace(mLauncher);
- }
+ mLauncher.clickLauncherObject(
+ mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector));
}
}
@@ -86,14 +87,14 @@
* Flings backward (right) and waits the fling's end.
*/
public void flingBackward() {
- try (LauncherInstrumentation.Closable c =
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to fling backward in overview")) {
LauncherInstrumentation.log("Overview.flingBackward before fling");
final UiObject2 overview = verifyActiveContainer();
- final int rightMargin = mLauncher.getTestInfo(
- TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
- getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
- mLauncher.scroll(overview, Direction.RIGHT, new Rect(0, 0, rightMargin, 0), 20);
+ final int rightMargin = mLauncher.getTargetInsets().right;
+ mLauncher.scroll(
+ overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false);
verifyActiveContainer();
}
}
@@ -109,7 +110,7 @@
"want to get current task")) {
verifyActiveContainer();
final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
- mLauncher.getLauncherObjectSelector("snapshot"));
+ mLauncher.getOverviewObjectSelector("snapshot"));
mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
// taskViews contains up to 3 task views: the 'main' (having the widest visible
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
deleted file mode 100644
index 6e6734d..0000000
--- a/tests/tapl/com/android/launcher3/tapl/Folder.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.tapl;
-
-import android.widget.FrameLayout;
-
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.UiObject2;
-
-/**
- * App folder in workspace/
- */
-public final class Folder {
- Folder(LauncherInstrumentation launcher, UiObject2 icon) {
- }
-
- static BySelector getSelector(String folderName, LauncherInstrumentation launcher) {
- return By.clazz(FrameLayout.class).desc(folderName).pkg(launcher.getLauncherPackageName());
- }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index e0fe933..c06e254 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -16,7 +16,6 @@
package com.android.launcher3.tapl;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import androidx.annotation.NonNull;
@@ -49,10 +48,11 @@
@NonNull
@Override
public Overview switchToOverview() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to switch from home to overview")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to switch from home to overview")) {
verifyActiveContainer();
- goToOverviewUnchecked(OVERVIEW_STATE_ORDINAL);
+ goToOverviewUnchecked();
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"performed the switch action")) {
return new Overview(mLauncher);
@@ -61,6 +61,11 @@
}
@Override
+ protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
+ return true;
+ }
+
+ @Override
protected int getExpectedStateForQuickSwitch() {
return QUICK_SWITCH_STATE_ORDINAL;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index df80a51..b20384e 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -46,17 +46,22 @@
* Clicks the object to launch its app.
*/
public Background launch(String expectedPackageName) {
- return launch(By.pkg(expectedPackageName));
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ return launch(By.pkg(expectedPackageName));
+ }
}
+ protected abstract void expectActivityStartEvents();
+
private Background launch(BySelector selector) {
LauncherInstrumentation.log("Launchable.launch before click " +
mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
mLauncher.executeAndWaitForEvent(
- () -> mObject.click(),
+ () -> mLauncher.clickLauncherObject(mObject),
event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- "Launching an app didn't open a new window: " + mObject.getText());
+ () -> "Launching an app didn't open a new window: " + mObject.getText());
+ expectActivityStartEvents();
mLauncher.assertTrue(
"App didn't start: " + selector,
@@ -69,17 +74,20 @@
* Drags an object to the center of homescreen.
*/
public void dragToWorkspace() {
- final Point launchableCenter = getObject().getVisibleCenter();
- final Point displaySize = mLauncher.getRealDisplaySize();
- final int width = displaySize.x / 2;
- Workspace.dragIconToWorkspace(
- mLauncher,
- this,
- new Point(
- launchableCenter.x >= width ?
- launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
- displaySize.y / 2),
- getLongPressIndicator());
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ final Point launchableCenter = getObject().getVisibleCenter();
+ final Point displaySize = mLauncher.getRealDisplaySize();
+ final int width = displaySize.x / 2;
+ Workspace.dragIconToWorkspace(
+ mLauncher,
+ this,
+ new Point(
+ launchableCenter.x >= width
+ ? launchableCenter.x - width / 2
+ : launchableCenter.x + width / 2,
+ displaySize.y / 2),
+ getLongPressIndicator());
+ }
}
protected abstract String getLongPressIndicator();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index c52650d..d894843 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -22,7 +22,6 @@
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
-import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
import android.app.ActivityManager;
@@ -34,6 +33,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
@@ -50,12 +50,12 @@
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Configurator;
import androidx.test.uiautomator.Direction;
+import androidx.test.uiautomator.StaleObjectException;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
@@ -69,6 +69,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
@@ -77,6 +78,9 @@
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -89,6 +93,14 @@
private static final int GESTURE_STEP_MS = 16;
private static long START_TIME = System.currentTimeMillis();
+ private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
+ private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
+ private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
+ private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
+
+ static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
+ static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
+
// Types for launcher containers that the user is interacting with. "Background" is a
// pseudo-container corresponding to inactive launcher covered by another app.
public enum ContainerType {
@@ -97,6 +109,13 @@
public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
+ // Where the gesture happens: outside of Launcher, inside or from inside to outside.
+ public enum GestureScope {
+ OUTSIDE, INSIDE, INSIDE_TO_OUTSIDE
+ }
+
+ ;
+
// Base class for launcher containers.
static abstract class VisibleContainer {
protected final LauncherInstrumentation mLauncher;
@@ -120,7 +139,7 @@
}
}
- interface Closable extends AutoCloseable {
+ public interface Closable extends AutoCloseable {
void close();
}
@@ -128,6 +147,7 @@
private static final String APPS_RES_ID = "apps_view";
private static final String OVERVIEW_RES_ID = "overview_panel";
private static final String WIDGETS_RES_ID = "widgets_list_view";
+ private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
public static final int WAIT_TIME_MS = 10000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
@@ -142,6 +162,30 @@
private Consumer<ContainerType> mOnSettledStateAction;
+ private static LogEventChecker sEventChecker;
+ // True if there is an gesture in progress that needs event verification.
+ private static boolean sCheckingEvents;
+
+ private boolean mCheckEventsForSuccessfulGestures = false;
+ private int mExpectedPid;
+ private Runnable mOnLauncherCrashed;
+
+ private static Pattern getTouchEventPattern(String prefix, String action) {
+ // The pattern includes sanity checks that we don't get a multi-touch events or other
+ // surprises.
+ return Pattern.compile(
+ prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
+ + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
+ }
+
+ private static Pattern getTouchEventPattern(String action) {
+ return getTouchEventPattern("Touch event", action);
+ }
+
+ private static Pattern getTouchEventPatternTIS(String action) {
+ return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
+ }
+
/**
* Constructs the root of TAPL hierarchy. You get all other objects from it.
*/
@@ -180,13 +224,8 @@
.authority(testProviderAuthority)
.build();
- try {
- mDevice.executeShellCommand("pm grant " + testPackage +
- " android.permission.WRITE_SECURE_SETTINGS");
- } catch (IOException e) {
- fail(e.toString());
- }
-
+ mInstrumentation.getUiAutomation().grantRuntimePermission(
+ testPackage, "android.permission.WRITE_SECURE_SETTINGS");
PackageManager pm = getContext().getPackageManager();
ProviderInfo pi = pm.resolveContentProvider(
@@ -208,6 +247,14 @@
}
}
+ public void enableCheckEventsForSuccessfulGestures() {
+ mCheckEventsForSuccessfulGestures = true;
+ }
+
+ public void setOnLauncherCrashed(Runnable onLauncherCrashed) {
+ mOnLauncherCrashed = onLauncherCrashed;
+ }
+
Context getContext() {
return mInstrumentation.getContext();
}
@@ -216,6 +263,11 @@
return getContext().getContentResolver().call(mTestProviderUri, request, null, null);
}
+ Insets getTargetInsets() {
+ return getTestInfo(TestProtocol.REQUEST_WINDOW_INSETS)
+ .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
void setActiveContainer(VisibleContainer container) {
sActiveContainer = new WeakReference<>(container);
}
@@ -257,9 +309,9 @@
Closable addContextLayer(String piece) {
mDiagnosticContext.addLast(piece);
- log("Added context: " + getContextDescription());
+ log("Entering context: " + piece);
return () -> {
- log("Removing context: " + getContextDescription());
+ log("Leaving context: " + piece);
mDiagnosticContext.removeLast();
};
}
@@ -278,31 +330,84 @@
}
}
- private String getAnomalyMessage() {
- UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
- if (object != null) {
- return "System alert popup is visible: " + object.getText();
+ private String getSystemAnomalyMessage() {
+ try {
+ UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
+ if (object != null) {
+ return "System alert popup is visible: " + object.getText();
+ }
+
+ object = mDevice.findObject(By.res("android", "message"));
+ if (object != null) {
+ return "Message popup by " + object.getApplicationPackage() + " is visible: "
+ + object.getText();
+ }
+
+ if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
+
+ if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
+ } catch (Throwable e) {
+ Log.w(TAG, "getSystemAnomalyMessage failed", e);
}
- object = mDevice.findObject(By.res("android", "message"));
- if (object != null) {
- return "Message popup by " + object.getApplicationPackage() + " is visible: "
- + object.getText();
- }
-
- if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
-
- if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
-
return null;
}
+ private String getAnomalyMessage() {
+ if (mExpectedPid != 0 && mExpectedPid != getPid()) {
+ mExpectedPid = 0;
+ if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
+ return "Launcher crashed";
+ }
+
+ final String systemAnomalyMessage = getSystemAnomalyMessage();
+ if (systemAnomalyMessage != null) {
+ return "http://go/tapl : Tests are broken by a non-Launcher system error: "
+ + systemAnomalyMessage;
+ }
+
+ return null;
+ }
+
+ public void checkForAnomaly() {
+ final String anomalyMessage = getAnomalyMessage();
+ if (anomalyMessage != null) {
+ if (sCheckingEvents) {
+ sCheckingEvents = false;
+ sEventChecker.finishNoWait();
+ }
+ log("Hierarchy dump for: " + anomalyMessage);
+ dumpViewHierarchy();
+
+ Assert.fail(formatSystemHealthMessage(anomalyMessage));
+ }
+ }
+
+ private String getVisiblePackages() {
+ return mDevice.findObjects(By.textStartsWith(""))
+ .stream()
+ .map(LauncherInstrumentation::getApplicationPackageSafe)
+ .distinct()
+ .filter(pkg -> pkg != null && !"com.android.systemui".equals(pkg))
+ .collect(Collectors.joining(", "));
+ }
+
+ private static String getApplicationPackageSafe(UiObject2 object) {
+ try {
+ return object.getApplicationPackage();
+ } catch (StaleObjectException e) {
+ // We are looking at all object in the system; external ones can suddenly go away.
+ return null;
+ }
+ }
+
private String getVisibleStateMessage() {
+ if (hasLauncherObject(CONTEXT_MENU_RES_ID)) return "Context Menu";
if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview";
if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
- return "Background";
+ return "Background (" + getVisiblePackages() + ")";
}
public void setSystemHealthSupplier(Function<Long, String> supplier) {
@@ -313,44 +418,44 @@
mOnSettledStateAction = onSettledStateAction;
}
- private String getSystemHealthMessage() {
+ private String formatSystemHealthMessage(String message) {
final String testPackage = getContext().getPackageName();
- try {
- mDevice.executeShellCommand("pm grant " + testPackage +
- " android.permission.READ_LOGS");
- mDevice.executeShellCommand("pm grant " + testPackage +
- " android.permission.PACKAGE_USAGE_STATS");
- } catch (IOException e) {
- e.printStackTrace();
- }
- return mSystemHealthSupplier != null
+ mInstrumentation.getUiAutomation().grantRuntimePermission(
+ testPackage, "android.permission.READ_LOGS");
+ mInstrumentation.getUiAutomation().grantRuntimePermission(
+ testPackage, "android.permission.PACKAGE_USAGE_STATS");
+
+ final String systemHealth = mSystemHealthSupplier != null
? mSystemHealthSupplier.apply(START_TIME)
: TestHelpers.getSystemHealthMessage(getContext(), START_TIME);
- }
- private void fail(String message) {
- message = "http://go/tapl : " + getContextDescription() + message;
-
- final String anomaly = getAnomalyMessage();
- if (anomaly != null) {
- message = anomaly + ", which causes:\n" + message;
- } else {
- message = message + " (visible state: " + getVisibleStateMessage() + ")";
- }
-
- final String systemHealth = getSystemHealthMessage();
if (systemHealth != null) {
- message = message
- + ", which might be a consequence of system health "
- + "problems:\n<<<<<<<<<<<<<<<<<<\n"
+ return message
+ + ",\nperhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n"
+ systemHealth + "\n>>>>>>>>>>>>>>>>>>";
}
+ return message;
+ }
+
+ private void fail(String message) {
+ checkForAnomaly();
+
+ message = "http://go/tapl : " + getContextDescription() + message
+ + " (visible state: " + getVisibleStateMessage() + ")";
log("Hierarchy dump for: " + message);
dumpViewHierarchy();
- Assert.fail(message);
+ if (sCheckingEvents) {
+ sCheckingEvents = false;
+ final String eventMismatch = sEventChecker.verify(0);
+ if (eventMismatch != null) {
+ message = message + ", having produced " + eventMismatch;
+ }
+ }
+
+ Assert.fail(formatSystemHealthMessage(message));
}
private String getContextDescription() {
@@ -420,14 +525,10 @@
assertEquals("Unexpected display rotation",
mExpectedRotation, mDevice.getDisplayRotation());
- // b/136278866
- for (int i = 0; i != 100; ++i) {
+ // b/148422894
+ for (int i = 0; i != 600; ++i) {
if (getNavigationModeMismatchError() == null) break;
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ sleep(100);
}
final String error = getNavigationModeMismatchError();
@@ -468,7 +569,7 @@
return waitForLauncherObject(APPS_RES_ID);
}
case OVERVIEW: {
- if (mDevice.isNaturalOrientation()) {
+ if (hasAllAppsInOverview()) {
waitForLauncherObject(APPS_RES_ID);
} else {
waitUntilGone(APPS_RES_ID);
@@ -495,7 +596,7 @@
}
}
- private void waitForLauncherInitialized() {
+ public void waitForLauncherInitialized() {
for (int i = 0; i < 100; ++i) {
if (getTestInfo(
TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).
@@ -508,84 +609,91 @@
}
Parcelable executeAndWaitForEvent(Runnable command,
- UiAutomation.AccessibilityEventFilter eventFilter, String message) {
+ UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
try {
final AccessibilityEvent event =
mInstrumentation.getUiAutomation().executeAndWaitForEvent(
command, eventFilter, WAIT_TIME_MS);
assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
- return event.getParcelableData();
+ final Parcelable parcelableData = event.getParcelableData();
+ event.recycle();
+ return parcelableData;
} catch (TimeoutException e) {
- fail(message);
+ fail(message.get());
return null;
}
}
- Bundle getAnswerFromLauncher(UiObject2 view, String requestTag) {
- // Send a fake set-text request to Launcher to initiate a response with requested data.
- final String responseTag = requestTag + TestProtocol.RESPONSE_MESSAGE_POSTFIX;
- return (Bundle) executeAndWaitForEvent(
- () -> view.setText(requestTag),
- event -> responseTag.equals(event.getClassName()),
- "Launcher didn't respond to request: " + requestTag);
- }
-
/**
* Presses nav bar home button.
*
* @return the Workspace object.
*/
public Workspace pressHome() {
- // Click home, then wait for any accessibility event, then wait until accessibility events
- // stop.
- // We need waiting for any accessibility event generated after pressing Home because
- // otherwise waitForIdle may return immediately in case when there was a big enough pause in
- // accessibility events prior to pressing Home.
- final String action;
- if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
- final String anomaly = getAnomalyMessage();
- if (anomaly != null) fail("Can't swipe up to Home: " + anomaly);
+ try (LauncherInstrumentation.Closable e = eventsCheck()) {
+ // Click home, then wait for any accessibility event, then wait until accessibility
+ // events stop.
+ // We need waiting for any accessibility event generated after pressing Home because
+ // otherwise waitForIdle may return immediately in case when there was a big enough
+ // pause in accessibility events prior to pressing Home.
+ final String action;
+ if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+ checkForAnomaly();
- final Point displaySize = getRealDisplaySize();
+ final Point displaySize = getRealDisplaySize();
- if (hasLauncherObject("deep_shortcuts_container")) {
- linearGesture(
- displaySize.x / 2, displaySize.y - 1,
- displaySize.x / 2, 0,
- ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
- try (LauncherInstrumentation.Closable c = addContextLayer(
- "Swiped up from context menu to home")) {
- waitUntilGone("deep_shortcuts_container");
+ if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
+ linearGesture(
+ displaySize.x / 2, displaySize.y - 1,
+ displaySize.x / 2, 0,
+ ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+ false, GestureScope.INSIDE_TO_OUTSIDE);
+ try (LauncherInstrumentation.Closable c = addContextLayer(
+ "Swiped up from context menu to home")) {
+ waitUntilGone(CONTEXT_MENU_RES_ID);
+ }
+ }
+ if (hasLauncherObject(WORKSPACE_RES_ID)) {
+ log(action = "already at home");
+ } else {
+ log("Hierarchy before swiping up to home:");
+ dumpViewHierarchy();
+ log(action = "swiping up to home from " + getVisibleStateMessage());
+
+ try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
+ swipeToState(
+ displaySize.x / 2, displaySize.y - 1,
+ displaySize.x / 2, 0,
+ ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
+ hasLauncherObject(By.textStartsWith(""))
+ ? GestureScope.INSIDE_TO_OUTSIDE
+ : GestureScope.OUTSIDE);
+ }
+ }
+ } else {
+ log("Hierarchy before clicking home:");
+ dumpViewHierarchy();
+ log(action = "clicking home button from " + getVisibleStateMessage());
+ try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
+ mDevice.waitForIdle();
+
+ if (!isLauncher3() && getNavigationModel() == NavigationModel.TWO_BUTTON) {
+ expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
+ expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+ }
+ runToState(
+ waitForSystemUiObject("home")::click,
+ NORMAL_STATE_ORDINAL,
+ !hasLauncherObject(WORKSPACE_RES_ID)
+ && (hasLauncherObject(APPS_RES_ID)
+ || hasLauncherObject(OVERVIEW_RES_ID)));
+ mDevice.waitForIdle();
}
}
- if (hasLauncherObject(WORKSPACE_RES_ID)) {
- log(action = "already at home");
- } else {
- log("Hierarchy before swiping up to home");
- dumpViewHierarchy();
- log(action = "swiping up to home from " + getVisibleStateMessage());
- final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName()))
- ? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL;
-
- swipeToState(
- displaySize.x / 2, displaySize.y - 1,
- displaySize.x / 2, 0,
- ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState);
+ try (LauncherInstrumentation.Closable c = addContextLayer(
+ "performed action to switch to Home - " + action)) {
+ return getWorkspace();
}
- } else {
- log(action = "clicking home button");
- executeAndWaitForEvent(
- () -> {
- log("LauncherInstrumentation.pressHome before clicking");
- waitForSystemUiObject("home").click();
- },
- event -> true,
- "Pressing Home didn't produce any events");
- mDevice.waitForIdle();
- }
- try (LauncherInstrumentation.Closable c = addContextLayer(
- "performed action to switch to Home - " + action)) {
- return getWorkspace();
}
}
@@ -676,6 +784,20 @@
}
}
+ /**
+ * Gets the Options Popup Menu object if the current state is showing the popup menu. Fails if
+ * the launcher is not in that state.
+ *
+ * @return Options Popup Menu object.
+ */
+ @NonNull
+ public OptionsPopupMenu getOptionsPopupMenu() {
+ try (LauncherInstrumentation.Closable c = addContextLayer(
+ "want to get context menu object")) {
+ return new OptionsPopupMenu(this);
+ }
+ }
+
void waitUntilGone(String resId) {
assertTrue("Unexpected launcher object visible: " + resId,
mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
@@ -704,8 +826,8 @@
final UiObject2 object = container.wait(
Until.findObject(getLauncherObjectSelector(resName)),
WAIT_TIME_MS);
- assertNotNull("Can't find a launcher object id: " + resName + " in container: " +
- container.getResourceName(), object);
+ assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
+ + container.getResourceName(), object);
return object;
}
@@ -714,16 +836,23 @@
final UiObject2 object = container.wait(
Until.findObject(selector),
WAIT_TIME_MS);
- assertNotNull("Can't find a launcher object id: " + selector + " in container: " +
- container.getResourceName(), object);
+ assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: "
+ + container.getResourceName(), object);
return object;
}
- @Nullable
private boolean hasLauncherObject(String resId) {
return mDevice.hasObject(getLauncherObjectSelector(resId));
}
+ boolean hasLauncherObject(BySelector selector) {
+ return mDevice.hasObject(makeLauncherSelector(selector));
+ }
+
+ private BySelector makeLauncherSelector(BySelector selector) {
+ return By.copy(selector).pkg(getLauncherPackageName());
+ }
+
@NonNull
UiObject2 waitForLauncherObject(String resName) {
return waitForObjectBySelector(getLauncherObjectSelector(resName));
@@ -731,22 +860,22 @@
@NonNull
UiObject2 waitForLauncherObject(BySelector selector) {
- return waitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()));
+ return waitForObjectBySelector(makeLauncherSelector(selector));
}
@NonNull
UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
- return tryWaitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()), timeout);
+ return tryWaitForObjectBySelector(makeLauncherSelector(selector), timeout);
}
@NonNull
UiObject2 waitForFallbackLauncherObject(String resName) {
- return waitForObjectBySelector(getFallbackLauncherObjectSelector(resName));
+ return waitForObjectBySelector(getOverviewObjectSelector(resName));
}
private UiObject2 waitForObjectBySelector(BySelector selector) {
final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
- assertNotNull("Can't find a launcher object; selector: " + selector, object);
+ assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
return object;
}
@@ -758,7 +887,7 @@
return By.res(getLauncherPackageName(), resName);
}
- BySelector getFallbackLauncherObjectSelector(String resName) {
+ BySelector getOverviewObjectSelector(String resName) {
return By.res(getOverviewPackageName(), resName);
}
@@ -775,14 +904,49 @@
return mDevice;
}
- void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState) {
- final Bundle parcel = (Bundle) executeAndWaitForEvent(
- () -> linearGesture(startX, startY, endX, endY, steps),
- event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
- "Swipe failed to receive an event for the swipe end");
- assertEquals("Swipe switched launcher to a wrong state;",
- TestProtocol.stateOrdinalToString(expectedState),
- TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
+ private static String eventListToString(List<Integer> actualEvents) {
+ if (actualEvents.isEmpty()) return "no events";
+
+ return "["
+ + actualEvents.stream()
+ .map(state -> TestProtocol.stateOrdinalToString(state))
+ .collect(Collectors.joining(", "))
+ + "]";
+ }
+
+ void runToState(Runnable command, int expectedState, boolean requireEvent) {
+ if (requireEvent) {
+ runToState(command, expectedState);
+ } else {
+ command.run();
+ }
+ }
+
+ void runToState(Runnable command, int expectedState) {
+ final List<Integer> actualEvents = new ArrayList<>();
+ executeAndWaitForEvent(
+ command,
+ event -> isSwitchToStateEvent(event, expectedState, actualEvents),
+ () -> "Failed to receive an event for the state change: expected "
+ + TestProtocol.stateOrdinalToString(expectedState)
+ + ", actual: " + eventListToString(actualEvents));
+ }
+
+ private boolean isSwitchToStateEvent(
+ AccessibilityEvent event, int expectedState, List<Integer> actualEvents) {
+ if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false;
+
+ final Bundle parcel = (Bundle) event.getParcelableData();
+ final int actualState = parcel.getInt(TestProtocol.STATE_FIELD);
+ actualEvents.add(actualState);
+ return actualState == expectedState;
+ }
+
+ void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState,
+ GestureScope gestureScope) {
+ runToState(
+ () -> linearGesture(startX, startY, endX, endY, steps, false, gestureScope),
+ expectedState);
}
int getBottomGestureSize() {
@@ -790,31 +954,48 @@
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
}
- int getBottomGestureMargin(UiObject2 container) {
- return container.getVisibleBounds().bottom - getRealDisplaySize().y +
- getBottomGestureSize();
+ int getBottomGestureMarginInContainer(UiObject2 container) {
+ final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize();
+ return container.getVisibleBounds().bottom - bottomGestureStartOnScreen;
}
- void scrollToLastVisibleRow(UiObject2 container, Collection<UiObject2> items, int topPadding) {
+ void clickLauncherObject(UiObject2 object) {
+ expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_DOWN);
+ expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_UP);
+ if (!isLauncher3() && getNavigationModel() != NavigationModel.THREE_BUTTON) {
+ expectEvent(TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
+ expectEvent(TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
+ }
+ object.click();
+ }
+
+ void scrollToLastVisibleRow(
+ UiObject2 container,
+ Collection<UiObject2> items,
+ int topPaddingInContainer) {
final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top));
- final int gestureStart = lowestItem.getVisibleBounds().top + getTouchSlop();
- final int distance = gestureStart - container.getVisibleBounds().top - topPadding;
- final int bottomMargin = container.getVisibleBounds().height() - distance;
+ final int itemRowCurrentTopOnScreen = lowestItem.getVisibleBounds().top;
+ final Rect containerRect = container.getVisibleBounds();
+ final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
+ final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
+ final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
scroll(
container,
Direction.DOWN,
new Rect(
0,
+ containerRect.height() - distance - bottomGestureMarginInContainer,
0,
- 0,
- Math.max(bottomMargin, getBottomGestureMargin(container))),
- 150);
+ bottomGestureMarginInContainer),
+ 10,
+ true);
}
- void scroll(UiObject2 container, Direction direction, Rect margins, int steps) {
+ void scroll(
+ UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) {
final Rect rect = container.getVisibleBounds();
if (margins != null) {
rect.left += margins.left;
@@ -831,34 +1012,26 @@
switch (direction) {
case UP: {
startX = endX = rect.centerX();
- final int vertCenter = rect.centerY();
- final float halfGestureHeight = rect.height() / 2.0f;
- startY = (int) (vertCenter - halfGestureHeight) + 1;
- endY = (int) (vertCenter + halfGestureHeight);
+ startY = rect.top;
+ endY = rect.bottom - 1;
}
break;
case DOWN: {
startX = endX = rect.centerX();
- final int vertCenter = rect.centerY();
- final float halfGestureHeight = rect.height() / 2.0f;
- startY = (int) (vertCenter + halfGestureHeight) - 1;
- endY = (int) (vertCenter - halfGestureHeight);
+ startY = rect.bottom - 1;
+ endY = rect.top;
}
break;
case LEFT: {
startY = endY = rect.centerY();
- final int horizCenter = rect.centerX();
- final float halfGestureWidth = rect.width() / 2.0f;
- startX = (int) (horizCenter - halfGestureWidth) + 1;
- endX = (int) (horizCenter + halfGestureWidth);
+ startX = rect.left;
+ endX = rect.right - 1;
}
break;
case RIGHT: {
startY = endY = rect.centerY();
- final int horizCenter = rect.centerX();
- final float halfGestureWidth = rect.width() / 2.0f;
- startX = (int) (horizCenter + halfGestureWidth) - 1;
- endX = (int) (horizCenter - halfGestureWidth);
+ startX = rect.right - 1;
+ endX = rect.left;
}
break;
default:
@@ -867,22 +1040,36 @@
}
executeAndWaitForEvent(
- () -> linearGesture(startX, startY, endX, endY, steps),
+ () -> linearGesture(
+ startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE),
event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
- "Didn't receive a scroll end message: " + startX + ", " + startY
+ () -> "Didn't receive a scroll end message: " + startX + ", " + startY
+ ", " + endX + ", " + endY);
}
// Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
// fixed interval each time.
- void linearGesture(int startX, int startY, int endX, int endY, int steps) {
+ public void linearGesture(int startX, int startY, int endX, int endY, int steps,
+ boolean slowDown,
+ GestureScope gestureScope) {
log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
final long downTime = SystemClock.uptimeMillis();
final Point start = new Point(startX, startY);
final Point end = new Point(endX, endY);
- sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
- final long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end);
- sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end);
+ sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
+ final long endTime = movePointer(start, end, steps, downTime, slowDown, gestureScope);
+ sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope);
+ }
+
+ long movePointer(Point start, Point end, int steps, long downTime, boolean slowDown,
+ GestureScope gestureScope) {
+ long endTime = movePointer(
+ downTime, downTime, steps * GESTURE_STEP_MS, start, end, gestureScope);
+ if (slowDown) {
+ endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end,
+ end, gestureScope);
+ }
+ return endTime;
}
void waitForIdle() {
@@ -915,13 +1102,39 @@
0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
}
- void sendPointer(long downTime, long currentTime, int action, Point point) {
+ public void sendPointer(long downTime, long currentTime, int action, Point point,
+ GestureScope gestureScope) {
+ final boolean notLauncher3 = !isLauncher3();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (gestureScope != GestureScope.OUTSIDE) {
+ expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
+ }
+ if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
+ expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (notLauncher3 && gestureScope != GestureScope.INSIDE) {
+ expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
+ }
+ if (gestureScope != GestureScope.OUTSIDE) {
+ expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE
+ ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
+ }
+ if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
+ expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+ }
+ break;
+ }
+
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
mInstrumentation.getUiAutomation().injectInputEvent(event, true);
event.recycle();
}
- long movePointer(long downTime, long startTime, long duration, Point from, Point to) {
+ public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
+ GestureScope gestureScope) {
log("movePointer: " + from + " to " + to);
final Point point = new Point();
long steps = duration / GESTURE_STEP_MS;
@@ -935,7 +1148,7 @@
point.x = from.x + (int) (progress * (to.x - from.x));
point.y = from.y + (int) (progress * (to.y - from.y));
- sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point);
+ sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
}
return currentTime;
}
@@ -944,6 +1157,17 @@
return getSystemIntegerRes(context, "config_navBarInteractionMode");
}
+ @NonNull
+ UiObject2 clickAndGet(@NonNull final UiObject2 target, @NonNull String resName) {
+ final Point targetCenter = target.getVisibleCenter();
+ final long downTime = SystemClock.uptimeMillis();
+ sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.INSIDE);
+ final UiObject2 result = waitForLauncherObject(resName);
+ sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
+ GestureScope.INSIDE);
+ return result;
+ }
+
private static int getSystemIntegerRes(Context context, String resName) {
Resources res = context.getResources();
int resId = res.getIdentifier(resName, "integer", "android");
@@ -994,6 +1218,28 @@
getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
}
+ public boolean hasAllAppsInOverview() {
+ // Vertical bar layouts don't contain all apps
+ if (!mDevice.isNaturalOrientation()) {
+ return false;
+ }
+ // Portrait two button (quickstep) always has all apps.
+ if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
+ return true;
+ }
+ // Overview actions hide all apps
+ if (overviewActionsEnabled()) {
+ return false;
+ }
+ // ...otherwise there should be all apps
+ return true;
+ }
+
+ private boolean overviewActionsEnabled() {
+ return getTestInfo(TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED).getBoolean(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
public void disableDebugTracing() {
getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
}
@@ -1003,6 +1249,10 @@
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ public int getPid() {
+ return getTestInfo(TestProtocol.REQUEST_PID).getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
public void produceJavaLeak() {
getTestInfo(TestProtocol.REQUEST_JAVA_LEAK);
}
@@ -1010,4 +1260,51 @@
public void produceNativeLeak() {
getTestInfo(TestProtocol.REQUEST_NATIVE_LEAK);
}
+
+ public void produceViewLeak() {
+ getTestInfo(TestProtocol.REQUEST_VIEW_LEAK);
+ }
+
+ public ArrayList<ComponentName> getRecentTasks() {
+ ArrayList<ComponentName> tasks = new ArrayList<>();
+ ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST)
+ .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ for (String s : components) {
+ tasks.add(ComponentName.unflattenFromString(s));
+ }
+ return tasks;
+ }
+
+ public Closable eventsCheck() {
+ Assert.assertTrue("Nested event checking", !sCheckingEvents);
+ sCheckingEvents = true;
+ mExpectedPid = getPid();
+ if (sEventChecker == null) sEventChecker = new LogEventChecker();
+ sEventChecker.start();
+
+ return () -> {
+ checkForAnomaly();
+
+ if (sCheckingEvents) {
+ sCheckingEvents = false;
+ if (mCheckEventsForSuccessfulGestures) {
+ final String message = sEventChecker.verify(WAIT_TIME_MS);
+ if (message != null) {
+ Assert.fail(formatSystemHealthMessage(
+ "http://go/tapl : successful gesture produced " + message));
+ }
+ } else {
+ sEventChecker.finishNoWait();
+ }
+ }
+ };
+ }
+
+ boolean isLauncher3() {
+ return "com.android.launcher3".equals(getLauncherPackageName());
+ }
+
+ void expectEvent(String sequence, Pattern expected) {
+ if (sCheckingEvents) sEventChecker.expectPattern(sequence, expected);
+ }
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
new file mode 100644
index 0000000..0fc88ee
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.util.Log;
+
+import com.android.launcher3.testing.TestProtocol;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class to read log on a background thread.
+ */
+public class LogEventChecker {
+
+ private static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
+ ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / (?<event>.*)");
+
+ private static final String START_PREFIX = "START_READER ";
+ private static final String FINISH_PREFIX = "FINISH_READER ";
+
+ private volatile CountDownLatch mFinished;
+
+ // Map from an event sequence name to an ordered list of expected events in that sequence.
+ private final ListMap<Pattern> mExpectedEvents = new ListMap<>();
+
+ private final ListMap<String> mEvents = new ListMap<>();
+ private final Semaphore mEventsCounter = new Semaphore(0);
+
+ private volatile String mStartCommand;
+ private volatile String mFinishCommand;
+
+ LogEventChecker() {
+ final Thread thread = new Thread(this::onRun, "log-reader-thread");
+ thread.setPriority(Thread.NORM_PRIORITY);
+ thread.start();
+ }
+
+ void start() {
+ if (mFinished != null) {
+ try {
+ mFinished.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ mFinished = null;
+ }
+ mEvents.clear();
+ mExpectedEvents.clear();
+ mEventsCounter.drainPermits();
+ final String id = UUID.randomUUID().toString();
+ mStartCommand = START_PREFIX + id;
+ mFinishCommand = FINISH_PREFIX + id;
+ Log.d(TestProtocol.TAPL_EVENTS_TAG, mStartCommand);
+ }
+
+ private void onRun() {
+ try {
+ // Note that we use Runtime.exec to start the log reading process instead of running
+ // it via UIAutomation, so that we can directly access the "Process" object and
+ // ensure that the instrumentation is not stuck forever.
+ final String cmd = "logcat -s " + TestProtocol.TAPL_EVENTS_TAG;
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ Runtime.getRuntime().exec(cmd).getInputStream()))) {
+ for (;;) {
+ // Skip everything before the next start command.
+ for (;;) {
+ final String event = reader.readLine();
+ if (event.contains(TestProtocol.TAPL_EVENTS_TAG)
+ && event.contains(mStartCommand)) {
+ break;
+ }
+ }
+
+ // Store all actual events until the finish command.
+ for (;;) {
+ final String event = reader.readLine();
+ if (event.contains(TestProtocol.TAPL_EVENTS_TAG)) {
+ if (event.contains(mFinishCommand)) {
+ mFinished.countDown();
+ break;
+ } else {
+ final Matcher matcher = EVENT_LOG_ENTRY.matcher(event);
+ if (matcher.find()) {
+ mEvents.add(matcher.group("sequence"), matcher.group("event"));
+ mEventsCounter.release();
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void expectPattern(String sequence, Pattern pattern) {
+ mExpectedEvents.add(sequence, pattern);
+ }
+
+ private void finishSync(long waitForExpectedCountMs) {
+ try {
+ // Wait until Launcher generates the expected number of events.
+ int expectedCount = mExpectedEvents.entrySet()
+ .stream().mapToInt(e -> e.getValue().size()).sum();
+ mEventsCounter.tryAcquire(expectedCount, waitForExpectedCountMs, MILLISECONDS);
+ finishNoWait();
+ mFinished.await();
+ mFinished = null;
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void finishNoWait() {
+ mFinished = new CountDownLatch(1);
+ Log.d(TestProtocol.TAPL_EVENTS_TAG, mFinishCommand);
+ }
+
+ String verify(long waitForExpectedCountMs) {
+ finishSync(waitForExpectedCountMs);
+
+ final StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) {
+ String sequence = expectedEvents.getKey();
+
+ List<String> actual = new ArrayList<>(mEvents.getNonNull(sequence));
+ final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
+ if (mismatchPosition != -1) {
+ formatSequenceWithMismatch(
+ sb,
+ sequence,
+ expectedEvents.getValue(),
+ actual,
+ mismatchPosition);
+ }
+ }
+ // Check for unexpected event sequences in the actual data.
+ for (String actualNamedSequence : mEvents.keySet()) {
+ if (!mExpectedEvents.containsKey(actualNamedSequence)) {
+ formatSequenceWithMismatch(
+ sb,
+ actualNamedSequence,
+ new ArrayList<>(),
+ mEvents.get(actualNamedSequence),
+ 0);
+ }
+ }
+
+ return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
+ }
+
+ // If the list of actual events matches the list of expected events, returns -1, otherwise
+ // the position of the mismatch.
+ private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
+ for (int i = 0; i < expected.size(); ++i) {
+ if (i >= actual.size()
+ || !expected.get(i).matcher(actual.get(i)).find()) {
+ return i;
+ }
+ }
+
+ if (actual.size() > expected.size()) return expected.size();
+
+ return -1;
+ }
+
+ private static void formatSequenceWithMismatch(
+ StringBuilder sb,
+ String sequenceName,
+ List<Pattern> expected,
+ List<String> actualEvents,
+ int mismatchPosition) {
+ sb.append("\n>> Sequence " + sequenceName);
+ sb.append("\n Expected:");
+ formatEventListWithMismatch(sb, expected, mismatchPosition);
+ sb.append("\n Actual:");
+ formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
+ }
+
+ private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) {
+ for (int i = 0; i < events.size(); ++i) {
+ sb.append("\n | ");
+ sb.append(i == position ? "---> " : " ");
+ sb.append(events.get(i).toString());
+ }
+ if (position == events.size()) sb.append("\n | ---> (end)");
+ }
+
+ private static class ListMap<T> extends HashMap<String, List<T>> {
+
+ void add(String key, T value) {
+ getNonNull(key).add(value);
+ }
+
+ List<T> getNonNull(String key) {
+ List<T> list = get(key);
+ if (list == null) {
+ list = new ArrayList<>();
+ put(key, list);
+ }
+ return list;
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenu.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenu.java
new file mode 100644
index 0000000..282fca9
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenu.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+
+public class OptionsPopupMenu {
+
+ private final LauncherInstrumentation mLauncher;
+ private final UiObject2 mDeepShortcutsContainer;
+
+ OptionsPopupMenu(LauncherInstrumentation launcher) {
+ mLauncher = launcher;
+ mDeepShortcutsContainer = launcher.waitForLauncherObject("deep_shortcuts_container");
+ }
+
+ /**
+ * Returns a menu item with a given label. Fails if it doesn't exist.
+ */
+ @NonNull
+ public OptionsPopupMenuItem getMenuItem(@NonNull final String label) {
+ final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
+ By.text(label));
+ return new OptionsPopupMenuItem(mLauncher, menuItem);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
new file mode 100644
index 0000000..c2f701b
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+public class OptionsPopupMenuItem {
+
+ private final LauncherInstrumentation mLauncher;
+ private final UiObject2 mObject;
+
+ OptionsPopupMenuItem(@NonNull LauncherInstrumentation launcher, @NonNull UiObject2 shortcut) {
+ mLauncher = launcher;
+ mObject = shortcut;
+ }
+
+ /**
+ * Clicks the option.
+ */
+ @NonNull
+ public void launch(@NonNull String expectedPackageName) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ LauncherInstrumentation.log("OptionsPopupMenuItem before click "
+ + mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
+ mLauncher.clickLauncherObject(mObject);
+ mLauncher.assertTrue(
+ "App didn't start: " + By.pkg(expectedPackageName),
+ mLauncher.getDevice().wait(Until.hasObject(By.pkg(expectedPackageName)),
+ LauncherInstrumentation.WAIT_TIME_MS));
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index da68da3..4d673a8c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -45,8 +45,9 @@
*/
@NonNull
public AllAppsFromOverview switchToAllApps() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to switch from overview to all apps")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to switch from overview to all apps")) {
verifyActiveContainer();
// Swipe from an app icon to the top.
@@ -58,8 +59,9 @@
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD),
mLauncher.getDevice().getDisplayWidth() / 2,
0,
- 50,
- ALL_APPS_STATE_ORDINAL);
+ 12,
+ ALL_APPS_STATE_ORDINAL,
+ LauncherInstrumentation.GestureScope.INSIDE);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"swiped all way up from overview")) {
@@ -67,4 +69,13 @@
}
}
}
+
+ @Override
+ public void dismissAllTasks() {
+ super.dismissAllTasks();
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "dismissed all tasks")) {
+ new Workspace(mLauncher);
+ }
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 91f0fc4..f955cf2 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -22,10 +22,16 @@
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.regex.Pattern;
+
/**
* A recent task in the overview panel carousel.
*/
public final class OverviewTask {
+ static final Pattern TASK_START_EVENT =
+ Pattern.compile("startActivityFromRecentsAsync");
private final LauncherInstrumentation mLauncher;
private final UiObject2 mTask;
private final BaseOverview mOverview;
@@ -45,14 +51,16 @@
* Swipes the task up.
*/
public void dismiss() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to dismiss a task")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to dismiss a task")) {
verifyActiveContainer();
// Dismiss the task via flinging it up.
final Rect taskBounds = mTask.getVisibleBounds();
final int centerX = taskBounds.centerX();
final int centerY = taskBounds.centerY();
- mLauncher.linearGesture(centerX, centerY, centerX, 0, 10);
+ mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
+ LauncherInstrumentation.GestureScope.INSIDE);
mLauncher.waitForIdle();
}
}
@@ -61,15 +69,18 @@
* Clicks at the task.
*/
public Background open() {
- verifyActiveContainer();
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "clicking an overview task")) {
- mLauncher.executeAndWaitForEvent(
- () -> mTask.click(),
- event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- "Launching task didn't open a new window: " +
- mTask.getParent().getContentDescription());
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ verifyActiveContainer();
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "clicking an overview task")) {
+ mLauncher.executeAndWaitForEvent(
+ () -> mLauncher.clickLauncherObject(mTask),
+ event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+ () -> "Launching task didn't open a new window: "
+ + mTask.getParent().getContentDescription());
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+ }
+ return new Background(mLauncher);
}
- return new Background(mLauncher);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index 0c9fda3..b8791e8 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -77,13 +77,18 @@
return launchers.get(0).activityInfo;
}
- public static String getOverviewPackageName() {
+ public static ComponentName getOverviewComponentName() {
Resources res = Resources.getSystem();
int id = res.getIdentifier("config_recentsComponentName", "string", "android");
if (id != 0) {
- return ComponentName.unflattenFromString(res.getString(id)).getPackageName();
+ return ComponentName.unflattenFromString(res.getString(id));
}
- return "com.android.systemui";
+ return new ComponentName("com.android.systemui",
+ "com.android.systemui.recents.RecentsActivity");
+ }
+
+ public static String getOverviewPackageName() {
+ return getOverviewComponentName().getPackageName();
}
private static String truncateCrash(String text, int maxLines) {
@@ -146,8 +151,7 @@
? "Current time: " + new Date(System.currentTimeMillis()) + "\n" + errors
: null;
} catch (Exception e) {
- return "Failed to get system health diags, maybe build your test via .bp instead of "
- + ".mk? " + android.util.Log.getStackTraceString(e);
+ return null;
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 1b6d8c4..a658f16 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -22,6 +22,7 @@
* Widget in workspace or a widget list.
*/
public final class Widget extends Launchable {
+
Widget(LauncherInstrumentation launcher, UiObject2 icon) {
super(launcher, icon);
}
@@ -30,4 +31,8 @@
protected String getLongPressIndicator() {
return "drop_target_bar";
}
+
+ @Override
+ protected void expectActivityStartEvents() {
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 51239c9..084138c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -24,6 +24,8 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
+
import java.util.Collection;
/**
@@ -41,15 +43,17 @@
* Flings forward (down) and waits the fling's end.
*/
public void flingForward() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to fling forward in widgets")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to fling forward in widgets")) {
LauncherInstrumentation.log("Widgets.flingForward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
mLauncher.scroll(
widgetsContainer,
Direction.DOWN,
- new Rect(0, 0, 0, mLauncher.getBottomGestureMargin(widgetsContainer)),
- FLING_STEPS);
+ new Rect(0, 0, 0,
+ mLauncher.getBottomGestureMarginInContainer(widgetsContainer) + 1),
+ FLING_STEPS, false);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
verifyActiveContainer();
}
@@ -61,15 +65,16 @@
* Flings backward (up) and waits the fling's end.
*/
public void flingBackward() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to fling backwards in widgets")) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to fling backwards in widgets")) {
LauncherInstrumentation.log("Widgets.flingBackward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
mLauncher.scroll(
widgetsContainer,
Direction.UP,
new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0),
- FLING_STEPS);
+ FLING_STEPS, false);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
verifyActiveContainer();
}
@@ -90,7 +95,7 @@
int i = 0;
for (; ; ) {
final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
- widgetsContainer, "widgets_cell_list_container");
+ widgetsContainer, "widgets_scroll_container");
mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
for (UiObject2 cell : cells) {
final UiObject2 label = cell.findObject(labelSelector);
@@ -102,8 +107,22 @@
"com.android.launcher3.widget.WidgetCell",
widget.getClassName());
- if (widget.getVisibleBounds().bottom <=
- displaySize.y - mLauncher.getBottomGestureSize()) {
+ int maxWidth = 0;
+ for (UiObject2 sibling : widget.getParent().getChildren()) {
+ maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+ }
+
+ int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+ if (visibleDelta > 0) {
+ Rect parentBounds = cell.getVisibleBounds();
+ mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
+ + mLauncher.getTouchSlop(),
+ parentBounds.centerY(), parentBounds.centerX(),
+ parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+ }
+
+ if (widget.getVisibleBounds().bottom
+ <= displaySize.y - mLauncher.getBottomGestureSize()) {
return new Widget(mLauncher, widget);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 0aa36dd..3f5dc8d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -17,9 +17,12 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
import static junit.framework.TestCase.assertTrue;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
@@ -32,14 +35,24 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
+import java.util.regex.Pattern;
+
/**
* Operations on the workspace screen.
*/
public final class Workspace extends Home {
- private static final int DRAG_DURACTION = 2000;
private static final int FLING_STEPS = 10;
+
+ static final Pattern EVENT_CTRL_W_DOWN = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_W"
+ + ".*?metaState=META_CTRL_ON");
+ static final Pattern EVENT_CTRL_W_UP = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_W"
+ + ".*?metaState=META_CTRL_ON");
+
private final UiObject2 mHotseat;
Workspace(LauncherInstrumentation launcher) {
@@ -47,6 +60,34 @@
mHotseat = launcher.waitForLauncherObject("hotseat");
}
+ private static boolean supportsRoundedCornersOnWindows(Resources resources) {
+ return ResourceUtils.getBoolByName(
+ "config_supportsRoundedCornersOnWindows", resources, false);
+ }
+
+ private static float getWindowCornerRadius(Resources resources) {
+ if (!supportsRoundedCornersOnWindows(resources)) {
+ return 0f;
+ }
+
+ // Radius that should be used in case top or bottom aren't defined.
+ float defaultRadius = ResourceUtils.getDimenByName("rounded_corner_radius", resources, 0);
+
+ float topRadius = ResourceUtils.getDimenByName("rounded_corner_radius_top", resources, 0);
+ if (topRadius == 0f) {
+ topRadius = defaultRadius;
+ }
+ float bottomRadius = ResourceUtils.getDimenByName(
+ "rounded_corner_radius_bottom", resources, 0);
+ if (bottomRadius == 0f) {
+ bottomRadius = defaultRadius;
+ }
+
+ // Always use the smallest radius to make sure the rounded corners will
+ // completely cover the display.
+ return Math.min(topRadius, bottomRadius);
+ }
+
/**
* Swipes up to All Apps.
*
@@ -54,12 +95,16 @@
*/
@NonNull
public AllApps switchToAllApps() {
- try (LauncherInstrumentation.Closable c =
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to switch from workspace to all apps")) {
verifyActiveContainer();
- final UiObject2 hotseat = mHotseat;
- final Point start = hotseat.getVisibleCenter();
- start.y = hotseat.getVisibleBounds().bottom - 1;
+ final int deviceHeight = mLauncher.getDevice().getDisplayHeight();
+ final int bottomGestureMargin = ResourceUtils.getNavbarSize(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources());
+ final int windowCornerRadius = (int) Math.ceil(getWindowCornerRadius(
+ mLauncher.getResources()));
+ final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1;
final int swipeHeight = mLauncher.getTestInfo(
TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT).
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -68,12 +113,12 @@
+ mLauncher.getTouchSlop());
mLauncher.swipeToState(
- start.x,
- start.y,
- start.x,
- start.y - swipeHeight - mLauncher.getTouchSlop(),
- 60,
- ALL_APPS_STATE_ORDINAL);
+ 0,
+ startY,
+ 0,
+ startY - swipeHeight - mLauncher.getTouchSlop(),
+ 12,
+ ALL_APPS_STATE_ORDINAL, LauncherInstrumentation.GestureScope.INSIDE);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"swiped to all apps")) {
@@ -122,21 +167,23 @@
* second screen.
*/
public void ensureWorkspaceIsScrollable() {
- final UiObject2 workspace = verifyActiveContainer();
- if (!isWorkspaceScrollable(workspace)) {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "dragging icon to a second page of workspace to make it scrollable")) {
- dragIconToWorkspace(
- mLauncher,
- getHotseatAppIcon("Chrome"),
- new Point(mLauncher.getDevice().getDisplayWidth(),
- workspace.getVisibleBounds().centerY()),
- "deep_shortcuts_container");
- verifyActiveContainer();
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ final UiObject2 workspace = verifyActiveContainer();
+ if (!isWorkspaceScrollable(workspace)) {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "dragging icon to a second page of workspace to make it scrollable")) {
+ dragIconToWorkspace(
+ mLauncher,
+ getHotseatAppIcon("Chrome"),
+ new Point(mLauncher.getDevice().getDisplayWidth(),
+ workspace.getVisibleBounds().centerY()),
+ "deep_shortcuts_container");
+ verifyActiveContainer();
+ }
}
+ assertTrue("Home screen workspace didn't become scrollable",
+ isWorkspaceScrollable(workspace));
}
- assertTrue("Home screen workspace didn't become scrollable",
- isWorkspaceScrollable(workspace));
}
private boolean isWorkspaceScrollable(UiObject2 workspace) {
@@ -149,27 +196,29 @@
mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
}
- @NonNull
- public Folder getHotseatFolder(String appName) {
- return new Folder(mLauncher, mLauncher.waitForObjectInContainer(
- mHotseat, Folder.getSelector(appName, mLauncher)));
- }
-
static void dragIconToWorkspace(
LauncherInstrumentation launcher, Launchable launchable, Point dest,
String longPressIndicator) {
LauncherInstrumentation.log("dragIconToWorkspace: begin");
final Point launchableCenter = launchable.getObject().getVisibleCenter();
final long downTime = SystemClock.uptimeMillis();
- launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, launchableCenter);
- LauncherInstrumentation.log("dragIconToWorkspace: sent down");
- launcher.waitForLauncherObject(longPressIndicator);
- LauncherInstrumentation.log("dragIconToWorkspace: indicator");
- launcher.movePointer(
- downTime, SystemClock.uptimeMillis(), DRAG_DURACTION, launchableCenter, dest);
+ launcher.runToState(
+ () -> {
+ launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
+ launchableCenter, LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.log("dragIconToWorkspace: sent down");
+ launcher.waitForLauncherObject(longPressIndicator);
+ LauncherInstrumentation.log("dragIconToWorkspace: indicator");
+ launcher.movePointer(launchableCenter, dest, 10, downTime, true,
+ LauncherInstrumentation.GestureScope.INSIDE);
+ },
+ SPRING_LOADED_STATE_ORDINAL);
LauncherInstrumentation.log("dragIconToWorkspace: moved pointer");
- launcher.sendPointer(
- downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);
+ launcher.runToState(
+ () -> launcher.sendPointer(
+ downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
+ LauncherInstrumentation.GestureScope.INSIDE),
+ NORMAL_STATE_ORDINAL);
LauncherInstrumentation.log("dragIconToWorkspace: end");
launcher.waitUntilGone("drop_target_bar");
}
@@ -179,11 +228,13 @@
* recoil to complete.
*/
public void flingForward() {
- final UiObject2 workspace = verifyActiveContainer();
- mLauncher.scroll(workspace, Direction.RIGHT,
- new Rect(0, 0, mLauncher.getEdgeSensitivityWidth(), 0),
- FLING_STEPS);
- verifyActiveContainer();
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ final UiObject2 workspace = verifyActiveContainer();
+ mLauncher.scroll(workspace, Direction.RIGHT,
+ new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0),
+ FLING_STEPS, false);
+ verifyActiveContainer();
+ }
}
/**
@@ -191,11 +242,13 @@
* recoil to complete.
*/
public void flingBackward() {
- final UiObject2 workspace = verifyActiveContainer();
- mLauncher.scroll(workspace, Direction.LEFT,
- new Rect(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0),
- FLING_STEPS);
- verifyActiveContainer();
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ final UiObject2 workspace = verifyActiveContainer();
+ mLauncher.scroll(workspace, Direction.LEFT,
+ new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0),
+ FLING_STEPS, false);
+ verifyActiveContainer();
+ }
}
/**
@@ -205,10 +258,14 @@
*/
@NonNull
public Widgets openAllWidgets() {
- verifyActiveContainer();
- mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) {
- return new Widgets(mLauncher);
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ verifyActiveContainer();
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_UP);
+ mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) {
+ return new Widgets(mLauncher);
+ }
}
}
diff --git a/tools/checkstyle.xml b/tools/checkstyle.xml
new file mode 100644
index 0000000..0f4163d
--- /dev/null
+++ b/tools/checkstyle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd" [
+ <!ENTITY defaultCopyrightCheck SYSTEM "../../../../prebuilts/checkstyle/default-copyright-check.xml">
+ <!ENTITY defaultJavadocChecks SYSTEM "../../../../prebuilts/checkstyle/default-javadoc-checks.xml">
+ <!ENTITY defaultTreewalkerChecks SYSTEM "../../../../prebuilts/checkstyle/default-treewalker-checks.xml">
+ <!ENTITY defaultModuleChecks SYSTEM "../../../../prebuilts/checkstyle/default-module-checks.xml">
+]>
+
+<module name="Checker">
+ &defaultModuleChecks;
+ &defaultCopyrightCheck;
+ <module name="TreeWalker">
+ &defaultJavadocChecks;
+ &defaultTreewalkerChecks;
+ </module>
+
+ <module name="SuppressionFilter">
+ <property name="file" value="tools/checkstyle_suppression.xml" />
+ </module>
+</module>
diff --git a/tools/checkstyle_suppression.xml b/tools/checkstyle_suppression.xml
new file mode 100644
index 0000000..799e750
--- /dev/null
+++ b/tools/checkstyle_suppression.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN" "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
+<suppressions>
+
+ <!-- Robolectric uses magic method names like `__constructor__` -->
+ <suppress files="/robolectric_tests" checks="MethodName|JavadocType|JavadocMethod" />
+
+</suppressions>
diff --git a/print_db.py b/tools/print_db.py
similarity index 100%
rename from print_db.py
rename to tools/print_db.py