diff --git a/Android.bp b/Android.bp index 0a55675d43..330c32ec89 100644 --- a/Android.bp +++ b/Android.bp @@ -35,9 +35,7 @@ android_library { ], srcs: [ "tests/tapl/**/*.java", - "src/com/android/launcher3/ResourceUtils.java", - "src/com/android/launcher3/testing/TestProtocol.java", - "src/com/android/launcher3/testing/*Request.java", + "src/com/android/launcher3/testing/shared/**/*.java", ], resource_dirs: [ ], manifest: "tests/tapl/AndroidManifest.xml", @@ -313,3 +311,138 @@ android_library { baseline_filename: "lint-baseline-launcher3.xml", }, } + +// Build rule for Launcher3 Go app for Android Go devices. +android_app { + name: "Launcher3Go", + + static_libs: ["Launcher3CommonDepsLib"], + + srcs: [ + "src/**/*.java", + "src_ui_overrides/**/*.java", + "go/src/**/*.java", + ], + + resource_dirs: ["go/res"], + + optimize: { + proguard_flags_files: ["proguard.flags"], + }, + + sdk_version: "current", + min_sdk_version: "current", + target_sdk_version: "current", + privileged: true, + system_ext_specific: true, + overrides: [ + "Home", + "Launcher2", + "Launcher3", + "Launcher3QuickStep", + ], + required: ["privapp_whitelist_com.android.launcher3"], + + additional_manifests: [ + "AndroidManifest.xml", + "AndroidManifest-common.xml", + ], + + manifest: "go/AndroidManifest.xml", + jacoco: { + include_filter: ["com.android.launcher3.*"], + } + +} + +// Build rule for Quickstep app. +android_app { + name: "Launcher3QuickStep", + + static_libs: ["Launcher3QuickStepLib"], + optimize: { + enabled: false, + }, + + platform_apis: true, + min_sdk_version: "current", + target_sdk_version: "current", + + privileged: true, + system_ext_specific: true, + overrides: [ + "Home", + "Launcher2", + "Launcher3", + ], + required: ["privapp_whitelist_com.android.launcher3"], + + resource_dirs: ["quickstep/res"], + + additional_manifests: [ + "quickstep/AndroidManifest-launcher.xml", + "AndroidManifest-common.xml", + ], + + manifest: "quickstep/AndroidManifest.xml", + jacoco: { + include_filter: ["com.android.launcher3.*"], + } + +} + +// Build rule for Launcher3 Go app with quickstep for Android Go devices. +android_app { + name: "Launcher3QuickStepGo", + + static_libs: [ + "SystemUI-statsd", + "SystemUISharedLib", + "LauncherGoResLib", + ], + + platform_apis: true, + min_sdk_version: "current", + target_sdk_version: "current", + + srcs: [ + "src/**/*.java", + "quickstep/src/**/*.java", + "go/src/**/*.java", + "go/quickstep/src/**/*.java", + ], + + resource_dirs: [ + "go/quickstep/res", + "go/res", + "quickstep/res", + ], + + optimize: { + proguard_flags_files: ["proguard.flags"], + enabled: true, + }, + + privileged: true, + system_ext_specific: true, + overrides: [ + "Home", + "Launcher2", + "Launcher3", + "Launcher3QuickStep", + ], + required: ["privapp_whitelist_com.android.launcher3"], + + additional_manifests: [ + "go/AndroidManifest.xml", + "go/AndroidManifest-launcher.xml", + "AndroidManifest-common.xml", + ], + + manifest: "quickstep/AndroidManifest.xml", + jacoco: { + include_filter: ["com.android.launcher3.*"], + } + +} + diff --git a/Android.mk b/Android.mk deleted file mode 100644 index 1bc8b283c7..0000000000 --- a/Android.mk +++ /dev/null @@ -1,147 +0,0 @@ -# -# Copyright (C) 2013 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH := $(call my-dir) - -# -# Build rule for Launcher3 Go app for Android Go devices. -# -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_MODULE_TAGS := optional -LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib - -LOCAL_SRC_FILES := \ - $(call all-java-files-under, src) \ - $(call all-java-files-under, src_ui_overrides) \ - $(call all-java-files-under, go/src) - -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res - -LOCAL_PROGUARD_FLAG_FILES := proguard.flags - -LOCAL_SDK_VERSION := current -LOCAL_MIN_SDK_VERSION := 26 -LOCAL_PACKAGE_NAME := Launcher3Go -LOCAL_PRIVILEGED_MODULE := true -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)/AndroidManifest.xml \ - $(LOCAL_PATH)/AndroidManifest-common.xml - -LOCAL_MANIFEST_FILE := go/AndroidManifest.xml -LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.* -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3 -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -include $(BUILD_PACKAGE) - -# -# Build rule for Quickstep app. -# -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_MODULE_TAGS := optional - -LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3QuickStepLib -LOCAL_PROGUARD_ENABLED := disabled - -ifneq (,$(wildcard frameworks/base)) - LOCAL_PRIVATE_PLATFORM_APIS := true -else - LOCAL_SDK_VERSION := system_current - LOCAL_MIN_SDK_VERSION := 26 -endif -LOCAL_PACKAGE_NAME := Launcher3QuickStep -LOCAL_PRIVILEGED_MODULE := true -LOCAL_SYSTEM_EXT_MODULE := true -LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 -LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 - -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res - -LOCAL_FULL_LIBS_MANIFEST_FILES := \ - $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \ - $(LOCAL_PATH)/AndroidManifest-common.xml - -LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml -LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.* - -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3 -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -include $(BUILD_PACKAGE) - - -# -# Build rule for Launcher3 Go app with quickstep for Android Go devices. -# -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_MODULE_TAGS := optional - -LOCAL_STATIC_JAVA_LIBRARIES := \ - SystemUI-statsd \ - SystemUISharedLib -ifneq (,$(wildcard frameworks/base)) - LOCAL_PRIVATE_PLATFORM_APIS := true -else - LOCAL_SDK_VERSION := system_current - LOCAL_MIN_SDK_VERSION := 26 -endif -LOCAL_STATIC_ANDROID_LIBRARIES := LauncherGoResLib - -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)/go/quickstep/res \ - $(LOCAL_PATH)/go/res \ - $(LOCAL_PATH)/quickstep/res - -LOCAL_PROGUARD_FLAG_FILES := proguard.flags -LOCAL_PROGUARD_ENABLED := full - -LOCAL_PACKAGE_NAME := Launcher3QuickStepGo -LOCAL_PRIVILEGED_MODULE := true -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)/go/AndroidManifest-launcher.xml \ - $(LOCAL_PATH)/AndroidManifest-common.xml - -LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml -LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.* -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_LICENSE_PACKAGE_NAME := Android Launcher3 -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -include $(BUILD_PACKAGE) - - -# ================================================== -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/OWNERS b/OWNERS index 7f98ea6a78..560b5629fb 100644 --- a/OWNERS +++ b/OWNERS @@ -7,13 +7,6 @@ alexchau@google.com andraskloczl@google.com patmanning@google.com -petrcermak@google.com -pbdr@google.com -kideckel@google.com -stevenckng@google.com -ydixit@google.com -boadway@google.com -alinazaidi@google.com adamcohen@google.com hyunyoungs@google.com mrcasey@google.com @@ -24,10 +17,8 @@ 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 @@ -37,8 +28,6 @@ hwwang@google.com tracyzhou@google.com peanutbutter@google.com xuqiu@google.com -sreyasr@google.com -thiruram@google.com brianji@google.com per-file FeatureFlags.java, globs = set noparent diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java index d16e12c9b8..a91ff44c8a 100644 --- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java +++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java @@ -33,6 +33,7 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.testing.shared.TestProtocol; import java.util.ArrayList; import java.util.Collection; diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java index 1aa5d03f6d..9a000d6b52 100644 --- a/go/src/com/android/launcher3/model/WidgetsModel.java +++ b/go/src/com/android/launcher3/model/WidgetsModel.java @@ -41,8 +41,10 @@ import java.util.Set; */ public class WidgetsModel { - // True is the widget support is disabled. + // True if the widget support is disabled. public static final boolean GO_DISABLE_WIDGETS = true; + // True if the shortcut support is disabled. + public static final boolean GO_DISABLE_SHORTCUTS = true; public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true; private static final ArrayList EMPTY_WIDGET_LIST = new ArrayList<>(); diff --git a/proguard.flags b/proguard.flags index a45018346a..53a68ded91 100644 --- a/proguard.flags +++ b/proguard.flags @@ -2,10 +2,6 @@ *; } --keep class com.android.launcher3.graphics.ShadowDrawable { - public (...); -} - # The support library contains references to newer platform versions. # Don't warn about those in case this app is linking against an older # platform version. We know about them, and they are safe. diff --git a/protos/view_capture.proto b/protos/view_capture.proto new file mode 100644 index 0000000000..f363f36820 --- /dev/null +++ b/protos/view_capture.proto @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.launcher3.view; + +option java_outer_classname = "ViewCaptureData"; + +message ExportedData { + + repeated FrameData frameData = 1; + repeated string classname = 2; +} + +message FrameData { + optional int64 timestamp = 1; + optional ViewNode node = 2; +} + +message ViewNode { + optional int32 classname_index = 1; + optional int32 hashcode = 2; + + repeated ViewNode children = 3; + + optional string id = 4; + optional int32 left = 5; + optional int32 top = 6; + optional int32 width = 7; + optional int32 height = 8; + optional int32 scrollX = 9; + optional int32 scrollY = 10; + + optional float translationX = 11; + optional float translationY = 12; + optional float scaleX = 13 [default = 1]; + optional float scaleY = 14 [default = 1]; + optional float alpha = 15 [default = 1]; + + optional bool willNotDraw = 16; + optional bool clipChildren = 17; + optional int32 visibility = 18; + + optional float elevation = 19; +} diff --git a/quickstep/Android.bp b/quickstep/Android.bp index 70b1438c38..f739f8140c 100644 --- a/quickstep/Android.bp +++ b/quickstep/Android.bp @@ -26,7 +26,7 @@ filegroup { filegroup { name: "launcher3-quickstep-tests-src", path: "tests", - srcs: ["tests/src/**/*.java"], + srcs: ["tests/src/**/*.java", "tests/src/**/*.kt"], } filegroup { diff --git a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java index e5f0295ff6..0b17a7b7d7 100644 --- a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java +++ b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java @@ -15,22 +15,13 @@ */ package com.android.quickstep; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; - import android.content.Context; -import android.content.res.Resources; import android.os.Bundle; import androidx.annotation.Nullable; -import com.android.launcher3.BaseQuickstepLauncher; -import com.android.launcher3.Launcher; -import com.android.launcher3.R; -import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.testing.DebugTestInformationHandler; -import com.android.launcher3.testing.TestProtocol; - -import java.util.concurrent.ExecutionException; +import com.android.launcher3.testing.shared.TestProtocol; /** * Class to handle requests from tests, including debug ones, to Quickstep Launcher builds. @@ -47,74 +38,14 @@ public abstract class DebugQuickstepTestInformationHandler extends QuickstepTest @Override public Bundle call(String method, String arg, @Nullable Bundle extras) { Bundle response = new Bundle(); - switch (method) { - case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING: - runOnUIThread(l -> { - enableManualTaskbarStashing(l, true); - }); - return response; - - case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING: - runOnUIThread(l -> { - enableManualTaskbarStashing(l, false); - }); - return response; - - case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED: - runOnUIThread(l -> { - enableManualTaskbarStashing(l, true); - - BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) l; - LauncherTaskbarUIController taskbarUIController = - quickstepLauncher.getTaskbarUIController(); - - // Allow null-pointer to catch illegal states. - taskbarUIController.unstashTaskbarIfStashed(); - - enableManualTaskbarStashing(l, false); - }); - return response; - - case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: { - final Resources resources = mContext.getResources(); - response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, - resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size)); - return response; - } - - default: - response = super.call(method, arg, extras); - if (response != null) return response; - return mDebugTestInformationHandler.call(method, arg, extras); + if (TestProtocol.REQUEST_RECREATE_TASKBAR.equals(method)) { + // Allow null-pointer to catch illegal states. + runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar()); + return response; } - } - - private void enableManualTaskbarStashing(Launcher launcher, boolean enable) { - BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) launcher; - LauncherTaskbarUIController taskbarUIController = - quickstepLauncher.getTaskbarUIController(); - - // Allow null-pointer to catch illegal states. - taskbarUIController.enableManualStashingForTests(enable); - } - - /** - * Runs the given command on the UI thread. - */ - private static void runOnUIThread(UIThreadCommand command) { - try { - MAIN_EXECUTOR.submit(() -> { - command.execute(Launcher.ACTIVITY_TRACKER.getCreatedActivity()); - return null; - }).get(); - } catch (ExecutionException | InterruptedException e) { - throw new RuntimeException(e); - } - } - - private interface UIThreadCommand { - - void execute(Launcher launcher); + response = super.call(method, arg, extras); + if (response != null) return response; + return mDebugTestInformationHandler.call(method, arg, extras); } } diff --git a/quickstep/res/drawable/ic_floating_task_button.xml b/quickstep/res/drawable/ic_floating_task_button.xml new file mode 100644 index 0000000000..e50f65cee7 --- /dev/null +++ b/quickstep/res/drawable/ic_floating_task_button.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/res/drawable/ic_uninstall_shadow.xml b/quickstep/res/drawable/split_instructions_background.xml similarity index 57% rename from res/drawable/ic_uninstall_shadow.xml rename to quickstep/res/drawable/split_instructions_background.xml index b441b0e7b6..6d0e7dba1c 100644 --- a/res/drawable/ic_uninstall_shadow.xml +++ b/quickstep/res/drawable/split_instructions_background.xml @@ -1,11 +1,11 @@ - - + + + + \ No newline at end of file diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml index 0cae733dbe..56e1d16281 100644 --- a/quickstep/res/layout/activity_allset.xml +++ b/quickstep/res/layout/activity_allset.xml @@ -26,7 +26,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/content_view" - android:fitsSystemWindows="true"> + android:fitsSystemWindows="false"> + + + + \ No newline at end of file diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index 3b1d217ec5..94388b42ec 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -45,8 +45,8 @@ android:id="@+id/start_contextual_buttons" android:layout_width="wrap_content" android:layout_height="match_parent" - android:paddingLeft="@dimen/taskbar_nav_buttons_spacing" - android:paddingRight="@dimen/taskbar_nav_buttons_spacing" + android:paddingStart="@dimen/taskbar_contextual_button_padding" + android:paddingEnd="@dimen/taskbar_contextual_button_padding" android:paddingTop="@dimen/taskbar_contextual_padding_top" android:gravity="center_vertical" android:layout_gravity="start"/> @@ -56,9 +56,6 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" - android:paddingLeft="@dimen/taskbar_nav_buttons_spacing" - android:paddingRight="@dimen/taskbar_nav_buttons_spacing" - android:layout_marginEnd="@dimen/taskbar_contextual_button_margin" android:gravity="center_vertical" android:layout_gravity="end"/> @@ -66,8 +63,6 @@ android:id="@+id/end_contextual_buttons" android:layout_width="wrap_content" android:layout_height="match_parent" - android:paddingLeft="@dimen/taskbar_nav_buttons_spacing" - android:paddingRight="@dimen/taskbar_nav_buttons_spacing" android:paddingTop="@dimen/taskbar_contextual_padding_top" android:gravity="center_vertical" android:layout_gravity="end"/> diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml index e834157504..34805eae3d 100644 --- a/quickstep/res/values-af/strings.xml +++ b/quickstep/res/values-af/strings.xml @@ -35,7 +35,6 @@ "Kry programvoorstelle op jou tuisskerm se gunstelingery" "Kry maklik toegang tot jou programme wat die meeste gebruik word, direk van die tuisskerm af. Voorstelle sal verander op grond van jou roetines. Programme in die onderste ry sal opskuif na jou tuisskerm." "Kry maklik toegang tot jou programme wat die meeste gebruik word, direk van die tuisskerm af. Voorstelle sal verander op grond van jou roetines. Programme in die gunstelingery sal na jou tuisskerm toe skuif." - "Kry maklik toegang tot jou programme wat die meeste gebruik word, direk van die tuisskerm af. Voorstelle sal verander op grond van jou roetines. Programme in die onderste ry sal na \'n nuwe vouer toe skuif." "Kry programvoorstelle" "Nee, dankie" "Instellings" diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml index 1834bc5128..1789c2b8dd 100644 --- a/quickstep/res/values-am/strings.xml +++ b/quickstep/res/values-am/strings.xml @@ -35,7 +35,6 @@ "በመነሻ ማያ ገጽዎ የተወዳጆች ረድፍ ላይ የመተግበሪያ አስተያየት ጥቆማዎችን ያግኙ" "በጣም ስራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመስረት ይቀየራሉ። በታችኛው ረድፍ ላይ ያሉ መተግበሪያዎች ወደ መነሻ ገጽዎ ይወሰዳሉ።" "በጣም ሥራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመሥረት ይቀየራሉ። በተወዳጆች ረድፍ ውስጥ ያሉ መተግበሪያዎች ወደ የእርስዎ መነሻ ማያ ገጽ ይንቀሳቀሳሉ።" - "በጣም ስራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመስረት ይቀየራሉ። በታችኛው ረድፍ ላይ ያሉ መተግበሪያዎች ወደ አዲስ አቃፊ ይወሰዳሉ።" "የመተግበሪያ አስተያየት ጥቆማዎችን አግኝ" "አይ፣ አመሰግናለሁ" "ቅንብሮች" diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml index f15fcd4a32..f9755d2d37 100644 --- a/quickstep/res/values-ar/strings.xml +++ b/quickstep/res/values-ar/strings.xml @@ -35,7 +35,6 @@ "رؤية التطبيقات المقترحة في صف التطبيقات المفضّلة في الشاشة الرئيسية" "يمكنك الوصول إلى التطبيقات الأكثر استخدامًا بسهولة من الشاشة الرئيسية مباشرةً. سيتم تغيير الاقتراحات استنادًا إلى استخدامك الروتيني. وسيتم نقل التطبيقات من الصف السفلي في الشاشة الرئيسية إلى الصف الأعلى." "يمكنك الوصول إلى التطبيقات الأكثر استخدامًا بسهولة من الشاشة الرئيسية مباشرةً. سيتم تغيير الاقتراحات استنادًا إلى سلاسل الإجراءات. سيتم نقل التطبيقات من صف التطبيقات المفضّلة إلى الشاشة الرئيسية." - "يمكنك الوصول إلى التطبيقات الأكثر استخدامًا بسهولة من الشاشة الرئيسية مباشرةً. سيتم تغيير الاقتراحات استنادًا إلى سلاسل الإجراءات. سيتم نقل التطبيقات من الصف الأسفل إلى مجلد جديد." "رؤية تطبيقات مقترحة" "لا، شكرًا" "الإعدادات" diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml index b33abede88..ec47dd4570 100644 --- a/quickstep/res/values-as/strings.xml +++ b/quickstep/res/values-as/strings.xml @@ -35,7 +35,6 @@ "আপোনাৰ গৃহ স্ক্ৰীনৰ প্ৰিয় সমলৰ শাৰীটোত এপৰ পৰামর্শসমূহ পাওক" "আপোনাৰ সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্‌সমূহ গৃহ স্ক্ৰীনতে সহজে এক্সেছ কৰক। আপোনাৰ ৰুটিনসমূহৰ ভিত্তিত পৰামর্শসমূহ সলনি হ\'ব। একেবাৰে তলৰ শাৰীটোত থকা এপ্‌সমূহ ওপৰৰ আপোনাৰ গৃহ স্ক্ৰীনলৈ যাব।" "আপোনাৰ সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্‌সমূহ গৃহ স্ক্ৰীনতে সহজে এক্সেছ কৰক। আপোনাৰ ৰুটিনসমূহৰ ভিত্তিত পৰামর্শসমূহ সলনি হ’ব। প্ৰিয় সমলৰ শাৰীত থকা এপ্‌সমূহ আপোনাৰ গৃহ স্ক্রীনলৈ যাব।" - "আপোনাৰ সকলোতকৈ বেছিকৈ ব্যৱহৃত এপ্‌সমূহ গৃহ স্ক্ৰীনতে সহজে এক্সেছ কৰক। আপোনাৰ ৰুটিনসমূহৰ ভিত্তিত পৰামর্শসমূহ সলনি হ\'ব। একেবাৰে তলৰ শাৰীটোত থকা এপ্‌সমূহ এটা নতুন ফ\'ল্ডাৰলৈ যাব।" "এপৰ পৰামর্শসমূহ পাওক" "নালাগে, ধন্যবাদ" "ছেটিং" diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml index 07e29bb7db..7d15232fd6 100644 --- a/quickstep/res/values-az/strings.xml +++ b/quickstep/res/values-az/strings.xml @@ -35,7 +35,6 @@ "Ana ekranın sevimlilər sırasında tətbiq təklifləri alın" "Birbaşa Ana ekrandan ən çox istifadə edilən tətbiqlərə asanlıqla daxil olun. Təkliflər rejimlərinizə uyğun olaraq dəyişəcək. Aşağı sıradakı tətbiqlər Ana ekrana köçürüləcək." "Birbaşa Ana ekrandan ən çox işlədilən tətbiqlərə asanlıqla girin. Təkliflər rejimlərinizə uyğun olaraq dəyişəcək. Sevimlilər sırasındakı tətbiqlər Əsas ekrana köçürüləcək." - "Birbaşa Əsas səhifədən ən çox istifadə edilən tətbiqlərə asanlıqla daxil olun. Təkliflər rejimlərinizə uyğun olaraq dəyişəcək. Aşağı sıradakı tətbiqlər yeni qovluğa köçürüləcək." "Tətbiq təklifləri əldə edin" "Yox, çox sağolun" "Ayarlar" diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml index 42023d3af6..638795369c 100644 --- a/quickstep/res/values-b+sr+Latn/strings.xml +++ b/quickstep/res/values-b+sr+Latn/strings.xml @@ -35,7 +35,6 @@ "Dobijajte predloge aplikacija u redu sa omiljenim stavkama na početnom ekranu" "Lako pristupajte aplikacijama koje najčešće koristite direktno sa početnog ekrana. Predlozi se menjaju na osnovu upotrebe. Aplikacije iz donjeg reda se premeštaju nagore na početni ekran." "Lako pristupajte aplikacijama koje najčešće koristite direktno sa početnog ekrana. Predlozi se menjaju na osnovu vaših rutina. Aplikacije iz reda sa omiljenim stavkama se premeštaju na početni ekran." - "Lako pristupajte aplikacijama koje najčešće koristite direktno sa početnog ekrana. Predlozi se menjaju na osnovu upotrebe. Aplikacije iz donjeg reda se premeštaju u nov folder." "Prikazuj predloge aplikacija" "Ne, hvala" "Podešavanja" diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml index dc4dc6570c..5e5f147b16 100644 --- a/quickstep/res/values-be/strings.xml +++ b/quickstep/res/values-be/strings.xml @@ -35,7 +35,6 @@ "Атрымлівайце прапановы праграм у пераліку абраных на Галоўным экране" "Атрымлiвайце доступ да праграм, якімі вы карыстаецеся найбольш часта, непасрэдна з Галоўнага экрана. Прапановы будуць змяняцца ў залежнасці ад вашых дзеянняў. Праграмы, якія знаходзяцца ў ніжнім радку, будуць перамешчаны на Галоўны экран." "Атрымлівайце доступ да праграм, якімі вы карыстаецеся найбольш часта, непасрэдна з Галоўнага экрана. Прапановы будуць змяняцца ў залежнасці ад вашых дзеянняў. Пералік абраных праграм будзе перамешчаны на Галоўны экран." - "Атрымлiвайце просты доступ да праграм, якімі вы карыстаецеся найбольш часта, непасрэдна з Галоўнага экрана. Прапановы будуць змяняцца ў залежнасці ад вашых дзеянняў. Праграмы, якія знаходзяцца ў ніжнім радку, будуць перамешчаны ў новую папку." "Атрымліваць прапановы праграм" "Не, дзякуй" "Налады" diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml index 560a03b7e1..ca61151db2 100644 --- a/quickstep/res/values-bg/strings.xml +++ b/quickstep/res/values-bg/strings.xml @@ -35,7 +35,6 @@ "Получаване на предложения за приложения в реда с любими на началния екран" "Осъществявайте лесен достъп до най-използваните от вас приложения директно от началния екран. Предложенията ще се променят въз основа на действията ви. Приложенията на най-долния ред ще се преместят на началния ви екран." "Осъществявайте лесен достъп до най-използваните от вас приложения директно от началния екран. Предложенията ще се променят въз основа на действията ви. Приложенията в реда с любими ще бъдат преместени на началния екран." - "Осъществявайте лесен достъп до най-използваните от вас приложения директно от началния екран. Предложенията ще се променят въз основа на действията ви. Приложенията на най-долния ред ще се преместят в нова папка." "Предложения" "Не, благодаря" "Настройки" diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml index d4774a7ad4..268e73667f 100644 --- a/quickstep/res/values-bn/strings.xml +++ b/quickstep/res/values-bn/strings.xml @@ -35,7 +35,6 @@ "হোম স্ক্রিনের \'ফেভারিট রো\' বিকল্পের জন্য অ্যাপ সাজেশন পান" "হোম স্ক্রিন থেকে সরাসরি সব থেকে বেশি ব্যবহার করা অ্যাপগুলি অ্যাক্সেস করুন। আপনার রুটিনের উপর ভিত্তি করে সাজেশন পরির্তন করা হবে। নিচের সারিতে থাকা অ্যাপ আপনার হোম স্ক্রিনে সরানো হবে।" "খুব বেশি ব্যবহার করেন এমন অ্যাপগুলি হোম স্ক্রিন থেকে সহজে সরাসরি অ্যাক্সেস করুন। আপনার রুটিন অনুযায়ী সাজেশন পরির্তন করা হবে। \'ফেভারিট রো\' বিকল্পে থাকা অ্যাপগুলি হোম স্ক্রিনে সরিয়ে দেওয়া হবে।" - "হোম স্ক্রিনের পাশে সব থেকে ব্যবহার করা অ্যাপ সহজেই অ্যাক্সেস করুন। আপনার রুটিনের উপর ভিত্তি করে সাজেশন পরির্তন করা হবে। নিচের সারিতে থাকা অ্যাপ নতুন ফোল্ডারে সরানো হবে।" "অ্যাপ সাজেশন পান" "না থাক" "সেটিংস" diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml index 3743e24f87..1ff0817246 100644 --- a/quickstep/res/values-bs/strings.xml +++ b/quickstep/res/values-bs/strings.xml @@ -35,7 +35,6 @@ "Primajte prijedloge aplikacija u redu omiljenih stavki početnog ekrana" "Jednostavno pristupite najčešće korištenim aplikacijama direktno s početnog ekrana. Prijedlozi će se mijenjati na osnovu vaših rutina. Aplikacije koje se nalaze u donjem redu će se premjestiti na početni ekran." "Jednostavno pristupite najčešće korištenim aplikacijama direktno s početnog ekrana. Prijedlozi će se mijenjati na osnovu vaših rutina. Aplikacije u redu omiljenih stavki će se premjestiti na početni ekran." - "Jednostavno pristupite najčešće korištenim aplikacijama, direktno s početnog ekrana. Prijedlozi će se mijenjati na osnovu vaših rutina. Aplikacije koje se nalaze u donjem redu će se premjestiti u novi folder." "Prikaži prijedloge aplikacija" "Ne, hvala" "Postavke" diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml index 9400fba601..4957aee817 100644 --- a/quickstep/res/values-ca/strings.xml +++ b/quickstep/res/values-ca/strings.xml @@ -35,7 +35,6 @@ "Obtén suggeriments d\'aplicacions a la fila Preferides de la teva pantalla d\'inici" "Accedeix fàcilment a les aplicacions que més utilitzes des de la pantalla d\'inici. Els suggeriments variaran en funció dels teus hàbits. Les aplicacions de la fila inferior pujaran a la pantalla d\'inici." "Accedeix fàcilment a les aplicacions que més utilitzes des de la pantalla d\'inici. Els suggeriments variaran en funció dels teus hàbits. Les aplicacions de la fila Preferides es mouran a la teva pantalla d\'inici." - "Accedeix fàcilment a les aplicacions que més utilitzes des de la pantalla d\'inici. Els suggeriments variaran en funció dels teus hàbits. Les aplicacions de la fila inferior es mouran a una carpeta nova." "Mostra suggeriments d\'aplicacions" "No, gràcies" "Configuració" diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml index cb49a35673..fa707ac652 100644 --- a/quickstep/res/values-cs/strings.xml +++ b/quickstep/res/values-cs/strings.xml @@ -35,7 +35,6 @@ "Nechte si na řádku oblíbených na ploše zobrazovat návrhy aplikací" "Mějte nejpoužívanější aplikace k dispozici přímo na ploše. Návrhy se budou měnit podle vašich zvyklostí. Aplikace ve spodním řádku se přesunou nahoru na vaši plochu." "Mějte nejpoužívanější aplikace k dispozici přímo na ploše. Návrhy se budou měnit podle vašich zvyklostí. Aplikace na řádku oblíbených se přesunou na plochu." - "Mějte nejpoužívanější aplikace k dispozici přímo na ploše. Návrhy se budou měnit podle vašich zvyklostí. Aplikace ve spodním řádku se přesunou do nové složky." "Zobrazovat návrhy aplikací" "Ne, díky" "Nastavení" diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml index 621006b8ab..d88061d399 100644 --- a/quickstep/res/values-da/strings.xml +++ b/quickstep/res/values-da/strings.xml @@ -35,7 +35,6 @@ "Få appforslag i rækken med favoritter på din startskærm" "Få nem adgang til dine mest brugte apps direkte fra startskærmen. Forslagene ændres ud fra dine vaner. Apps i nederste række bliver flyttet op til din startskærm." "Få nem adgang til dine mest brugte apps direkte fra startskærmen. Forslagene ændres ud fra dine vaner. Apps i rækken med favoritter bliver flyttet til din startskærm." - "Få nem adgang til dine mest brugte apps direkte fra startskærmen. Forslagene ændres ud fra dine vaner. Apps i nederste række bliver flyttet til en ny mappe." "Få appforslag" "Nej tak" "Indstillinger" diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml index d19c23bd68..3728b35ee3 100644 --- a/quickstep/res/values-de/strings.xml +++ b/quickstep/res/values-de/strings.xml @@ -35,7 +35,6 @@ "Lass dir in der Favoritenleiste auf dem Startbildschirm App-Vorschläge anzeigen" "Schneller Zugriff auf deine meistverwendeten Apps direkt über den Startbildschirm. Die Vorschläge werden deiner Nutzung entsprechend laufend angepasst. Apps in der unteren Reihe werden nach oben auf den Startbildschirm verschoben." "Schneller Zugriff auf deine meistverwendeten Apps direkt über den Startbildschirm. Die Vorschläge werden deiner Nutzung entsprechend laufend angepasst. Apps der Favoritenleiste werden auf den Startbildschirm verschoben." - "Schneller Zugriff auf deine meistverwendeten Apps direkt über den Startbildschirm. Die Vorschläge werden deiner Nutzung entsprechend laufend angepasst. Apps in der unteren Reihe werden in einen neuen Ordner verschoben." "App-Vorschläge erhalten" "Nein danke" "Einstellungen" diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml index 2c6702bcc7..5ff882e967 100644 --- a/quickstep/res/values-el/strings.xml +++ b/quickstep/res/values-el/strings.xml @@ -35,7 +35,6 @@ "Δείτε τις προτεινόμενες εφαρμογές στη σειρά Αγαπημένα της αρχικής οθόνης." "Αποκτήστε εύκολα πρόσβαση στις εφαρμογές που χρησιμοποιείτε περισσότερο απευθείας από την αρχική οθόνη. Οι προτάσεις θα αλλάζουν με βάση τις ρουτίνες σας. Οι εφαρμογές στην κάτω σειρά θα μετακινηθούν προς τα επάνω στην αρχική οθόνη." "Αποκτήστε εύκολα πρόσβαση στις εφαρμογές που χρησιμοποιείτε περισσότερο απευθείας από την αρχική οθόνη. Οι προτάσεις θα αλλάζουν με βάση τις ρουτίνες σας. Οι εφαρμογές στην σειρά Αγαπημένα θα μετακινηθούν στην αρχική οθόνη σας." - "Αποκτήστε εύκολα πρόσβαση στις εφαρμογές που χρησιμοποιείτε περισσότερο, απευθείας από την αρχική οθόνη. Οι προτάσεις θα αλλάζουν με βάση τις ρουτίνες σας. Οι εφαρμογές στην κάτω σειρά θα μεταφερθούν σε νέο φάκελο." "Προβολή προτεινόμενων εφαρμογών" "Όχι, ευχαριστώ" "Ρυθμίσεις" diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml index 7b297af793..5b378b41ac 100644 --- a/quickstep/res/values-en-rAU/strings.xml +++ b/quickstep/res/values-en-rAU/strings.xml @@ -35,7 +35,6 @@ "Get app suggestions on the favourites row of your home screen" "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your home screen." "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps in the favourites row will move to your home screen." - "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will be moved to a new folder." "Get app suggestions" "No, thanks" "Settings" diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml index f6ad1692c2..f7d5c8890c 100644 --- a/quickstep/res/values-en-rCA/strings.xml +++ b/quickstep/res/values-en-rCA/strings.xml @@ -35,7 +35,6 @@ "Get app suggestions on favorites row of your Home screen" "Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen." "Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps in favorites row will move to your Home screen." - "Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder." "Get app suggestions" "No thanks" "Settings" diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml index 7b297af793..5b378b41ac 100644 --- a/quickstep/res/values-en-rGB/strings.xml +++ b/quickstep/res/values-en-rGB/strings.xml @@ -35,7 +35,6 @@ "Get app suggestions on the favourites row of your home screen" "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your home screen." "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps in the favourites row will move to your home screen." - "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will be moved to a new folder." "Get app suggestions" "No, thanks" "Settings" diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml index 7b297af793..5b378b41ac 100644 --- a/quickstep/res/values-en-rIN/strings.xml +++ b/quickstep/res/values-en-rIN/strings.xml @@ -35,7 +35,6 @@ "Get app suggestions on the favourites row of your home screen" "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your home screen." "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps in the favourites row will move to your home screen." - "Easily access your most-used apps directly from the home screen. Suggestions will change based on your routines. Apps on the bottom row will be moved to a new folder." "Get app suggestions" "No, thanks" "Settings" diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml index bd2a02471e..f0b2066d3f 100644 --- a/quickstep/res/values-en-rXC/strings.xml +++ b/quickstep/res/values-en-rXC/strings.xml @@ -35,7 +35,6 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎Get app suggestions on favorites row of your Home screen‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps in favorites row will move to your Home screen.‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎Get app suggestions‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎No thanks‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Settings‎‏‎‎‏‎" diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml index 06ee5bb1b5..b9bb70f36f 100644 --- a/quickstep/res/values-es-rUS/strings.xml +++ b/quickstep/res/values-es-rUS/strings.xml @@ -35,7 +35,6 @@ "Obtén sugerencias de apps en la fila de favoritos de la pantalla principal" "Accede fácilmente en la pantalla principal a las apps que más usas. Las sugerencias cambiarán según tus rutinas. Las apps de la fila inferior se desplazarán hacia arriba en la pantalla principal." "Accede fácilmente en la pantalla principal a las apps que más usas. Las sugerencias cambiarán según tus rutinas. Se moverán a la pantalla principal las apps que estén en la fila de favoritos." - "Accede fácilmente a las apps que más usas en la pantalla principal. Las sugerencias cambiarán según tus rutinas. Las apps de la fila inferior se moverán a una nueva carpeta." "Obtén sugerencias de aplicaciones" "No, gracias" "Configuración" diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml index 80c8b0bd26..f3d7a31623 100644 --- a/quickstep/res/values-es/strings.xml +++ b/quickstep/res/values-es/strings.xml @@ -35,7 +35,6 @@ "Recibe sugerencias de aplicaciones en la fila de aplicaciones favoritas de la pantalla de inicio" "Accede fácilmente a las aplicaciones que más usas desde la pantalla de inicio. Las sugerencias cambiarán según tus hábitos. Las aplicaciones de la fila inferior que tengas ahora se moverán hacia arriba en la pantalla de inicio." "Accede fácilmente a las aplicaciones que más usas desde la pantalla de inicio. Las sugerencias cambiarán según tus hábitos. Las aplicaciones de la fila de aplicaciones favoritas se moverán a la pantalla de inicio." - "Accede fácilmente a las aplicaciones que más usas desde la pantalla de inicio. Las sugerencias cambiarán según tus hábitos. Las aplicaciones de la fila inferior que tengas ahora se moverán a una carpeta nueva." "Sí, obtener sugerencias" "No, gracias" "Ajustes" diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml index 7f37a4bcc3..73c1c1da38 100644 --- a/quickstep/res/values-et/strings.xml +++ b/quickstep/res/values-et/strings.xml @@ -35,7 +35,6 @@ "Hankige avakuva lemmikute reale rakenduste soovitusi" "Pääsete enim kasutatavatele rakendustele hõlpsasti juurde otse avakuvalt. Soovitused muutuvad olenevalt teie rutiinist. Alumisel real olevad rakendused teisaldatakse teie avakuvale." "Pääsete enim kasutatavatele rakendustele hõlpsasti juurde otse avakuvalt. Soovitused muutuvad olenevalt teie rutiinist. Lemmikute real olevad rakendused teisaldatakse teie avakuvale." - "Pääsete enim kasutatavatele rakendustele hõlpsasti juurde otse avakuvalt. Soovitused muutuvad olenevalt teie rutiinist. Alumisel real olevad rakendused teisaldatakse uude kausta." "Hangi rakenduste soovitusi" "Tänan, ei" "Seaded" diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml index 4169838294..5b83633565 100644 --- a/quickstep/res/values-eu/strings.xml +++ b/quickstep/res/values-eu/strings.xml @@ -35,7 +35,6 @@ "Jaso aplikazioen iradokizunak hasierako pantailako gogokoen errenkadan" "Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Hasierako pantailara eramango dira beheko errenkadan dauden aplikazioak." "Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Gogokoen errenkadako aplikazioak hasierako pantailara eramango ditugu." - "Atzitu erraz aplikazio erabilienak hasierako pantailatik bertatik. Ohituren arabera aldatuko dira iradokizunak. Karpeta berri batera eramango dira beheko errenkadan dauden aplikazioak." "Jaso aplikazioen iradokizunak" "Ez, eskerrik asko" "Ezarpenak" diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml index de6d7e546c..a64a876980 100644 --- a/quickstep/res/values-fa/strings.xml +++ b/quickstep/res/values-fa/strings.xml @@ -35,7 +35,6 @@ "دریافت «پیشنهاد برنامه» در ردیف موارد دلخواه صفحه اصلی" "به‌راحتی در صفحه اصلی به پرکاربردترین برنامه‌ها دسترسی داشته باشید. پیشنهادها براساس روال‌هایتان تغییر خواهد کرد. برنامه‌های ردیف پایین در صفحه اصلی به بالا منتقل خواهند شد." "به‌راحتی در صفحه اصلی به پرکاربردترین برنامه‌ها دسترسی داشته باشید. پیشنهادها براساس روال‌هایتان تغییر خواهد کرد. برنامه‌های موجود در ردیف موارد دلخواه به صفحه اصلی منتقل می‌شوند." - "به‌راحتی در صفحه اصلی به پرکاربردترین برنامه‌ها دسترسی داشته باشید. پیشنهادها براساس روال‌هایتان تغییر خواهد کرد. برنامه‌های ردیف پایین به پوشه جدیدی منتقل خواهند شد." "دریافت پیشنهادهای برنامه" "نه متشکرم" "تنظیمات" diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml index 8c50f70fb3..08a9248692 100644 --- a/quickstep/res/values-fi/strings.xml +++ b/quickstep/res/values-fi/strings.xml @@ -35,7 +35,6 @@ "Näytä sovellusehdotuksia aloitusnäytön Suosikit-rivillä" "Voit avata käytetyimmät sovellukset kätevästi aloitusnäytöltä. Ehdotukset muuttuvat rutiiniesi perusteella. Alimmalla rivillä olevat sovellukset siirretään aloitusnäytön yläosaan." "Voit avata käytetyimmät sovellukset kätevästi aloitusnäytöltä. Ehdotukset muuttuvat rutiiniesi perusteella. Suosikit-rivillä olevat sovellukset siirretään aloitusnäytölle." - "Voit avata käytetyimmät sovellukset kätevästi aloitusnäytöltä. Ehdotukset muuttuvat rutiiniesi perusteella. Alimmalla rivillä olevat sovellukset siirretään uuteen kansioon." "Näytä sovellusehdotuksia" "Ei kiitos" "Asetukset" diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml index f92d55c246..6199a1421a 100644 --- a/quickstep/res/values-fr-rCA/strings.xml +++ b/quickstep/res/values-fr-rCA/strings.xml @@ -35,7 +35,6 @@ "Retrouvez des suggestions d\'applications dans la rangée des favoris de votre écran d\'accueil" "Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée du bas seront déplacées vers votre écran d\'accueil." "Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée des favoris seront déplacées vers votre écran d\'accueil." - "Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée du bas seront déplacées vers un nouveau dossier." "Obtenir des suggestions d\'applications" "Non merci" "Paramètres" diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml index 9ffa0cc415..c66da27883 100644 --- a/quickstep/res/values-fr/strings.xml +++ b/quickstep/res/values-fr/strings.xml @@ -35,7 +35,6 @@ "Retrouvez des suggestions d\'applications dans la zone des favoris de votre écran d\'accueil" "Les suggestions d\'applications permettent d\'afficher vos applications favorites au bas de votre écran d\'accueil. Elles s\'adaptent à vos habitudes d\'utilisation. Les icônes auparavant affichées au bas de l\'écran seront déplacées vers le haut." "Accédez facilement aux applications dont vous vous servez le plus, directement depuis l\'écran d\'accueil. Ces suggestions peuvent varier en fonction de vos habitudes d\'utilisation. Les applications de la zone des favoris seront transférées sur votre écran d\'accueil." - "Les suggestions d\'applications permettent d\'afficher vos applications favorites au bas de votre écran d\'accueil. Elles s\'adaptent à vos habitudes d\'utilisation. Les icônes auparavant affichées au bas de l\'écran seront placées dans un nouveau dossier." "Activer les suggestions" "Non, merci" "Paramètres" diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml index bea23b7880..9946f17b4b 100644 --- a/quickstep/res/values-gl/strings.xml +++ b/quickstep/res/values-gl/strings.xml @@ -35,7 +35,6 @@ "Recibe suxestións de aplicacións na fila de Favoritos da pantalla de inicio" "Accede facilmente desde a pantalla de inicio ás aplicacións que máis usas. As suxestións irán cambiando en función das túas rutinas. As aplicacións da fila inferior pasarán á pantalla de inicio." "Accede facilmente desde a pantalla de inicio ás aplicacións que máis usas. As suxestións irán cambiando en función das túas rutinas. As aplicacións da fila de Favoritos moveranse á túa pantalla de inicio." - "Accede facilmente desde a pantalla de inicio ás aplicacións que máis usas. As suxestións irán cambiando en función das túas rutinas. As aplicacións da fila inferior pasarán a un cartafol novo." "Recibir suxestións de aplicacións" "Non, grazas" "Configuración" diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml index a146b24c88..3439410d37 100644 --- a/quickstep/res/values-gu/strings.xml +++ b/quickstep/res/values-gu/strings.xml @@ -35,7 +35,6 @@ "તમારી હોમ સ્ક્રીનની મનપસંદ પંક્તિમાં ઍપના સુઝાવો મેળવો" "તમારી સૌથી વધુ વપરાતી ઍપને સીધી હોમ સ્ક્રીન પરથી જ સરળતાથી ઍક્સેસ કરો. સૂચનો તમારા રૂટિનના આધારે બદલાશે. નીચેની પંક્તિમાં રહેલી ઍપ તમારી હોમ સ્ક્રીન પર ખસેડાશે." "તમારી સૌથી વધુ વપરાતી ઍપને સીધી હોમ સ્ક્રીન પરથી જ સરળતાથી ઍક્સેસ કરો. સૂચનો તમારા રૂટિનના આધારે બદલાશે. મનપસંદ પંક્તિમાં રહેલી ઍપ તમારી હોમ સ્ક્રીન પર ખસેડાશે." - "તમારી સૌથી વધુ વપરાતી ઍપને સીધી હોમ સ્ક્રીન પરથી જ સરળતાથી ઍક્સેસ કરો. સૂચનો તમારા રૂટિનના આધારે બદલાશે. નીચેની પંક્તિમાં રહેલી ઍપ નવા ફોલ્ડરમાં ખસેડાશે." "ઍપ અંગેના સુઝાવો મેળવો" "ના, આભાર" "સેટિંગ" diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml index c938e6b64b..be5a2776b4 100644 --- a/quickstep/res/values-hi/strings.xml +++ b/quickstep/res/values-hi/strings.xml @@ -35,7 +35,6 @@ "अपनी होम स्क्रीन की सबसे नीचे वाली पंक्ति में पसंदीदा ऐप्लिकेशन के सुझाव पाएं" "आपके ज़्यादातर इस्तेमाल किए जाने वाले ऐप्लिकेशन, सीधा अपनी होम स्क्रीन पर पाएं. ऐप्लिकेशन इस्तेमाल करने के आपके रूटीन के हिसाब से सुझाव बदलते रहते हैं. नीचे की पंक्ति के ऐप्लिकेशन होम स्क्रीन पर आ जाएंगे." "सबसे ज़्यादा इस्तेमाल होने वाले ऐप्लिकेशन सीधे होम स्क्रीन पर देखें. आप ऐप्लिकेशन का कितना इस्तेमाल कर रहे हैं, उसके हिसाब से सुझाव बदलते रहते हैं. आपके पसंदीदा ऐप्लिकेशन, होम स्क्रीन पर नीचे की पंक्ति में दिखाई देंगे." - "सबसे ज़्यादा इस्तेमाल होने वाले ऐप्लिकेशन, सीधे होम स्क्रीन पर पाएं. आपके ऐप्लिकेशन इस्तेमाल करने के रूटीन के हिसाब से सुझाव बदलते रहते हैं. नीचे की पंक्ति के ऐप्लिकेशन एक नए फ़ोल्डर में चले जाएंगे." "ऐप्लिकेशन के बारे में सुझाव पाएं" "रहने दें" "सेटिंग" diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml index 640d7ccb1e..620ba51788 100644 --- a/quickstep/res/values-hr/strings.xml +++ b/quickstep/res/values-hr/strings.xml @@ -35,7 +35,6 @@ "Primajte prijedloge aplikacija u retku omiljenih na početnom zaslonu" "Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije iz donjeg retka pomaknut će se na početni zaslon." "Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije koje se nalaze u retku omiljenih pomaknut će se na početni zaslon." - "Lako pristupite najčešće upotrebljavanim aplikacijama s početnog zaslona. Prijedlozi će se mijenjati na temelju vaših rutina. Aplikacije iz donjeg retka pomaknut će se u novu mapu." "Predloži mi aplikacije" "Ne, hvala" "Postavke" diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml index 6369293de6..61f0a3b31a 100644 --- a/quickstep/res/values-hu/strings.xml +++ b/quickstep/res/values-hu/strings.xml @@ -35,7 +35,6 @@ "Alkalmazásjavaslatokat kaphat a kezdőképernyőn megjelenő kedvencek sorában" "A kezdőképernyőről könnyedén hozzáférhet a leggyakrabban használt alkalmazásokhoz. A javaslatok a rutinjai alapján változni fognak. Az alsó sorban lévő alkalmazások felkerülnek a kezdőképernyőre." "A kezdőképernyőről könnyedén hozzáférhet a leggyakrabban használt alkalmazásokhoz. A javaslatok a rutinjai alapján változnak majd. A kedvencek sorában lévő alkalmazások a kezdőképernyőre kerülnek." - "A kezdőképernyőről könnyedén hozzáférhet a leggyakrabban használt alkalmazásokhoz. A javaslatok a rutinjai alapján változni fognak. Az alsó sorban lévő alkalmazások egy új mappába kerülnek." "Kérek javaslatokat" "Köszönöm, nem" "Beállítások" diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml index 9eeaa98690..0f9356a42d 100644 --- a/quickstep/res/values-hy/strings.xml +++ b/quickstep/res/values-hy/strings.xml @@ -35,7 +35,6 @@ "Ստացեք հավելվածների առաջարկներ հիմնական էկրանի «Ընտրանի» տողում" "Անմիջապես հիմնական էկրանից բացեք հաճախ օգտագործվող հավելվածները։ Առաջարկվող հավելվածները կփոփոխվեն՝ կախված ձեր գործողություններից։ Ներքևի տողի հավելվածները կտեղափոխվեն վերև հիմնական էկրանին։" "Արագ բացեք հաճախ օգտագործվող հավելվածներն անմիջապես հիմնական էկրանից։ Առաջարկները կփոփոխվեն՝ կախված ձեր գործողություններից։ «Ընտրանի» տողի հավելվածները կտեղափոխվեն հիմնական էկրան։" - "Արագ բացեք հաճախ օգտագործվող հավելվածներն անմիջապես հիմնական էկրանից։ Առաջարկները կփոփոխվեն՝ կախված ձեր գործողություններից։ Ներքևում ցուցադրվող հավելվածները կտեղափոխվեն նոր պանակ։" "Ստանալ առաջարկներ" "Ոչ, շնորհակալություն" "Կարգավորումներ" diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml index 14d8c3effe..b95871f9f6 100644 --- a/quickstep/res/values-in/strings.xml +++ b/quickstep/res/values-in/strings.xml @@ -35,7 +35,6 @@ "Dapatkan saran aplikasi di baris favorit Layar utama" "Akses aplikasi yang paling sering digunakan dengan mudah, langsung di Layar utama. Saran akan berubah berdasarkan rutinitas Anda. Aplikasi di baris paling bawah akan berpindah naik ke Layar utama." "Mudah mengakses aplikasi yang paling sering digunakan, langsung di Layar utama. Saran akan berubah berdasarkan rutinitas Anda. Aplikasi di baris favorit akan berpindah ke Layar utama." - "Akses aplikasi yang paling sering digunakan dengan mudah, langsung di Layar utama. Saran akan berubah berdasarkan rutinitas Anda. Aplikasi di baris paling bawah akan berpindah ke folder baru." "Dapatkan saran aplikasi" "Lain kali" "Setelan" diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml index 1761203bf3..5730900804 100644 --- a/quickstep/res/values-is/strings.xml +++ b/quickstep/res/values-is/strings.xml @@ -35,7 +35,6 @@ "Fáðu tillögur að forritum á eftirlætissvæði heimaskjásins" "Nálgastu forritin sem þú notar mest auðveldlega á heimaskjánum. Tillögurnar breytast í samræmi við notkun þína. Forrit í neðstu röð færast upp á heimaskjáinn." "Nálgastu forritin sem þú notar mest á einfaldan hátt á heimaskjánum. Tillögurnar breytast í samræmi við notkun þína. Forrit á eftirlætissvæði færast á heimaskjáinn." - "Nálgastu forritin sem þú notar mest auðveldlega á heimaskjánum. Tillögurnar breytast í samræmi við notkun þína. Forrit í neðstu röð færast í nýja möppu." "Fá tillögur að forritum" "Nei, takk" "Stillingar" diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml index 874d07223d..7e04cf66ab 100644 --- a/quickstep/res/values-it/strings.xml +++ b/quickstep/res/values-it/strings.xml @@ -35,7 +35,6 @@ "Visualizza app suggerite nella riga dei Preferiti della schermata Home" "Accedi facilmente alle app più utilizzate direttamente dalla schermata Home. I suggerimenti varieranno in base alle tue routine. Le app nella riga inferiore verranno spostate più in alto sulla schermata Home." "Accedi facilmente alle app più utilizzate direttamente dalla schermata Home. I suggerimenti varieranno in base alle tue routine. Le app nella riga dei Preferiti verranno spostate nella schermata Home." - "Accedi facilmente alle app più utilizzate direttamente dalla schermata Home. I suggerimenti varieranno in base alle tue routine. Le app nella riga inferiore verranno spostate in una nuova cartella." "Visualizza app suggerite" "No, grazie" "Impostazioni" diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml index 09d76f89a1..56148e79ff 100644 --- a/quickstep/res/values-iw/strings.xml +++ b/quickstep/res/values-iw/strings.xml @@ -35,7 +35,6 @@ "קבלת הצעות לאפליקציות בשורת המועדפות של מסך הבית" "גישה נוחה לאפליקציות שנעשה בהן שימוש תכוף – ישירות ממסך הבית. ההצעות ישתנו בהתאם להרגלי השימוש שלך. אפליקציות שמופיעות בשורה התחתונה יעברו למעלה למסך הבית." "גישה נוחה לאפליקציות שהשתמשת בהן הכי הרבה, ישירות ממסך הבית. ההצעות ישתנו בהתאם להרגלי השימוש שלך. אפליקציות בשורת המועדפות יועברו למסך הבית." - "גישה נוחה לאפליקציות שנעשה בהן שימוש תכוף – ישירות ממסך הבית. ההצעות ישתנו בהתאם להרגלי השימוש שלך. אפליקציות שמופיעות בשורה התחתונה יעברו לתיקייה חדשה." "קבלת הצעות לאפליקציות" "לא, תודה" "הגדרות" diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml index 50c031ee90..013939f900 100644 --- a/quickstep/res/values-ja/strings.xml +++ b/quickstep/res/values-ja/strings.xml @@ -35,7 +35,6 @@ "ホーム画面のお気に入りの行でアプリの候補を利用できます" "ホーム画面で、使用頻度の高いアプリに簡単にアクセスできるようになります。アプリの候補はルーティンに応じて変わります。ホーム画面で今一番下の行にあるアプリは、一行上に移動します。" "ホーム画面で、使用頻度の高いアプリに簡単にアクセスできるようになります。アプリの候補はルーティンに応じて変わります。お気に入りの行にあるアプリがホーム画面に移動します。" - "ホーム画面で、使用頻度の高いアプリに簡単にアクセスできるようになります。アプリの候補はルーティンに応じて変わります。一番下の行にあるアプリが新しいフォルダに移動します。" "アプリの候補を利用" "使用しない" "設定" diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml index 2bc0108cd6..d54ac881c3 100644 --- a/quickstep/res/values-ka/strings.xml +++ b/quickstep/res/values-ka/strings.xml @@ -35,7 +35,6 @@ "მიიღეთ აპების შემოთავაზებები მთავარი ეკრანის რჩეულების მწკრივში" "მარტივად იქონიეთ ყველაზე ხშირად გამოყენებულ აპებზე წვდომა მთავარი ეკრანიდან. შეთავაზებები შეიცვლება თქვენი რუტინების მიხედვით. მოხდება ქვედა რიგში არსებული აპების მთავარ ეკრანზე გადატანა." "მარტივად იქონიეთ წვდომა ყველაზე ხშირად გამოყენებულ აპებზე მთავარი ეკრანიდან. შეთავაზებები შეიცვლება თქვენი რუტინების მიხედვით. რჩეულების მწკრივში არსებული აპები თქვენს მთავარ ეკრანზე გადავა." - "მარტივად იქონიეთ ყველაზე ხშირად გამოყენებულ აპებზე წვდომა მთავარი ეკრანიდან. შეთავაზებები შეიცვლება თქვენი რუტინების მიხედვით. მოხდება ქვედა რიგში არსებული აპების ახალ საქაღალდეში გადატანა." "აპის შეთავაზებების მიღება" "არა, გმადლობთ" "პარამეტრები" diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml index 7758125cad..bfa2a5cf37 100644 --- a/quickstep/res/values-kk/strings.xml +++ b/quickstep/res/values-kk/strings.xml @@ -35,7 +35,6 @@ "Қолданба ұсыныстары негізгі экрандағы таңдаулылар жолында көрсетілетін болады" "Жиі пайдаланылатын қолданбаларға негізгі экраннан кіруге болады. Ұсыныстар күнделікті әрекеттеріңізге сәйкес өзгереді. Төменгі қатардағы қолданбалар негізгі экранға қарай жоғары жылжиды." "Жиі пайдаланылатын қолданбаларға негізгі экраннан оңай кіре аласыз. Ұсыныстар күнделікті әрекеттеріңізге сәйкес өзгереді. Таңдаулылар жолындағы қолданбалар негізгі экранға ауысады." - "Жиі пайдаланылатын қолданбаларға негізгі экраннан кіруге болады. Ұсыныстар күнделікті әрекеттеріңізге сәйкес өзгереді. Төменгі қатардағы қолданбалар жаңа қалтаға жылжиды." "Қолданба ұсыныстарын алу" "Жоқ, рақмет" "Параметрлер" diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml index 93b23611be..4a78d9d79a 100644 --- a/quickstep/res/values-km/strings.xml +++ b/quickstep/res/values-km/strings.xml @@ -35,7 +35,6 @@ "ទទួលបាន​ការណែនាំកម្មវិធី​នៅលើ​ជួរដេកសំណព្វ​នៃអេក្រង់ដើម​របស់អ្នក" "ចូលប្រើ​កម្មវិធី​ដែលអ្នកប្រើ​ញឹកញាប់បំផុត​បានយ៉ាង​ងាយស្រួល​នៅលើ​អេក្រង់ដើម​ផ្ទាល់។ ការណែនាំ​នឹងប្រែប្រួល​ទៅតាម​ទម្លាប់​របស់អ្នក។ កម្មវិធី​នៅជួរ​ខាងក្រោម​នឹងផ្លាស់ទីឡើង​ទៅអេក្រង់ដើម​របស់អ្នក។" "ចូលប្រើ​កម្មវិធី​ដែលអ្នកប្រើ​ញឹកញាប់បំផុត​បានយ៉ាង​ងាយស្រួល​នៅលើ​អេក្រង់ដើមដោយផ្ទាល់។ ការណែនាំ​នឹងប្រែប្រួល​ទៅតាម​ទម្លាប់​របស់អ្នក។ កម្មវិធី​នៅក្នុង​ជួរដេក​សំណព្វ​នឹងផ្លាស់ទី​ទៅអេក្រង់ដើម​របស់អ្នក។" - "ចូលប្រើ​កម្មវិធី​ដែលអ្នកប្រើ​ញឹកញាប់បំផុត​បានយ៉ាង​ងាយស្រួល​នៅលើ​អេក្រង់ដើម​ផ្ទាល់។ ការណែនាំ​នឹងប្រែប្រួល​ទៅតាម​ទម្លាប់​របស់អ្នក។ កម្មវិធី​នៅជួរ​ខាងក្រោម​នឹងផ្លាស់ទី​ទៅថតថ្មី។" "ទទួលការណែនាំ​កម្មវិធី" "ទេ អរគុណ" "ការកំណត់" diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml index 2c1f6995c1..91a12f86b7 100644 --- a/quickstep/res/values-kn/strings.xml +++ b/quickstep/res/values-kn/strings.xml @@ -35,7 +35,6 @@ "ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನ ಮೆಚ್ಚಿನವುಗಳ ಸಾಲಿನಲ್ಲಿ ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಪಡೆಯಿರಿ" "ನೀವು ಹೆಚ್ಚು ಬಳಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿಯೇ ಸುಲಭವಾಗಿ ಪ್ರವೇಶಿಸಿ. ನಿಮ್ಮ ದಿನಚರಿಯನ್ನು ಆಧರಿಸಿ ಸಲಹೆಗಳು ಬದಲಾಗುತ್ತವೆ. ಕೆಳಭಾಗದ ಸಾಲಿನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳು ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ ಕಡೆಗೆ ಚಲಿಸುತ್ತವೆ." "ನೀವು ಹೆಚ್ಚು ಬಳಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿಯೇ ಸುಲಭವಾಗಿ ಪ್ರವೇಶಿಸಿ. ನಿಮ್ಮ ದಿನಚರಿಯನ್ನು ಆಧರಿಸಿ ಸಲಹೆಗಳು ಬದಲಾಗುತ್ತವೆ. ಮೆಚ್ಚಿನವುಗಳ ಸಾಲಿನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳು ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಚಲಿಸುತ್ತವೆ." - "ನೀವು ಹೆಚ್ಚು ಬಳಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿಯೇ ಸುಲಭವಾಗಿ ಪ್ರವೇಶಿಸಿ. ನಿಮ್ಮ ದಿನಚರಿಯನ್ನು ಆಧರಿಸಿ ಸಲಹೆಗಳು ಬದಲಾಗುತ್ತವೆ. ಕೆಳಭಾಗದ ಸಾಲಿನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳು ಹೊಸ ಫೋಲ್ಡರ್‌ಗೆ ಚಲಿಸುತ್ತವೆ." "ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಪಡೆಯಿರಿ" "ಬೇಡ" "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml index 6626bc1759..bfe98f9780 100644 --- a/quickstep/res/values-ko/strings.xml +++ b/quickstep/res/values-ko/strings.xml @@ -35,7 +35,6 @@ "홈 화면의 즐겨찾기 행에서 앱 제안 보기" "홈 화면에서 자주 사용하는 앱에 바로 액세스할 수 있습니다. 제안은 사용 습관에 따라 바뀌며, 하단의 앱들은 홈 화면으로 이동합니다." "홈 화면에서 가장 많이 사용한 앱에 바로 액세스할 수 있습니다. 제안은 루틴에 따라 달라집니다. 즐겨찾기 행의 앱이 홈 화면으로 이동합니다." - "홈 화면에서 자주 사용하는 앱에 바로 액세스할 수 있습니다. 제안은 사용 습관에 따라 바뀌며, 하단의 앱들은 새 폴더로 이동합니다." "앱 제안받기" "나중에" "설정" diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml index dac4d1d7cf..5472eadc8b 100644 --- a/quickstep/res/values-ky/strings.xml +++ b/quickstep/res/values-ky/strings.xml @@ -35,7 +35,6 @@ "Сунушталган колдонмолор башкы экрандагы тандалмалардын катарында көрүнөт." "Көп колдонулган колдонмолор башкы экранда жайгашып, алардын тизмеси маал-маалы менен өзгөрүп турат. Ылдый жакта жайгашкан тилкедеги колдонмолор башкы экранга жылдырылат." "Көп иштетилген колдонмолорго Башкы экрандан оңой кириңиз. Сунуштар тартиптин негизинде өзгөрөт. Тандалмалардын катарындагы колдонмолор башкы экраныңызга жылдырылат." - "Көп колдонулган колдонмолор башкы экранда жайгашып, алардын тизмеси маал-маалы менен өзгөрүп турат. Ылдый жакта жайгашкан тилкедеги колдонмолор жаңы папкага жылдырылат." "Сунушталган колдонолорду алуу" "Жок, рахмат" "Жөндөөлөр" diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml index f233bde73a..905fbda7a3 100644 --- a/quickstep/res/values-land/dimens.xml +++ b/quickstep/res/values-land/dimens.xml @@ -15,7 +15,8 @@ limitations under the License. --> - 8dp + + 12dp 126dp @@ -73,4 +74,10 @@ 218dp + + + 94.5dp + 219.6dp + 84dp + 79dp \ No newline at end of file diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml index f3c3cbda0e..7d87ec483e 100644 --- a/quickstep/res/values-lo/strings.xml +++ b/quickstep/res/values-lo/strings.xml @@ -35,7 +35,6 @@ "ຮັບການແນະນຳແອັບຢູ່ແຖວລາຍການທີ່ມັກຂອງໜ້າຈໍຫຼັກຂອງທ່ານ" "ເຂົ້າເຖິງແອັບທີ່ທ່ານໃຊ້ຫຼາຍທີ່ສຸດໄດ້ຢ່າງງ່າຍດາຍທັນທີຈາກໜ້າຈໍຫຼັກ. ການແນະນຳຈະປ່ຽນແປງຕາມການນຳໃຊ້ປະຈຳຂອງທ່ານ. ແອັບຢູ່ແຖວລຸ່ມສຸດຈະຍ້າຍຂຶ້ນໄປໃສ່ໜ້າຈໍຫຼັກຂອງທ່ານ." "ເຂົ້າເຖິງແອັບທີ່ທ່ານໃຊ້ຫຼາຍທີ່ສຸດໄດ້ຢ່າງງ່າຍດາຍທັນທີຈາກໜ້າຈໍຫຼັກ. ການແນະນຳຈະປ່ຽນແປງຕາມການນຳໃຊ້ປະຈຳຂອງທ່ານ. ຕອນນີ້ແອັບໃນລາຍການທີ່ມັກຈະຍ້າຍໄປໃສ່ໜ້າຈໍຫຼັກຂອງທ່ານ." - "ເຂົ້າເຖິງແອັບທີ່ທ່ານໃຊ້ຫຼາຍທີ່ສຸດໄດ້ຢ່າງງ່າຍດາຍທັນທີຈາກໜ້າຈໍຫຼັກ. ການແນະນຳຈະປ່ຽນແປງຕາມການນຳໃຊ້ປະຈຳຂອງທ່ານ. ແອັບຢູ່ແຖວລຸ່ມສຸດຈະຍ້າຍໄປໂຟນເດີໃໝ່." "ຮັບການແນະນຳແອັບ" "ບໍ່, ຂອບໃຈ" "ການຕັ້ງຄ່າ" diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml index 25a84a7313..b60146f35d 100644 --- a/quickstep/res/values-lt/strings.xml +++ b/quickstep/res/values-lt/strings.xml @@ -35,7 +35,6 @@ "Gaukite programų pasiūlymų pagrindinio ekrano eilutėje „Mėgstamiausios“" "Lengvai pasiekite dažniausiai naudojamas programas iškart pagrindiniame ekrane. Pasiūlymai keisis atsižvelgiant į tai, kaip jas naudojate. Apatinėje eilutėje esančios programos bus perkeltos į pagrindinį ekraną." "Lengvai pasiekite dažniausiai naudojamas programas iškart pagrindiniame ekrane. Pasiūlymai keisis atsižvelgiant į tai, kaip jas naudojate. Eilutėje „Mėgstamiausios“ rodomos programos bus perkeltos į pagrindinį ekraną." - "Lengvai pasiekite dažniausiai naudojamas programas iškart pagrindiniame ekrane. Pasiūlymai keisis atsižvelgiant į tai, kaip jas naudojate. Apatinėje eilutėje esančios programos bus perkeltos į naują aplanką." "Gauti programų pasiūlymų" "Ne, ačiū" "Nustatymai" diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml index ee25cb5008..d3c3b80cad 100644 --- a/quickstep/res/values-lv/strings.xml +++ b/quickstep/res/values-lv/strings.xml @@ -35,7 +35,6 @@ "Saņemiet lietotņu ieteikumus izlases rindā sākuma ekrānā" "Varat sākuma ekrānā ērti piekļūt savām visbiežāk izmantotajām lietotnēm. Ieteikumi mainīsies atkarībā no jūsu paradumiem. Apakšējā rindā esošās lietotnes tiks pārvietotas uz augšu — uz sākuma ekrānu." "Varat sākuma ekrānā ērti piekļūt savām visbiežāk izmantotajām lietotnēm. Ieteikumi mainīsies atkarībā no jūsu paradumiem. Lietotnes no izlases rindas tiks pārvietotas uz sākuma ekrānu." - "Varat sākuma ekrānā ērti piekļūt savām visbiežāk izmantotajām lietotnēm. Ieteikumi mainīsies atkarībā no jūsu paradumiem. Apakšējā rindā esošās lietotnes tiks pārvietotas uz jaunu mapi." "Rādīt ieteicamās lietotnes" "Nē, paldies" "Iestatījumi" diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml index 75f093316e..7ac9ad0f9e 100644 --- a/quickstep/res/values-mk/strings.xml +++ b/quickstep/res/values-mk/strings.xml @@ -35,7 +35,6 @@ "Добивајте предлози за апликации во редот со омилени на почетниот екран" "Лесно пристапувајте до најкористените апликации директно на почетниот екран. Предлозите ќе се менуваат според рутините. Апликациите од последниот ред ќе се поместуваат нагоре до почетниот екран." "Лесно пристапувајте до најкористените апликации на почетниот екран. Предлозите ќе се менуваат според рутините. Апликациите од редот со омилени ќе се преместат на почетниот екран." - "Лесно пристапувајте до најкористените апликации директно на почетниот екран. Предлозите ќе се менуваат според рутините. Апликациите од последниот ред ќе се преместуваат во нова папка." "Добивајте предлози за апликации" "Не, фала" "Поставки" diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml index 9f00e91b64..90fc5138b4 100644 --- a/quickstep/res/values-ml/strings.xml +++ b/quickstep/res/values-ml/strings.xml @@ -35,7 +35,6 @@ "നിങ്ങളുടെ ഹോം സ്‌ക്രീനിന്റെ \'പ്രിയപ്പെട്ടവ\' വരിയിൽ ആപ്പ് നിർദ്ദേശങ്ങൾ നേടുക" "നിങ്ങൾ ഏറ്റവും കൂടുതൽ ഉപയോഗിച്ച ആപ്പുകൾ ഹോം സ്ക്രീനിൽ നിന്ന് തന്നെ എളുപ്പത്തിൽ ആക്‌സസ് ചെയ്യൂ. നിങ്ങളുടെ ദിനചര്യകളുടെ അടിസ്ഥാനത്തിൽ നിർദ്ദേശങ്ങൾ മാറും. താഴത്തെ നിരയിലുള്ള ആപ്പുകൾ നിങ്ങളുടെ ഹോം സ്‌ക്രീനിലേക്ക് നീങ്ങും." "നിങ്ങൾ ഏറ്റവും കൂടുതൽ ഉപയോഗിച്ച ആപ്പുകൾ ഹോം സ്ക്രീനിൽ നിന്ന് തന്നെ എളുപ്പത്തിൽ ആക്‌സസ് ചെയ്യൂ. നിങ്ങളുടെ ദിനചര്യകളുടെ അടിസ്ഥാനത്തിൽ നിർദ്ദേശങ്ങൾ മാറും. \'പ്രിയപ്പെട്ടവ\' വരിയിലുള്ള ആപ്പുകൾ നിങ്ങളുടെ ഹോം സ്‌ക്രീനിലേക്ക് നീങ്ങും." - "നിങ്ങൾ ഏറ്റവും കൂടുതൽ ഉപയോഗിച്ച ആപ്പുകൾ ഹോം സ്ക്രീനിൽ നിന്ന് തന്നെ എളുപ്പത്തിൽ ആക്‌സസ് ചെയ്യൂ. നിങ്ങളുടെ ദിനചര്യകളുടെ അടിസ്ഥാനത്തിൽ നിർദ്ദേശങ്ങൾ മാറും. താഴത്തെ നിരയിലുള്ള ആപ്പുകൾ പുതിയൊരു ഫോൾഡറിലേക്ക് നീങ്ങും." "ആപ്പ് നിർദ്ദേശങ്ങൾ നേടുക" "വേണ്ട" "ക്രമീകരണം" diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml index 59728a6136..c435528b66 100644 --- a/quickstep/res/values-mn/strings.xml +++ b/quickstep/res/values-mn/strings.xml @@ -35,7 +35,6 @@ "Үндсэн нүүрний дуртай мөрнөөсөө санал болгож буй аппуудыг аваарай" "Хамгийн их ашигладаг аппууддаа Үндсэн нүүрнээс хялбархан хандаарай. Санал болгож буй аппуудыг таны хэвшлээс хамаарч өөрчилнө. Доод мөрд буй аппуудыг таны Үндсэн нүүр лүү дээш зөөнө." "Хамгийн их ашигладаг аппууддаа Үндсэн нүүрнээсээ хялбархан хандаарай. Санал болголтыг таны хэвшлээс хамааран өөрчилнө. Дуртай мөрөнд буй аппуудыг таны үндсэн нүүр лүү зөөнө." - "Хамгийн их ашигладаг аппууддаа Үндсэн нүүрнээс хялбархан хандаарай. Санал болгож буй аппуудыг таны хэвшлээс хамаарч өөрчилнө. Доод мөрөнд буй аппуудыг шинэ фолдер луу зөөнө." "Санал болгож буй аппуудыг авах" "Үгүй, баярлалаа" "Тохиргоо" diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml index 5f2fb0c5d2..fc9027133b 100644 --- a/quickstep/res/values-mr/strings.xml +++ b/quickstep/res/values-mr/strings.xml @@ -35,7 +35,6 @@ "तुमच्या होम स्क्रीनच्या पसंतीच्या पंक्तीवर अ‍ॅप सूचना मिळवा" "तुमची सर्वाधिक वापरली जाणारी अ‍ॅप्स होम स्क्रीनवरच सहजपणे अ‍ॅक्सेस करा. तुमच्या दिनक्रमानुसार तुम्हाला मिळणाऱ्या सूचना बदलतील. तळाशी असणारी अ‍ॅप्स तुमच्या होम स्क्रीनवर जातील." "तुमची सर्वाधिक वापरली जाणारी अ‍ॅप्स होम स्क्रीनवर सहजपणे अ‍ॅक्सेस करा. सूचना तुमच्या दिनक्रमांनुसार बदलतील. पसंतीच्या पंक्तीमधील अ‍ॅप्स तुमच्या होम स्क्रीनवर हलवली जातील." - "तुमची सर्वाधिक वापरली जाणारी अ‍ॅप्स होम स्क्रीनवरच सहजपणे अ‍ॅक्सेस करा. सूचना तुमच्या दिनक्रमांच्या आधारावर बदलतील. तळाच्या पंक्तीवरील अ‍ॅप्स नवीन फोल्डरवर जातील." "अ‍ॅप सूचना मिळवा" "नाही, नको" "सेटिंग्ज" diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml index 27dcc41cb6..5d3f4a8896 100644 --- a/quickstep/res/values-ms/strings.xml +++ b/quickstep/res/values-ms/strings.xml @@ -35,7 +35,6 @@ "Dapatkan cadangan apl di baris kegemaran Skrin utama anda" "Akses apl yang paling kerap anda gunakan dengan mudah pada Skrin utama. Cadangan akan berubah berdasarkan rutin anda. Apl di baris bawah akan beralih ke atas, ke Skrin utama anda." "Akses apl yang paling kerap anda gunakan dengan mudah pada Skrin utama. Cadangan akan berubah berdasarkan rutin anda. Apl di baris kegemaran akan beralih ke Skrin utama anda." - "Akses apl yang paling kerap anda gunakan dengan mudah, terus pada Skrin utama. Cadangan akan berubah berdasarkan rutin anda. Apl di baris bawah akan beralih ke folder baharu." "Dapatkan cadangan apl" "Tidak perlu" "Tetapan" diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml index 825b6f23e9..8f6af710ee 100644 --- a/quickstep/res/values-my/strings.xml +++ b/quickstep/res/values-my/strings.xml @@ -35,7 +35,6 @@ "သင်၏ \'ပင်မစာမျက်နှာ\' ၏ အနှစ်သက်ဆုံးများအတန်းတွင် အက်ပ်အကြံပြုချက်များ ရယူခြင်း" "အသုံးအများဆုံးအက်ပ်များကို \'ပင်မစာမျက်နှာ\' တွင် အလွယ်တကူ ဖွင့်နိုင်သည်။ သင်၏ ပုံမှန်လုပ်ဆောင်ချက်များပေါ် အခြေခံ၍ အကြံပြုချက်များ ပြောင်းလဲပါမည်။ အောက်ခြေအတန်းရှိ အက်ပ်များကို သင်၏ \'ပင်မစာမျက်နှာ\' သို့ရွှေ့လိုက်မည်။" "အသုံးအများဆုံးအက်ပ်များကို \'ပင်မစာမျက်နှာ\' တွင် အလွယ်တကူ သုံးနိုင်သည်။ သင်၏ ပုံမှန်အစီအစဉ်များပေါ် အခြေခံ၍ အကြံပြုချက်များ ပြောင်းလဲပါမည်။ အနှစ်သက်ဆုံးများအတန်းရှိ အက်ပ်များကို သင်၏ \'ပင်မစာမျက်နှာ\' သို့ရွှေ့လိုက်မည်။" - "အသုံးအများဆုံးအက်ပ်များကို ပင်မစာမျက်နှာတွင် အလွယ်တကူ သုံးနိုင်သည်။ သင်၏ ပုံမှန်အစီအစဉ်များပေါ် အခြေခံ၍ အကြံပြုချက်များ ပြောင်းလဲပါမည်။ အောက်ခြေအတန်းရှိ အက်ပ်များကို ဖိုင်တွဲအသစ်သို့ ရွှေ့လိုက်မည်။" "အက်ပ်အကြံပြုချက်များ ရယူရန်" "မလိုပါ" "ဆက်တင်များ" diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml index 250724e1af..eff2e3ca92 100644 --- a/quickstep/res/values-nb/strings.xml +++ b/quickstep/res/values-nb/strings.xml @@ -35,7 +35,6 @@ "Få appforslag i favoritter-raden på startskjermen" "Få enkel tilgang til appene du bruker oftest, rett fra startskjermen. Forslagene endres basert på rutinene dine. Appene i den nederste raden flyttes opp til startskjermen." "Få enkel tilgang til appene du bruker oftest, rett fra startskjermen. Forslagene endres basert på rutinene dine. Apper i favoritter-raden blir flyttet til startskjermen." - "Få enkel tilgang til appene du bruker oftest, rett fra startskjermen. Forslagene endres basert på rutinene dine. Appene i den nederste raden flyttes til en ny mappe." "Få appforslag" "Nei takk" "Innstillinger" diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml index f7214b02d0..3a23567b74 100644 --- a/quickstep/res/values-ne/strings.xml +++ b/quickstep/res/values-ne/strings.xml @@ -35,7 +35,6 @@ "आफ्नो होम स्क्रिनको मन पर्ने नामक पङ्क्तिमा एपसम्बन्धी सिफारिस प्राप्त गर्नुहोस्" "आफूले सबैभन्दा बढी प्रयोग गर्ने एप होम स्क्रिनबाट सजिलै चलाउनुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिने छ। फेदको रोमा रहेका एपहरू तपाईंको होम स्क्रिनको सिरानमा सर्ने छन्।" "आफूले सबैभन्दा बढी प्रयोग गर्ने एपहरू गृह स्क्रिनबाटै सजिलैसँग खोल्नुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंको दिनचर्याअनुसार बदलिने छ। मन पर्ने नामक पङ्क्तिमा रहेका एपहरू सारेर होम स्क्रिनमा लगिने छन्।" - "गृह स्क्रिनबाटै आफूले सबैभन्दा बढी प्रयोग गर्ने एप सजिलै चलाउनुहोस्। सिफारिस गरिने एपहरूको क्रम तपाईंले एप प्रयोग गर्ने समयतालिकाअनुसार बदलिने छ। फेदको पङ्क्तिमा रहेका एपहरू एउटा नयाँ फोल्डरमा सर्ने छन्।" "एपसम्बन्धी सिफारिस प्राप्त गर्नुहोस्" "पर्दैन, धन्यवाद" "सेटिङ" diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml index ff5e45ebd6..650e3403d0 100644 --- a/quickstep/res/values-nl/strings.xml +++ b/quickstep/res/values-nl/strings.xml @@ -35,7 +35,6 @@ "App-suggesties krijgen op de rij met favorieten op het startscherm" "Open je meestgebruikte apps makkelijk vanaf het startscherm. De suggesties veranderen op basis van je routines. Apps in de onderste rij worden naar je startscherm verplaatst." "Open je meestgebruikte apps makkelijk vanaf het startscherm. De suggesties veranderen op basis van je routines. Apps in de rij met favorieten worden naar het startscherm verplaatst." - "Open je meestgebruikte apps makkelijk vanaf het startscherm. De suggesties veranderen op basis van je routines. Apps in de onderste rij worden naar een nieuwe map verplaatst." "App-suggesties krijgen" "Nee, bedankt" "Instellingen" diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml index d7246c4a11..5016399d95 100644 --- a/quickstep/res/values-or/strings.xml +++ b/quickstep/res/values-or/strings.xml @@ -35,7 +35,6 @@ "ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନର ପସନ୍ଦର ଧାଡ଼ିରେ ଆପ ପରାମର୍ଶଗୁଡ଼ିକ ପାଆନ୍ତୁ" "ଆପଣଙ୍କର ସବୁଠାରୁ ଅଧିକ-ବ୍ୟବହୃତ ଆପ୍ସକୁ ସିଧା ହୋମ ସ୍କ୍ରିନରେ ସହଜରେ ଆକ୍ସେସ କରନ୍ତୁ। ଆପଣଙ୍କ ରୁଟିନଗୁଡ଼ିକ ଆଧାରରେ ପରାମର୍ଶଗୁଡ଼ିକ ପରିବର୍ତ୍ତିତ ହେବ। ତଳ ଧାଡ଼ିରେ ଥିବା ଆପ୍ସ ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନକୁ ମୁଭ ହୋଇଯିବ।" "ହୋମ ସ୍କ୍ରିନରେ ହିଁ ଆପଣଙ୍କର ସବୁଠାରୁ ଅଧିକ-ବ୍ୟବହୃତ ଆପ୍ସକୁ ସହଜରେ ଆକ୍ସେସ କରନ୍ତୁ। ଆପଣଙ୍କ ରୁଟିନଗୁଡ଼ିକ ଆଧାରରେ ପରାମର୍ଶଗୁଡ଼ିକ ବଦଳିବ। ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନକୁ ପସନ୍ଦର ଧାଡ଼ିରେ ଥିବା ଆପ୍ସ ମୁଭ ହୋଇଯିବ।" - "ଆପଣଙ୍କର ସବୁଠାରୁ ଅଧିକ-ବ୍ୟବହୃତ ଆପ୍ସକୁ, ସିଧା ହୋମ ସ୍କ୍ରିନରେ ସହଜରେ ଆକ୍ସେସ କରନ୍ତୁ। ଆପଣଙ୍କ ରୁଟିନଗୁଡ଼ିକ ଆଧାରରେ ପରାମର୍ଶଗୁଡ଼ିକ ପରିବର୍ତ୍ତିତ ହେବ। ତଳ ଧାଡ଼ିରେ ଥିବା ଆପ୍ସ ଏକ ନୂଆ ଫୋଲ୍ଡରକୁ ମୁଭ ହୋଇଯିବ।" "ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକ ପାଆନ୍ତୁ" "ନାହିଁ, ଥାଉ" "ସେଟିଂସ" diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml index 83def858df..7d1d2b5f66 100644 --- a/quickstep/res/values-pa/strings.xml +++ b/quickstep/res/values-pa/strings.xml @@ -35,7 +35,6 @@ "ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ ਦੀ ਮਨਪਸੰਦ ਕਤਾਰ \'ਤੇ ਐਪ ਸੁਝਾਅ ਹਾਸਲ ਕਰੋ" "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਆਪਣੀਆਂ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀਆਂ ਗਈਆਂ ਐਪਾਂ ਤੱਕ ਆਸਾਨੀ ਨਾਲ ਪਹੁੰਚ ਕਰੋ। ਸੁਝਾਅ ਤੁਹਾਡੇ ਨਿਯਮਬੱਧ ਕੰਮਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਬਦਲਣਗੇ। ਹੇਠਲੀ ਕਤਾਰ ਵਾਲੀਆਂ ਐਪਾਂ ਤੁਹਾਡੀ ਹੋਮ ਸਕ੍ਰੀਨ ਵੱਲ ਉੱਪਰ ਆ ਜਾਣਗੀਆਂ।" "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਆਪਣੀਆਂ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀਆਂ ਗਈਆਂ ਐਪਾਂ ਤੱਕ ਆਸਾਨੀ ਨਾਲ ਪਹੁੰਚ ਕਰੋ। ਸੁਝਾਅ ਤੁਹਾਡੇ ਨਿਯਮਬੱਧ ਕੰਮਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਬਦਲਣਗੇ। ਮਨਪਸੰਦ ਕਤਾਰ ਵਿਚਲੀਆਂ ਐਪਾਂ ਤੁਹਾਡੀ ਹੋਮ ਸਕ੍ਰੀਨ ਉੱਪਰ ਆ ਜਾਣਗੀਆਂ।" - "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਆਪਣੀਆਂ ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀਆਂ ਗਈਆਂ ਐਪਾਂ ਤੱਕ ਆਸਾਨੀ ਨਾਲ ਪਹੁੰਚ ਕਰੋ। ਸੁਝਾਅ ਤੁਹਾਡੇ ਨਿਯਮਬੱਧ ਕੰਮਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਬਦਲਣਗੇ। ਹੇਠਲੀ ਕਤਾਰ ਵਾਲੀਆਂ ਐਪਾਂ ਇੱਕ ਨਵੇਂ ਫੋਲਡਰ ਵਿੱਚ ਚਲੀਆਂ ਜਾਣਗੀਆਂ।" "ਐਪ ਸੁਝਾਅ ਪ੍ਰਾਪਤ ਕਰੋ" "ਨਹੀਂ ਧੰਨਵਾਦ" "ਸੈਟਿੰਗਾਂ" diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml index 077982a30b..3c73de487f 100644 --- a/quickstep/res/values-pl/strings.xml +++ b/quickstep/res/values-pl/strings.xml @@ -35,7 +35,6 @@ "Otrzymuj sugestie aplikacji w wierszu ulubionych na ekranie głównym" "Łatwo uruchamiaj najczęściej używane aplikacje z ekranu głównego. Sugestie będą zmieniać się w zależności od Twoich nawyków. Aplikacje z dolnych wierszy będą przesuwane w górę, do ekranu głównego." "Zyskaj łatwy dostęp do najczęściej używanych aplikacji na ekranie głównym. Sugestie będą się zmieniać na podstawie Twojej rutyny. Aplikacje z wiersza ulubionych zostaną przeniesione na ekran główny." - "Korzystaj z najczęściej używanych aplikacji na ekranie głównym w łatwy sposób. Sugestie będą się zmieniać na podstawie Twojej rutyny. Aplikacje z dolnych wierszy będą się przenosić do nowego folderu." "Otrzymuj sugestie aplikacji" "Nie, dziękuję" "Ustawienia" diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml index 124b33b758..5e01dc187f 100644 --- a/quickstep/res/values-pt-rPT/strings.xml +++ b/quickstep/res/values-pt-rPT/strings.xml @@ -35,7 +35,6 @@ "Receba sugestões de apps na fila dos favoritos do ecrã principal" "Aceda facilmente às suas apps mais utilizadas, diretamente no ecrã principal. As sugestões mudam em função das suas rotinas. As apps na última fila passam para o ecrã principal." "Aceda facilmente às suas apps mais utilizadas no ecrã principal. As sugestões mudam em função das suas rotinas. As apps na fila dos favoritos passam para o ecrã principal." - "Aceda facilmente às suas apps mais utilizadas, diretamente no ecrã principal. As sugestões mudam em função das suas rotinas. As apps na última fila passam para uma nova pasta." "Obter sugestões de apps" "Não, obrigado" "Definições" diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml index 199c2f45ff..fad2170e85 100644 --- a/quickstep/res/values-pt/strings.xml +++ b/quickstep/res/values-pt/strings.xml @@ -35,7 +35,6 @@ "Receba sugestões de apps na linha \"Favoritos\" da tela inicial" "Acesse diretamente na tela inicial os apps que você mais usa. As sugestões mudam de acordo com sua rotina, e os apps na linha inferior são movidos para a tela inicial." "Acesse os apps mais usados na tela inicial de forma simples. As sugestões mudam de acordo com sua rotina, e os apps na linha \"Favoritos\" são movidos para a tela inicial." - "Acesse diretamente na tela inicial os apps que você mais usa. As sugestões mudam de acordo com sua rotina, e os apps na linha inferior são movidos para uma nova pasta." "Receber sugestões de apps" "Não" "Configurações" diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml index ba4badf090..8b8c187033 100644 --- a/quickstep/res/values-ro/strings.xml +++ b/quickstep/res/values-ro/strings.xml @@ -35,7 +35,6 @@ "Primește sugestii de aplicații în rândul de preferințe al ecranului de pornire" "Accesează cu ușurință cele mai folosite aplicații direct din ecranul de pornire. Sugestiile se vor modifica în funcție de rutine. Aplicațiile din rândul de jos se vor muta în sus pe ecranul de pornire." "Accesează cu ușurință cele mai folosite aplicații direct din ecranul de pornire. Sugestiile se vor schimba în funcție de rutina ta. Aplicațiile din rândul de preferințe se vor muta în ecranul de pornire." - "Accesează cu ușurință cele mai folosite aplicații, direct din ecranul de pornire. Sugestiile se vor modifica în funcție de rutine. Aplicațiile din rândul de jos se vor muta într-un dosar nou." "Primește sugestii de aplicații" "Nu, mulțumesc" "Setări" diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml index 44b72cf7d2..e078ba0aad 100644 --- a/quickstep/res/values-ru/strings.xml +++ b/quickstep/res/values-ru/strings.xml @@ -35,7 +35,6 @@ "Рекомендуемые приложения будут появляться в разделе избранных на главном экране" "Приложения, которыми вы часто пользуетесь, будут доступны прямо на главном экране. Их список может меняться с учетом ваших предпочтений. Приложения из нижнего ряда будут перемещены выше на главном экране." "Включите функцию для быстрого доступа к часто используемым приложениям на главном экране. Список меняется с учетом ваших действий. Приложения из раздела избранных будут перемещены на главный экран." - "Приложения, которыми вы часто пользуетесь, будут доступны прямо на главном экране. Их список может меняться с учетом ваших предпочтений. Приложения из нижнего ряда будут перемещены в новую папку." "Показывать рекомендации" "Отмена" "Настройки" diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml index 31749ccf04..f890548f58 100644 --- a/quickstep/res/values-si/strings.xml +++ b/quickstep/res/values-si/strings.xml @@ -35,7 +35,6 @@ "ඔබේ මුල් තිරයේ ප්‍රියතම පේළියේ යෙදුම් යෝජනා ලබා ගන්න" "ඔබගේ වැඩිපුරම භාවිත කරන යෙදුම්වලට මුල් තිරයේ සිටම පහසුවෙන් ප්‍රවේශ වන්න. ඔබගේ දිනචරියා මත පදනම්ව යෝජනා වෙනස් වනු ඇත. පහළ පේළියේ යෙදුම් ඔබගේ මුල් තිරය දක්වා ගෙන යනු ඇත." "ඔබගේ වැඩිපුරම භාවිත කරන යෙදුම්වලට මුල් තිරයේ සිටම පහසුවෙන් ප්‍රවේශ වන්න. ඔබගේ දිනචරියා මත පදනම්ව යෝජනා වෙනස් වනු ඇත. ප්‍රියතම තුළ යෙදුම් ඔබේ මුල් තිරය වෙත ගෙන යනු ඇත." - "ඔබගේ වැඩිපුරම භාවිත කරන යෙදුම්වලට මුල් තිරයේ සිටම පහසුවෙන් ප්‍රවේශ වන්න. ඔබගේ දිනචරියා මත පදනම්ව යෝජනා වෙනස් වනු ඇත. පහළ පේළියේ යෙදුම් නව ෆෝල්ඩරයකට ගෙන යනු ඇත." "යෙදුම් යෝජනා ලබා ගන්න" "එපා ස්තුතියි" "සැකසීම්" diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml index 5d51d45424..cdf33391ee 100644 --- a/quickstep/res/values-sk/strings.xml +++ b/quickstep/res/values-sk/strings.xml @@ -35,7 +35,6 @@ "Nechajte si na ploche na riadku obľúbených zobrazovať návrhy aplikácií" "Získajte jednoduchý prístup k najpoužívanejším aplikáciám priamo na ploche. Návrhy sa budú meniť podľa vašich zvyklostí. Aplikácie v spodnom riadku sa presunú nahor na plochu." "Získajte jednoduchý prístup k najpoužívanejším aplikáciám priamo na ploche. Návrhy sa budú meniť podľa vašich postupov. Aplikácie v riadku s obľúbenými sa presunú na plochu." - "Získajte jednoduchý prístup k najpoužívanejším aplikáciám priamo na ploche. Návrhy sa budú meniť podľa vašich zvyklostí. Aplikácie v spodnom riadku sa presunú do nového priečinka." "Zobrazovať návrhy aplikácií" "Nie, ďakujem" "Nastavenia" diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml index e196e5df09..3e7c5ce681 100644 --- a/quickstep/res/values-sl/strings.xml +++ b/quickstep/res/values-sl/strings.xml @@ -35,7 +35,6 @@ "Prejemajte predloge aplikacij v vrstici s priljubljenimi na začetnem zaslonu" "Preprosto dostopajte do najpogosteje uporabljenih aplikacij kar na začetnem zaslonu. Predlogi se spreminjajo na podlagi dejanj, ki jih pogosto izvajate. Aplikacije iz spodnje vrstice se premaknejo na začetni zaslon." "Preprosto dostopajte do najpogosteje uporabljenih aplikacij kar na začetnem zaslonu. Predlogi se spreminjajo na podlagi dejanj, ki jih pogosto izvajate. Aplikacije v vrstici s priljubljenimi bodo premaknjene na začetni zaslon." - "Preprosto dostopajte do najpogosteje uporabljenih aplikacij kar na začetnem zaslonu. Predlogi se spreminjajo na podlagi dejanj, ki jih pogosto izvajate. Aplikacije iz spodnje vrstice se premaknejo v novo mapo." "Prikaži predlagane aplikacije" "Ne, hvala" "Nastavitve" diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml index 39771cb616..04b7bc152f 100644 --- a/quickstep/res/values-sq/strings.xml +++ b/quickstep/res/values-sq/strings.xml @@ -35,7 +35,6 @@ "Merr aplikacione të sugjeruara në rreshtin e të preferuarave të ekranit tënd bazë" "Qasu me lehtësi në aplikacionet më të përdorura direkt në ekranin bazë. Sugjerimet do të ndryshojnë bazuar në rutinat e tua. Aplikacionet në rreshtin e poshtëm do të zhvendosen lart në ekranin tënd bazë." "Qasu me lehtësi në aplikacionet më të përdorura direkt në ekranin bazë. Sugjerimet do të ndryshojnë bazuar në rutinat e tua. Aplikacionet në rreshtin e të preferuarave do të zhvendosen në ekranin tënd bazë." - "Qasu me lehtësi në aplikacionet më të përdorura, direkt në ekranin bazë. Sugjerimet do të ndryshojnë bazuar në rutinat e tua. Aplikacionet në rreshtin e poshtëm do të zhvendosen në një dosje tjetër." "Merr aplikacione të sugjeruara" "Jo, faleminderit" "Cilësimet" diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml index 067a7fe325..215fde9100 100644 --- a/quickstep/res/values-sr/strings.xml +++ b/quickstep/res/values-sr/strings.xml @@ -35,7 +35,6 @@ "Добијајте предлоге апликација у реду са омиљеним ставкама на почетном екрану" "Лако приступајте апликацијама које најчешће користите директно са почетног екрана. Предлози се мењају на основу употребе. Апликације из доњег реда се премештају нагоре на почетни екран." "Лако приступајте апликацијама које најчешће користите директно са почетног екрана. Предлози се мењају на основу ваших рутина. Апликације из реда са омиљеним ставкама се премештају на почетни екран." - "Лако приступајте апликацијама које најчешће користите директно са почетног екрана. Предлози се мењају на основу употребе. Апликације из доњег реда се премештају у нов фолдер." "Приказуј предлоге апликација" "Не, хвала" "Подешавања" diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml index 3bb47c3caf..e7c0226337 100644 --- a/quickstep/res/values-sv/strings.xml +++ b/quickstep/res/values-sv/strings.xml @@ -35,7 +35,6 @@ "Få appförslag på raden Favoriter på startskärmen" "Kom enkelt åt de appar du använder mest, direkt från startskärmen. Förslagen ändras efter dina rutiner. Appar på nedersta raden flyttas upp till startskärmen." "Kom enkelt åt de appar du använder mest, direkt från startskärmen. Förslagen ändras efter dina rutiner. Appar på raden Favoriter flyttas till startskärmen." - "Kom enkelt åt de appar du använder mest, direkt från startskärmen. Förslagen ändras efter dina rutiner. Appar på nedersta raden flyttas till en ny mapp." "Få appförslag" "Nej tack" "Inställningar" diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml index 16891a8ceb..b1a178e9b3 100644 --- a/quickstep/res/values-sw/strings.xml +++ b/quickstep/res/values-sw/strings.xml @@ -35,7 +35,6 @@ "Pata mapendekezo ya programu katika safu ya vipendwa ya Skrini yako ya kwanza" "Fikia kwa urahisi programu unazotumia sana moja kwa moja kwenye Skrini ya kwanza. Mapendekezo yatabadilika kulingana na ratiba zako. Programu zilizo kwenye sehemu ya chini zitahamishiwa kwenye Skrini yako ya kwanza." "Fikia kwa urahisi programu unazotumia sana moja kwa moja kwenye Skrini ya kwanza. Mapendekezo yatabadilika kulingana na utumiaji wako. Programu zilizo katika safu ya vipendwa zitahamishiwa kwenye Skrini yako ya kwanza." - "Fikia kwa urahisi programu unazotumia zaidi, moja kwa moja kwenye Skrini ya kwanza. Mapendekezo yatabadilika kulingana na ratiba zako. Programu zilizo kwenye safu ya chini zitahamishiwa kwenye folda mpya." "Pata mapendekezo ya programu" "Hapana" "Mipangilio" diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml index 7494683880..cfbbf8dbf3 100644 --- a/quickstep/res/values-sw600dp/dimens.xml +++ b/quickstep/res/values-sw600dp/dimens.xml @@ -14,16 +14,23 @@ * limitations under the License. */ --> + 25dp - + + 48dp + 44dp + 12dp + 44dp - 12dp + 28dp + 36dp + 64dp diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml index ceaa8f866b..284ce11582 100644 --- a/quickstep/res/values-sw720dp/dimens.xml +++ b/quickstep/res/values-sw720dp/dimens.xml @@ -14,14 +14,23 @@ * limitations under the License. */ --> + - + + + 0.7 + 48dp + 44dp + 16dp + 44dp - 16dp + 36dp + 44dp + 64dp diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml index 5c73734a14..431ae3cce5 100644 --- a/quickstep/res/values-ta/strings.xml +++ b/quickstep/res/values-ta/strings.xml @@ -35,7 +35,6 @@ "உங்கள் முகப்புத் திரையின் \'பிடித்தவை\' வரிசையில் ஆப்ஸ் பரிந்துரைகளைப் பெறலாம்" "அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். கடைசி வரிசையிலுள்ள ஆப்ஸ் உங்கள் முகப்புத் திரைக்கு நகர்த்தப்படும்." "அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே எளிதாக அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். பிடித்தவை வரிசையில் உள்ள ஆப்ஸ் உங்கள் முகப்புத் திரைக்கு நகர்த்தப்படும்." - "அதிகமாகப் பயன்படுத்திய ஆப்ஸை முகப்புத் திரையிலேயே அணுகலாம். உங்கள் வழக்கங்களின் அடிப்படையில் பரிந்துரைகள் மாறும். கடைசி வரிசையிலுள்ள ஆப்ஸ் புதிய ஃபோல்டருக்கு நகர்த்தப்படும்." "ஆப்ஸ் பரிந்துரைகளைப் பெறுக" "வேண்டாம்" "அமைப்புகள்" diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml index 332e95dfe8..7b6dedf757 100644 --- a/quickstep/res/values-te/strings.xml +++ b/quickstep/res/values-te/strings.xml @@ -35,7 +35,6 @@ "మీ హోమ్ స్క్రీన్‌లోని ఇష్టమైన వాటి వరుసలో యాప్ సూచ‌న‌లు పొందండి" "మీరు ఎక్కువగా ఉపయోగించే యాప్‌లను నేరుగా మొదటి స్క్రీన్‌లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్‌లు మీ మొదటి స్క్రీన్ పైకి చేరుకుంటాయి." "మీరు ఎక్కువగా ఉపయోగించే యాప్‌లను నేరుగా మొదటి స్క్రీన్‌లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్‌లు మీ మొదటి స్క్రీన్‌కు చేరుకుంటాయి." - "మీరు ఎక్కువగా ఉపయోగించే యాప్‌లను నేరుగా మొదటి స్క్రీన్‌లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్‌లు కొత్త ఫోల్డర్‌కు తరలించబడతాయి." "యాప్ సూచ‌న‌లను పొందండి" "వద్దు" "సెట్టింగ్‌లు" diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml index 59a84ff286..4d5794b8a7 100644 --- a/quickstep/res/values-th/strings.xml +++ b/quickstep/res/values-th/strings.xml @@ -35,7 +35,6 @@ "รับคำแนะนำเกี่ยวกับแอปในแถวรายการโปรดของหน้าจอหลัก" "เข้าถึงแอปที่คุณใช้มากที่สุดได้อย่างง่ายดายจากหน้าจอหลัก การแนะนำจะเปลี่ยนไปตามแอปที่ใช้งานเป็นประจำ แอปในแถวล่างจะย้ายขึ้นมาอยู่ในหน้าจอหลัก" "เข้าถึงแอปที่คุณใช้มากที่สุดได้อย่างง่ายดายจากหน้าจอหลัก การแนะนำจะเปลี่ยนไปตามแอปที่ใช้งานเป็นประจำ แอปในแถวรายการโปรดจะย้ายไปอยู่ในหน้าจอหลัก" - "เข้าถึงแอปที่คุณใช้มากที่สุดได้อย่างง่ายดายจากหน้าจอหลัก การแนะนำจะเปลี่ยนไปตามแอปที่ใช้งานเป็นประจำ แอปในแถวล่างจะย้ายไปอยู่ในโฟลเดอร์ใหม่" "ดูแอปแนะนำ" "ไม่เป็นไร" "การตั้งค่า" diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml index b22d85d9af..6699ed876d 100644 --- a/quickstep/res/values-tl/strings.xml +++ b/quickstep/res/values-tl/strings.xml @@ -35,7 +35,6 @@ "Makakuha ng mga iminumungkahing app sa row ng mga paborito ng iyong Home screen" "Madaling ma-access ang mga pinakaginagamit mong app nang direkta sa Home screen. Magbabago ang mga suhestyon batay sa iyong mga routine. Mapupunta sa iyong Home screen ang mga app na nasa ibabang row." "Madaling ma-access ang mga pinakaginagamit mong app nang direkta sa Home screen. Magbabago ang mga suhestyon batay sa iyong mga routine. Mapupunta sa iyong Home screen ang mga app sa row ng mga paborito." - "Madaling ma-access ang mga pinakaginagamit mong app, direkta sa Home screen. Magbabago ang mga suhestyon batay sa iyong mga routine. Mapupunta sa isang bagong folder ang mga app na nasa ibabang row." "Kumuha ng mga suhestiyon sa app" "Huwag na lang" "Mga Setting" diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml index b3054f7a69..24e25ff5bb 100644 --- a/quickstep/res/values-tr/strings.xml +++ b/quickstep/res/values-tr/strings.xml @@ -35,7 +35,6 @@ "Ana ekranınızın favoriler satırında uygulama önerileri alın" "En çok kullanılan uygulamalarınıza Ana ekranda kolayca erişin. Öneriler, rutinlerinize dayalı olarak değişir. Alt satırdaki uygulamalar, yukarı taşınarak Ana ekranınıza alınır." "En çok kullanılan uygulamalarınıza Ana ekrandan kolayca erişin. Öneriler rutinlerinize dayalı olarak değişir. Favoriler satırındaki uygulamalar Ana ekranınıza taşınır." - "En çok kullanılan uygulamalarınıza Ana ekranda kolayca erişin. Öneriler, rutinlerinize dayalı olarak değişir. Alt satırdaki uygulamalar yeni bir klasöre taşınır." "Uygulama önerileri al" "Hayır, teşekkürler" "Ayarlar" diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml index 490ac2da1a..8dd65e2cf0 100644 --- a/quickstep/res/values-uk/strings.xml +++ b/quickstep/res/values-uk/strings.xml @@ -35,7 +35,6 @@ "Рекомендовані додатки з\'являтимуться в рядку \"Вибране\" на головному екрані" "З легкістю відкривайте на головному екрані ті додатки, які використовуєте найчастіше. Рекомендації змінюватимуться залежно від ваших дій. Додатки в нижньому рядку перемістяться на головний екран." "З легкістю відкривайте найпотрібніші додатки просто з головного екрана. Рекомендації змінюватимуться залежно від ваших дій. Додатки з рядка \"Вибране\" буде переміщено на головний екран." - "З легкістю відкривайте найвикористовуваніші додатки просто з головного екрана. Рекомендації змінюватимуться залежно від ваших дій. Додатки в нижньому рядку буде переміщено в нову папку." "Показувати рекомендації" "Не потрібно" "Налаштування" diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml index 8f2da6560d..2d9a1e30f2 100644 --- a/quickstep/res/values-ur/strings.xml +++ b/quickstep/res/values-ur/strings.xml @@ -35,7 +35,6 @@ "اپنی ہوم اسکرین کی پسندیدہ قطار پر ایپ کی تجاویز حاصل کریں" "ہوم اسکرین پر آسانی سے اپنی سب سے زیادہ مستعمل ایپس تک رسائی حاصل کریں۔ آپ کی روٹینز کی بنیاد پر تجاویز تبدیل ہوں گی۔ نچلی قطار میں موجود ایپس آپ کی ہوم اسکرین کے اوپر منتقل ہوں گی۔" "ہوم اسکرین پر آسانی سے اپنی سب سے زیادہ مستعمل ایپس تک رسائی حاصل کریں۔ آپ کی روٹینز کی بنیاد پر تجاویز تبدیل ہوں گی۔ پسندیدہ میں موجود ایپس آپ کی ہوم اسکرین کے اوپر منتقل ہوں گی۔" - "ہوم اسکرین پر، آسانی سے اپنی سب سے زیادہ مستعمل ایپس تک رسائی حاصل کریں۔ آپ کی روٹینز کی بنیاد پر تجاویز تبدیل ہوں گی۔ نچلی قطار میں موجود ایپس نئے فولڈر میں منتقل ہوں گی۔" "ایپس کی تجاویز حاصل کریں" "نہیں شکریہ" "ترتیبات" diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml index 9c47cef299..9446325829 100644 --- a/quickstep/res/values-uz/strings.xml +++ b/quickstep/res/values-uz/strings.xml @@ -35,7 +35,6 @@ "Tavsiya etiladigan ilovalar bosh ekranning saralanganlar ruknida chiqadi" "Faol ishlatiladigan ilovalarga bosh ekrandan osongina kira olasiz. Tavsiyalar oxirgi faoliyatingiz asosida almashib boradi. Pastki qatordagi ilovalar bosh ekranga chiqadi." "Faol ishlatiladigan ilovalarga bosh ekrandan osongina kira olasiz. Tavsiyalar oxirgi faoliyatingiz asosida almashib boradi. Saralanganlar qatoridagi ilovalar bosh ekranga chiqadi." - "Faol ishlatiladigan ilovalarga bosh ekrandan osongina kira olasiz. Tavsiyalar oxirgi faoliyatingiz asosida almashib boradi. Pastki qatordagi ilovalar yangi jildga chiqadi." "Tavsiyalarni chiqarish" "Kerak emas" "Sozlamalar" diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml index 2e5c3e7976..55027aeda3 100644 --- a/quickstep/res/values-vi/strings.xml +++ b/quickstep/res/values-vi/strings.xml @@ -35,7 +35,6 @@ "Nhận các ứng dụng đề xuất trên hàng mục ưa thích của Màn hình chính" "Bạn có thể dễ dàng truy cập những ứng dụng mà mình dùng thường xuyên nhất ngay trên màn hình chính. Các ứng dụng đề xuất sẽ thay đổi dựa trên thói quen của bạn. Các ứng dụng ở hàng dưới cùng sẽ chuyển lên phía trên của Màn hình chính." "Bạn có thể dễ dàng mở những ứng dụng mà mình dùng thường xuyên nhất ngay trên màn hình chính. Các ứng dụng đề xuất sẽ thay đổi dựa trên thói quen của bạn. Các ứng dụng ở hàng mục ưa thích sẽ chuyển sang Màn hình chính." - "Bạn có thể dễ dàng truy cập những ứng dụng mà mình dùng thường xuyên nhất ngay trên màn hình chính. Các ứng dụng đề xuất sẽ thay đổi dựa trên thói quen của bạn. Các ứng dụng ở hàng dưới cùng sẽ chuyển đến một thư mục mới." "Nhận ứng dụng đề xuất" "Không, cảm ơn" "Cài đặt" diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml index 0b5193f449..debf23a9cc 100644 --- a/quickstep/res/values-zh-rCN/strings.xml +++ b/quickstep/res/values-zh-rCN/strings.xml @@ -35,7 +35,6 @@ "在主屏幕的收藏行获取应用建议" "直接在主屏幕上轻松访问您最常用的应用。系统会根据您的日常安排提供不同的建议。最下面一排中的应用会向上移到主屏幕中。" "直接在主屏幕上轻松访问您最常用的应用。建议会因您的日常安排而变化,收藏行中的应用将移到主屏幕上。" - "直接在主屏幕上轻松访问您最常用的应用。系统会根据您的日常安排提供不同的建议。最下面一排中的应用会移到新文件夹中。" "获取应用建议" "不用了" "设置" diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml index b700da01bd..c7a44e7d3f 100644 --- a/quickstep/res/values-zh-rHK/strings.xml +++ b/quickstep/res/values-zh-rHK/strings.xml @@ -35,7 +35,6 @@ "在主畫面「我的最愛」列取得應用程式建議" "在主畫面輕鬆存取常用的應用程式。系統會根據您的日常安排更改建議,並將底部的應用程式移到主畫面。" "在主畫面輕鬆存取最常用的應用程式。系統會根據您的日常安排變更建議,「我的最愛」列中的應用程式會移至主畫面。" - "在主畫面輕鬆存取最常用的應用程式。系統會根據您的日常安排變更建議,並將底列的應用程式移至新資料夾。" "取得應用程式建議" "不用了,謝謝" "設定" diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml index e7b74a4c84..15d5de5ecb 100644 --- a/quickstep/res/values-zh-rTW/strings.xml +++ b/quickstep/res/values-zh-rTW/strings.xml @@ -35,7 +35,6 @@ "在主畫面的收藏列取得應用程式建議" "你可以輕鬆地在主畫面上找到自己常用的應用程式。應用程式建議會依據你的日常使用習慣而有所不同。系統會將底部列出的應用程式上移到主畫面。" "你可以輕鬆地在主畫面上找到自己常用的應用程式。系統會根據你的日常使用習慣提供不同的應用程式建議,並在主畫面顯示收藏列中的應用程式。" - "你可以輕鬆地在主畫面上找到自己常用的應用程式。應用程式建議會根據日常安排有所不同。系統會將底部列出的應用程式移到新的資料夾。" "取得應用程式建議" "不用了,謝謝" "設定" diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml index c69f63d200..3db58360db 100644 --- a/quickstep/res/values-zu/strings.xml +++ b/quickstep/res/values-zu/strings.xml @@ -35,7 +35,6 @@ "Thola iziphakamiso zohlelo lokusebenza kumugqa wezintandokazi Zesikrini sakho sasekhaya" "Finyelela kalula izinhlelo zakho zokusebenza ezisetshenziswa kakhulu khona kusikrini sasekhaya. Iziphakamiso zizoshintsha ngokususelwe kwimijikelezo yakho. Izinhlelo zokusebenza ezisemgqeni ongezansi zizoya phezulu kusikrini sakho sasekhaya." "Finyelela kalula izinhlelo zakho zokusebenza ezisetshenziswa kakhulu khona kusikrini sasekhaya. Iziphakamiso zizoshintsha ngokususelwe kwimijikelezo yakho. Izintandokazi zomugqa wezinhlelo zokusebenza zizoya Kusikrini sakho sasekhaya." - "Finyelela kalula izinhlelo zakho zokusebenza ezisetshenziswa njalo, kusikrini sasekhaya. Iziphakamiso zizoshintsha ngokususelwe kwimijikelezo yakho. Izinhlelo zokusebenza ezisemgqeni ongezansi zizoya phezulu kufolda entsha." "Thola iziphakamiso zohlelo lokusebenza" "Cha ngiyabonga" "Amasethingi" diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 3b4a28b39c..198a6763ed 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -43,4 +43,9 @@ + + + + @*android:dimen/config_wallpaperMaxScale + diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 3072a3ee10..c85e71cebc 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -31,13 +31,19 @@ 50dp - + + + 0.7 + 48dp + 44dp + 16dp + 16dp - 0.7 + 72dp 1.1 @@ -66,7 +72,6 @@ 0.5dp - 0.97 115dp 100dp @@ -239,7 +244,7 @@ 0dp - 40dp + 52dp 20dp 20dp 10dp @@ -250,14 +255,15 @@ 48dp 54dp 16dp - 16dp + 16dp 8dp 44dp - 40dp - 42dp + 47dp + 24dp 35dp 24dp 220dp + 108dp 316dp 4dp 25dp @@ -268,4 +274,13 @@ 48dp 48dp 32dp + + + 24dp + 40dp + 26dp + 75dp + 47dp + 47dp + 47dp diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml index 705ec9d48c..4f472f0625 100644 --- a/quickstep/res/values/override.xml +++ b/quickstep/res/values/override.xml @@ -25,4 +25,6 @@ com.android.launcher3.model.QuickstepModelDelegate + com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl + diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 81b0dd297c..c0d52a42bd 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -68,7 +68,6 @@ Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps in favorites row will move to your Home screen. - Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder. Get app suggestions diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java deleted file mode 100644 index 2239102c4d..0000000000 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ /dev/null @@ -1,641 +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.AbstractFloatingView.TYPE_ALL; -import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; -import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON; -import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.LauncherState.NO_OFFSET; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE; -import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; -import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition; -import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX; -import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX; -import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX; -import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; -import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; -import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS; -import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY; - -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; -import android.hardware.SensorManager; -import android.hardware.devicestate.DeviceStateManager; -import android.os.Bundle; -import android.os.CancellationSignal; -import android.os.IBinder; -import android.view.Display; -import android.view.View; -import android.window.SplashScreen; - -import androidx.annotation.Nullable; - -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dragndrop.DragOptions; -import com.android.launcher3.model.WellbeingModel; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.popup.SystemShortcut; -import com.android.launcher3.proxy.ProxyActivityStarter; -import com.android.launcher3.proxy.StartActivityParams; -import com.android.launcher3.statehandlers.BackButtonAlphaHandler; -import com.android.launcher3.statehandlers.DepthController; -import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.taskbar.LauncherTaskbarUIController; -import com.android.launcher3.taskbar.TaskbarManager; -import com.android.launcher3.uioverrides.RecentsViewStateController; -import com.android.launcher3.util.ActivityOptionsWrapper; -import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; -import com.android.launcher3.util.IntSet; -import com.android.launcher3.util.ObjectWrapper; -import com.android.launcher3.util.RunnableList; -import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; -import com.android.launcher3.util.UiThreadHelper; -import com.android.quickstep.OverviewCommandHelper; -import com.android.quickstep.RecentsModel; -import com.android.quickstep.SystemUiProxy; -import com.android.quickstep.TaskUtils; -import com.android.quickstep.TouchInteractionService.TISBinder; -import com.android.quickstep.util.LauncherUnfoldAnimationController; -import com.android.quickstep.util.ProxyScreenStatusProvider; -import com.android.quickstep.util.RemoteAnimationProvider; -import com.android.quickstep.util.RemoteFadeOutAnimationListener; -import com.android.quickstep.util.SplitSelectStateController; -import com.android.quickstep.util.TISBindHelper; -import com.android.quickstep.views.OverviewActionsView; -import com.android.quickstep.views.RecentsView; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.ActivityOptionsCompat; -import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.unfold.UnfoldTransitionFactory; -import com.android.systemui.unfold.UnfoldTransitionProgressProvider; -import com.android.systemui.unfold.config.UnfoldTransitionConfig; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -/** - * Extension of Launcher activity to provide quickstep specific functionality - */ -public abstract class BaseQuickstepLauncher extends Launcher { - - private DepthController mDepthController = new DepthController(this); - private QuickstepTransitionManager mAppTransitionManager; - - /** - * 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).setNavBarButtonAlpha( - Float.intBitsToFloat(arg1), arg2 != 0); - - private OverviewActionsView mActionsView; - - private TISBindHelper mTISBindHelper; - private @Nullable TaskbarManager mTaskbarManager; - private @Nullable OverviewCommandHelper mOverviewCommandHelper; - private @Nullable LauncherTaskbarUIController mTaskbarUIController; - - // Will be updated when dragging from taskbar. - private @Nullable DragOptions mNextWorkspaceDragOptions = null; - - private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider; - private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addMultiWindowModeChangedListener(mDepthController); - initUnfoldTransitionProgressProvider(); - } - - @Override - protected void onResume() { - super.onResume(); - - if (mLauncherUnfoldAnimationController != null) { - mLauncherUnfoldAnimationController.onResume(); - } - } - - @Override - protected void onPause() { - if (mLauncherUnfoldAnimationController != null) { - mLauncherUnfoldAnimationController.onPause(); - } - - super.onPause(); - } - - @Override - public void onDestroy() { - mAppTransitionManager.onActivityDestroyed(); - if (mUnfoldTransitionProgressProvider != null) { - mUnfoldTransitionProgressProvider.destroy(); - } - - mTISBindHelper.onDestroy(); - if (mTaskbarManager != null) { - mTaskbarManager.clearActivity(this); - } - - if (mLauncherUnfoldAnimationController != null) { - mLauncherUnfoldAnimationController.onDestroy(); - } - - super.onDestroy(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - if (mOverviewCommandHelper != null) { - mOverviewCommandHelper.clearPendingCommands(); - } - } - - public QuickstepTransitionManager getAppTransitionManager() { - return mAppTransitionManager; - } - - @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 - protected void handleGestureContract(Intent intent) { - if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) { - super.handleGestureContract(intent); - } - } - - @Override - public void onTrimMemory(int level) { - super.onTrimMemory(level); - RecentsModel.INSTANCE.get(this).onTrimMemory(level); - } - - @Override - public void onUiChangedWhileSleeping() { - // Remove the snapshot because the content view may have obvious changes. - UI_HELPER_EXECUTOR.execute( - () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this)); - } - - @Override - protected void onScreenOff() { - super.onScreenOff(); - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - RecentsView recentsView = getOverviewPanel(); - recentsView.finishRecentsAnimation(true /* toRecents */, null); - } - } - - /** - * {@code LauncherOverlayCallbacks} scroll amount. - * Indicates transition progress to -1 screen. - * @param progress From 0 to 1. - */ - @Override - public void onScrollChanged(float progress) { - super.onScrollChanged(progress); - mDepthController.onOverlayScrollChanged(progress); - onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX); - } - - @Override - public void onAllAppsTransition(float progress) { - super.onAllAppsTransition(progress); - onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX); - } - - @Override - public void onWidgetsTransition(float progress) { - super.onWidgetsTransition(progress); - onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX); - } - - private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) { - if (mTaskbarManager == null - || mTaskbarManager.getCurrentActivityContext() == null - || mTaskbarUIController == null) { - return; - } - mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag); - } - - @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() { - super.onDeferredResumed(); - handlePendingActivityRequest(); - } - - @Override - public void onStateSetEnd(LauncherState state) { - super.onStateSetEnd(state); - handlePendingActivityRequest(); - } - - private void handlePendingActivityRequest() { - if (mPendingActivityRequestCode != -1 && isInState(NORMAL) - && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) { - // 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)); - } - } - - @Override - protected void setupViews() { - super.setupViews(); - - mActionsView = findViewById(R.id.overview_actions_view); - RecentsView overviewPanel = (RecentsView) getOverviewPanel(); - SplitSelectStateController controller = - new SplitSelectStateController(this, mHandler, getStateManager(), - getDepthController()); - overviewPanel.init(mActionsView, controller); - mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize()); - mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this)); - - mAppTransitionManager = new QuickstepTransitionManager(this); - mAppTransitionManager.registerRemoteAnimations(); - mAppTransitionManager.registerRemoteTransitions(); - - mTISBindHelper = new TISBindHelper(this, this::onTISConnected); - } - - private void onTISConnected(TISBinder binder) { - mTaskbarManager = binder.getTaskbarManager(); - mTaskbarManager.setActivity(this); - mOverviewCommandHelper = binder.getOverviewCommandHelper(); - } - - @Override - public void runOnBindToTouchInteractionService(Runnable r) { - mTISBindHelper.runOnBindToTouchInteractionService(r); - } - - private void initUnfoldTransitionProgressProvider() { - final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this); - if (config.isEnabled()) { - mUnfoldTransitionProgressProvider = - UnfoldTransitionFactory.createUnfoldTransitionProgressProvider( - this, - config, - ProxyScreenStatusProvider.INSTANCE, - getSystemService(DeviceStateManager.class), - getSystemService(ActivityManager.class), - getSystemService(SensorManager.class), - getMainThreadHandler(), - getMainExecutor(), - /* backgroundExecutor= */ THREAD_POOL_EXECUTOR, - /* tracingTagPrefix= */ "launcher" - ); - - mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController( - this, - getWindowManager(), - mUnfoldTransitionProgressProvider - ); - } - } - - public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) { - mTaskbarUIController = taskbarUIController; - } - - public @Nullable LauncherTaskbarUIController getTaskbarUIController() { - return mTaskbarUIController; - } - - public T getActionsView() { - return (T) mActionsView; - } - - @Override - protected void closeOpenViews(boolean animate) { - super.closeOpenViews(animate); - TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); - } - - @Override - protected void collectStateHandlers(List out) { - super.collectStateHandlers(out); - out.add(getDepthController()); - out.add(new RecentsViewStateController(this)); - out.add(new BackButtonAlphaHandler(this)); - } - - public DepthController getDepthController() { - return mDepthController; - } - - @Nullable - public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() { - return mUnfoldTransitionProgressProvider; - } - - @Override - public boolean supportsAdaptiveIconAnimation(View clickedView) { - return mAppTransitionManager.hasControlRemoteAppTransitionPermission(); - } - - @Override - public DragOptions getDefaultWorkspaceDragOptions() { - if (mNextWorkspaceDragOptions != null) { - DragOptions options = mNextWorkspaceDragOptions; - mNextWorkspaceDragOptions = null; - return options; - } - return super.getDefaultWorkspaceDragOptions(); - } - - public void setNextWorkspaceDragOptions(DragOptions dragOptions) { - mNextWorkspaceDragOptions = dragOptions; - } - - @Override - public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { - QuickstepTransitionManager appTransitionManager = getAppTransitionManager(); - appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() { - @Override - public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, - RemoteAnimationTargetCompat[] wallpaperTargets) { - - // On the first call clear the reference. - signal.cancel(); - - ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); - fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, - wallpaperTargets)); - AnimatorSet anim = new AnimatorSet(); - anim.play(fadeAnimation); - return anim; - } - }, signal); - } - - @Override - public float[] getNormalOverviewScaleAndOffset() { - return DisplayController.getNavigationMode(this).hasGestures - ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET}; - } - - @Override - public void onDragLayerHierarchyChanged() { - onLauncherStateOrFocusChanged(); - } - - @Override - protected void onActivityFlagsChanged(int changeBits) { - if ((changeBits - & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { - onLauncherStateOrFocusChanged(); - } - - if ((changeBits & ACTIVITY_STATE_STARTED) != 0) { - mDepthController.setActivityStarted(isStarted()); - } - - if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) { - if (mTaskbarUIController != null) { - mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed()); - } - } - - super.onActivityFlagsChanged(changeBits); - } - - public boolean shouldBackButtonBeHidden(LauncherState toState) { - NavigationMode mode = DisplayController.getNavigationMode(this); - boolean shouldBackButtonBeHidden = mode.hasGestures - && toState.hasFlag(FLAG_HIDE_BACK_BUTTON) - && 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; - } - return shouldBackButtonBeHidden; - } - - /** - * Sets the back button visibility based on the current state/window focus. - */ - private void onLauncherStateOrFocusChanged() { - boolean shouldBackButtonBeHidden = shouldBackButtonBeHidden(getStateManager().getState()); - if (DisplayController.getNavigationMode(this) == TWO_BUTTONS) { - UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA, - shouldBackButtonBeHidden ? 0f : 1f, true /* animate */); - } - if (getDragLayer() != null) { - getRootView().setDisallowBackGesture(shouldBackButtonBeHidden); - } - } - - @Override - public void finishBindingItems(IntSet pagesBoundFirst) { - super.finishBindingItems(pagesBoundFirst); - // 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 void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) { - pendingTasks.add(() -> { - // This is added in pending task as we need to wait for views to be positioned - // correctly before registering them for the animation. - if (mLauncherUnfoldAnimationController != null) { - // This is needed in case items are rebound while the unfold animation is in - // progress. - mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded(); - } - }); - super.onInitialBindComplete(boundPages, pendingTasks); - } - - @Override - public Stream getSupportedShortcuts() { - Stream base = Stream.of(WellbeingModel.SHORTCUT_FACTORY); - if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) { - RecentsView recentsView = getOverviewPanel(); - // TODO: Pull it out of PagedOrentationHandler for split from workspace. - List positions = - recentsView.getPagedOrientationHandler().getSplitPositionOptions( - mDeviceProfile); - List> splitShortcuts = new ArrayList<>(); - for (SplitPositionOption position : positions) { - splitShortcuts.add(getSplitSelectShortcutByPosition(position)); - } - base = Stream.concat(base, splitShortcuts.stream()); - } - return Stream.concat(base, super.getSupportedShortcuts()); - } - - @Override - public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { - ActivityOptionsWrapper activityOptions = - mAppTransitionManager.hasControlRemoteAppTransitionPermission() - ? mAppTransitionManager.getActivityLaunchOptions(v) - : super.getActivityLaunchOptions(v, item); - if (mLastTouchUpTime > 0) { - ActivityOptionsCompat.setLauncherSourceInfo( - activityOptions.options, mLastTouchUpTime); - } - activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); - activityOptions.options.setLaunchDisplayId( - (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() - : Display.DEFAULT_DISPLAY); - addLaunchCookie(item, activityOptions.options); - return activityOptions; - } - - /** - * Adds a new launch cookie for the activity launch if supported. - * - * @param info the item info for the launch - * @param opts the options to set the launchCookie on. - */ - public void addLaunchCookie(ItemInfo info, ActivityOptions opts) { - IBinder launchCookie = getLaunchCookie(info); - if (launchCookie != null) { - opts.setLaunchCookie(launchCookie); - } - } - - /** - * Return a new launch cookie for the activity launch if supported. - * - * @param info the item info for the launch - */ - public IBinder getLaunchCookie(ItemInfo info) { - if (info == null) { - return null; - } - switch (info.container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: - // Fall through and continue it's on the workspace (we don't support swiping back - // to other containers like all apps or the hotseat predictions (which can change) - break; - default: - if (info.container >= 0) { - // Also allow swiping to folders - break; - } - // Reset any existing launch cookies associated with the cookie - return ObjectWrapper.wrap(NO_MATCHING_ID); - } - switch (info.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - // Fall through and continue if it's an app, shortcut, or widget - break; - default: - // Reset any existing launch cookies associated with the cookie - return ObjectWrapper.wrap(NO_MATCHING_ID); - } - return ObjectWrapper.wrap(new Integer(info.id)); - } - - public void setHintUserWillBeActive() { - addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); - } - - @Override - public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) { - super.onDisplayInfoChanged(context, info, flags); - // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as - // StatefulActivity isn't called consistently. - if ((flags & CHANGE_ACTIVE_SCREEN) != 0) { - getStateManager().moveToRestState(); - } - - if ((flags & CHANGE_NAVIGATION_MODE) != 0) { - getDragLayer().recreateControllers(); - if (mActionsView != null) { - mActionsView.updateVerticalMargin(info.navigationMode); - } - } - } - - @Override - public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - super.dump(prefix, fd, writer, args); - if (mDepthController != null) { - mDepthController.dump(prefix, writer); - } - } -} diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java index 35151f1a68..c4e85f6014 100644 --- a/quickstep/src/com/android/launcher3/LauncherInitListener.java +++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java @@ -20,6 +20,7 @@ import android.annotation.TargetApi; import android.os.Build; import android.os.CancellationSignal; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -44,7 +45,7 @@ public class LauncherInitListener extends ActivityInitListener { public boolean handleInit(Launcher launcher, boolean alreadyOnHome) { if (mRemoteAnimationProvider != null) { QuickstepTransitionManager appTransitionManager = - ((BaseQuickstepLauncher) launcher).getAppTransitionManager(); + ((QuickstepLauncher) launcher).getAppTransitionManager(); // Set a one-time animation provider. After the first call, this will get cleared. // TODO: Probably also check the intended target id. diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 1539c9fee8..2d6b6c301c 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -31,7 +31,6 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.Utilities.mapBoundToRange; -import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5; @@ -43,8 +42,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAU import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; -import static com.android.launcher3.statehandlers.DepthController.DEPTH; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; +import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; @@ -59,6 +57,8 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; +import android.app.ActivityOptions; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -78,13 +78,14 @@ import android.os.Looper; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; -import android.util.Log; import android.util.Pair; import android.util.Size; +import android.view.CrossWindowBlurListeners; import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; +import android.view.WindowManager; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -96,12 +97,15 @@ import androidx.core.graphics.ColorUtils; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.taskbar.LauncherTaskbarUIController; +import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.ObjectWrapper; @@ -122,8 +126,6 @@ import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.WorkspaceRevealAnim; import com.android.quickstep.views.FloatingWidgetView; import com.android.quickstep.views.RecentsView; -import com.android.systemui.shared.system.ActivityCompat; -import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.BlurUtils; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.QuickStepContract; @@ -133,11 +135,9 @@ import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.RemoteTransitionCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; -import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.wm.shell.startingsurface.IStartingWindowListener; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; @@ -146,8 +146,6 @@ import java.util.List; */ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener { - private static final String TAG = "QuickstepTransition"; - private static final boolean ENABLE_SHELL_STARTING_SURFACE = SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); @@ -184,6 +182,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener public static final int SPLIT_DIVIDER_ANIM_DURATION = 100; public static final int CONTENT_ALPHA_DURATION = 217; + public static final int TASKBAR_TO_APP_DURATION = 600; + // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation + // is solved. + public static final int TASKBAR_TO_HOME_DURATION = 300; protected static final int CONTENT_SCALE_DURATION = 350; protected static final int CONTENT_SCRIM_DURATION = 350; @@ -192,12 +194,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // Cross-fade duration between App Widget and App private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125; - protected final BaseQuickstepLauncher mLauncher; + protected final QuickstepLauncher mLauncher; private final DragLayer mDragLayer; final Handler mHandler; - private final float mContentScale; private final float mClosingWindowTransY; private final float mMaxShadowRadius; @@ -242,7 +243,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mBackAnimationController = new LauncherBackAnimationController(mLauncher, this); Resources res = mLauncher.getResources(); - mContentScale = res.getFloat(R.dimen.content_scale); mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y); mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius); @@ -293,8 +293,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay, mLauncher.getIApplicationThread()); - return new ActivityOptionsWrapper( - ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback); + ActivityOptions options = ActivityOptions.makeRemoteAnimation( + adapterCompat.getWrapped(), + adapterCompat.getRemoteTransition().getTransition()); + return new ActivityOptionsWrapper(options, onEndCallback); } /** @@ -384,7 +386,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener @Override public void onAnimationStart(Animator animation) { mLauncher.addOnResumeCallback(() -> - ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, + ObjectAnimator.ofFloat(mLauncher.getDepthController(), STATE_DEPTH, mLauncher.getStateManager().getState().getDepth( mLauncher)).start()); } @@ -408,7 +410,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener @Override public void onAnimationStart(Animator animation) { mLauncher.addOnResumeCallback(() -> - ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, + ObjectAnimator.ofFloat(mLauncher.getDepthController(), STATE_DEPTH, mLauncher.getStateManager().getState().getDepth( mLauncher)).start()); } @@ -445,7 +447,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener 4 - rotationChange); } } - if (mDeviceProfile.isTaskbarPresentInApps) { + if (mDeviceProfile.isTaskbarPresentInApps && !target.willShowImeOnTarget) { // Animate to above the taskbar. bounds.bottom -= target.contentInsets.bottom; } @@ -480,8 +482,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener : new float[]{0, 1}; float[] scales = isAppOpening - ? new float[]{1, mContentScale} - : new float[]{mContentScale, 1}; + ? new float[]{1, mDeviceProfile.workspaceContentScale} + : new float[]{mDeviceProfile.workspaceContentScale, 1}; // Pause expensive view updates as they can lead to layer thrashing and skipped frames. mLauncher.pauseExpensiveViewUpdates(); @@ -518,6 +520,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener appsView.setAlpha(startAlpha); SCALE_PROPERTY.set(appsView, startScale); appsView.setLayerType(View.LAYER_TYPE_NONE, null); + mLauncher.resumeExpensiveViewUpdates(); }; } else if (mLauncher.isInState(OVERVIEW)) { endListener = composeViewContentAnimator(launcherAnimator, alphas, scales); @@ -527,7 +530,15 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener workspace.forEachVisiblePage( view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); - viewsToAnimate.add(mLauncher.getHotseat()); + // Do not scale hotseat as a whole when taskbar is present, and scale QSB only if it's + // not inline. + if (mDeviceProfile.isTaskbarPresent) { + if (!mDeviceProfile.isQsbInline) { + viewsToAnimate.add(mLauncher.getHotseat().getQsb()); + } + } else { + viewsToAnimate.add(mLauncher.getHotseat()); + } viewsToAnimate.forEach(view -> { view.setLayerType(View.LAYER_TYPE_HARDWARE, null); @@ -613,28 +624,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener RecentsView overview = mLauncher.getOverviewPanel(); ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, RecentsView.CONTENT_ALPHA, alphas); - Log.d(BAD_STATE, "QTM composeViewContentAnimator alphas=" + Arrays.toString(alphas)); - alpha.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - Log.d(BAD_STATE, "QTM composeViewContentAnimator onStart"); - } - - @Override - public void onAnimationCancel(Animator animation) { - float alpha = overview == null ? -1 : RecentsView.CONTENT_ALPHA.get(overview); - Log.d(BAD_STATE, "QTM composeViewContentAnimator onCancel, alpha=" + alpha); - } - - @Override - public void onAnimationEnd(Animator animation) { - Log.d(BAD_STATE, "QTM composeViewContentAnimator onEnd"); - } - }); alpha.setDuration(CONTENT_ALPHA_DURATION); alpha.setInterpolator(LINEAR); anim.play(alpha); - Log.d(BAD_STATE, "QTM composeViewContentAnimator setFreezeVisibility=true"); overview.setFreezeViewVisibility(true); ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(overview, SCALE_PROPERTY, scales); @@ -643,10 +635,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener anim.play(scaleAnim); return () -> { - Log.d(BAD_STATE, "QTM composeViewContentAnimator onEnd setFreezeVisibility=false"); overview.setFreezeViewVisibility(false); SCALE_PROPERTY.set(overview, 1f); mLauncher.getStateManager().reapplyState(); + mLauncher.resumeExpensiveViewUpdates(); }; } @@ -1057,54 +1049,37 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private ObjectAnimator getBackgroundAnimator() { // 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; - DepthController depthController = mLauncher.getDepthController(); - ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH, - BACKGROUND_APP.getDepth(mLauncher)) + boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW + && BlurUtils.supportsBlursOnWindows(); + + MyDepthController depthController = new MyDepthController(mLauncher); + ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, STATE_DEPTH, + BACKGROUND_APP.getDepth(mLauncher)) .setDuration(APP_LAUNCH_DURATION); + if (allowBlurringLauncher) { - final SurfaceControl dimLayer; - if (BlurUtils.supportsBlursOnWindows()) { - // Create a temporary effect layer, that lives on top of launcher, so we can apply - // the blur to it. The EffectLayer will be fullscreen, which will help with caching - // optimizations on the SurfaceFlinger side: - // - Results would be able to be cached as a texture - // - There won't be texture allocation overhead, because EffectLayers don't have - // buffers - ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); - SurfaceControl parent = viewRootImpl != null - ? viewRootImpl.getSurfaceControl() - : null; - dimLayer = new SurfaceControl.Builder() - .setName("Blur layer") - .setParent(parent) - .setOpaque(false) - .setHidden(false) - .setEffectLayer() - .build(); - } else { - dimLayer = null; - } + // Create a temporary effect layer, that lives on top of launcher, so we can apply + // the blur to it. The EffectLayer will be fullscreen, which will help with caching + // optimizations on the SurfaceFlinger side: + // - Results would be able to be cached as a texture + // - There won't be texture allocation overhead, because EffectLayers don't have + // buffers + ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); + SurfaceControl parent = viewRootImpl != null + ? viewRootImpl.getSurfaceControl() + : null; + SurfaceControl dimLayer = new SurfaceControl.Builder() + .setName("Blur layer") + .setParent(parent) + .setOpaque(false) + .setHidden(false) + .setEffectLayer() + .build(); - depthController.setSurface(dimLayer); - backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - depthController.setIsInLaunchTransition(true); - } - - @Override - public void onAnimationEnd(Animator animation) { - depthController.setIsInLaunchTransition(false); - depthController.setSurface(null); - if (dimLayer != null) { - new SurfaceControl.Transaction() - .remove(dimLayer) - .apply(); - } - } - }); + backgroundRadiusAnim.addListener(AnimatorListeners.forEndCallback(() -> + new SurfaceControl.Transaction().remove(dimLayer).apply())); } + return backgroundRadiusAnim; } @@ -1119,8 +1094,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */); RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat(); - definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN, - WindowManagerWrapper.ACTIVITY_TYPE_STANDARD, + definition.addRemoteAnimation(WindowManager.TRANSIT_OLD_WALLPAPER_OPEN, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, new RemoteAnimationAdapterCompat( new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner, false /* startAtFrontOfQueue */), @@ -1130,7 +1105,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener if (KEYGUARD_ANIMATION.get()) { mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */); definition.addRemoteAnimation( - WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER, new RemoteAnimationAdapterCompat( new LauncherAnimationRunner( mHandler, mKeyguardGoingAwayRunner, @@ -1139,7 +1114,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mLauncher.getIApplicationThread())); } - new ActivityCompat(mLauncher).registerRemoteAnimations(definition); + mLauncher.registerRemoteAnimations(definition.getWrapped()); } } @@ -1175,7 +1150,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener return; } if (hasControlRemoteAppTransitionPermission()) { - new ActivityCompat(mLauncher).unregisterRemoteAnimations(); + 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 @@ -1446,7 +1421,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener animation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - anim.start(mLauncher, velocityPxPerS); + anim.start(mLauncher, mDeviceProfile, velocityPxPerS); } }); return anim; @@ -1621,7 +1596,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener true /* animateOverviewScrim */, launcherView).getAnimators()); if (!areAllTargetsTranslucent(appTargets)) { - anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, + anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController(), + STATE_DEPTH, BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher))); } @@ -1949,4 +1925,17 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); } } + + private static class MyDepthController extends DepthController { + MyDepthController(Launcher l) { + super(l); + setCrossWindowBlursEnabled( + CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled()); + } + + @Override + public void setSurface(SurfaceControl surface) { + super.setSurface(surface); + } + } } diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java index f42b39fb2d..e8374b813c 100644 --- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java +++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java @@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat; import com.android.launcher3.R; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; +import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; @@ -92,8 +93,10 @@ public class AppsDividerView extends View implements FloatingHeaderRow { ? R.color.all_apps_label_text_dark : R.color.all_apps_label_text); - mShowAllAppsLabel = !ActivityContext.lookupContext( - getContext()).getOnboardingPrefs().hasReachedMaxCount(ALL_APPS_VISITED_COUNT); + OnboardingPrefs onboardingPrefs = ActivityContext.lookupContext( + getContext()).getOnboardingPrefs(); + mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount( + ALL_APPS_VISITED_COUNT); } public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { @@ -216,8 +219,8 @@ public class AppsDividerView extends View implements FloatingHeaderRow { CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label); mAllAppsLabelLayout = StaticLayout.Builder.obtain( - allAppsLabelText, 0, allAppsLabelText.length(), mPaint, - Math.round(mPaint.measureText(allAppsLabelText.toString()))) + allAppsLabelText, 0, allAppsLabelText.length(), mPaint, + Math.round(mPaint.measureText(allAppsLabelText.toString()))) .setAlignment(Layout.Alignment.ALIGN_CENTER) .setMaxLines(1) .setIncludePad(true) diff --git a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java index 9c3b8816cb..8baee004cc 100644 --- a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java +++ b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java @@ -22,6 +22,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import androidx.annotation.NonNull; + import com.android.launcher3.LauncherSettings; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -33,11 +35,13 @@ public class InstantAppItemInfo extends AppInfo { this.componentName = new ComponentName(packageName, COMPONENT_CLASS_MARKER); } + @NonNull @Override public ComponentName getTargetComponent() { return componentName; } + @NonNull @Override public WorkspaceItemInfo makeWorkspaceItem(Context context) { WorkspaceItemInfo workspaceItemInfo = super.makeWorkspaceItem(context); diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java index 351a3bc164..c54d119904 100644 --- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -33,6 +33,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; import com.android.launcher3.anim.AlphaUpdateListener; @@ -117,9 +118,14 @@ public class PredictionRowView mLauncher.startActivity(getSettingsIntent())); } - /** - * This migration places all non folder items in the hotseat into a folder and then moves - * all folders in the hotseat to a workspace page that has enough empty spots. - * - * @return pageId that has accepted the items. - */ - private int migrateToFolder() { - ArrayDeque folders = new ArrayDeque<>(); - ArrayList putIntoFolder = new ArrayList<>(); - - //separate folders and items that can get in folders - for (int i = 0; i < mLauncher.getDeviceProfile().numShownHotseatIcons; i++) { - View view = mHotseat.getChildAt(i, 0); - if (view == null) continue; - ItemInfo info = (ItemInfo) view.getTag(); - if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { - folders.add((FolderInfo) info); - } else if (info instanceof WorkspaceItemInfo && info.container == LauncherSettings - .Favorites.CONTAINER_HOTSEAT) { - putIntoFolder.add((WorkspaceItemInfo) info); - } - } - - // create a temp folder and add non folder items to it - if (!putIntoFolder.isEmpty()) { - ItemInfo firstItem = putIntoFolder.get(0); - FolderInfo folderInfo = new FolderInfo(); - mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container, - firstItem.screenId, firstItem.cellX, firstItem.cellY); - folderInfo.setTitle("", mLauncher.getModelWriter()); - folderInfo.contents.addAll(putIntoFolder); - for (int i = 0; i < folderInfo.contents.size(); i++) { - ItemInfo item = folderInfo.contents.get(i); - item.rank = i; - mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0, - item.cellX, item.cellY); - } - folders.add(folderInfo); - } - mNewItems.addAll(folders); - - return placeFoldersInWorkspace(folders); - } - - private int placeFoldersInWorkspace(ArrayDeque folders) { - if (folders.isEmpty()) return 0; - - Workspace workspace = mLauncher.getWorkspace(); - InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv; - - GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()]; - for (int i = 0; i < occupancyList.length; i++) { - occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy(); - } - //scan every screen to find available spots to place folders - int occupancyIndex = 0; - int[] itemXY = new int[2]; - while (occupancyIndex < occupancyList.length && !folders.isEmpty()) { - GridOccupancy occupancy = occupancyList[occupancyIndex]; - if (occupancy.findVacantCell(itemXY, 1, 1)) { - FolderInfo info = folders.poll(); - mLauncher.getModelWriter().moveItemInDatabase(info, - LauncherSettings.Favorites.CONTAINER_DESKTOP, - workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]); - occupancy.markCells(info, true); - } else { - occupancyIndex++; - } - } - if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex); - int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(), - LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) - .getInt(LauncherSettings.Settings.EXTRA_VALUE); - // if all screens are full and we still have folders left, put those on a new page - FolderInfo folderInfo; - int col = 0; - while ((folderInfo = folders.poll()) != null) { - mLauncher.getModelWriter().moveItemInDatabase(folderInfo, - LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++, - idp.numRows - 1); - } - mNewScreens = IntArray.wrap(screenId); - return workspace.getPageCount(); - } - /** * This migration option attempts to move the entire hotseat up to the first workspace that * has space to host items. If no such page is found, it moves items to a new page. diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index 119ae907f7..21008344f7 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -15,8 +15,7 @@ */ package com.android.launcher3.hybridhotseat; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent - .LAUNCHER_HOTSEAT_EDU_ACCEPT; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_ACCEPT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN; @@ -39,9 +38,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.views.AbstractSlideInView; @@ -107,19 +104,12 @@ public class HotseatEduDialog extends AbstractSlideInView implements I mDismissBtn.setOnClickListener(this::onDismiss); LinearLayout buttonContainer = findViewById(R.id.button_container); - int adjustedMarginEnd = ApiWrapper.getHotseatEndOffset(context) - - buttonContainer.getPaddingEnd(); + int adjustedMarginEnd = grid.hotseatBarEndOffset - buttonContainer.getPaddingEnd(); if (InvariantDeviceProfile.INSTANCE.get(context) .getDeviceProfile(context).isTaskbarPresent && adjustedMarginEnd > 0) { ((LinearLayout.LayoutParams) buttonContainer.getLayoutParams()).setMarginEnd( adjustedMarginEnd); } - - // update ui to reflect which migration method is going to be used - if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { - ((TextView) findViewById(R.id.hotseat_edu_content)).setText( - R.string.hotseat_edu_message_migrate_alt); - } } private void onAccept(View v) { diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java index 7e3ee7dada..bc3253fcfb 100644 --- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java +++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java @@ -27,6 +27,8 @@ import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.os.UserHandle; +import androidx.annotation.NonNull; + import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.model.BgDataModel.FixedContainerItems; @@ -52,7 +54,8 @@ public class PredictionUpdateTask extends BaseModelUpdateTask { } @Override - public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel, + @NonNull final AllAppsList apps) { Context context = app.getContext(); // TODO: remove this diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index 770dfb2e07..de0b14d4fa 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -117,14 +117,15 @@ public class QuickstepModelDelegate extends ModelDelegate { // TODO: Implement caching and preloading super.loadItems(ums, pinnedShortcuts); - WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory( - mApp, ums, pinnedShortcuts, mIDP.numDatabaseAllAppsColumns); - FixedContainerItems allAppsItems = new FixedContainerItems(mAllAppsState.containerId, - mAllAppsState.storage.read(mApp.getContext(), allAppsFactory, ums.allUsers::get)); - mDataModel.extraItems.put(mAllAppsState.containerId, allAppsItems); + WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, + mIDP.numDatabaseAllAppsColumns, mAllAppsState.containerId); + FixedContainerItems allAppsPredictionItems = new FixedContainerItems( + mAllAppsState.containerId, mAllAppsState.storage.read(mApp.getContext(), + allAppsFactory, ums.allUsers::get)); + mDataModel.extraItems.put(mAllAppsState.containerId, allAppsPredictionItems); - WorkspaceItemFactory hotseatFactory = - new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numDatabaseHotseatIcons); + WorkspaceItemFactory hotseatFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, + mIDP.numDatabaseHotseatIcons, mHotseatState.containerId); FixedContainerItems hotseatItems = new FixedContainerItems(mHotseatState.containerId, mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get)); mDataModel.extraItems.put(mHotseatState.containerId, hotseatItems); @@ -432,15 +433,17 @@ public class QuickstepModelDelegate extends ModelDelegate { private final UserManagerState mUMS; private final Map mPinnedShortcuts; private final int mMaxCount; + private final int mContainer; private int mReadCount = 0; protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums, - Map pinnedShortcuts, int maxCount) { + Map pinnedShortcuts, int maxCount, int container) { mAppState = appState; mUMS = ums; mPinnedShortcuts = pinnedShortcuts; mMaxCount = maxCount; + mContainer = container; } @Nullable @@ -458,6 +461,7 @@ public class QuickstepModelDelegate extends ModelDelegate { return null; } AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user)); + info.container = mContainer; mAppState.getIconCache().getTitleAndIcon(info, lai, false); mReadCount++; return info.makeWorkspaceItem(mAppState.getContext()); @@ -472,6 +476,7 @@ public class QuickstepModelDelegate extends ModelDelegate { return null; } WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext()); + wii.container = mContainer; mAppState.getIconCache().getShortcutIcon(wii, si); mReadCount++; return wii; diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java index 9cd9d8597c..7a483a808a 100644 --- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java +++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java @@ -21,6 +21,8 @@ import android.app.prediction.AppTarget; import android.content.ComponentName; import android.text.TextUtils; +import androidx.annotation.NonNull; + import com.android.launcher3.LauncherAppState; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.BgDataModel.FixedContainerItems; @@ -52,7 +54,8 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask { * workspace. */ @Override - public void execute(LauncherAppState appState, BgDataModel dataModel, AllAppsList apps) { + public void execute(@NonNull final LauncherAppState appState, + @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { Set widgetsInWorkspace = dataModel.appWidgets.stream().map( widget -> new ComponentKey(widget.providerName, widget.user)).collect( Collectors.toSet()); diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java index 4e59790771..7c3281a046 100644 --- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java +++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.popup; +import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition; + import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; @@ -22,9 +24,10 @@ import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; -import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.quickstep.views.RecentsView; @@ -32,18 +35,18 @@ public interface QuickstepSystemShortcut { String TAG = QuickstepSystemShortcut.class.getSimpleName(); - static SystemShortcut.Factory getSplitSelectShortcutByPosition( + static SystemShortcut.Factory getSplitSelectShortcutByPosition( SplitPositionOption position) { return (activity, itemInfo, originalView) -> new QuickstepSystemShortcut.SplitSelectSystemShortcut(activity, itemInfo, originalView, position); } - class SplitSelectSystemShortcut extends SystemShortcut { + class SplitSelectSystemShortcut extends SystemShortcut { private final SplitPositionOption mPosition; - public SplitSelectSystemShortcut(BaseQuickstepLauncher launcher, ItemInfo itemInfo, + public SplitSelectSystemShortcut(QuickstepLauncher launcher, ItemInfo itemInfo, View originalView, SplitPositionOption position) { super(position.iconResId, position.textResId, launcher, itemInfo, originalView); @@ -52,6 +55,7 @@ public interface QuickstepSystemShortcut { @Override public void onClick(View view) { + // Initiate splitscreen from the Home screen or Home All Apps Bitmap bitmap; Intent intent; if (mItemInfo instanceof WorkspaceItemInfo) { @@ -69,9 +73,10 @@ public interface QuickstepSystemShortcut { } RecentsView recentsView = mTarget.getOverviewPanel(); + StatsLogManager.EventEnum splitEvent = getLogEventForPosition(mPosition.stagePosition); recentsView.initiateSplitSelect( new SplitSelectSource(mOriginalView, new BitmapDrawable(bitmap), intent, - mPosition)); + mPosition, mItemInfo, splitEvent)); } } @@ -81,13 +86,18 @@ public interface QuickstepSystemShortcut { public final Drawable drawable; public final Intent intent; public final SplitPositionOption position; + public final ItemInfo mItemInfo; + public final StatsLogManager.EventEnum splitEvent; public SplitSelectSource(View view, Drawable drawable, Intent intent, - SplitPositionOption position) { + SplitPositionOption position, ItemInfo itemInfo, + StatsLogManager.EventEnum splitEvent) { this.view = view; this.drawable = drawable; this.intent = intent; this.position = position; + this.mItemInfo = itemInfo; + this.splitEvent = splitEvent; } } } diff --git a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java new file mode 100644 index 0000000000..5bf727a60b --- /dev/null +++ b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.secondarydisplay; + +import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT; + +import android.content.Context; + +import com.android.launcher3.appprediction.AppsDividerView; +import com.android.launcher3.appprediction.PredictionRowView; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.util.OnboardingPrefs; +import com.android.launcher3.views.ActivityContext; + +/** + * Implementation of SecondaryDisplayPredictions. + */ +@SuppressWarnings("unused") +public final class SecondaryDisplayPredictionsImpl extends SecondaryDisplayPredictions { + private final ActivityContext mActivityContext; + + public SecondaryDisplayPredictionsImpl(Context context) { + mActivityContext = ActivityContext.lookupContext(context); + } + + @Override + void updateAppDivider() { + OnboardingPrefs onboardingPrefs = mActivityContext.getOnboardingPrefs(); + mActivityContext.getAppsView().getFloatingHeaderView() + .findFixedRowByType(AppsDividerView.class) + .setShowAllAppsLabel(!onboardingPrefs.hasReachedMaxCount(ALL_APPS_VISITED_COUNT)); + onboardingPrefs.incrementEventCount(ALL_APPS_VISITED_COUNT); + } + + @Override + public void setPredictedApps(BgDataModel.FixedContainerItems item) { + mActivityContext.getAppsView().getFloatingHeaderView() + .findFixedRowByType(PredictionRowView.class) + .setPredictedApps(item.items); + } +} diff --git a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java deleted file mode 100644 index 07d3a51603..0000000000 --- a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java +++ /dev/null @@ -1,64 +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.statehandlers; - -import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS; -import static com.android.quickstep.AnimatedFloat.VALUE; - -import com.android.launcher3.BaseQuickstepLauncher; -import com.android.launcher3.LauncherState; -import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.states.StateAnimationConfig; -import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.UiThreadHelper; -import com.android.quickstep.AnimatedFloat; -import com.android.quickstep.SystemUiProxy; - -/** - * State handler for animating back button alpha in two-button nav mode. - */ -public class BackButtonAlphaHandler implements StateHandler { - - private final BaseQuickstepLauncher mLauncher; - private final AnimatedFloat mBackAlpha = new AnimatedFloat(this::updateBackAlpha); - - public BackButtonAlphaHandler(BaseQuickstepLauncher launcher) { - mLauncher = launcher; - } - - @Override - public void setState(LauncherState state) { } - - @Override - public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, - PendingAnimation animation) { - if (DisplayController.getNavigationMode(mLauncher) != TWO_BUTTONS) { - return; - } - - mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastNavButtonAlpha(); - animation.setFloat(mBackAlpha, VALUE, - mLauncher.shouldBackButtonBeHidden(toState) ? 0 : 1, LINEAR); - } - - private void updateBackAlpha() { - UiThreadHelper.setBackButtonAlphaAsync(mLauncher, - BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, mBackAlpha.value, false /* animate */); - } -} diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java index eda08239d6..e3fd3f9a06 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -23,13 +23,8 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTR import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.app.WallpaperManager; -import android.os.IBinder; -import android.os.SystemProperties; import android.util.FloatProperty; -import android.view.AttachedSurfaceControl; import android.view.CrossWindowBlurListeners; -import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; @@ -37,12 +32,11 @@ import android.view.ViewTreeObserver; import com.android.launcher3.BaseActivity; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.states.StateAnimationConfig; -import com.android.systemui.shared.system.BlurUtils; +import com.android.quickstep.util.BaseDepthController; import java.io.PrintWriter; import java.util.function.Consumer; @@ -50,23 +44,9 @@ import java.util.function.Consumer; /** * Controls blur and wallpaper zoom, for the Launcher surface only. */ -public class DepthController implements StateHandler, +public class DepthController extends BaseDepthController implements StateHandler, BaseActivity.MultiWindowModeChangedListener { - private static final boolean OVERLAY_SCROLL_ENABLED = false; - public static final FloatProperty DEPTH = - new FloatProperty("depth") { - @Override - public void setValue(DepthController depthController, float depth) { - depthController.setDepth(depth); - } - - @Override - public Float get(DepthController depthController) { - return depthController.mDepth; - } - }; - /** * 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). @@ -92,96 +72,46 @@ public class DepthController implements StateHandler, } } - private final ViewTreeObserver.OnDrawListener mOnDrawListener = - new ViewTreeObserver.OnDrawListener() { - @Override - public void onDraw() { - View view = mLauncher.getDragLayer(); - ViewRootImpl viewRootImpl = view.getViewRootImpl(); - boolean applied = setSurface( - viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null); - if (!applied) { - dispatchTransactionSurface(mDepth); - } - view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); - } - }; + private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::onLauncherDraw; - private final Consumer mCrossWindowBlurListener = new Consumer() { - @Override - public void accept(Boolean enabled) { - mCrossWindowBlursEnabled = enabled; - dispatchTransactionSurface(mDepth); - } - }; + private final Consumer mCrossWindowBlurListener = this::setCrossWindowBlursEnabled; - private final Runnable mOpaquenessListener = new Runnable() { - @Override - public void run() { - dispatchTransactionSurface(mDepth); - } - }; + private final Runnable mOpaquenessListener = this::applyDepthAndBlur; - private final Launcher mLauncher; - /** - * Blur radius when completely zoomed out, in pixels. - */ - private int mMaxBlurRadius; - private boolean mCrossWindowBlursEnabled; - private WallpaperManager mWallpaperManager; - private SurfaceControl mSurface; - /** - * How visible the -1 overlay is, from 0 to 1. - */ - private float mOverlayScrollProgress; - /** - * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in. - * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float) - */ - private float mDepth; - /** - * Last blur value, in pixels, that was applied. - * For debugging purposes. - */ - private int mCurrentBlur; /** * If we're launching and app and should not be blurring the screen for performance reasons. */ private boolean mBlurDisabledForAppLaunch; - /** - * If we requested early wake-up offsets to SurfaceFlinger. - */ - private boolean mInEarlyWakeUp; + // Workaround for animating the depth when multiwindow mode changes. private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false; - // Hints that there is potentially content behind Launcher and that we shouldn't optimize by - // marking the launcher surface as opaque. Only used in certain Launcher states. - private boolean mHasContentBehindLauncher; - private View.OnAttachStateChangeListener mOnAttachListener; public DepthController(Launcher l) { - mLauncher = l; + super(l); + } + + private void onLauncherDraw() { + View view = mLauncher.getDragLayer(); + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null); + view.post(() -> view.getViewTreeObserver().removeOnDrawListener(mOnDrawListener)); } private void ensureDependencies() { - if (mWallpaperManager == null) { - mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); - mWallpaperManager = mLauncher.getSystemService(WallpaperManager.class); - } - if (mLauncher.getRootView() != null && mOnAttachListener == null) { + View rootView = mLauncher.getRootView(); mOnAttachListener = new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { + CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(), + mCrossWindowBlurListener); + mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener); + // To handle the case where window token is invalid during last setDepth call. - IBinder windowToken = mLauncher.getRootView().getWindowToken(); - if (windowToken != null) { - mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); - } - onAttached(); + applyDepthAndBlur(); } @Override @@ -190,23 +120,13 @@ public class DepthController implements StateHandler, mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener); } }; - mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener); - if (mLauncher.getRootView().isAttachedToWindow()) { - onAttached(); + rootView.addOnAttachStateChangeListener(mOnAttachListener); + if (rootView.isAttachedToWindow()) { + mOnAttachListener.onViewAttachedToWindow(rootView); } } } - private void onAttached() { - CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(), - mCrossWindowBlurListener); - mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener); - } - - public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) { - mHasContentBehindLauncher = hasContentBehindLauncher; - } - /** * Sets if the underlying activity is started or not */ @@ -219,29 +139,9 @@ public class DepthController implements StateHandler, } } - /** - * Sets the specified app target surface to apply the blur to. - * @return true when surface was valid and transaction was dispatched. - */ - public boolean setSurface(SurfaceControl surface) { - // Set launcher as the SurfaceControl when we don't need an external target anymore. - if (surface == null) { - ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); - surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null; - } - if (mSurface != surface) { - mSurface = surface; - if (surface != null) { - dispatchTransactionSurface(mDepth); - return true; - } - } - return false; - } - @Override public void setState(LauncherState toState) { - if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) { + if (mIgnoreStateChangesDuringMultiWindowAnimation) { return; } @@ -249,7 +149,7 @@ public class DepthController implements StateHandler, if (Float.compare(mDepth, toDepth) != 0) { setDepth(toDepth); } else if (toState == LauncherState.OVERVIEW) { - dispatchTransactionSurface(mDepth); + applyDepthAndBlur(); } else if (toState == LauncherState.BACKGROUND_APP) { mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener); } @@ -265,101 +165,22 @@ public class DepthController implements StateHandler, float toDepth = toState.getDepth(mLauncher); if (Float.compare(mDepth, toDepth) != 0) { - animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR)); + animation.setFloat(this, STATE_DEPTH, toDepth, + config.getInterpolator(ANIM_DEPTH, LINEAR)); } } - /** - * If we're launching an app from the home screen. - */ - public void setIsInLaunchTransition(boolean inLaunchTransition) { - boolean blurEnabled = SystemProperties.getBoolean("ro.launcher.blur.appLaunch", true); - mBlurDisabledForAppLaunch = inLaunchTransition && !blurEnabled; - if (!inLaunchTransition) { - // Reset depth at the end of the launch animation, so the wallpaper won't be - // zoomed out if an app crashes. - setDepth(0f); - } - } - - private void setDepth(float depth) { - depth = Utilities.boundToRange(depth, 0, 1); - // Round out the depth to dedupe frequent, non-perceptable updates - int depthI = (int) (depth * 256); - float depthF = depthI / 256f; - if (Float.compare(mDepth, depthF) == 0) { - return; - } - dispatchTransactionSurface(depthF); - mDepth = depthF; - } - - public void onOverlayScrollChanged(float progress) { - if (!OVERLAY_SCROLL_ENABLED) { - return; - } - // Add some padding to the progress, such we don't change the depth on the last frames of - // the animation. It's possible that a user flinging the feed quickly would scroll - // horizontally by accident, causing the device to enter client composition unnecessarily. - progress = Math.min(progress * 1.1f, 1f); - - // Round out the progress to dedupe frequent, non-perceptable updates - int progressI = (int) (progress * 256); - float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f); - if (Float.compare(mOverlayScrollProgress, progressF) == 0) { - return; - } - mOverlayScrollProgress = progressF; - dispatchTransactionSurface(mDepth); - } - - private boolean dispatchTransactionSurface(float depth) { - boolean supportsBlur = BlurUtils.supportsBlursOnWindows(); - if (supportsBlur && (mSurface == null || !mSurface.isValid())) { - return false; - } + @Override + protected void applyDepthAndBlur() { ensureDependencies(); - depth = Math.max(depth, mOverlayScrollProgress); - IBinder windowToken = mLauncher.getRootView().getWindowToken(); - if (windowToken != null) { - mWallpaperManager.setWallpaperZoomOut(windowToken, depth); - } - - if (supportsBlur) { - boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque(); - boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg; - - mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg - ? 0 : (int) (depth * mMaxBlurRadius); - SurfaceControl.Transaction transaction = new SurfaceControl.Transaction() - .setBackgroundBlurRadius(mSurface, mCurrentBlur) - .setOpaque(mSurface, isSurfaceOpaque); - - // Set early wake-up flags when we know we're executing an expensive operation, this way - // SurfaceFlinger will adjust its internal offsets to avoid jank. - boolean wantsEarlyWakeUp = depth > 0 && depth < 1; - if (wantsEarlyWakeUp && !mInEarlyWakeUp) { - transaction.setEarlyWakeupStart(); - mInEarlyWakeUp = true; - } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) { - transaction.setEarlyWakeupEnd(); - mInEarlyWakeUp = false; - } - - AttachedSurfaceControl rootSurfaceControl = - mLauncher.getRootView().getRootSurfaceControl(); - if (rootSurfaceControl != null) { - rootSurfaceControl.applyTransactionOnDraw(transaction); - } - } - return true; + super.applyDepthAndBlur(); } @Override public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { mIgnoreStateChangesDuringMultiWindowAnimation = true; - ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH, + ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, STATE_DEPTH, mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode)) .setDuration(300); mwAnimation.addListener(new AnimatorListenerAdapter() { @@ -377,7 +198,6 @@ public class DepthController implements StateHandler, writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius); writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled); writer.println(prefix + "\tmSurface=" + mSurface); - writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress); writer.println(prefix + "\tmDepth=" + mDepth); writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur); writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch); diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java new file mode 100644 index 0000000000..acfbea38b7 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.util.SparseArray; + +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.quickstep.RecentsModel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Provides recent apps functionality specifically in a desktop environment. + */ +public class DesktopTaskbarRecentAppsController extends TaskbarRecentAppsController { + + private final TaskbarActivityContext mContext; + private ArrayList mRunningApps = new ArrayList<>(); + private AppInfo[] mApps; + + public DesktopTaskbarRecentAppsController(TaskbarActivityContext context) { + mContext = context; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mApps = null; + } + + @Override + protected void setApps(AppInfo[] apps) { + mApps = apps; + } + + @Override + protected boolean isEnabled() { + return true; + } + + /** + * Set mRunningApps to hold currently running applications using the list of currently running + * tasks. Filtering is also done to ignore applications that are already on the taskbar in the + * original hotseat. + */ + @Override + protected void updateRunningApps(SparseArray hotseatItems) { + ArrayList runningApps = getRunningAppsFromTasks(); + ArrayList filteredRunningApps = new ArrayList<>(); + for (AppInfo runningApp : runningApps) { + boolean shouldAddOnTaskbar = true; + for (int i = 0; i < hotseatItems.size(); i++) { + if (hotseatItems.keyAt(i) >= mControllers.taskbarActivityContext.getDeviceProfile() + .numShownHotseatIcons) { + break; + } + if (hotseatItems.valueAt(i).getTargetPackage() + .equals(runningApp.getTargetPackage())) { + shouldAddOnTaskbar = false; + break; + } + } + if (shouldAddOnTaskbar) { + filteredRunningApps.add(new WorkspaceItemInfo(runningApp)); + } + } + mRunningApps = filteredRunningApps; + mControllers.taskbarViewController.commitRunningAppsToUI(); + } + + /** + * Returns a copy of hotseatItems with the addition of currently running applications. + */ + @Override + protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItemInfos) { + // hotseatItemInfos.length would be 0 if deviceProfile.numShownHotseatIcons is 0, so we + // don't want to show anything in the hotseat + if (hotseatItemInfos.length == 0) return hotseatItemInfos; + + int runningAppsIndex = 0; + ItemInfo[] newHotseatItemsInfo = Arrays.copyOf( + hotseatItemInfos, hotseatItemInfos.length + mRunningApps.size()); + for (int i = hotseatItemInfos.length; i < newHotseatItemsInfo.length; i++) { + newHotseatItemsInfo[i] = mRunningApps.get(runningAppsIndex); + runningAppsIndex++; + } + return newHotseatItemsInfo; + } + + + /** + * Returns a list of running applications from the list of currently running tasks. + */ + private ArrayList getRunningAppsFromTasks() { + ArrayList tasks = + RecentsModel.INSTANCE.get(mContext).getRunningTasks(); + ArrayList runningApps = new ArrayList<>(); + // early return if apps is empty, since we would have no AppInfo to compare + if (mApps == null) { + return runningApps; + } + + Set seenPackages = new HashSet<>(); + for (ActivityManager.RunningTaskInfo taskInfo : tasks) { + if (taskInfo.realActivity == null) continue; + + // If a different task for the same package has already been handled, skip this one + String taskPackage = taskInfo.realActivity.getPackageName(); + if (seenPackages.contains(taskPackage)) continue; + + // Otherwise, get the corresponding AppInfo and add it to the list + seenPackages.add(taskPackage); + AppInfo app = getAppInfo(taskInfo.realActivity); + if (app == null) continue; + runningApps.add(app); + } + return runningApps; + } + + /** + * Retrieves the corresponding AppInfo for the activity. + */ + private AppInfo getAppInfo(ComponentName activity) { + String packageName = activity.getPackageName(); + for (AppInfo app : mApps) { + if (!packageName.equals(app.getTargetPackage())) { + continue; + } + return app; + } + return null; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java index bb55a9b15f..633325bf4b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java @@ -16,7 +16,7 @@ package com.android.launcher3.taskbar; -import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.uioverrides.QuickstepLauncher; /** * A data source which integrates with a Launcher instance, used specifically for a @@ -24,26 +24,29 @@ import com.android.launcher3.BaseQuickstepLauncher; */ public class DesktopTaskbarUIController extends TaskbarUIController { - private final BaseQuickstepLauncher mLauncher; + private final QuickstepLauncher mLauncher; - public DesktopTaskbarUIController(BaseQuickstepLauncher launcher) { + public DesktopTaskbarUIController(QuickstepLauncher launcher) { mLauncher = launcher; } @SuppressWarnings("MissingSuperCall") // TODO: Fix me @Override protected void init(TaskbarControllers taskbarControllers) { + super.init(taskbarControllers); mLauncher.getHotseat().setIconsAlpha(0f); + mControllers.taskbarViewController.updateRunningApps(); } @SuppressWarnings("MissingSuperCall") // TODO: Fix me @Override protected void onDestroy() { + super.onDestroy(); mLauncher.getHotseat().setIconsAlpha(1f); } - @Override /** Disable taskbar stashing in desktop environment. */ + @Override public boolean supportsVisualStashing() { return false; } diff --git a/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java new file mode 100644 index 0000000000..5f4d239532 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar; + +import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.R; + +// TODO: This would be replaced by the thing that has the role and provides the intent. +/** + * Helper to determine what intent should be used to display in a floating window, if one + * exists. + */ +public class FloatingTaskIntentResolver { + private static final String TAG = FloatingTaskIntentResolver.class.getSimpleName(); + + @Nullable + /** Gets an intent for a floating task, if one exists. */ + public static Intent getIntent(Context context) { + PackageManager pm = context.getPackageManager(); + String pkg = context.getString(R.string.floating_task_package); + String action = context.getString(R.string.floating_task_action); + if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(action)) { + Log.d(TAG, "intent could not be found, pkg= " + pkg + " action= " + action); + return null; + } + Intent intent = createIntent(pm, null, pkg, action); + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + Log.d(TAG, "No valid intent found!"); + return null; + } + + @Nullable + private static Intent createIntent(PackageManager pm, @Nullable String activityName, + String packageName, String action) { + if (TextUtils.isEmpty(activityName)) { + activityName = queryActivityForAction(pm, packageName, action); + } + if (TextUtils.isEmpty(activityName)) { + Log.d(TAG, "Activity name is empty even after action search: " + action); + return null; + } + ComponentName component = new ComponentName(packageName, activityName); + Intent intent = new Intent(action).setComponent(component).setPackage(packageName); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Log.d(TAG, "createIntent returning: " + intent); + return intent; + } + + @Nullable + private static String queryActivityForAction(PackageManager pm, String packageName, + String action) { + Intent intent = new Intent(action).setPackage(packageName); + ResolveInfo resolveInfo = pm.resolveActivity(intent, MATCH_DEFAULT_ONLY); + if (resolveInfo == null || resolveInfo.activityInfo == null) { + Log.d(TAG, "queryActivityForAction: + " + resolveInfo); + return null; + } + ActivityInfo info = resolveInfo.activityInfo; + if (!info.exported) { + Log.d(TAG, "queryActivityForAction: + " + info + " not exported"); + return null; + } + if (!info.enabled) { + Log.d(TAG, "queryActivityForAction: + " + info + " not enabled"); + return null; + } + return resolveInfo.activityInfo.name; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java new file mode 100644 index 0000000000..b15669b5ef --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.icons.FastBitmapDrawable; + +/** + * Button in Taskbar that opens something in a floating task. + */ +public class LaunchFloatingTaskButton extends BubbleTextView { + + public LaunchFloatingTaskButton(Context context) { + this(context, null); + } + + public LaunchFloatingTaskButton(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LaunchFloatingTaskButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + Context theme = new ContextThemeWrapper(context, R.style.AllAppsButtonTheme); + Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory() + .createScaledBitmapWithShadow( + theme.getDrawable(R.drawable.ic_floating_task_button)); + setIcon(new FastBitmapDrawable(bitmap)); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index ca30e72609..a219ac6324 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -15,8 +15,10 @@ */ package com.android.launcher3.taskbar; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; + import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED; -import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; +import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import android.animation.Animator; import android.animation.AnimatorSet; @@ -30,9 +32,7 @@ import android.view.WindowManagerGlobal; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherState; import com.android.launcher3.QuickstepTransitionManager; @@ -42,6 +42,7 @@ import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.OnboardingPrefs; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.RecentsAnimationCallbacks; @@ -65,7 +66,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { private final SparseArray mTaskbarInAppDisplayProgress = new SparseArray<>(4); - private final BaseQuickstepLauncher mLauncher; + private final QuickstepLauncher mLauncher; private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = dp -> { @@ -81,7 +82,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { private final TaskbarLauncherStateController mTaskbarLauncherStateController = new TaskbarLauncherStateController(); - public LauncherTaskbarUIController(BaseQuickstepLauncher launcher) { + public LauncherTaskbarUIController(QuickstepLauncher launcher) { mLauncher = launcher; } @@ -123,24 +124,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController { shouldDelayLauncherStateAnim); } - /** - * Enables manual taskbar stashing. This method should only be used for tests that need to - * stash/unstash the taskbar. - */ - @VisibleForTesting - public void enableManualStashingForTests(boolean enableManualStashing) { - mControllers.taskbarStashController.enableManualStashingForTests(enableManualStashing); - } - - /** - * Unstashes the Taskbar if it is stashed. This method should only be used to unstash the - * taskbar at the end of a test. - */ - @VisibleForTesting - public void unstashTaskbarIfStashed() { - mControllers.taskbarStashController.onLongPressToUnstashTaskbar(); - } - /** * Adds the Launcher resume animator to the given animator set. * @@ -171,7 +154,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController { isResumed, fromInit, /* startAnimation= */ true, - QuickstepTransitionManager.CONTENT_ALPHA_DURATION); + !isResumed + ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION + : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION); } @Nullable @@ -186,6 +171,13 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } } + if (ENABLE_SHELL_TRANSITIONS + && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) { + // Launcher is resumed, but in a state where taskbar is still independent, so + // ignore the state change. + return null; + } + mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed); return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation); } @@ -351,6 +343,13 @@ public class LauncherTaskbarUIController extends TaskbarUIController { .get(); } + @Override + public void onExpandPip() { + super.onExpandPip(); + mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, false); + mTaskbarLauncherStateController.applyState(); + } + @Override public void dumpLogs(String prefix, PrintWriter pw) { super.dumpLogs(prefix, pw); diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index 5d576f7bf5..1c345a6aa4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -15,15 +15,22 @@ */ package com.android.launcher3.taskbar; +import static android.view.View.AccessibilityDelegate; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; + import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX; +import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode; +import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD; +import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_SMALL_SCREEN; import static com.android.launcher3.taskbar.Utilities.appendFlag; 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; @@ -35,7 +42,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N 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.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; @@ -45,6 +52,7 @@ import android.annotation.LayoutRes; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.graphics.Region; @@ -61,11 +69,13 @@ import android.view.View.OnAttachStateChangeListener; import android.view.View.OnClickListener; import android.view.View.OnHoverListener; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -79,7 +89,6 @@ import com.android.systemui.shared.rotation.FloatingRotationButton; import com.android.systemui.shared.rotation.RotationButton; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.ViewTreeObserverWrapper; import java.io.PrintWriter; import java.util.ArrayList; @@ -93,7 +102,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT private final Rect mTempRect = new Rect(); - private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0; + private static final int FLAG_SWITCHER_SHOWING = 1 << 0; private static final int FLAG_IME_VISIBLE = 1 << 1; private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2; private static final int FLAG_A11Y_VISIBLE = 1 << 3; @@ -105,8 +114,13 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT private static final int FLAG_DISABLE_BACK = 1 << 9; private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10; private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11; + private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12; + private static final int FLAG_SMALL_SCREEN = 1 << 13; + private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14; - private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE; + /** Flags where a UI could be over a slide in view, so the color override should be disabled. */ + private static final int FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED = + FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING; private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons"; @@ -120,12 +134,14 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT private final TaskbarActivityContext mContext; private final FrameLayout mNavButtonsView; - private final ViewGroup mNavButtonContainer; + private final LinearLayout mNavButtonContainer; // Used for IME+A11Y buttons private final ViewGroup mEndContextualContainer; private final ViewGroup mStartContextualContainer; private final int mLightIconColor; private final int mDarkIconColor; + /** Color to use for navigation bar buttons, if a slide in view is visible. */ + private final int mSlideInViewIconColor; private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat( this::updateNavButtonTranslationY); @@ -140,6 +156,9 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT this::updateNavButtonDarkIntensity); private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat( this::updateNavButtonDarkIntensity); + /** Overrides the navigation button color to {@code mSlideInViewIconColor} when {@code 1}. */ + private final AnimatedFloat mSlideInViewNavButtonColorOverride = new AnimatedFloat( + this::updateNavButtonDarkIntensity); private final RotationButtonListener mRotationButtonListener = new RotationButtonListener(); private final Rect mFloatingRotationButtonBounds = new Rect(); @@ -158,7 +177,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT // Variables for moving nav buttons to a separate window above IME private boolean mAreNavButtonsInSeparateWindow = false; private BaseDragLayer mSeparateWindowParent; // Initialized in init. - private final ViewTreeObserverWrapper.OnComputeInsetsListener mSeparateWindowInsetsComputer = + private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer = this::onComputeInsetsForSeparateWindow; private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender(); @@ -171,6 +190,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color); mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color); + // Can precompute color since dark theme change recreates taskbar. + mSlideInViewIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor; } /** @@ -178,9 +199,13 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT */ public void init(TaskbarControllers controllers) { mControllers = controllers; - mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize; - boolean isThreeButtonNav = mContext.isThreeButtonNav(); + DeviceProfile deviceProfile = mContext.getDeviceProfile(); + Resources resources = mContext.getResources(); + mNavButtonsView.getLayoutParams().height = !isPhoneMode(deviceProfile) ? + deviceProfile.taskbarSize : + resources.getDimensionPixelSize(R.dimen.taskbar_size); + mIsImeRenderingNavButtons = InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar(); if (!mIsImeRenderingNavButtons) { @@ -189,7 +214,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer, mControllers.navButtonController, R.id.ime_switcher); mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton, - flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE) + flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0) && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0))); } @@ -199,6 +224,11 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0)); + mPropertyHolders.add(new StatePropertyHolder( + mControllers.taskbarViewController.getTaskbarIconAlpha() + .getProperty(ALPHA_INDEX_SMALL_SCREEN), + flags -> (flags & FLAG_SMALL_SCREEN) == 0)); + mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0)); @@ -207,9 +237,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT boolean isInKidsMode = mContext.isNavBarKidsModeActive(); boolean alwaysShowButtons = isThreeButtonNav || isInSetup; - // Make sure to remove nav bar buttons translation when notification shade is expanded or - // IME is showing (add separate translation for IME). - int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE; + // Make sure to remove nav bar buttons translation when any of the following occur: + // - Notification shade is expanded + // - IME is showing (add separate translation for IME) + // - VoiceInteractionWindow (assistant) is showing + int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE + | FLAG_VOICE_INTERACTION_WINDOW_SHOWING; mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui, flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE, 1, 0)); @@ -222,10 +255,16 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE, transForIme, defaultButtonTransY)); + mPropertyHolders.add(new StatePropertyHolder( + mSlideInViewNavButtonColorOverride, + flags -> ((flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0) + && ((flags & FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED) == 0))); + if (alwaysShowButtons) { initButtons(mNavButtonContainer, mEndContextualContainer, mControllers.navButtonController); - + updateButtonLayoutSpacing(); + updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext)); if (isInSetup) { // Since setup wizard only has back button enabled, it looks strange to be // end-aligned, so start-align instead. @@ -238,18 +277,18 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set // it based on dark theme for now. - int mode = mContext.getResources().getConfiguration().uiMode + int mode = resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES; mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1); } else if (isInKidsMode) { - int iconSize = mContext.getResources().getDimensionPixelSize( + int iconSize = resources.getDimensionPixelSize( R.dimen.taskbar_icon_size_kids); - int buttonWidth = mContext.getResources().getDimensionPixelSize( + int buttonWidth = resources.getDimensionPixelSize( R.dimen.taskbar_nav_buttons_width_kids); - int buttonHeight = mContext.getResources().getDimensionPixelSize( + int buttonHeight = resources.getDimensionPixelSize( R.dimen.taskbar_nav_buttons_height_kids); - int buttonRadius = mContext.getResources().getDimensionPixelSize( + int buttonRadius = resources.getDimensionPixelSize( R.dimen.taskbar_nav_buttons_corner_radius_kids); int paddingleft = (buttonWidth - iconSize) / 2; int paddingRight = paddingleft; @@ -271,7 +310,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT buttonWidth, buttonHeight ); - int homeButtonLeftMargin = mContext.getResources().getDimensionPixelSize( + int homeButtonLeftMargin = resources.getDimensionPixelSize( R.dimen.taskbar_home_button_left_margin_kids); homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0); mHomeButton.setLayoutParams(homeLayoutparams); @@ -281,7 +320,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT buttonWidth, buttonHeight ); - int backButtonLeftMargin = mContext.getResources().getDimensionPixelSize( + int backButtonLeftMargin = resources.getDimensionPixelSize( R.dimen.taskbar_back_button_left_margin_kids); backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0); mBackButton.setLayoutParams(backLayoutParams); @@ -336,7 +375,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT if (!mIsImeRenderingNavButtons) { View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK, mStartContextualContainer, mControllers.navButtonController, R.id.back); - imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90); + imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90); // Only show when IME is visible. mPropertyHolders.add(new StatePropertyHolder(imeDownButton, flags -> (flags & FLAG_IME_VISIBLE) != 0)); @@ -415,7 +454,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT return recentsCoords; }, new Handler()); recentsButton.setOnClickListener(v -> { - navButtonController.onButtonClick(BUTTON_RECENTS); + navButtonController.onButtonClick(BUTTON_RECENTS, v); mHitboxExtender.onRecentsButtonClicked(); }); mPropertyHolders.add(new StatePropertyHolder(recentsButton, @@ -443,22 +482,26 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0; boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0; + boolean isVoiceInteractionWindowShowing = + (sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0; // TODO(b/202218289) we're getting IME as not visible on lockscreen from system updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible); - updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing); + updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing); updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible); updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled); updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled); updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled); updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded); updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive); + updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing); if (mA11yButton != null) { // Only used in 3 button boolean a11yLongClickable = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; mA11yButton.setLongClickable(a11yLongClickable); + updateButtonLayoutSpacing(); } } @@ -473,6 +516,13 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT } } + /** + * @return {@code true} if A11y is showing in 3 button nav taskbar + */ + private boolean isContextualButtonShowing() { + return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0; + } + /** * Should be called when we need to show back button for bouncer */ @@ -491,6 +541,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT applyState(); } + /** {@code true} if a slide in view is currently visible over taskbar. */ + public void setSlideInViewVisible(boolean isSlideInViewVisible) { + updateStateForFlag(FLAG_SLIDE_IN_VIEW_VISIBLE, isSlideInViewVisible); + applyState(); + } + /** * Returns true if IME bar is visible */ @@ -498,6 +554,13 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT return (mState & FLAG_IME_VISIBLE) != 0; } + /** + * Returns true if IME switcher is visible + */ + public boolean isImeSwitcherVisible() { + return (mState & FLAG_SWITCHER_SHOWING) != 0; + } + /** * Returns true if the home button is disabled */ @@ -543,6 +606,26 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT return mHomeButtonAlpha; } + /** + * Sets the AccessibilityDelegate for the home button. + */ + public void setHomeButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) { + if (mHomeButton == null) { + return; + } + mHomeButton.setAccessibilityDelegate(accessibilityDelegate); + } + + /** + * Sets the AccessibilityDelegate for the back button. + */ + public void setBackButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) { + if (mBackButton == null) { + return; + } + mBackButton.setAccessibilityDelegate(accessibilityDelegate); + } + /** Use to set the translationY for the all nav+contextual buttons */ public AnimatedFloat getTaskbarNavButtonTranslationY() { return mTaskbarNavButtonTranslationY; @@ -590,6 +673,9 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT } private void updateNavButtonTranslationY() { + if (isPhoneButtonNavMode(mContext)) { + return; + } final float normalTranslationY = mTaskbarNavButtonTranslationY.value; final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value; TaskbarUIController uiController = mControllers.uiController; @@ -606,8 +692,11 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT private void updateNavButtonDarkIntensity() { float darkIntensity = mTaskbarNavButtonDarkIntensity.value * mNavButtonDarkIntensityMultiplier.value; - int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor, - mDarkIconColor); + ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance(); + int iconColor = (int) argbEvaluator.evaluate( + darkIntensity, mLightIconColor, mDarkIconColor); + iconColor = (int) argbEvaluator.evaluate( + mSlideInViewNavButtonColorOverride.value, iconColor, mSlideInViewIconColor); for (ImageView button : mAllButtons) { button.setImageTintList(ColorStateList.valueOf(iconColor)); } @@ -626,9 +715,9 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT buttonView.setImageResource(drawableId); buttonView.setContentDescription(parent.getContext().getString( navButtonController.getButtonContentDescription(buttonType))); - buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType)); + buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType, view)); buttonView.setOnLongClickListener(view -> - navButtonController.onButtonLongClick(buttonType)); + navButtonController.onButtonLongClick(buttonType, view)); return buttonView; } @@ -649,6 +738,91 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT if (mFloatingRotationButton != null) { mFloatingRotationButton.onConfigurationChanged(configChanges); } + updateButtonLayoutSpacing(); + } + + /** + * Adds the correct spacing to 3 button nav container. No-op if using gesture nav or kids mode. + */ + private void updateButtonLayoutSpacing() { + if (!mContext.isThreeButtonNav() || mContext.isNavBarKidsModeActive()) { + return; + } + + if (isPhoneButtonNavMode(mContext)) { + updatePhoneButtonSpacing(); + return; + } + + DeviceProfile dp = mContext.getDeviceProfile(); + Resources res = mContext.getResources(); + + // Add spacing after the end of the last nav button + FrameLayout.LayoutParams navButtonParams = + (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams(); + navButtonParams.gravity = Gravity.END; + navButtonParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + navButtonParams.height = MATCH_PARENT; + + int navMarginEnd = (int) res.getDimension(dp.inv.inlineNavButtonsEndSpacing); + int contextualWidth = mEndContextualContainer.getWidth(); + // If contextual buttons are showing, we check if the end margin is enough for the + // contextual button to be showing - if not, move the nav buttons over a smidge + if (isContextualButtonShowing() && navMarginEnd < contextualWidth) { + // Additional spacing, eat up half of space between last icon and nav button + navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2; + } + navButtonParams.setMarginEnd(navMarginEnd); + mNavButtonContainer.setLayoutParams(navButtonParams); + + // Add the spaces in between the nav buttons + int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween); + for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) { + View navButton = mNavButtonContainer.getChildAt(i); + LinearLayout.LayoutParams buttonLayoutParams = + (LinearLayout.LayoutParams) navButton.getLayoutParams(); + buttonLayoutParams.weight = 0; + if (i == 0) { + buttonLayoutParams.setMarginEnd(spaceInBetween / 2); + } else if (i == mNavButtonContainer.getChildCount() - 1) { + buttonLayoutParams.setMarginStart(spaceInBetween / 2); + } else { + buttonLayoutParams.setMarginStart(spaceInBetween / 2); + buttonLayoutParams.setMarginEnd(spaceInBetween / 2); + } + } + } + + /** Uniformly spaces out the 3 button nav for smaller phone screens */ + private void updatePhoneButtonSpacing() { + DeviceProfile dp = mContext.getDeviceProfile(); + Resources res = mContext.getResources(); + + // TODO: Polish pending, this is just to make it usable + FrameLayout.LayoutParams navContainerParams = + (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams(); + int endStartMargins = res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size); + navContainerParams.gravity = Gravity.CENTER; + navContainerParams.setMarginEnd(endStartMargins); + navContainerParams.setMarginStart(endStartMargins); + mNavButtonContainer.setLayoutParams(navContainerParams); + + // Add the spaces in between the nav buttons + int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone); + for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) { + View navButton = mNavButtonContainer.getChildAt(i); + LinearLayout.LayoutParams buttonLayoutParams = + (LinearLayout.LayoutParams) navButton.getLayoutParams(); + buttonLayoutParams.weight = 1; + if (i == 0) { + buttonLayoutParams.setMarginEnd(spaceInBetween / 2); + } else if (i == mNavButtonContainer.getChildCount() - 1) { + buttonLayoutParams.setMarginStart(spaceInBetween / 2); + } else { + buttonLayoutParams.setMarginStart(spaceInBetween / 2); + buttonLayoutParams.setMarginEnd(spaceInBetween / 2); + } + } } public void onDestroy() { @@ -659,6 +833,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT } moveNavButtonsBackToTaskbarWindow(); + mNavButtonContainer.removeAllViews(); + mAllButtons.clear(); } /** @@ -677,14 +853,14 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { - ViewTreeObserverWrapper.addOnComputeInsetsListener( - mSeparateWindowParent.getViewTreeObserver(), mSeparateWindowInsetsComputer); + mSeparateWindowParent.getViewTreeObserver().addOnComputeInternalInsetsListener( + mSeparateWindowInsetsComputer); } @Override public void onViewDetachedFromWindow(View view) { mSeparateWindowParent.removeOnAttachStateChangeListener(this); - ViewTreeObserverWrapper.removeOnComputeInsetsListener( + mSeparateWindowParent.getViewTreeObserver().removeOnComputeInternalInsetsListener( mSeparateWindowInsetsComputer); } }); @@ -712,7 +888,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT mContext.getDragLayer().addView(mNavButtonsView); } - private void onComputeInsetsForSeparateWindow(ViewTreeObserverWrapper.InsetsInfo insetsInfo) { + private void onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo) { addVisibleButtonsRegion(mSeparateWindowParent, insetsInfo.touchableRegion); insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); } @@ -721,22 +897,17 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "NavbarButtonsViewController:"); - pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState))); - pw.println(String.format( - "%s\tmLightIconColor=0x%s", prefix, Integer.toHexString(mLightIconColor))); - pw.println(String.format( - "%s\tmDarkIconColor=0x%s", prefix, Integer.toHexString(mDarkIconColor))); - pw.println(String.format( - "%s\tmFloatingRotationButtonBounds=%s", prefix, mFloatingRotationButtonBounds)); - pw.println(String.format( - "%s\tmSysuiStateFlags=%s", - prefix, - QuickStepContract.getSystemUiStateString(mSysuiStateFlags))); + pw.println(prefix + "\tmState=" + getStateString(mState)); + pw.println(prefix + "\tmLightIconColor=" + Integer.toHexString(mLightIconColor)); + pw.println(prefix + "\tmDarkIconColor=" + Integer.toHexString(mDarkIconColor)); + pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds); + pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString( + mSysuiStateFlags)); } private static String getStateString(int flags) { StringJoiner str = new StringJoiner("|"); - appendFlag(str, flags, FLAG_SWITCHER_SUPPORTED, "FLAG_SWITCHER_SUPPORTED"); + appendFlag(str, flags, FLAG_SWITCHER_SHOWING, "FLAG_SWITCHER_SHOWING"); appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE"); appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE"); appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE"); @@ -750,6 +921,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT appendFlag(str, flags, FLAG_NOTIFICATION_SHADE_EXPANDED, "FLAG_NOTIFICATION_SHADE_EXPANDED"); appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE"); + appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING, + "FLAG_VOICE_INTERACTION_WINDOW_SHOWING"); return str.toString(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java index b7978076c1..6b67b50fd8 100644 --- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java @@ -25,6 +25,7 @@ import android.graphics.Rect; import android.view.View; import android.view.ViewOutlineProvider; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.RevealOutlineAnimation; @@ -43,7 +44,8 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT public static final int ALPHA_INDEX_STASHED = 0; public static final int ALPHA_INDEX_HOME_DISABLED = 1; - private static final int NUM_ALPHA_CHANNELS = 2; + public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2; + private static final int NUM_ALPHA_CHANNELS = 3; /** * The SharedPreferences key for whether the stashed handle region is dark. @@ -54,9 +56,9 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT private final TaskbarActivityContext mActivity; private final SharedPreferences mPrefs; private final StashedHandleView mStashedHandleView; - private final int mStashedHandleWidth; + private int mStashedHandleWidth; private final int mStashedHandleHeight; - private final RegionSamplingHelper mRegionSamplingHelper; + private RegionSamplingHelper mRegionSamplingHelper; private final MultiValueAlpha mTaskbarStashedHandleAlpha; private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat( this::updateStashedHandleHintScale); @@ -84,30 +86,27 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false), false /* animate */); final Resources resources = mActivity.getResources(); - mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width); mStashedHandleHeight = resources.getDimensionPixelSize( R.dimen.taskbar_stashed_handle_height); - mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView, - new RegionSamplingHelper.SamplingCallback() { - @Override - public void onRegionDarknessChanged(boolean isRegionDark) { - mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */); - mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, - isRegionDark).apply(); - } - - @Override - public Rect getSampledRegion(View sampledView) { - return mStashedHandleView.getSampledRegion(); - } - }, Executors.UI_HELPER_EXECUTOR); } public void init(TaskbarControllers controllers) { mControllers = controllers; - mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + DeviceProfile deviceProfile = mActivity.getDeviceProfile(); + Resources resources = mActivity.getResources(); + if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) { + mStashedHandleView.getLayoutParams().height = + resources.getDimensionPixelSize(R.dimen.taskbar_size); + mStashedHandleWidth = + resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen); + } else { + mStashedHandleView.getLayoutParams().height = deviceProfile.taskbarSize; + mStashedHandleWidth = + resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width); + } - mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(0); + mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue( + isPhoneGestureNavMode(deviceProfile) ? 1 : 0); mTaskbarStashedHandleHintScale.updateValue(1f); final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight(); @@ -134,10 +133,37 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT view.setPivotX(stashedCenterX); view.setPivotY(stashedCenterY); }); + initRegionSampler(); + if (isPhoneGestureNavMode(deviceProfile)) { + onIsStashedChanged(true); + } } + private void initRegionSampler() { + mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView, + new RegionSamplingHelper.SamplingCallback() { + @Override + public void onRegionDarknessChanged(boolean isRegionDark) { + mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */); + mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, + isRegionDark).apply(); + } + + @Override + public Rect getSampledRegion(View sampledView) { + return mStashedHandleView.getSampledRegion(); + } + }, Executors.UI_HELPER_EXECUTOR); + } + + public void onDestroy() { mRegionSamplingHelper.stopAndDestroy(); + mRegionSamplingHelper = null; + } + + private boolean isPhoneGestureNavMode(DeviceProfile deviceProfile) { + return TaskbarManager.isPhoneMode(deviceProfile) && !mActivity.isThreeButtonNav(); } public MultiValueAlpha getStashedHandleAlpha() { @@ -208,10 +234,9 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "StashedHandleViewController:"); - pw.println(String.format( - "%s\tisStashedHandleVisible=%b", prefix, isStashedHandleVisible())); - pw.println(String.format("%s\tmStashedHandleWidth=%dpx", prefix, mStashedHandleWidth)); - pw.println(String.format("%s\tmStashedHandleHeight=%dpx", prefix, mStashedHandleHeight)); + pw.println(prefix + "\tisStashedHandleVisible=" + isStashedHandleVisible()); + pw.println(prefix + "\tmStashedHandleWidth=" + mStashedHandleWidth); + pw.println(prefix + "\tmStashedHandleHeight=" + mStashedHandleHeight); mRegionSamplingHelper.dump(prefix, pw); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index dc2e3b624c..9d15ea88c3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -23,10 +23,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; -import static com.android.launcher3.ResourceUtils.getBoolByName; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN; +import static com.android.launcher3.taskbar.TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW; +import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; import android.animation.AnimatorSet; import android.animation.ValueAnimator; @@ -75,10 +77,10 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.TraceHelper; @@ -117,7 +119,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext { // The size we should return to when we call setTaskbarWindowFullscreen(false) private int mLastRequestedNonFullscreenHeight; - private final NavigationMode mNavMode; + private NavigationMode mNavMode; private final boolean mImeDrawsImeNavBar; private final ViewCache mViewCache = new ViewCache(); @@ -129,6 +131,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { // The flag to know if the window is excluded from magnification region computation. private boolean mIsExcludeFromMagnificationRegion = false; private boolean mBindingItems = false; + private boolean mAddedWindow = false; + private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate; @@ -172,13 +176,15 @@ public class TaskbarActivityContext extends BaseTaskbarContext { mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this); + final boolean isDesktopMode = getPackageManager().hasSystemFeature(FEATURE_PC); + // Construct controllers. mControllers = new TaskbarControllers(this, new TaskbarDragController(this), buttonController, - getPackageManager().hasSystemFeature(FEATURE_PC) - ? new DesktopNavbarButtonsViewController(this, navButtonsView) : - new NavbarButtonsViewController(this, navButtonsView), + isDesktopMode + ? new DesktopNavbarButtonsViewController(this, navButtonsView) + : new NavbarButtonsViewController(this, navButtonsView), new RotationButtonController(this, c.getColor(R.color.taskbar_nav_icon_light_color), c.getColor(R.color.taskbar_nav_icon_dark_color), @@ -200,7 +206,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new TaskbarPopupController(this), new TaskbarForceVisibleImmersiveController(this), new TaskbarAllAppsController(this, dp), - new TaskbarInsetsController(this)); + new TaskbarInsetsController(this), + new VoiceInteractionWindowController(this), + isDesktopMode + ? new DesktopTaskbarRecentAppsController(this) + : TaskbarRecentAppsController.DEFAULT); } public void init(@NonNull TaskbarSharedState sharedState) { @@ -211,7 +221,12 @@ public class TaskbarActivityContext extends BaseTaskbarContext { mControllers.init(sharedState); updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */); - mWindowManager.addView(mDragLayer, mWindowLayoutParams); + if (!mAddedWindow) { + mWindowManager.addView(mDragLayer, mWindowLayoutParams); + mAddedWindow = true; + } else { + mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams); + } } @Override @@ -220,7 +235,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { } /** Updates {@link DeviceProfile} instances for any Taskbar windows. */ - public void updateDeviceProfile(DeviceProfile dp) { + public void updateDeviceProfile(DeviceProfile dp, NavigationMode navMode) { + mNavMode = navMode; mControllers.taskbarAllAppsController.updateDeviceProfile(dp); mDeviceProfile = dp.copy(this); updateIconSize(getResources()); @@ -246,14 +262,23 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return super.getStatsLogManager(); } - /** Creates LayoutParams for adding a view directly to WindowManager as a new window */ + /** @see #createDefaultWindowLayoutParams(int) */ public WindowManager.LayoutParams createDefaultWindowLayoutParams() { + return createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL); + } + + /** + * Creates LayoutParams for adding a view directly to WindowManager as a new window. + * @param type The window type to pass to the created WindowManager.LayoutParams. + */ + public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type) { WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams( MATCH_PARENT, mLastRequestedNonFullscreenHeight, - TYPE_NAVIGATION_BAR_PANEL, + type, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_SLIPPERY, + | WindowManager.LayoutParams.FLAG_SLIPPERY + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); windowLayoutParams.setTitle(WINDOW_TITLE); windowLayoutParams.packageName = getPackageName(); @@ -445,7 +470,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { mIsDestroyed = true; setUIController(TaskbarUIController.DEFAULT); mControllers.onDestroy(); - mWindowManager.removeViewImmediate(mDragLayer); + if (!FLAG_HIDE_NAVBAR_WINDOW) { + mWindowManager.removeViewImmediate(mDragLayer); + mAddedWindow = false; + } } public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) { @@ -453,6 +481,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { fromInit); mControllers.taskbarViewController.setImeIsVisible( mControllers.navbarButtonsViewController.isImeVisible()); + mControllers.taskbarViewController.setIsImeSwitcherVisible( + mControllers.navbarButtonsViewController.isImeSwitcherVisible()); int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0, fromInit); @@ -468,6 +498,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { fromInit); mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags); mControllers.taskbarForceVisibleImmersiveController.updateSysuiFlags(systemUiStateFlags); + mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible( + (systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit); } /** @@ -576,6 +608,12 @@ public class TaskbarActivityContext extends BaseTaskbarContext { * Returns the default height of the window, including the static corner radii above taskbar. */ public int getDefaultTaskbarWindowHeight() { + if (FLAG_HIDE_NAVBAR_WINDOW && mDeviceProfile.isPhone) { + Resources resources = getResources(); + return isThreeButtonNav() ? + resources.getDimensionPixelSize(R.dimen.taskbar_size) : + resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size); + } return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius()); } @@ -607,12 +645,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext { /** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */ public void addWindowView(View view, WindowManager.LayoutParams windowLayoutParams) { - mWindowManager.addView(view, windowLayoutParams); + if (!view.isAttachedToWindow()) { + mWindowManager.addView(view, windowLayoutParams); + } } /** Removes the given view from WindowManager. See {@link #addWindowView}. */ public void removeWindowView(View view) { - mWindowManager.removeViewImmediate(view); + if (view.isAttachedToWindow()) { + mWindowManager.removeViewImmediate(view); + } } protected void onTaskbarIconClicked(View view) { @@ -731,6 +773,24 @@ public class TaskbarActivityContext extends BaseTaskbarContext { mControllers.taskbarStashController.startUnstashHint(animateForward); } + /** + * Enables manual taskbar stashing. This method should only be used for tests that need to + * stash/unstash the taskbar. + */ + @VisibleForTesting + public void enableManualStashingDuringTests(boolean enableManualStashing) { + mControllers.taskbarStashController.enableManualStashingDuringTests(enableManualStashing); + } + + /** + * Unstashes the Taskbar if it is stashed. This method should only be used to unstash the + * taskbar at the end of a test. + */ + @VisibleForTesting + public void unstashTaskbarIfStashed() { + mControllers.taskbarStashController.onLongPressToUnstashTaskbar(); + } + protected boolean isUserSetupComplete() { return mIsUserSetupComplete; } @@ -808,6 +868,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { btv.post(() -> mControllers.taskbarPopupController.showForIcon(btv)); } + public boolean isInApp() { + return mControllers.taskbarStashController.isInApp(); + } + protected void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarActivityContext:"); @@ -822,6 +886,6 @@ public class TaskbarActivityContext extends BaseTaskbarContext { pw.println(String.format( "%s\tmBindInProgress=%b", prefix, mBindingItems)); mControllers.dumpLogs(prefix + "\t", pw); - mDeviceProfile.dump(prefix, pw); + mDeviceProfile.dump(this, prefix, pw); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java index c5615c7ba1..3cf9c997c9 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java @@ -71,8 +71,7 @@ public class TaskbarAutohideSuspendController implements public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarAutohideSuspendController:"); - pw.println(String.format( - "%s\tmAutohideSuspendFlags=%s", prefix, getStateString(mAutohideSuspendFlags))); + pw.println(prefix + "\tmAutohideSuspendFlags=" + getStateString(mAutohideSuspendFlags)); } private static String getStateString(int flags) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index 449e0a7311..2b80b753fb 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -52,6 +52,8 @@ public class TaskbarControllers { public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController; public final TaskbarAllAppsController taskbarAllAppsController; public final TaskbarInsetsController taskbarInsetsController; + public final VoiceInteractionWindowController voiceInteractionWindowController; + public final TaskbarRecentAppsController taskbarRecentAppsController; @Nullable private LoggableTaskbarController[] mControllersToLog = null; @@ -80,7 +82,9 @@ public class TaskbarControllers { TaskbarPopupController taskbarPopupController, TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController, TaskbarAllAppsController taskbarAllAppsController, - TaskbarInsetsController taskbarInsetsController) { + TaskbarInsetsController taskbarInsetsController, + VoiceInteractionWindowController voiceInteractionWindowController, + TaskbarRecentAppsController taskbarRecentAppsController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -99,6 +103,8 @@ public class TaskbarControllers { this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController; this.taskbarAllAppsController = taskbarAllAppsController; this.taskbarInsetsController = taskbarInsetsController; + this.voiceInteractionWindowController = voiceInteractionWindowController; + this.taskbarRecentAppsController = taskbarRecentAppsController; } /** @@ -126,13 +132,16 @@ public class TaskbarControllers { taskbarAllAppsController.init(this, sharedState.allAppsVisible); navButtonController.init(this); taskbarInsetsController.init(this); + voiceInteractionWindowController.init(this); + taskbarRecentAppsController.init(this); mControllersToLog = new LoggableTaskbarController[] { taskbarDragController, navButtonController, navbarButtonsViewController, taskbarDragLayerController, taskbarScrimViewController, taskbarViewController, taskbarUnfoldAnimationController, taskbarKeyguardController, stashedHandleViewController, taskbarStashController, taskbarEduController, - taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController + taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController, + voiceInteractionWindowController }; mAreAllControllersInitialized = true; @@ -172,6 +181,8 @@ public class TaskbarControllers { taskbarAllAppsController.onDestroy(); navButtonController.onDestroy(); taskbarInsetsController.onDestroy(); + voiceInteractionWindowController.onDestroy(); + taskbarRecentAppsController.onDestroy(); mControllersToLog = null; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java index c522888fce..9a1e0642bb 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import android.animation.Animator; @@ -31,6 +32,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.UserHandle; +import android.util.Pair; import android.view.DragEvent; import android.view.MotionEvent; import android.view.SurfaceControl; @@ -41,7 +43,6 @@ import android.window.SurfaceSyncer; import androidx.annotation.Nullable; import com.android.internal.logging.InstanceId; -import com.android.internal.logging.InstanceIdSequence; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DragSource; @@ -65,9 +66,10 @@ import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.quickstep.util.LogUtils; import com.android.systemui.shared.recents.model.Task; import java.io.PrintWriter; @@ -358,11 +360,11 @@ public class TaskbarDragController extends DragController im } if (clipDescription != null && intent != null) { + Pair instanceIds = + LogUtils.getShellShareableInstanceId(); // Need to share the same InstanceId between launcher3 and WM Shell (internal). - InstanceId internalInstanceId = new InstanceIdSequence( - com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId(); - com.android.launcher3.logging.InstanceId launcherInstanceId = - new com.android.launcher3.logging.InstanceId(internalInstanceId.getId()); + InstanceId internalInstanceId = instanceIds.first; + com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second; intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId); @@ -435,7 +437,7 @@ public class TaskbarDragController extends DragController im if (tag instanceof ItemInfo) { ItemInfo item = (ItemInfo) tag; TaskbarViewController taskbarViewController = mControllers.taskbarViewController; - if (item.container == CONTAINER_ALL_APPS) { + if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) { // Since all apps closes when the drag starts, target the all apps button instead. target = taskbarViewController.getAllAppsButtonView(); } else if (item.container >= 0) { @@ -558,13 +560,11 @@ public class TaskbarDragController extends DragController im public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarDragController:"); - pw.println(String.format("%s\tmDragIconSize=%dpx", prefix, mDragIconSize)); - pw.println(String.format("%s\tmTempXY=%s", prefix, Arrays.toString(mTempXY))); - pw.println(String.format("%s\tmRegistrationX=%d", prefix, mRegistrationX)); - pw.println(String.format("%s\tmRegistrationY=%d", prefix, mRegistrationY)); - pw.println(String.format( - "%s\tmIsSystemDragInProgress=%b", prefix, mIsSystemDragInProgress)); - pw.println(String.format( - "%s\tisInternalDragInProgess=%b", prefix, super.isDragging())); + pw.println(prefix + "\tmDragIconSize=" + mDragIconSize); + pw.println(prefix + "\tmTempXY=" + Arrays.toString(mTempXY)); + pw.println(prefix + "\tmRegistrationX=" + mRegistrationX); + pw.println(prefix + "\tmRegistrationY=" + mRegistrationY); + pw.println(prefix + "\tmIsSystemDragInProgress=" + mIsSystemDragInProgress); + pw.println(prefix + "\tisInternalDragInProgess=" + super.isDragging()); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java index c1a6185d23..7e75779c62 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java @@ -24,17 +24,15 @@ import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewTreeObserver; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.views.BaseDragLayer; -import com.android.systemui.shared.system.ViewTreeObserverWrapper; -import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; -import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener; /** * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder. @@ -42,7 +40,8 @@ import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInset public class TaskbarDragLayer extends BaseDragLayer { private final TaskbarBackgroundRenderer mBackgroundRenderer; - private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets; + private final ViewTreeObserver.OnComputeInternalInsetsListener mTaskbarInsetsComputer = + this::onComputeTaskbarInsets; // Initialized in init. private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks; @@ -80,28 +79,33 @@ public class TaskbarDragLayer extends BaseDragLayer { mControllers = mControllerCallbacks.getTouchControllers(); } - private void onComputeTaskbarInsets(InsetsInfo insetsInfo) { + private void onComputeTaskbarInsets(ViewTreeObserver.InternalInsetsInfo insetsInfo) { if (mControllerCallbacks != null) { mControllerCallbacks.updateInsetsTouchability(insetsInfo); } } + protected void onDestroy(boolean forceDestroy) { + if (forceDestroy) { + getViewTreeObserver().removeOnComputeInternalInsetsListener(mTaskbarInsetsComputer); + } + } + protected void onDestroy() { - ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer); + onDestroy(!TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(), - mTaskbarInsetsComputer); + getViewTreeObserver().addOnComputeInternalInsetsListener(mTaskbarInsetsComputer); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - onDestroy(); + onDestroy(true); } @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java index 99c59a8a98..025fe7a017 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java @@ -17,11 +17,12 @@ package com.android.launcher3.taskbar; import android.content.res.Resources; import android.graphics.Rect; +import android.view.ViewTreeObserver; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.util.TouchController; import com.android.quickstep.AnimatedFloat; -import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; import java.io.PrintWriter; @@ -144,10 +145,9 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarDragLayerController:"); - pw.println(String.format("%s\tmBgOffset=%.2f", prefix, mBgOffset.value)); - pw.println(String.format("%s\tmFolderMargin=%dpx", prefix, mFolderMargin)); - pw.println(String.format( - "%s\tmLastSetBackgroundAlpha=%.2f", prefix, mLastSetBackgroundAlpha)); + pw.println(prefix + "\tmBgOffset=" + mBgOffset.value); + pw.println(prefix + "\tmFolderMargin=" + mFolderMargin); + pw.println(prefix + "\tmLastSetBackgroundAlpha=" + mLastSetBackgroundAlpha); } /** @@ -157,9 +157,9 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa /** * Called to update the touchable insets. - * @see InsetsInfo#setTouchableInsets(int) + * @see ViewTreeObserver.InternalInsetsInfo#setTouchableInsets(int) */ - public void updateInsetsTouchability(InsetsInfo insetsInfo) { + public void updateInsetsTouchability(ViewTreeObserver.InternalInsetsInfo insetsInfo) { mControllers.taskbarInsetsController.updateInsetsTouchability(insetsInfo); } @@ -174,7 +174,15 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa * Returns how tall the background should be drawn at the bottom of the screen. */ public int getTaskbarBackgroundHeight() { - return mActivity.getDeviceProfile().taskbarSize; + DeviceProfile deviceProfile = mActivity.getDeviceProfile(); + if (TaskbarManager.isPhoneMode(deviceProfile)) { + Resources resources = mActivity.getResources(); + return mActivity.isThreeButtonNav() ? + resources.getDimensionPixelSize(R.dimen.taskbar_size) : + resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size); + } else { + return deviceProfile.taskbarSize; + } } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java index e29b14b76f..454a2a4569 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java @@ -90,6 +90,9 @@ public class TaskbarEduController implements TaskbarControllers.LoggableTaskbarC mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate( R.layout.taskbar_edu, mActivity.getDragLayer(), false); mTaskbarEduView.init(new TaskbarEduCallbacks()); + mControllers.navbarButtonsViewController.setSlideInViewVisible(true); + mTaskbarEduView.setOnCloseBeginListener( + () -> mControllers.navbarButtonsViewController.setSlideInViewVisible(false)); mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null); mTaskbarEduView.show(); startAnim(createWaveAnim()); @@ -191,12 +194,10 @@ public class TaskbarEduController implements TaskbarControllers.LoggableTaskbarC public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarEduController:"); - pw.println(String.format("%s\tisShowingEdu=%b", prefix, mTaskbarEduView != null)); - pw.println(String.format("%s\tmWaveAnimTranslationY=%.2f", prefix, mWaveAnimTranslationY)); - pw.println(String.format( - "%s\tmWaveAnimTranslationYReturnOvershoot=%.2f", - prefix, - mWaveAnimTranslationYReturnOvershoot)); + pw.println(prefix + "\tisShowingEdu=" + (mTaskbarEduView != null)); + pw.println(prefix + "\tmWaveAnimTranslationY=" + mWaveAnimTranslationY); + pw.println(prefix + "\tmWaveAnimTranslationYReturnOvershoot=" + + mWaveAnimTranslationYReturnOvershoot); } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java index c99cebb1a7..6c793a6119 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java @@ -18,13 +18,17 @@ package com.android.launcher3.taskbar; import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static com.android.launcher3.taskbar.NavbarButtonsViewController.ALPHA_INDEX_IMMERSIVE_MODE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.MotionEvent; +import android.view.View; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.util.MultiValueAlpha; @@ -52,6 +56,21 @@ public class TaskbarForceVisibleImmersiveController implements TouchController { this::updateIconDimmingAlpha); private final Consumer mImmersiveModeAlphaUpdater = alpha -> alpha.getProperty( ALPHA_INDEX_IMMERSIVE_MODE).setValue(mIconAlphaForDimming.value); + private final View.AccessibilityDelegate mKidsModeAccessibilityDelegate = + new View.AccessibilityDelegate() { + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (action == ACTION_ACCESSIBILITY_FOCUS || action == ACTION_CLICK) { + // Animate undimming of icons on an a11y event, followed by starting the + // dimming animation (after its timeout has expired). Both can be called in + // succession, as the playing of the two animations in a row is managed by + // mHandler's message queue. + startIconUndimming(); + startIconDimming(); + } + return super.performAccessibilityAction(host, action, args); + } + }; // Initialized in init. private TaskbarControllers mControllers; @@ -77,12 +96,21 @@ public class TaskbarForceVisibleImmersiveController implements TouchController { } else { startIconUndimming(); } + mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate( + mKidsModeAccessibilityDelegate); + mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate( + mKidsModeAccessibilityDelegate); + } else { + mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null); + mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null); } } /** Clean up animations. */ public void onDestroy() { startIconUndimming(); + mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null); + mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null); } private void startIconUndimming() { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt index 6a6a693dc3..7b2b7eca7f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt @@ -17,17 +17,23 @@ package com.android.launcher3.taskbar import android.graphics.Insets import android.graphics.Region +import android.view.InsetsFrameProvider import android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES +import android.view.InsetsState +import android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT +import android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR +import android.view.ViewTreeObserver +import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME +import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION import android.view.WindowManager +import android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD +import android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION import com.android.launcher3.AbstractFloatingView import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS import com.android.launcher3.DeviceProfile import com.android.launcher3.anim.AlphaUpdateListener import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController import com.android.quickstep.KtR -import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo -import com.android.systemui.shared.system.WindowManagerWrapper -import com.android.systemui.shared.system.WindowManagerWrapper.* import java.io.PrintWriter /** @@ -51,8 +57,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTask this.controllers = controllers windowLayoutParams = context.windowLayoutParams - val wmWrapper: WindowManagerWrapper = getInstance() - wmWrapper.setProvidesInsetsTypes( + setProvidesInsetsTypes( windowLayoutParams, intArrayOf( ITYPE_EXTRA_NAVIGATION_BAR, @@ -61,9 +66,6 @@ class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTask ) ) - windowLayoutParams.providedInternalInsets = arrayOfNulls(ITYPE_SIZE) - windowLayoutParams.providedInternalImeInsets = arrayOfNulls(ITYPE_SIZE) - onTaskbarWindowHeightOrInsetsChanged() windowLayoutParams.insetsRoundedCornerFrame = true @@ -75,39 +77,54 @@ class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTask } fun onTaskbarWindowHeightOrInsetsChanged() { - var reducingSize = getReducingInsetsForTaskbarInsetsHeight( - controllers.taskbarStashController.contentHeightToReportToApps) + var contentHeight = controllers.taskbarStashController.contentHeightToReportToApps + contentRegion.set(0, windowLayoutParams.height - contentHeight, + context.deviceProfile.widthPx, windowLayoutParams.height) + var tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps + for (provider in windowLayoutParams.providedInsets) { + if (provider.type == ITYPE_EXTRA_NAVIGATION_BAR) { + provider.insetsSize = Insets.of(0, 0, 0, contentHeight) + } else if (provider.type == ITYPE_BOTTOM_TAPPABLE_ELEMENT + || provider.type == ITYPE_BOTTOM_MANDATORY_GESTURES) { + provider.insetsSize = Insets.of(0, 0, 0, tappableHeight) + } + } - contentRegion.set(0, reducingSize.top, - context.deviceProfile.widthPx, windowLayoutParams.height) - windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize - windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize - reducingSize = getReducingInsetsForTaskbarInsetsHeight( - controllers.taskbarStashController.tappableHeightToReportToApps) - windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize - windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize - - reducingSize = getReducingInsetsForTaskbarInsetsHeight(taskbarHeightForIme) - windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize - windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize - windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_MANDATORY_GESTURES] = reducingSize + val imeInsetsSize = Insets.of(0, 0, 0, taskbarHeightForIme) + // Use 0 insets for the VoiceInteractionWindow (assistant) when gesture nav is enabled. + val visInsetsSize = Insets.of(0, 0, 0, if (context.isGestureNav) 0 else tappableHeight) + val insetsSizeOverride = arrayOf( + InsetsFrameProvider.InsetsSizeOverride( + TYPE_INPUT_METHOD, + imeInsetsSize + ), + InsetsFrameProvider.InsetsSizeOverride( + TYPE_VOICE_INTERACTION, + visInsetsSize + ) + ) + for (provider in windowLayoutParams.providedInsets) { + provider.insetsSizeOverrides = insetsSizeOverride + } } /** - * WindowLayoutParams.providedInternal*Insets expects Insets that subtract from the window frame - * height (i.e. WindowLayoutParams#height). So for Taskbar to report bottom insets to apps, it - * actually provides insets from the top of its window frame. - * @param height The number of pixels from the bottom of the screen that Taskbar insets. + * Sets {@param providesInsetsTypes} as the inset types provided by {@param params}. + * @param params The window layout params. + * @param providesInsetsTypes The inset types we would like this layout params to provide. */ - private fun getReducingInsetsForTaskbarInsetsHeight(height: Int): Insets { - return Insets.of(0, windowLayoutParams.height - height, 0, 0) + fun setProvidesInsetsTypes(params: WindowManager.LayoutParams, providesInsetsTypes: IntArray) { + params.providedInsets = arrayOfNulls(providesInsetsTypes.size); + for (i in providesInsetsTypes.indices) { + params.providedInsets[i] = InsetsFrameProvider(providesInsetsTypes[i]); + } } /** * Called to update the touchable insets. - * @see InsetsInfo.setTouchableInsets + * @see InternalInsetsInfo.setTouchableInsets */ - fun updateInsetsTouchability(insetsInfo: InsetsInfo) { + fun updateInsetsTouchability(insetsInfo: ViewTreeObserver.InternalInsetsInfo) { insetsInfo.touchableRegion.setEmpty() // Always have nav buttons be touchable controllers.navbarButtonsViewController.addVisibleButtonsRegion( @@ -116,18 +133,18 @@ class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTask var insetsIsTouchableRegion = true if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) { // Let touches pass through us. - insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION) + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION) } else if (controllers.navbarButtonsViewController.isImeVisible) { - insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION) + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION) } else if (!controllers.uiController.isTaskbarTouchable) { // Let touches pass through us. - insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION) + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION) } else if (controllers.taskbarDragController.isSystemDragInProgress) { // Let touches pass through us. - insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION) + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION) } else if (AbstractFloatingView.hasOpenView(context, TYPE_TASKBAR_ALL_APPS)) { // Let touches pass through us. - insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION) + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION) } else if (controllers.taskbarViewController.areIconsVisible() || AbstractFloatingView.hasOpenView(context, AbstractFloatingView.TYPE_ALL) || context.isNavBarKidsModeActive @@ -135,15 +152,15 @@ class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTask // Taskbar has some touchable elements, take over the full taskbar area insetsInfo.setTouchableInsets( if (context.isTaskbarWindowFullscreen) { - InsetsInfo.TOUCHABLE_INSETS_FRAME + TOUCHABLE_INSETS_FRAME } else { insetsInfo.touchableRegion.set(contentRegion) - InsetsInfo.TOUCHABLE_INSETS_REGION + TOUCHABLE_INSETS_REGION } ) insetsIsTouchableRegion = false } else { - insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION) + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION) } context.excludeFromMagnificationRegion(insetsIsTouchableRegion) } @@ -151,13 +168,18 @@ class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTask override fun dumpLogs(prefix: String, pw: PrintWriter) { pw.println(prefix + "TaskbarInsetsController:") pw.println("$prefix\twindowHeight=${windowLayoutParams.height}") - pw.println("$prefix\tprovidedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" + - "${windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]}") - pw.println("$prefix\tprovidedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" + - "${windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]}") - pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" + - "${windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]}") - pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" + - "${windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]}") + for (provider in windowLayoutParams.providedInsets) { + pw.print("$prefix\tprovidedInsets: (type=" + InsetsState.typeToString(provider.type) + + " insetsSize=" + provider.insetsSize) + if (provider.insetsSizeOverrides != null) { + pw.print(" insetsSizeOverrides={") + for ((i, overrideSize) in provider.insetsSizeOverrides.withIndex()) { + if (i > 0) pw.print(", ") + pw.print(overrideSize) + } + pw.print("})") + } + pw.println() + } } -} \ No newline at end of file +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java index 56648eac38..0808faba1c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java @@ -111,11 +111,9 @@ public class TaskbarKeyguardController implements TaskbarControllers.LoggableTas public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarKeyguardController:"); - pw.println(String.format( - "%s\tmKeyguardSysuiFlags=%s", - prefix, - QuickStepContract.getSystemUiStateString(mKeyguardSysuiFlags))); - pw.println(String.format("%s\tmBouncerShowing=%b", prefix, mBouncerShowing)); - pw.println(String.format("%s\tmIsScreenOff=%b", prefix, mIsScreenOff)); + pw.println(prefix + "\tmKeyguardSysuiFlags=" + QuickStepContract.getSystemUiStateString( + mKeyguardSysuiFlags)); + pw.println(prefix + "\tmBouncerShowing=" + mBouncerShowing); + pw.println(prefix + "\tmIsScreenOff=" + mIsScreenOff); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index dc0ef27ba1..58c689bc36 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -19,20 +19,25 @@ import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP; import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE; import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION; import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME; +import static com.android.systemui.animation.Interpolators.EMPHASIZED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherState; +import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.statemanager.StateManager; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.MultiValueAlpha; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.RecentsAnimationCallbacks; @@ -53,6 +58,9 @@ import java.util.function.Supplier; */ public class TaskbarLauncherStateController { + private static final String TAG = TaskbarLauncherStateController.class.getSimpleName(); + private static final boolean DEBUG = false; + public static final int FLAG_RESUMED = 1 << 0; public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1; public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2; @@ -70,7 +78,7 @@ import java.util.function.Supplier; private TaskbarControllers mControllers; private AnimatedFloat mTaskbarBackgroundAlpha; private MultiValueAlpha.AlphaProperty mIconAlphaForHome; - private BaseQuickstepLauncher mLauncher; + private QuickstepLauncher mLauncher; private Integer mPrevState; private int mState; @@ -86,6 +94,19 @@ import java.util.function.Supplier; // We skip any view synchronizations during init/destroy. private boolean mCanSyncViews; + private final Consumer mIconAlphaForHomeConsumer = alpha -> { + /* + * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets + * should not be visible at the same time. + */ + mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1); + mLauncher.getHotseat().setQsbAlpha( + mLauncher.getDeviceProfile().isQsbInline && alpha > 0 ? 0 : 1); + }; + + private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = + dp -> mIconAlphaForHomeConsumer.accept(mIconAlphaForHome.getValue()); + private final StateManager.StateListener mStateListener = new StateManager.StateListener() { @@ -99,7 +120,11 @@ import java.util.function.Supplier; } updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true); if (!mShouldDelayLauncherStateAnim) { - applyState(); + if (toState == LauncherState.NORMAL) { + applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION); + } else { + applyState(); + } } } @@ -111,7 +136,7 @@ import java.util.function.Supplier; } }; - public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) { + public void init(TaskbarControllers controllers, QuickstepLauncher launcher) { mCanSyncViews = false; mControllers = controllers; @@ -121,8 +146,7 @@ import java.util.function.Supplier; .getTaskbarBackgroundAlpha(); MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha(); mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); - mIconAlphaForHome.setConsumer( - (Consumer) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1)); + mIconAlphaForHome.setConsumer(mIconAlphaForHomeConsumer); mIconAlignmentForResumedState.finishAnimation(); onIconAlignmentRatioChangedForAppAndHomeTransition(); @@ -135,6 +159,7 @@ import java.util.function.Supplier; applyState(0); mCanSyncViews = true; + mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); } public void onDestroy() { @@ -149,6 +174,7 @@ import java.util.function.Supplier; mLauncher.getStateManager().removeStateListener(mStateListener); mCanSyncViews = true; + mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); } public Animator createAnimToLauncher(@NonNull LauncherState toState, @@ -169,10 +195,8 @@ import java.util.function.Supplier; mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks); callbacks.addListener(mTaskBarRecentsAnimationListener); - RecentsView recentsView = mLauncher.getOverviewPanel(); - recentsView.setTaskLaunchListener(() -> { - mTaskBarRecentsAnimationListener.endGestureStateOverride(true); - }); + ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() -> + mTaskBarRecentsAnimationListener.endGestureStateOverride(true)); return animatorSet; } @@ -269,6 +293,11 @@ import java.util.function.Supplier; ObjectAnimator resumeAlignAnim = mIconAlignmentForResumedState .animateToValue(toAlignmentForResumedState) .setDuration(duration); + if (DEBUG) { + Log.d(TAG, "mIconAlignmentForResumedState - " + + mIconAlignmentForResumedState.value + + " -> " + toAlignmentForResumedState + ": " + duration); + } resumeAlignAnim.addListener(new AnimatorListenerAdapter() { @Override @@ -305,6 +334,11 @@ import java.util.function.Supplier; if (isRecentsAnimationRunning) { gestureAlignAnim.setDuration(duration); } + if (DEBUG) { + Log.d(TAG, "mIconAlignmentForGestureState - " + + mIconAlignmentForGestureState.value + + " -> " + toAlignmentForGestureState + ": " + duration); + } gestureAlignAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -330,6 +364,7 @@ import java.util.function.Supplier; .setDuration(duration)); } + animatorSet.setInterpolator(EMPHASIZED); if (start) { animatorSet.start(); } @@ -344,11 +379,14 @@ import java.util.function.Supplier; private void playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed) { boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); - float toAlignment = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) ? 1 : 0; + boolean willStashVisually = + isInStashedState && mControllers.taskbarStashController.supportsVisualStashing(); + float toAlignment = + mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) && !willStashVisually ? 1 : 0; - TaskbarStashController controller = mControllers.taskbarStashController; - controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState); - Animator stashAnimator = controller.applyStateWithoutStart(duration); + TaskbarStashController stashController = mControllers.taskbarStashController; + stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState); + Animator stashAnimator = stashController.applyStateWithoutStart(duration); if (stashAnimator != null) { stashAnimator.addListener(new AnimatorListenerAdapter() { @Override @@ -368,12 +406,22 @@ import java.util.function.Supplier; }); animatorSet.play(stashAnimator); } - - // If we're already animating to the value, just leave it be instead of restarting it. + if (mIconAlignmentForLauncherState.value == toAlignment) { + // Already at expected value, but make sure we run the callback at the end. + animatorSet.addListener(AnimatorListeners.forEndCallback( + this::onIconAlignmentRatioChangedForStateTransition)); + } if (!mIconAlignmentForLauncherState.isAnimatingToValue(toAlignment)) { + // If we're already animating to the value, just leave it be instead of restarting it. mIconAlignmentForLauncherState.finishAnimation(); animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment) .setDuration(duration)); + if (DEBUG) { + Log.d(TAG, "mIconAlignmentForLauncherState - " + + mIconAlignmentForLauncherState.value + + " -> " + toAlignment + ": " + duration); + } + animatorSet.setInterpolator(EMPHASIZED); } } @@ -396,17 +444,17 @@ import java.util.function.Supplier; onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome); } - private void onIconAlignmentRatioChanged(Supplier alignmentSupplier) { + private void onIconAlignmentRatioChanged(Supplier alignmentSupplier) { if (mControllers == null) { return; } - float alignment = alignmentSupplier.get(); + AnimatedFloat animatedFloat = alignmentSupplier.get(); float currentValue = mIconAlphaForHome.getValue(); - boolean taskbarWillBeVisible = alignment < 1; + boolean taskbarWillBeVisible = animatedFloat.value < 1; boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0) || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0); - updateIconAlignment(alignment); + updateIconAlignment(animatedFloat.value, animatedFloat.getEndValue()); // Sync the first frame where we swap taskbar and hotseat. if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) { @@ -416,21 +464,22 @@ import java.util.function.Supplier; } } - private void updateIconAlignment(float alignment) { + private void updateIconAlignment(float alignment, Float endAlignment) { mControllers.taskbarViewController.setLauncherIconAlignment( - alignment, mLauncher.getDeviceProfile()); + alignment, endAlignment, mLauncher.getDeviceProfile()); // Switch taskbar and hotseat in last frame setTaskbarViewVisible(alignment < 1); mControllers.navbarButtonsViewController.updateTaskbarAlignment(alignment); } - private float getCurrentIconAlignmentRatioBetweenAppAndHome() { - return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value); + private AnimatedFloat getCurrentIconAlignmentRatioBetweenAppAndHome() { + return mIconAlignmentForResumedState.value > mIconAlignmentForGestureState.value + ? mIconAlignmentForResumedState : mIconAlignmentForGestureState; } - private float getCurrentIconAlignmentRatioForLauncherState() { - return mIconAlignmentForLauncherState.value; + private AnimatedFloat getCurrentIconAlignmentRatioForLauncherState() { + return mIconAlignmentForLauncherState; } private void setTaskbarViewVisible(boolean isVisible) { @@ -459,6 +508,7 @@ import java.util.function.Supplier; private void endGestureStateOverride(boolean finishedToApp) { mCallbacks.removeListener(this); mTaskBarRecentsAnimationListener = null; + ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null); // Update the resumed state immediately to ensure a seamless handoff boolean launcherResumed = !finishedToApp; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index 06262c0a25..bc69088416 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -30,19 +30,22 @@ import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Handler; +import android.os.SystemProperties; import android.provider.Settings; import android.view.Display; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DisplayController; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.quickstep.RecentsActivity; @@ -58,6 +61,9 @@ import java.io.PrintWriter; */ public class TaskbarManager { + public static final boolean FLAG_HIDE_NAVBAR_WINDOW = + SystemProperties.getBoolean("persist.wm.debug.hide_navbar_window", false); + private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor( Settings.Secure.USER_SETUP_COMPLETE); @@ -78,6 +84,7 @@ public class TaskbarManager { // It's destruction/creation will be managed by the activity. private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider = new NonDestroyableScopedUnfoldTransitionProgressProvider(); + private NavigationMode mNavMode; private TaskbarActivityContext mTaskbarActivityContext; private StatefulActivity mActivity; @@ -128,9 +135,11 @@ public class TaskbarManager { | ActivityInfo.CONFIG_SCREEN_SIZE; boolean requiresRecreate = (configDiff & configsRequiringRecreate) != 0; if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0 - && mTaskbarActivityContext != null && dp != null) { + && mTaskbarActivityContext != null && dp != null + && !isPhoneMode(dp)) { // Additional check since this callback gets fired multiple times w/o // screen size changing, or when simply rotating the device. + // In the case of phone device rotation, we do want to call recreateTaskbar() DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile(); boolean isOrientationChange = (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0; @@ -147,8 +156,8 @@ public class TaskbarManager { } else { // Config change might be handled without re-creating the taskbar if (mTaskbarActivityContext != null) { - if (dp != null && dp.isTaskbarPresent) { - mTaskbarActivityContext.updateDeviceProfile(dp); + if (dp != null && isTaskbarPresent(dp)) { + mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode); } mTaskbarActivityContext.onConfigurationChanged(configDiff); } @@ -159,12 +168,15 @@ public class TaskbarManager { @Override public void onLowMemory() { } }; - mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar()); + mShutdownReceiver = new SimpleBroadcastReceiver(i -> + destroyExistingTaskbar()); mDispInfoChangeListener = (context, info, flags) -> { if ((flags & CHANGE_FLAGS) != 0) { + mNavMode = info.navigationMode; recreateTaskbar(); } }; + mNavMode = mDisplayController.getInfo().navigationMode; mDisplayController.addChangeListener(mDispInfoChangeListener); SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI, mUserSetupCompleteListener); @@ -179,7 +191,9 @@ public class TaskbarManager { private void destroyExistingTaskbar() { if (mTaskbarActivityContext != null) { mTaskbarActivityContext.onDestroy(); - mTaskbarActivityContext = null; + if (!FLAG_HIDE_NAVBAR_WINDOW) { + mTaskbarActivityContext = null; + } } } @@ -225,8 +239,8 @@ public class TaskbarManager { */ private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity( StatefulActivity activity) { - if (activity instanceof BaseQuickstepLauncher) { - return ((BaseQuickstepLauncher) activity).getUnfoldTransitionProgressProvider(); + if (activity instanceof QuickstepLauncher) { + return ((QuickstepLauncher) activity).getUnfoldTransitionProgressProvider(); } return null; } @@ -235,11 +249,11 @@ public class TaskbarManager { * Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active. */ private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) { - if (activity instanceof BaseQuickstepLauncher) { + if (activity instanceof QuickstepLauncher) { if (mTaskbarActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) { - return new DesktopTaskbarUIController((BaseQuickstepLauncher) activity); + return new DesktopTaskbarUIController((QuickstepLauncher) activity); } - return new LauncherTaskbarUIController((BaseQuickstepLauncher) activity); + return new LauncherTaskbarUIController((QuickstepLauncher) activity); } if (activity instanceof RecentsActivity) { return new FallbackTaskbarUIController((RecentsActivity) activity); @@ -260,24 +274,33 @@ public class TaskbarManager { } } - private void recreateTaskbar() { + /** + * This method is called multiple times (ex. initial init, then when user unlocks) in which case + * we fully want to destroy an existing taskbar and create a new one. + * In other case (folding/unfolding) we don't need to remove and add window. + */ + @VisibleForTesting + public void recreateTaskbar() { + DeviceProfile dp = mUserUnlocked ? + LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null; + destroyExistingTaskbar(); - DeviceProfile dp = - mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null; - - boolean isTaskBarEnabled = dp != null && dp.isTaskbarPresent; - + boolean isTaskBarEnabled = dp != null && isTaskbarPresent(dp); if (!isTaskBarEnabled) { SystemUiProxy.INSTANCE.get(mContext) .notifyTaskbarStatus(/* visible */ false, /* stashed */ false); return; } - mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController, - mUnfoldProgressProvider); - + if (mTaskbarActivityContext == null) { + mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController, + mUnfoldProgressProvider); + } else { + mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode); + } mTaskbarActivityContext.init(mSharedState); + if (mActivity != null) { mTaskbarActivityContext.setUIController( createTaskbarUIControllerForActivity(mActivity)); @@ -301,6 +324,26 @@ public class TaskbarManager { } } + /** + * @return {@code true} if provided device profile isn't a large screen profile + * and we are using a single window for taskbar and navbar. + */ + public static boolean isPhoneMode(DeviceProfile deviceProfile) { + return TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW && deviceProfile.isPhone; + } + + /** + * @return {@code true} if {@link #isPhoneMode(DeviceProfile)} is true and we're using + * 3 button-nav + */ + public static boolean isPhoneButtonNavMode(TaskbarActivityContext context) { + return isPhoneMode(context.getDeviceProfile()) && context.isThreeButtonNav(); + } + + private boolean isTaskbarPresent(DeviceProfile deviceProfile) { + return FLAG_HIDE_NAVBAR_WINDOW || deviceProfile.isTaskbarPresent; + } + public void onRotationProposal(int rotation, boolean isValid) { if (mTaskbarActivityContext != null) { mTaskbarActivityContext.onRotationProposal(rotation, isValid); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java index 75881a31f2..5e670d2946 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java @@ -29,6 +29,7 @@ import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LauncherBindableItemsContainer; +import com.android.quickstep.RecentsModel; import java.io.PrintWriter; import java.util.ArrayList; @@ -42,7 +43,7 @@ import java.util.function.Predicate; * Launcher model Callbacks for rendering taskbar. */ public class TaskbarModelCallbacks implements - BgDataModel.Callbacks, LauncherBindableItemsContainer { + BgDataModel.Callbacks, LauncherBindableItemsContainer, RecentsModel.RunningTasksListener { private final SparseArray mHotseatItems = new SparseArray<>(); private List mPredictedItems = Collections.emptyList(); @@ -61,6 +62,16 @@ public class TaskbarModelCallbacks implements public void init(TaskbarControllers controllers) { mControllers = controllers; + if (mControllers.taskbarRecentAppsController.isEnabled()) { + RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this); + } + } + + /** + * Unregisters listeners in this class. + */ + public void unregisterListeners() { + RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener(); } @Override @@ -185,6 +196,8 @@ public class TaskbarModelCallbacks implements isHotseatEmpty = false; } } + hotseatItemInfos = mControllers.taskbarRecentAppsController + .updateHotseatItemInfos(hotseatItemInfos); mContainer.updateHotseatItems(hotseatItemInfos); final boolean finalIsHotseatEmpty = isHotseatEmpty; @@ -195,6 +208,21 @@ public class TaskbarModelCallbacks implements }); } + @Override + public void onRunningTasksChanged() { + updateRunningApps(); + } + + /** Called when there's a change in running apps to update the UI. */ + public void commitRunningAppsToUI() { + commitItemsToUI(); + } + + /** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */ + public void updateRunningApps() { + mControllers.taskbarRecentAppsController.updateRunningApps(mHotseatItems); + } + @Override public void bindDeepShortcutMap(HashMap deepShortcutMapCopy) { mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy); @@ -203,6 +231,7 @@ public class TaskbarModelCallbacks implements @Override public void bindAllApplications(AppInfo[] apps, int flags) { mControllers.taskbarAllAppsController.setApps(apps, flags); + mControllers.taskbarRecentAppsController.setApps(apps); } protected void dumpLogs(String prefix, PrintWriter pw) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index 4ff0649440..a395548f84 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -18,7 +18,6 @@ package com.android.launcher3.taskbar; import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY; -import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS; @@ -28,11 +27,14 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.os.Bundle; import android.os.Handler; import android.util.Log; +import android.view.HapticFeedbackConstants; +import android.view.View; import androidx.annotation.IntDef; import androidx.annotation.Nullable; @@ -41,7 +43,7 @@ import androidx.annotation.StringRes; import com.android.launcher3.R; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.quickstep.OverviewCommandHelper; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskUtils; @@ -70,9 +72,8 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarNavButtonController:"); - pw.println(String.format( - "%s\tmLastScreenPinLongPress=%dms", prefix, mLastScreenPinLongPress)); - pw.println(String.format("%s\tmScreenPinned=%b", prefix, mScreenPinned)); + pw.println(prefix + "\tmLastScreenPinLongPress=" + mLastScreenPinLongPress); + pw.println(prefix + "\tmScreenPinned=" + mScreenPinned); } @Retention(RetentionPolicy.SOURCE) @@ -113,7 +114,9 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa mHandler = handler; } - public void onButtonClick(@TaskbarButton int buttonType) { + public void onButtonClick(@TaskbarButton int buttonType, View view) { + // Provide the same haptic feedback that the system offers for virtual keys. + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); switch (buttonType) { case BUTTON_BACK: logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP); @@ -144,7 +147,9 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa } } - public boolean onButtonLongClick(@TaskbarButton int buttonType) { + public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) { + // Provide the same haptic feedback that the system offers for virtual keys. + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); switch (buttonType) { case BUTTON_HOME: logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java index 7b4501a003..da6dab1e10 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java @@ -15,14 +15,20 @@ */ package com.android.launcher3.taskbar; +import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition; + +import android.content.ClipDescription; import android.content.Intent; import android.content.pm.LauncherApps; import android.graphics.Point; +import android.os.Bundle; +import android.util.Pair; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; +import com.android.internal.logging.InstanceId; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.LauncherSettings; @@ -47,6 +53,7 @@ import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.views.ActivityContext; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.LogUtils; import java.io.PrintWriter; import java.util.HashMap; @@ -263,8 +270,15 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba @Override public void onClick(View view) { - AbstractFloatingView.closeAllOpenViews(mTarget); + // Initiate splitscreen from the in-app Taskbar or Taskbar All Apps + Pair instanceIds = + LogUtils.getShellShareableInstanceId(); + mTarget.getStatsLogManager().logger() + .withItemInfo(mItemInfo) + .withInstanceId(instanceIds.second) + .log(getLogEventForPosition(mPosition.stagePosition)); + AbstractFloatingView.closeAllOpenViews(mTarget); if (mItemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo; SystemUiProxy.INSTANCE.get(mTarget).startShortcut( @@ -272,7 +286,8 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba workspaceItemInfo.getDeepShortcutId(), mPosition.stagePosition, null, - workspaceItemInfo.user); + workspaceItemInfo.user, + instanceIds.first); } else { SystemUiProxy.INSTANCE.get(mTarget).startIntent( mTarget.getSystemService(LauncherApps.class).getMainActivityLaunchIntent( @@ -281,7 +296,8 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba mItemInfo.user), new Intent(), mPosition.stagePosition, - null); + null, + instanceIds.first); } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java new file mode 100644 index 0000000000..8445cff0ee --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import android.util.SparseArray; + +import androidx.annotation.CallSuper; + +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfo; + +/** + * Base class for providing recent apps functionality + */ +public class TaskbarRecentAppsController { + + public static final TaskbarRecentAppsController DEFAULT = new TaskbarRecentAppsController(); + + // Initialized in init. + protected TaskbarControllers mControllers; + + @CallSuper + protected void init(TaskbarControllers taskbarControllers) { + mControllers = taskbarControllers; + } + + @CallSuper + protected void onDestroy() { + mControllers = null; + } + + /** Stores the current {@link AppInfo} instances, no-op except in desktop environment. */ + protected void setApps(AppInfo[] apps) { } + + /** + * Indicates whether recent apps functionality is enabled, should return false except in + * desktop environment. + */ + protected boolean isEnabled() { + return false; + } + + /** Called to update hotseatItems, no-op except in desktop environment. */ + protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItems) { + return hotseatItems; + } + + /** Called to update the list of currently running apps, no-op except in desktop environment. */ + protected void updateRunningApps(SparseArray hotseatItems) { } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java index 58ace17787..c3b0f57c2d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java @@ -98,6 +98,6 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarScrimViewController:"); - pw.println(String.format("%s\tmScrimAlpha.value=%.2f", prefix, mScrimAlpha.value)); + pw.println(prefix + "\tmScrimAlpha.value=" + mScrimAlpha.value); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java index f131595126..c10b57ab87 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java @@ -17,24 +17,29 @@ package com.android.launcher3.taskbar; import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DEEP_SHORTCUTS; import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.SHORTCUTS_AND_NOTIFICATIONS; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; +import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; +import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition; import android.content.Intent; import android.content.pm.LauncherApps; +import android.util.Pair; import android.view.KeyEvent; import android.view.View; +import com.android.internal.logging.InstanceId; import com.android.launcher3.BubbleTextView; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.accessibility.BaseAccessibilityDelegate; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.util.ShortcutUtil; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.LogUtils; import java.util.List; @@ -49,10 +54,12 @@ public class TaskbarShortcutMenuAccessibilityDelegate public static final int MOVE_TO_BOTTOM_OR_RIGHT = R.id.action_move_to_bottom_or_right; private final LauncherApps mLauncherApps; + private final StatsLogManager mStatsLogManager; public TaskbarShortcutMenuAccessibilityDelegate(TaskbarActivityContext context) { super(context); mLauncherApps = context.getSystemService(LauncherApps.class); + mStatsLogManager = context.getStatsLogManager(); mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS, R.string.action_deep_shortcut, KeyEvent.KEYCODE_S)); @@ -82,7 +89,14 @@ public class TaskbarShortcutMenuAccessibilityDelegate && (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) { WorkspaceItemInfo info = (WorkspaceItemInfo) item; int side = action == MOVE_TO_TOP_OR_LEFT - ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT; + ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT; + + Pair instanceIds = + LogUtils.getShellShareableInstanceId(); + mStatsLogManager.logger() + .withItemInfo(item) + .withInstanceId(instanceIds.second) + .log(getLogEventForPosition(side)); if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { SystemUiProxy.INSTANCE.get(mContext).startShortcut( @@ -90,14 +104,15 @@ public class TaskbarShortcutMenuAccessibilityDelegate info.getDeepShortcutId(), side, /* bundleOpts= */ null, - info.user); + info.user, + instanceIds.first); } else { SystemUiProxy.INSTANCE.get(mContext).startIntent( mLauncherApps.getMainActivityLaunchIntent( item.getIntent().getComponent(), /* startActivityOptions= */null, item.user), - new Intent(), side, null); + new Intent(), side, null, instanceIds.first); } return true; } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index fc9f9d002b..4b0adb192f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -22,6 +22,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW; import static com.android.launcher3.taskbar.Utilities.appendFlag; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.animation.Animator; @@ -29,19 +30,23 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.annotation.Nullable; import android.content.SharedPreferences; +import android.content.res.Resources; import android.util.Log; +import android.view.InsetsController; +import android.view.View; import android.view.ViewConfiguration; -import android.view.WindowInsets; +import androidx.annotation.NonNull; + +import com.android.internal.jank.InteractionJankMonitor; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorListeners; -import com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SystemUiProxy; -import com.android.systemui.shared.system.WindowManagerWrapper; import java.io.PrintWriter; import java.util.StringJoiner; @@ -53,6 +58,8 @@ import java.util.function.IntPredicate; */ public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController { + private static final String TAG = "TaskbarStashController"; + public static final int FLAG_IN_APP = 1 << 0; public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning @@ -62,6 +69,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6; public static final int FLAG_STASHED_IN_APP_ALL_APPS = 1 << 7; // All apps is visible. public static final int FLAG_IN_SETUP = 1 << 8; // In the Setup Wizard + public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 9; // phone screen gesture nav, stashed // If any of these flags are enabled, isInApp should return true. private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP; @@ -69,7 +77,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba // If we're in an app and any of these flags are enabled, taskbar should be stashed. private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP - | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_APP_ALL_APPS; + | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_APP_ALL_APPS | + FLAG_STASHED_SMALL_SCREEN; private static final int FLAGS_STASHED_IN_APP_IGNORING_IME = FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME; @@ -85,7 +94,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * How long to stash/unstash when manually invoked via long press. */ public static final long TASKBAR_STASH_DURATION = - WindowManagerWrapper.ANIMATION_DURATION_RESIZE; + InsetsController.ANIMATION_DURATION_RESIZE; /** * How long to stash/unstash when keyboard is appearing/disappearing. @@ -149,8 +158,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba private @Nullable AnimatorSet mAnimator; private boolean mIsSystemGestureInProgress; private boolean mIsImeShowing; + private boolean mIsImeSwitcherShowing; - private boolean mEnableManualStashingForTests = false; + private boolean mEnableManualStashingDuringTests = false; // Evaluate whether the handle should be stashed private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder( @@ -158,15 +168,25 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP); boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP); boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE); - return (inApp && stashedInApp) || (!inApp && stashedLauncherState); + boolean stashedForSmallScreen = hasAnyFlag(flags, FLAG_STASHED_SMALL_SCREEN); + return (inApp && stashedInApp) || (!inApp && stashedLauncherState) + || stashedForSmallScreen; }); public TaskbarStashController(TaskbarActivityContext activity) { mActivity = activity; mPrefs = Utilities.getPrefs(mActivity); mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity); - mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize; - mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarSize; + if (isPhoneMode()) { + // DeviceProfile's taskbar vars aren't initialized w/ the flag off + Resources resources = mActivity.getResources(); + mUnstashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_size); + mStashedHeight = resources.getDimensionPixelOffset(R.dimen.taskbar_stashed_size); + } else { + mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize; + mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarSize; + } + } public void init(TaskbarControllers controllers, boolean setupUIVisible) { @@ -188,13 +208,21 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba StashedHandleViewController.ALPHA_INDEX_STASHED); mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale(); - boolean isManuallyStashedInApp = supportsManualStashing() + // We use supportsVisualStashing() here instead of supportsManualStashing() because we want + // it to work properly for tests that recreate taskbar. This check is here just to ensure + // that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false). + boolean isManuallyStashedInApp = supportsVisualStashing() && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF); boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible; updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp); updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup); updateStateForFlag(FLAG_IN_SETUP, isInSetup); - applyState(); + updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode() + && !mActivity.isThreeButtonNav()); + // For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell + // us that we're paused until a bit later. This avoids flickering upon recreating taskbar. + updateStateForFlag(FLAG_IN_APP, true); + applyState(/* duration = */ 0); notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp()); } @@ -204,7 +232,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * state. */ public boolean supportsVisualStashing() { - return mControllers.uiController.supportsVisualStashing(); + return !mActivity.isThreeButtonNav() && mControllers.uiController.supportsVisualStashing(); } /** @@ -212,15 +240,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba */ protected boolean supportsManualStashing() { return supportsVisualStashing() - && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingForTests); + && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests); } /** * Enables support for manual stashing. This should only be used to add this functionality * to Launcher specific tests. */ - public void enableManualStashingForTests(boolean enableManualStashing) { - mEnableManualStashingForTests = enableManualStashing; + public void enableManualStashingDuringTests(boolean enableManualStashing) { + mEnableManualStashingDuringTests = enableManualStashing; } /** @@ -258,7 +286,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * Returns whether the taskbar should be stashed in the current LauncherState. */ public boolean isInStashedLauncherState() { - return hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing(); + return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing()); + } + + /** + * @return {@code true} if we're not on a large screen AND using gesture nav + */ + private boolean isPhoneMode() { + return TaskbarManager.isPhoneMode(mActivity.getDeviceProfile()); } private boolean hasAnyFlag(int flagMask) { @@ -287,6 +322,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * @see WindowInsets.Type#systemBars() */ public int getContentHeightToReportToApps() { + if (isPhoneMode() && !mActivity.isThreeButtonNav()) { + return getStashedHeight(); + } + if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) { DeviceProfile dp = mActivity.getDeviceProfile(); if (hasAnyFlag(FLAG_STASHED_IN_APP_SETUP) && dp.isTaskbarPresent && !dp.isLandscape) { @@ -401,11 +440,19 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba mAnimator.cancel(); } mAnimator = new AnimatorSet(); + addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed); + final float stashTranslation = isPhoneMode() ? 0 : + (mUnstashedHeight - mStashedHeight) / 2f; if (!supportsVisualStashing()) { // Just hide/show the icons and background instead of stashing into a handle. mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1) .setDuration(duration)); + mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0) + .setDuration(duration)); + mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed ? + stashTranslation : 0) + .setDuration(duration)); mAnimator.play(mTaskbarImeBgAlpha.animateToValue( hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration)); mAnimator.setStartDelay(startDelay); @@ -429,7 +476,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba if (isStashed) { firstHalfDurationScale = 0.75f; secondHalfDurationScale = 0.5f; - final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f; fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation)); if (animateBg) { @@ -441,7 +487,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba firstHalfAnimatorSet.playTogether( mIconAlphaForStash.animateToValue(0), - mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE) + mIconScaleForStash.animateToValue(isPhoneMode() ? + 0 : STASHED_TASKBAR_SCALE) ); secondHalfAnimatorSet.playTogether( mTaskbarStashedHandleAlpha.animateToValue(1) @@ -496,6 +543,22 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba }); } + private void addJankMonitorListener(AnimatorSet animator, boolean expanding) { + View v = mControllers.taskbarActivityContext.getDragLayer(); + int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND : + InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE; + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(@NonNull Animator animation) { + InteractionJankMonitor.getInstance().begin(v, action); + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + InteractionJankMonitor.getInstance().end(action); + } + }); + } /** * Creates and starts a partial stash animation, hinting at the new state that will trigger when * long press is detected. @@ -571,9 +634,11 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } // Only update the following flags when system gesture is not in progress. - maybeResetStashedInAppAllApps(hasAnyFlag(FLAG_STASHED_IN_APP_IME) == mIsImeShowing); - if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != mIsImeShowing) { - updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing); + boolean shouldStashForIme = shouldStashForIme(); + maybeResetStashedInAppAllApps( + hasAnyFlag(FLAG_STASHED_IN_APP_IME) == shouldStashForIme); + if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) { + updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme); applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme()); } } @@ -625,8 +690,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress. mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING); + mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING); if (!mIsSystemGestureInProgress) { - updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing); + updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme()); animDuration = TASKBAR_STASH_DURATION_FOR_IME; startDelay = getTaskbarStashStartDelayForIme(); } @@ -634,6 +700,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay); } + private boolean shouldStashForIme() { + return mIsImeShowing || mIsImeSwitcherShowing; + } + /** * Updates the proper flag to indicate whether the task bar should be stashed. * @@ -690,15 +760,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarStashController:"); - pw.println(String.format("%s\tmStashedHeight=%dpx", prefix, mStashedHeight)); - pw.println(String.format("%s\tmUnstashedHeight=%dpx", prefix, mUnstashedHeight)); - pw.println(String.format("%s\tmIsStashed=%b", prefix, mIsStashed)); - pw.println(String.format( - "%s\tappliedState=%s", prefix, getStateString(mStatePropertyHolder.mPrevFlags))); - pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState))); - pw.println(String.format( - "%s\tmIsSystemGestureInProgress=%b", prefix, mIsSystemGestureInProgress)); - pw.println(String.format("%s\tmIsImeShowing=%b", prefix, mIsImeShowing)); + pw.println(prefix + "\tmStashedHeight=" + mStashedHeight); + pw.println(prefix + "\tmUnstashedHeight=" + mUnstashedHeight); + pw.println(prefix + "\tmIsStashed=" + mIsStashed); + pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags)); + pw.println(prefix + "\tmState=" + getStateString(mState)); + pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress); + pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing); + pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing); } private static String getStateString(int flags) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index fcc34c6d99..114bfecb6e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -49,9 +49,13 @@ public class TaskbarUIController { return true; } + /** + * This should only be called by TaskbarStashController so that a TaskbarUIController can + * disable stashing. All other controllers should use + * {@link TaskbarStashController#supportsVisualStashing()} as the source of truth. + */ public boolean supportsVisualStashing() { - if (mControllers == null) return false; - return !mControllers.taskbarActivityContext.isThreeButtonNav(); + return true; } protected void onStashedInAppChanged() { } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 6f88d649fb..bb82d19734 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -16,10 +16,14 @@ package com.android.launcher3.taskbar; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.SystemProperties; import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -31,6 +35,7 @@ import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; import com.android.launcher3.BubbleTextView; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -40,7 +45,6 @@ import com.android.launcher3.icons.ThemedIconDrawable; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.AllAppsButton; @@ -52,6 +56,7 @@ import java.util.function.Predicate; * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. */ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable { + private static final String TAG = TaskbarView.class.getSimpleName(); private static final float TASKBAR_BACKGROUND_LUMINANCE = 0.30f; public int mThemeIconsBackground; @@ -78,6 +83,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar // Only non-null when device supports having an All Apps button. private @Nullable AllAppsButton mAllAppsButton; + private View mQsb; + + // Only non-null when device supports having a floating task. + private @Nullable BubbleTextView mFloatingTaskButton; + private @Nullable Intent mFloatingTaskIntent; + private static final boolean FLOATING_TASKS_ENABLED = + SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); + public TaskbarView(@NonNull Context context) { this(context, null); } @@ -117,6 +130,22 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize)); mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding); } + + // TODO: Disable touch events on QSB otherwise it can crash. + mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false); + + if (FLOATING_TASKS_ENABLED) { + mFloatingTaskIntent = FloatingTaskIntentResolver.getIntent(context); + if (mFloatingTaskIntent != null) { + mFloatingTaskButton = new LaunchFloatingTaskButton(context); + mFloatingTaskButton.setLayoutParams( + new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize)); + mFloatingTaskButton.setPadding(mItemPadding, mItemPadding, mItemPadding, + mItemPadding); + } else { + Log.d(TAG, "Floating tasks is enabled but no intent was found!"); + } + } } private int getColorWithGivenLuminance(int color, float luminance) { @@ -144,6 +173,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar if (mAllAppsButton != null) { mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener()); } + if (mFloatingTaskButton != null) { + mFloatingTaskButton.setOnClickListener( + mControllerCallbacks.getFloatingTaskButtonListener(mFloatingTaskIntent)); + } } private void removeAndRecycle(View view) { @@ -166,6 +199,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar if (mAllAppsButton != null) { removeView(mAllAppsButton); } + removeView(mQsb); + + if (mFloatingTaskButton != null) { + removeView(mFloatingTaskButton); + } for (int i = 0; i < hotseatItemInfos.length; i++) { ItemInfo hotseatItemInfo = hotseatItemInfos[i]; @@ -242,6 +280,16 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar int index = Utilities.isRtl(getResources()) ? 0 : getChildCount(); addView(mAllAppsButton, index); } + if (mActivityContext.getDeviceProfile().isQsbInline) { + addView(mQsb, Utilities.isRtl(getResources()) ? getChildCount() : 0); + // Always set QSB to invisible after re-adding. + mQsb.setVisibility(View.INVISIBLE); + } + + if (mFloatingTaskButton != null) { + int index = Utilities.isRtl(getResources()) ? 0 : getChildCount(); + addView(mFloatingTaskButton, index); + } mThemeIconsBackground = calculateThemeIconsBackground(); setThemedIconsBackgroundColor(mThemeIconsBackground); @@ -273,8 +321,13 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int count = getChildCount(); - int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize); - int navSpaceNeeded = ApiWrapper.getHotseatEndOffset(getContext()); + int countExcludingQsb = count; + DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); + if (deviceProfile.isQsbInline) { + countExcludingQsb--; + } + int spaceNeeded = countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize); + int navSpaceNeeded = deviceProfile.hotseatBarEndOffset; boolean layoutRtl = isLayoutRtl(); int iconEnd = right - (right - left - spaceNeeded) / 2; boolean needMoreSpaceForNav = layoutRtl ? @@ -292,10 +345,25 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize; for (int i = count; i > 0; i--) { View child = getChildAt(i - 1); - iconEnd -= mItemMarginLeftRight; - int iconStart = iconEnd - mIconTouchSize; - child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom); - iconEnd = iconStart - mItemMarginLeftRight; + if (child == mQsb) { + int qsbStart; + int qsbEnd; + if (layoutRtl) { + qsbStart = iconEnd + mItemMarginLeftRight; + qsbEnd = qsbStart + deviceProfile.hotseatQsbWidth; + } else { + qsbEnd = iconEnd - mItemMarginLeftRight; + qsbStart = qsbEnd - deviceProfile.hotseatQsbWidth; + } + int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2; + int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight; + child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom); + } else { + iconEnd -= mItemMarginLeftRight; + int iconStart = iconEnd - mIconTouchSize; + child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom); + iconEnd = iconStart - mItemMarginLeftRight; + } } mIconLayoutBounds.left = iconEnd; } @@ -367,6 +435,13 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar return mAllAppsButton; } + /** + * Returns the QSB in the taskbar. + */ + public View getQsb() { + return mQsb; + } + // FolderIconParent implemented methods. @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 3562f5bc3c..00d5083dd3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -16,12 +16,15 @@ package com.android.launcher3.taskbar; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP; +import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode; import static com.android.quickstep.AnimatedFloat.VALUE; import android.annotation.NonNull; +import android.content.Intent; import android.graphics.Rect; import android.util.FloatProperty; import android.util.Log; @@ -34,17 +37,22 @@ import androidx.core.view.OneShotPreDrawListener; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AlphaUpdateListener; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.ThemedIconDrawable; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.util.HorizontalInsettableView; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.MultiValueAlpha; import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.SystemUiProxy; import java.io.PrintWriter; import java.util.function.Predicate; @@ -63,7 +71,10 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar public static final int ALPHA_INDEX_STASH = 2; public static final int ALPHA_INDEX_RECENTS_DISABLED = 3; public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4; - private static final int NUM_ALPHA_CHANNELS = 5; + public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5; + public static final int ALPHA_INDEX_IME_BUTTON_NAV = 6; + public static final int ALPHA_INDEX_SMALL_SCREEN = 7; + private static final int NUM_ALPHA_CHANNELS = 8; private final TaskbarActivityContext mActivity; private final TaskbarView mTaskbarView; @@ -102,7 +113,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar public void init(TaskbarControllers controllers) { mControllers = controllers; mTaskbarView.init(new TaskbarViewCallbacks()); - mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile()) + ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size) + : mActivity.getDeviceProfile().taskbarSize; mThemeIconsColor = ThemedIconDrawable.getColors(mTaskbarView.getContext())[0]; mTaskbarIconScaleForStash.updateValue(1f); @@ -120,6 +133,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar public void onDestroy() { LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); + mModelCallbacks.unregisterListeners(); } public boolean areIconsVisible() { @@ -137,6 +151,14 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar mTaskbarView.setTouchesEnabled(!isImeVisible); } + /** + * Should be called when the IME switcher visibility changes. + */ + public void setIsImeSwitcherVisible(boolean isImeSwitcherVisible) { + mTaskbarIconAlpha.getProperty(ALPHA_INDEX_IME_BUTTON_NAV).setValue( + isImeSwitcherVisible ? 0 : 1); + } + /** * Should be called when the recents button is disabled, so we can hide taskbar icons as well. */ @@ -210,9 +232,10 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar * 0 => not aligned * 1 => fully aligned */ - public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) { + public void setLauncherIconAlignment(float alignmentRatio, Float endAlignment, + DeviceProfile launcherDp) { if (mIconAlignControllerLazy == null) { - mIconAlignControllerLazy = createIconAlignmentController(launcherDp); + mIconAlignControllerLazy = createIconAlignmentController(launcherDp, endAlignment); } mIconAlignControllerLazy.setPlayFraction(alignmentRatio); if (alignmentRatio <= 0 || alignmentRatio >= 1) { @@ -224,11 +247,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar /** * Creates an animation for aligning the taskbar icons with the provided Launcher device profile */ - private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) { + private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp, + Float endAlignment) { mOnControllerPreCreateCallback.run(); PendingAnimation setter = new PendingAnimation(100); + DeviceProfile taskbarDp = mActivity.getDeviceProfile(); Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity); - float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx; + float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.iconSizePx; int borderSpacing = launcherDp.hotseatBorderSpace; int hotseatCellSize = DeviceProfile.calculateCellWidth( launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right, @@ -245,14 +270,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar } int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight(); - int expandedHeight = Math.max(collapsedHeight, - mActivity.getDeviceProfile().taskbarSize + offsetY); + int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarSize + offsetY); setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight( anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight)); + boolean isToHome = endAlignment != null && endAlignment == 1; for (int i = 0; i < mTaskbarView.getChildCount(); i++) { View child = mTaskbarView.getChildAt(i); - int positionInHotseat; if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get() && child == mTaskbarView.getAllAppsButtonView()) { @@ -260,13 +284,45 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar // as its convenient for animation purposes. positionInHotseat = Utilities.isRtl(child.getResources()) ? -1 - : mActivity.getDeviceProfile().numShownHotseatIcons; + : taskbarDp.numShownHotseatIcons; if (!FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) { - setter.setViewAlpha(child, 0, LINEAR); + setter.setViewAlpha(child, 0, + isToHome + ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f) + : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f)); } } else if (child.getTag() instanceof ItemInfo) { positionInHotseat = ((ItemInfo) child.getTag()).screenId; + } else if (child == mTaskbarView.getQsb()) { + boolean isRtl = Utilities.isRtl(child.getResources()); + float hotseatIconCenter = isRtl + ? launcherDp.widthPx - hotseatPadding.right + borderSpacing + + launcherDp.hotseatQsbWidth / 2f + : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f; + float childCenter = (child.getLeft() + child.getRight()) / 2f; + float halfQsbIconWidthDiff = + (launcherDp.hotseatQsbWidth - taskbarDp.iconSizePx) / 2f; + setter.addFloat(child, ICON_TRANSLATE_X, + isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff, + hotseatIconCenter - childCenter, LINEAR); + + float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight; + setter.addFloat(child, SCALE_PROPERTY, scale, 1f, LINEAR); + + setter.addFloat(child, VIEW_ALPHA, 0f, 1f, + isToHome + ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f) + : Interpolators.clampToProgress(LINEAR, 0.84f, 1f)); + setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child)); + + float qsbInsetFraction = halfQsbIconWidthDiff / launcherDp.hotseatQsbWidth; + if (child instanceof HorizontalInsettableView) { + setter.addFloat((HorizontalInsettableView) child, + HorizontalInsettableView.HORIZONTAL_INSETS, qsbInsetFraction, 0, + LINEAR); + } + continue; } else { Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child); continue; @@ -326,9 +382,33 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar @Override public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarViewController:"); + + mTaskbarIconAlpha.dump( + prefix + "\t", + pw, + "mTaskbarIconAlpha", + "ALPHA_INDEX_HOME", + "ALPHA_INDEX_KEYGUARD", + "ALPHA_INDEX_STASH", + "ALPHA_INDEX_RECENTS_DISABLED", + "ALPHA_INDEX_NOTIFICATION_EXPANDED", + "ALPHA_INDEX_ASSISTANT_INVOKED", + "ALPHA_INDEX_IME_BUTTON_NAV", + "ALPHA_INDEX_SMALL_SCREEN"); + mModelCallbacks.dumpLogs(prefix + "\t", pw); } + /** Called when there's a change in running apps to update the UI. */ + public void commitRunningAppsToUI() { + mModelCallbacks.commitRunningAppsToUI(); + } + + /** Call TaskbarModelCallbacks to update running apps. */ + public void updateRunningApps() { + mModelCallbacks.updateRunningApps(); + } + /** * Callbacks for {@link TaskbarView} to interact with its controller. */ @@ -349,6 +429,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar }; } + public View.OnClickListener getFloatingTaskButtonListener(@NonNull Intent intent) { + return v -> { + SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(v.getContext()); + proxy.showFloatingTask(intent); + }; + } + public View.OnLongClickListener getIconOnLongClickListener() { return mControllers.taskbarDragController::startDragOnLongClick; } diff --git a/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt new file mode 100644 index 0000000000..076900cd39 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt @@ -0,0 +1,118 @@ +package com.android.launcher3.taskbar + +import android.graphics.Canvas +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY +import com.android.launcher3.views.BaseDragLayer +import com.android.systemui.animation.ViewRootSync +import java.io.PrintWriter + +private const val TASKBAR_ICONS_FADE_DURATION = 300L +private const val STASHED_HANDLE_FADE_DURATION = 180L + +/** + * Controls Taskbar behavior while Voice Interaction Window (assistant) is showing. + */ +class VoiceInteractionWindowController(val context: TaskbarActivityContext) + : TaskbarControllers.LoggableTaskbarController { + + private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context) + + // Initialized in init. + private lateinit var controllers: TaskbarControllers + private lateinit var separateWindowForTaskbarBackground: BaseDragLayer + private lateinit var separateWindowLayoutParams: WindowManager.LayoutParams + + private var isVoiceInteractionWindowVisible: Boolean = false + + fun init(controllers: TaskbarControllers) { + this.controllers = controllers + + separateWindowForTaskbarBackground = + object : BaseDragLayer(context, null, 0) { + override fun recreateControllers() { + mControllers = emptyArray() + } + + override fun draw(canvas: Canvas) { + super.draw(canvas) + if (this@VoiceInteractionWindowController.context.isGestureNav + && controllers.taskbarStashController.isInAppAndNotStashed) { + taskbarBackgroundRenderer.draw(canvas) + } + } + } + separateWindowForTaskbarBackground.recreateControllers() + separateWindowForTaskbarBackground.setWillNotDraw(false) + + separateWindowLayoutParams = context.createDefaultWindowLayoutParams( + TYPE_APPLICATION_OVERLAY) + separateWindowLayoutParams.isSystemApplicationOverlay = true + } + + fun onDestroy() { + setIsVoiceInteractionWindowVisible(visible = false, skipAnim = true) + } + + fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) { + if (isVoiceInteractionWindowVisible == visible) { + return + } + isVoiceInteractionWindowVisible = visible + + // Fade out taskbar icons and stashed handle. + val taskbarIconAlpha = if (isVoiceInteractionWindowVisible) 0f else 1f + val fadeTaskbarIcons = controllers.taskbarViewController.taskbarIconAlpha + .getProperty(TaskbarViewController.ALPHA_INDEX_ASSISTANT_INVOKED) + .animateToValue(taskbarIconAlpha) + .setDuration(TASKBAR_ICONS_FADE_DURATION) + val fadeStashedHandle = controllers.stashedHandleViewController.stashedHandleAlpha + .getProperty(StashedHandleViewController.ALPHA_INDEX_ASSISTANT_INVOKED) + .animateToValue(taskbarIconAlpha) + .setDuration(STASHED_HANDLE_FADE_DURATION) + fadeTaskbarIcons.start() + fadeStashedHandle.start() + if (skipAnim) { + fadeTaskbarIcons.end() + fadeStashedHandle.end() + } + + moveTaskbarBackgroundToAppropriateLayer(skipAnim) + } + + /** + * Either: + * Hides the TaskbarDragLayer background and creates a new window to draw just that background. + * OR + * Removes the temporary window and show the TaskbarDragLayer background again. + */ + private fun moveTaskbarBackgroundToAppropriateLayer(skipAnim: Boolean) { + val taskbarBackgroundOverride = controllers.taskbarDragLayerController + .overrideBackgroundAlpha + val moveToLowerLayer = isVoiceInteractionWindowVisible + val onWindowsSynchronized = if (moveToLowerLayer) { + // First add the temporary window, then hide the overlapping taskbar background. + context.addWindowView(separateWindowForTaskbarBackground, separateWindowLayoutParams); + { taskbarBackgroundOverride.updateValue(0f) } + } else { + // First reapply the original taskbar background, then remove the temporary window. + taskbarBackgroundOverride.updateValue(1f); + { context.removeWindowView(separateWindowForTaskbarBackground) } + } + + if (skipAnim) { + onWindowsSynchronized() + } else { + ViewRootSync.synchronizeNextDraw( + separateWindowForTaskbarBackground, + context.dragLayer, + onWindowsSynchronized + ) + } + } + + override fun dumpLogs(prefix: String, pw: PrintWriter) { + pw.println(prefix + "VoiceInteractionWindowController:") + pw.println("$prefix\tisVoiceInteractionWindowVisible=$isVoiceInteractionWindowVisible") + } +} \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java index e2f752212e..0372f67b75 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java @@ -17,15 +17,16 @@ package com.android.launcher3.taskbar.allapps; import static android.view.KeyEvent.ACTION_UP; import static android.view.KeyEvent.KEYCODE_BACK; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; - import android.content.Context; import android.graphics.Insets; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsets; import com.android.launcher3.AbstractFloatingView; @@ -40,16 +41,14 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.taskbar.BaseTaskbarContext; import com.android.launcher3.taskbar.TaskbarActivityContext; +import com.android.launcher3.taskbar.TaskbarControllers; import com.android.launcher3.taskbar.TaskbarDragController; import com.android.launcher3.taskbar.TaskbarStashController; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.BaseDragLayer; -import com.android.systemui.shared.system.ViewTreeObserverWrapper; -import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; -import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener; /** * Window context for the taskbar all apps overlay. @@ -74,7 +73,7 @@ class TaskbarAllAppsContext extends BaseTaskbarContext { TaskbarAllAppsContext( TaskbarActivityContext taskbarContext, TaskbarAllAppsController windowController, - TaskbarStashController taskbarStashController) { + TaskbarControllers taskbarControllers) { super(taskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null)); mTaskbarContext = taskbarContext; mWindowController = windowController; @@ -88,9 +87,10 @@ class TaskbarAllAppsContext extends BaseTaskbarContext { this, slideInView, windowController, - taskbarStashController); + taskbarControllers); mAppsView = slideInView.getAppsView(); + TaskbarStashController taskbarStashController = taskbarControllers.taskbarStashController; mWillTaskbarBeVisuallyStashed = taskbarStashController.supportsVisualStashing(); mStashedTaskbarHeight = taskbarStashController.getStashedHeight(); } @@ -163,7 +163,7 @@ class TaskbarAllAppsContext extends BaseTaskbarContext { /** Root drag layer for this context. */ private static class TaskbarAllAppsDragLayer extends - BaseDragLayer implements OnComputeInsetsListener { + BaseDragLayer implements OnComputeInternalInsetsListener { private TaskbarAllAppsDragLayer(Context context) { super(context, null, 1); @@ -174,14 +174,13 @@ class TaskbarAllAppsContext extends BaseTaskbarContext { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - ViewTreeObserverWrapper.addOnComputeInsetsListener( - getViewTreeObserver(), this); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - ViewTreeObserverWrapper.removeOnComputeInsetsListener(this); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); } @Override @@ -207,7 +206,7 @@ class TaskbarAllAppsContext extends BaseTaskbarContext { } @Override - public void onComputeInsets(InsetsInfo inoutInfo) { + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { if (mActivity.mDragController.isSystemDragInProgress()) { inoutInfo.touchableRegion.setEmpty(); inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java index 6fd98db19a..1671a0f1d7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java @@ -144,9 +144,7 @@ public final class TaskbarAllAppsController { // to catch invalid states. mControllers.getSharedState().allAppsVisible = true; - mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext, - this, - mControllers.taskbarStashController); + mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext, this, mControllers); mAllAppsContext.getDragController().init(mControllers); TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); Optional.ofNullable(mAllAppsContext.getSystemService(WindowManager.class)) @@ -171,8 +169,8 @@ public final class TaskbarAllAppsController { * This method should be called after an exit animation finishes, if applicable. */ void maybeCloseWindow() { - if (AbstractFloatingView.getOpenView(mAllAppsContext, TYPE_ALL) != null - || mAllAppsContext.getDragController().isSystemDragInProgress()) { + if (mAllAppsContext != null && (AbstractFloatingView.hasOpenView(mAllAppsContext, TYPE_ALL) + || mAllAppsContext.getDragController().isSystemDragInProgress())) { return; } mProxyView.close(false); @@ -187,7 +185,7 @@ public final class TaskbarAllAppsController { TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); Optional.ofNullable(mAllAppsContext) .map(c -> c.getSystemService(WindowManager.class)) - .ifPresent(m -> m.removeView(mAllAppsContext.getDragLayer())); + .ifPresent(m -> m.removeViewImmediate(mAllAppsContext.getDragLayer())); mAllAppsContext = null; } @@ -207,7 +205,7 @@ public final class TaskbarAllAppsController { private LayoutParams createLayoutParams() { LayoutParams layoutParams = new LayoutParams( TYPE_APPLICATION_OVERLAY, - 0, + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); layoutParams.setTitle(WINDOW_TITLE); layoutParams.gravity = Gravity.BOTTOM; diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java index c4837a0c51..9d48c8de89 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java @@ -16,8 +16,7 @@ package com.android.launcher3.taskbar.allapps; import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; -import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; +import static com.android.launcher3.anim.Interpolators.EMPHASIZED; import android.animation.PropertyValuesHolder; import android.content.Context; @@ -31,13 +30,10 @@ import com.android.launcher3.Insettable; import com.android.launcher3.R; import com.android.launcher3.views.AbstractSlideInView; -import java.util.Optional; - /** Wrapper for taskbar all apps with slide-in behavior. */ public class TaskbarAllAppsSlideInView extends AbstractSlideInView implements Insettable, DeviceProfile.OnDeviceProfileChangeListener { private TaskbarAllAppsContainerView mAppsView; - private OnCloseListener mOnCloseBeginListener; private float mShiftRange; public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs) { @@ -60,7 +56,7 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView { + mNavbarButtonsViewController.setSlideInViewVisible(false); AbstractFloatingView.closeOpenContainer( mContext, AbstractFloatingView.TYPE_ACTION_POPUP); // Post in case view is closing due to gesture navigation. If a gesture is in progress, diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java index 2f8e4d9dde..f450496b3b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -17,14 +17,9 @@ package com.android.launcher3.uioverrides; import android.app.Person; -import android.content.Context; import android.content.pm.ShortcutInfo; -import android.content.res.Resources; -import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; public class ApiWrapper { @@ -34,24 +29,4 @@ public class ApiWrapper { Person[] persons = si.getPersons(); return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; } - - /** - * Returns the minimum space that should be left empty at the end of hotseat - */ - public static int getHotseatEndOffset(Context context) { - if (DisplayController.getNavigationMode(context) == NavigationMode.THREE_BUTTONS) { - Resources res = context.getResources(); - /* - * 3 nav buttons + - * Little space at the end for contextual buttons + - * Little space between icons and nav buttons - */ - return 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) - + res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin) - + res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing); - } else { - return 0; - } - - } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java index 84b3839825..8fb70300f6 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -23,25 +23,32 @@ import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y; import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; +import static com.android.quickstep.views.FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; +import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA; +import android.graphics.Rect; +import android.graphics.RectF; import android.util.FloatProperty; -import android.util.Log; +import android.view.animation.Interpolator; import androidx.annotation.NonNull; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; +import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.states.StateAnimationConfig; +import com.android.quickstep.views.FloatingTaskView; import com.android.quickstep.views.RecentsView; /** @@ -53,9 +60,9 @@ import com.android.quickstep.views.RecentsView; public abstract class BaseRecentsViewStateController implements StateHandler { protected final T mRecentsView; - protected final BaseQuickstepLauncher mLauncher; + protected final QuickstepLauncher mLauncher; - public BaseRecentsViewStateController(@NonNull BaseQuickstepLauncher launcher) { + public BaseRecentsViewStateController(@NonNull QuickstepLauncher launcher) { mLauncher = launcher; mRecentsView = launcher.getOverviewPanel(); } @@ -67,24 +74,25 @@ public abstract class BaseRecentsViewStateController ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]); TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f); - float recentsAlpha = state.overviewUi ? 1f : 0; - Log.d(BAD_STATE, "BaseRecentsViewStateController setState state=" + state - + ", alpha=" + recentsAlpha); - getContentAlphaProperty().set(mRecentsView, recentsAlpha); + getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0); getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness()); RECENTS_GRID_PROGRESS.set(mRecentsView, state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f); + TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, state.showTaskThumbnailSplash() ? 1f : 0f); } @Override public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation builder) { - Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimation state=" + toState - + ", config.skipOverview=" + config.hasAnimationFlag(SKIP_OVERVIEW)); if (config.hasAnimationFlag(SKIP_OVERVIEW)) { return; } setStateWithAnimationInternal(toState, config, builder); + builder.addEndListener(success -> { + if (!success) { + mRecentsView.reset(); + } + }); } /** @@ -104,19 +112,70 @@ public abstract class BaseRecentsViewStateController setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f, config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR)); - float recentsAlpha = toState.overviewUi ? 1 : 0; - Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimationInternal toState=" - + toState + ", alpha=" + recentsAlpha); - setter.setFloat(mRecentsView, getContentAlphaProperty(), recentsAlpha, + if (mRecentsView.isSplitSelectionActive()) { + // TODO (b/238651489): Refactor state management to avoid need for double check + FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView(); + if (floatingTask != null) { + // We are in split selection state currently, transitioning to another state + DragLayer dragLayer = mLauncher.getDragLayer(); + RectF onScreenRectF = new RectF(); + Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), floatingTask, + new Rect(0, 0, floatingTask.getWidth(), floatingTask.getHeight()), + false, null, onScreenRectF); + // Get the part of the floatingTask that intersects with the DragLayer (i.e. the + // on-screen portion) + onScreenRectF.intersect( + dragLayer.getLeft(), + dragLayer.getTop(), + dragLayer.getRight(), + dragLayer.getBottom() + ); + + setter.setFloat( + mRecentsView.getFirstFloatingTaskView(), + PRIMARY_TRANSLATE_OFFSCREEN, + mRecentsView.getPagedOrientationHandler() + .getFloatingTaskOffscreenTranslationTarget( + floatingTask, + onScreenRectF, + floatingTask.getStagePosition(), + mLauncher.getDeviceProfile() + ), + config.getInterpolator( + ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN, + LINEAR + )); + setter.setViewAlpha( + mRecentsView.getSplitInstructionsView(), + 0, + config.getInterpolator( + ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE, + LINEAR + ) + ); + } + } + + setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0, config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT)); setter.setFloat( mRecentsView, getTaskModalnessProperty(), toState.getOverviewModalness(), config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR)); + + LauncherState fromState = mLauncher.getStateManager().getState(); + setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA, + toState.showTaskThumbnailSplash() ? 1f : 0f, + !toState.showTaskThumbnailSplash() && fromState == LauncherState.QUICK_SWITCH + ? LINEAR : INSTANT); + boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()); + Interpolator gridProgressInterpolator = showAsGrid + ? fromState == LauncherState.QUICK_SWITCH ? LINEAR : INSTANT + : FINAL_FRAME; setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f, - showAsGrid ? INSTANT : FINAL_FRAME); + gridProgressInterpolator); } abstract FloatProperty getTaskModalnessProperty(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 4bb43431bc..192ac6251e 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -23,41 +23,83 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SH import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.NO_OFFSET; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; +import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; +import static com.android.launcher3.anim.Interpolators.EMPHASIZED; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_WIDGET_APP_START; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; +import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE; +import static com.android.launcher3.config.FeatureFlags.ENABLE_WIDGET_PICKER_DEPTH; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; -import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL; -import static com.android.launcher3.testing.TestProtocol.HINT_STATE_TWO_BUTTON_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.model.data.ItemInfo.NO_MATCHING_ID; +import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition; +import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX; +import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX; +import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX; +import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL; +import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; +import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; +import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.quickstep.util.BaseDepthController.WIDGET_DEPTH; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.content.Context; import android.content.Intent; +import android.content.IntentSender; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceStateManager; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.SystemProperties; +import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.View; +import android.window.SplashScreen; + +import androidx.annotation.Nullable; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; import com.android.launcher3.QuickstepAccessibilityDelegate; +import com.android.launcher3.QuickstepTransitionManager; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.appprediction.PredictionRowView; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.proxy.ProxyActivityStarter; +import com.android.launcher3.proxy.StartActivityParams; +import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; +import com.android.launcher3.statemanager.StateManager.StateHandler; +import com.android.launcher3.taskbar.LauncherTaskbarUIController; +import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController; @@ -68,41 +110,98 @@ import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchControll import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController; import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController; import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController; +import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.NavigationMode; +import com.android.launcher3.util.ObjectWrapper; import com.android.launcher3.util.PendingRequestArgs; +import com.android.launcher3.util.PendingSplitSelectInfo; +import com.android.launcher3.util.RunnableList; +import com.android.launcher3.util.SafeCloseable; +import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.TouchController; -import com.android.launcher3.util.UiThreadHelper; -import com.android.launcher3.util.UiThreadHelper.AsyncCommand; import com.android.launcher3.widget.LauncherAppWidgetHost; +import com.android.quickstep.OverviewCommandHelper; +import com.android.quickstep.RecentsModel; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskUtils; +import com.android.quickstep.TouchInteractionService.TISBinder; +import com.android.quickstep.util.LauncherUnfoldAnimationController; +import com.android.quickstep.util.ProxyScreenStatusProvider; import com.android.quickstep.util.QuickstepOnboardingPrefs; +import com.android.quickstep.util.RemoteAnimationProvider; +import com.android.quickstep.util.RemoteFadeOutAnimationListener; +import com.android.quickstep.util.SplitSelectStateController; +import com.android.quickstep.util.TISBindHelper; +import com.android.quickstep.util.ViewCapture; +import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.unfold.UnfoldTransitionFactory; +import com.android.systemui.unfold.UnfoldTransitionProgressProvider; +import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig; +import com.android.systemui.unfold.config.UnfoldTransitionConfig; +import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider; +import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; -public class QuickstepLauncher extends BaseQuickstepLauncher { +public class QuickstepLauncher extends Launcher { + + public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM = + SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false); 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); private FixedContainerItems mAllAppsPredictions; private HotseatPredictionController mHotseatPredictionController; + private DepthController mDepthController; + private QuickstepTransitionManager mAppTransitionManager; + private OverviewActionsView mActionsView; + private TISBindHelper mTISBindHelper; + private @Nullable TaskbarManager mTaskbarManager; + private @Nullable OverviewCommandHelper mOverviewCommandHelper; + private @Nullable LauncherTaskbarUIController mTaskbarUIController; + // Will be updated when dragging from taskbar. + private @Nullable DragOptions mNextWorkspaceDragOptions = null; + private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider; + private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController; + /** + * If Launcher restarted while in the middle of an Overview split select, it needs this data to + * recover. In all other cases this will remain null. + */ + private PendingSplitSelectInfo mPendingSplitSelectInfo = null; + + private SafeCloseable mViewCapture; @Override protected void setupViews() { super.setupViews(); + + mActionsView = findViewById(R.id.overview_actions_view); + RecentsView overviewPanel = (RecentsView) getOverviewPanel(); + SplitSelectStateController controller = + new SplitSelectStateController(this, mHandler, getStateManager(), + getDepthController(), getStatsLogManager()); + overviewPanel.init(mActionsView, controller); + mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize()); + mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this)); + + mAppTransitionManager = new QuickstepTransitionManager(this); + mAppTransitionManager.registerRemoteAnimations(); + mAppTransitionManager.registerRemoteTransitions(); + + mTISBindHelper = new TISBindHelper(this, this::onTISConnected); + mDepthController = new DepthController(this); mHotseatPredictionController = new HotseatPredictionController(this); } @@ -184,6 +283,16 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override protected void onActivityFlagsChanged(int changeBits) { + if ((changeBits & ACTIVITY_STATE_STARTED) != 0) { + mDepthController.setActivityStarted(isStarted()); + } + + if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) { + if (mTaskbarUIController != null) { + mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed()); + } + } + super.onActivityFlagsChanged(changeBits); if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { @@ -204,8 +313,22 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public Stream getSupportedShortcuts() { + Stream base = Stream.of(WellbeingModel.SHORTCUT_FACTORY); + if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) { + RecentsView recentsView = getOverviewPanel(); + // TODO: Pull it out of PagedOrentationHandler for split from workspace. + List positions = + recentsView.getPagedOrientationHandler().getSplitPositionOptions( + mDeviceProfile); + List> splitShortcuts = new ArrayList<>(); + for (SplitPositionOption position : positions) { + splitShortcuts.add(getSplitSelectShortcutByPosition(position)); + } + base = Stream.concat(base, splitShortcuts.stream()); + } return Stream.concat( - Stream.of(mHotseatPredictionController), super.getSupportedShortcuts()); + Stream.of(mHotseatPredictionController), + Stream.concat(base, super.getSupportedShortcuts())); } /** @@ -213,16 +336,17 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { */ private void onStateOrResumeChanging(boolean inTransition) { LauncherState state = getStateManager().getState(); - boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0; - if (started) { - DeviceProfile profile = getDeviceProfile(); - boolean willUserBeActive = - (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; - boolean visible = (state == NORMAL || state == OVERVIEW) - && (willUserBeActive || isUserActive()) - && !profile.isVerticalBarLayout(); - UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0, - profile.hotseatBarSizePx); + if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { + boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0; + if (started) { + DeviceProfile profile = getDeviceProfile(); + boolean willUserBeActive = + (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; + boolean visible = (state == NORMAL || state == OVERVIEW) + && (willUserBeActive || isUserActive()) + && !profile.isVerticalBarLayout(); + SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx); + } } if (state == NORMAL && !inTransition) { ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false); @@ -252,13 +376,29 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public void onDestroy() { + mAppTransitionManager.onActivityDestroyed(); + if (mUnfoldTransitionProgressProvider != null) { + mUnfoldTransitionProgressProvider.destroy(); + } + + mTISBindHelper.onDestroy(); + if (mTaskbarManager != null) { + mTaskbarManager.clearActivity(this); + } + + if (mLauncherUnfoldAnimationController != null) { + mLauncherUnfoldAnimationController.onDestroy(); + } + super.onDestroy(); mHotseatPredictionController.destroy(); + mViewCapture.close(); } @Override public void onStateSetEnd(LauncherState state) { super.onStateSetEnd(state); + handlePendingActivityRequest(); switch (state.ordinal) { case HINT_STATE_ORDINAL: { @@ -339,12 +479,459 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { protected LauncherAppWidgetHost createAppWidgetHost() { LauncherAppWidgetHost appWidgetHost = super.createAppWidgetHost(); - if (ENABLE_QUICKSTEP_WIDGET_APP_START.get()) { - appWidgetHost.setInteractionHandler(new QuickstepInteractionHandler(this)); - } + appWidgetHost.setInteractionHandler(new QuickstepInteractionHandler(this)); return appWidgetHost; } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mPendingSplitSelectInfo = ObjectWrapper.unwrap( + savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO)); + } + addMultiWindowModeChangedListener(mDepthController); + initUnfoldTransitionProgressProvider(); + mViewCapture = ViewCapture.INSTANCE.get(this).startCapture(getWindow()); + } + + @Override + protected void onResume() { + super.onResume(); + + if (mLauncherUnfoldAnimationController != null) { + mLauncherUnfoldAnimationController.onResume(); + } + } + + @Override + protected void onPause() { + if (mLauncherUnfoldAnimationController != null) { + mLauncherUnfoldAnimationController.onPause(); + } + + super.onPause(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + if (mOverviewCommandHelper != null) { + mOverviewCommandHelper.clearPendingCommands(); + } + } + + public QuickstepTransitionManager getAppTransitionManager() { + return mAppTransitionManager; + } + + @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 + protected void handleGestureContract(Intent intent) { + if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) { + super.handleGestureContract(intent); + } + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + RecentsModel.INSTANCE.get(this).onTrimMemory(level); + } + + @Override + public void onUiChangedWhileSleeping() { + // Remove the snapshot because the content view may have obvious changes. + UI_HELPER_EXECUTOR.execute( + () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this)); + } + + @Override + protected void onScreenOff() { + super.onScreenOff(); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + RecentsView recentsView = getOverviewPanel(); + recentsView.finishRecentsAnimation(true /* toRecents */, null); + } + } + + /** + * {@code LauncherOverlayCallbacks} scroll amount. + * Indicates transition progress to -1 screen. + * @param progress From 0 to 1. + */ + @Override + public void onScrollChanged(float progress) { + super.onScrollChanged(progress); + onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX); + } + + @Override + public void onAllAppsTransition(float progress) { + super.onAllAppsTransition(progress); + onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX); + } + + @Override + public void onWidgetsTransition(float progress) { + super.onWidgetsTransition(progress); + onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX); + // Change of wallpaper depth in widget picker is disabled for tests as it causes flakiness + // on very slow cuttlefish devices. + if (ENABLE_WIDGET_PICKER_DEPTH.get() && !Utilities.IS_RUNNING_IN_TEST_HARNESS) { + WIDGET_DEPTH.set(getDepthController(), + Utilities.mapToRange(progress, 0f, 1f, 0f, getDeviceProfile().bottomSheetDepth, + EMPHASIZED)); + } + } + + private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) { + if (mTaskbarManager == null + || mTaskbarManager.getCurrentActivityContext() == null + || mTaskbarUIController == null) { + return; + } + mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag); + } + + @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() { + super.onDeferredResumed(); + handlePendingActivityRequest(); + } + + private void handlePendingActivityRequest() { + if (mPendingActivityRequestCode != -1 && isInState(NORMAL) + && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) { + // 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)); + } + } + + private void onTISConnected(TISBinder binder) { + mTaskbarManager = binder.getTaskbarManager(); + mTaskbarManager.setActivity(this); + mOverviewCommandHelper = binder.getOverviewCommandHelper(); + } + + @Override + public void runOnBindToTouchInteractionService(Runnable r) { + mTISBindHelper.runOnBindToTouchInteractionService(r); + } + + private void initUnfoldTransitionProgressProvider() { + final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig(); + if (config.isEnabled()) { + mUnfoldTransitionProgressProvider = + UnfoldTransitionFactory.createUnfoldTransitionProgressProvider( + /* context= */ this, + config, + ProxyScreenStatusProvider.INSTANCE, + new DeviceStateManagerFoldProvider( + getSystemService(DeviceStateManager.class), /* context */this), + new ActivityManagerActivityTypeProvider( + getSystemService(ActivityManager.class)), + getSystemService(SensorManager.class), + getMainThreadHandler(), + getMainExecutor(), + /* backgroundExecutor= */ THREAD_POOL_EXECUTOR, + /* tracingTagPrefix= */ "launcher" + ); + + mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController( + this, + getWindowManager(), + mUnfoldTransitionProgressProvider + ); + } + } + + public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) { + mTaskbarUIController = taskbarUIController; + } + + public @Nullable LauncherTaskbarUIController getTaskbarUIController() { + return mTaskbarUIController; + } + + public T getActionsView() { + return (T) mActionsView; + } + + @Override + protected void closeOpenViews(boolean animate) { + super.closeOpenViews(animate); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); + } + + @Override + protected void collectStateHandlers(List out) { + super.collectStateHandlers(out); + out.add(getDepthController()); + out.add(new RecentsViewStateController(this)); + } + + public DepthController getDepthController() { + return mDepthController; + } + + @Nullable + public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() { + return mUnfoldTransitionProgressProvider; + } + + @Override + public boolean supportsAdaptiveIconAnimation(View clickedView) { + return mAppTransitionManager.hasControlRemoteAppTransitionPermission(); + } + + @Override + public DragOptions getDefaultWorkspaceDragOptions() { + if (mNextWorkspaceDragOptions != null) { + DragOptions options = mNextWorkspaceDragOptions; + mNextWorkspaceDragOptions = null; + return options; + } + return super.getDefaultWorkspaceDragOptions(); + } + + public void setNextWorkspaceDragOptions(DragOptions dragOptions) { + mNextWorkspaceDragOptions = dragOptions; + } + + @Override + public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { + QuickstepTransitionManager appTransitionManager = getAppTransitionManager(); + appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() { + @Override + public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, + RemoteAnimationTargetCompat[] wallpaperTargets) { + + // On the first call clear the reference. + signal.cancel(); + + ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); + fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, + wallpaperTargets)); + AnimatorSet anim = new AnimatorSet(); + anim.play(fadeAnimation); + return anim; + } + }, signal); + } + + @Override + public float[] getNormalOverviewScaleAndOffset() { + return DisplayController.getNavigationMode(this).hasGestures + ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET}; + } + + @Override + public void finishBindingItems(IntSet pagesBoundFirst) { + super.finishBindingItems(pagesBoundFirst); + // 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 void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) { + pendingTasks.add(() -> { + // This is added in pending task as we need to wait for views to be positioned + // correctly before registering them for the animation. + if (mLauncherUnfoldAnimationController != null) { + // This is needed in case items are rebound while the unfold animation is in + // progress. + mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded(); + } + }); + super.onInitialBindComplete(boundPages, pendingTasks); + } + + @Override + public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { + ActivityOptionsWrapper activityOptions = + mAppTransitionManager.hasControlRemoteAppTransitionPermission() + ? mAppTransitionManager.getActivityLaunchOptions(v) + : super.getActivityLaunchOptions(v, item); + if (mLastTouchUpTime > 0) { + activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER, + mLastTouchUpTime); + } + activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); + activityOptions.options.setLaunchDisplayId( + (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() + : Display.DEFAULT_DISPLAY); + addLaunchCookie(item, activityOptions.options); + return activityOptions; + } + + /** + * Adds a new launch cookie for the activity launch if supported. + * + * @param info the item info for the launch + * @param opts the options to set the launchCookie on. + */ + public void addLaunchCookie(ItemInfo info, ActivityOptions opts) { + IBinder launchCookie = getLaunchCookie(info); + if (launchCookie != null) { + opts.setLaunchCookie(launchCookie); + } + } + + /** + * Return a new launch cookie for the activity launch if supported. + * + * @param info the item info for the launch + */ + public IBinder getLaunchCookie(ItemInfo info) { + if (info == null) { + return null; + } + switch (info.container) { + case Favorites.CONTAINER_DESKTOP: + case Favorites.CONTAINER_HOTSEAT: + // Fall through and continue it's on the workspace (we don't support swiping back + // to other containers like all apps or the hotseat predictions (which can change) + break; + default: + if (info.container >= 0) { + // Also allow swiping to folders + break; + } + // Reset any existing launch cookies associated with the cookie + return ObjectWrapper.wrap(NO_MATCHING_ID); + } + switch (info.itemType) { + case Favorites.ITEM_TYPE_APPLICATION: + case Favorites.ITEM_TYPE_SHORTCUT: + case Favorites.ITEM_TYPE_DEEP_SHORTCUT: + case Favorites.ITEM_TYPE_APPWIDGET: + // Fall through and continue if it's an app, shortcut, or widget + break; + default: + // Reset any existing launch cookies associated with the cookie + return ObjectWrapper.wrap(NO_MATCHING_ID); + } + return ObjectWrapper.wrap(new Integer(info.id)); + } + + public void setHintUserWillBeActive() { + addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); + } + + @Override + public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) { + super.onDisplayInfoChanged(context, info, flags); + // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as + // StatefulActivity isn't called consistently. + if ((flags & CHANGE_ACTIVE_SCREEN) != 0) { + getStateManager().moveToRestState(); + } + + if ((flags & CHANGE_NAVIGATION_MODE) != 0) { + getDragLayer().recreateControllers(); + if (mActionsView != null) { + mActionsView.updateVerticalMargin(info.navigationMode); + } + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + // If Launcher shuts downs during split select, we save some extra data in the recovery + // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't + // work in this case because restoring straight to OverviewSplitSelect without staging data, + // or before the tasks themselves have loaded into Overview, causes a crash. So we tell + // Launcher to first restore into Overview state, wait for the relevant tasks and icons to + // load in, and then proceed to OverviewSplitSelect. + if (isInState(OVERVIEW_SPLIT_SELECT)) { + SplitSelectStateController splitSelectStateController = + ((RecentsView) getOverviewPanel()).getSplitSelectController(); + // Launcher will restart in Overview and then transition to OverviewSplitSelect. + outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap( + new PendingSplitSelectInfo( + splitSelectStateController.getInitialTaskId(), + splitSelectStateController.getActiveSplitStagePosition(), + splitSelectStateController.getSplitEvent()) + )); + outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal); + } + } + + /** + * When Launcher restarts, it sometimes needs to recover to a split selection state. + * This function checks if such a recovery is needed. + * @return a boolean representing whether the launcher is waiting to recover to + * OverviewSplitSelect state. + */ + public boolean hasPendingSplitSelectInfo() { + return mPendingSplitSelectInfo != null; + } + + /** + * See {@link #hasPendingSplitSelectInfo()} + */ + public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() { + return mPendingSplitSelectInfo; + } + + /** + * When the launcher has successfully recovered to OverviewSplitSelect state, this function + * deletes the recovery data, returning it to a null state. + */ + public void finishSplitSelectRecovery() { + mPendingSplitSelectInfo = null; + } + private static final class LauncherTaskViewController extends TaskViewTouchController { @@ -371,6 +958,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { super.dump(prefix, fd, writer, args); + if (mDepthController != null) { + mDepthController.dump(prefix, writer); + } RecentsView recentsView = getOverviewPanel(); writer.println("\nQuickstepLauncher:"); writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" : diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 00a98c0647..910b99b36a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -27,15 +27,14 @@ import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLA import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION; import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL; +import android.animation.AnimatorSet; import android.annotation.TargetApi; import android.os.Build; import android.util.FloatProperty; import android.util.Pair; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.anim.PendingAnimation; @@ -43,6 +42,8 @@ import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.MultiValueAlpha; +import com.android.quickstep.util.AnimUtils; +import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.views.ClearAllButton; import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; @@ -55,7 +56,7 @@ import com.android.quickstep.views.RecentsView; public final class RecentsViewStateController extends BaseRecentsViewStateController { - public RecentsViewStateController(BaseQuickstepLauncher launcher) { + public RecentsViewStateController(QuickstepLauncher launcher) { super(launcher); } @@ -72,7 +73,10 @@ public final class RecentsViewStateController extends // DepthController to prevent optimizations which might occlude the layers behind mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi); - handleSplitSelectionState(state, null); + PendingAnimation builder = + new PendingAnimation(state.getTransitionDuration(mLauncher, true)); + + handleSplitSelectionState(state, builder, /* animate */false); } @Override @@ -93,7 +97,7 @@ public final class RecentsViewStateController extends builder.addListener(AnimatorListeners.forSuccessCallback(() -> mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi))); - handleSplitSelectionState(toState, builder); + handleSplitSelectionState(toState, builder, /* animate */true); setAlphas(builder, config, toState); builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS, @@ -106,8 +110,14 @@ public final class RecentsViewStateController extends * will add animations to builder. */ private void handleSplitSelectionState(@NonNull LauncherState toState, - @Nullable PendingAnimation builder) { - boolean animate = builder != null; + @NonNull PendingAnimation builder, boolean animate) { + if (toState != OVERVIEW_SPLIT_SELECT) { + // Not going to split, nothing to do but ensure taskviews are at correct offset + mRecentsView.resetSplitPrimaryScrollOffset(); + return; + } + + // Create transition animations to split select PagedOrientationHandler orientationHandler = ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler(); Pair taskViewsFloat = @@ -115,25 +125,27 @@ public final class RecentsViewStateController extends TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION, mLauncher.getDeviceProfile()); - if (toState == OVERVIEW_SPLIT_SELECT) { - // Animation to "dismiss" selected taskView - PendingAnimation splitSelectInitAnimation = mRecentsView.createSplitSelectInitAnimation( - toState.getTransitionDuration(mLauncher, true /* isToState */)); - // Add properties to shift remaining taskViews to get out of placeholder view - splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first, - toState.getSplitSelectTranslation(mLauncher), LINEAR); - splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR); + SplitAnimationTimings timings = + AnimUtils.getDeviceOverviewToSplitTimings(mLauncher.getDeviceProfile().isTablet); - if (!animate) { - splitSelectInitAnimation.buildAnim().start(); - } else { - builder.add(splitSelectInitAnimation.buildAnim()); - } + mRecentsView.createSplitSelectInitAnimation(builder, + toState.getTransitionDuration(mLauncher, true /* isToState */)); + // Shift tasks vertically downward to get out of placeholder view + builder.setFloat(mRecentsView, taskViewsFloat.first, + toState.getSplitSelectTranslation(mLauncher), + timings.getGridSlidePrimaryInterpolator()); + // Zero out horizontal translation + builder.setFloat(mRecentsView, taskViewsFloat.second, + 0, + timings.getGridSlideSecondaryInterpolator()); - mRecentsView.applySplitPrimaryScrollOffset(); - } else { - mRecentsView.resetSplitPrimaryScrollOffset(); + if (!animate) { + AnimatorSet as = builder.buildAnim(); + as.start(); + as.end(); } + + mRecentsView.applySplitPrimaryScrollOffset(); } private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config, diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index a74774c62b..fd184c63a4 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -20,6 +20,7 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAP import android.content.Context; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; @@ -31,8 +32,6 @@ import com.android.launcher3.util.Themes; */ public class AllAppsState extends LauncherState { - private static final float WORKSPACE_SCALE_FACTOR = 0.97f; - private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS | FLAG_HOTSEAT_INACCESSIBLE; @@ -43,9 +42,9 @@ public class AllAppsState extends LauncherState { @Override public int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) { - return !context.getDeviceProfile().isTablet && isToState - ? 600 - : isToState ? 500 : 300; + return isToState + ? context.getDeviceProfile().allAppsOpenDuration + : context.getDeviceProfile().allAppsCloseDuration; } @Override @@ -60,7 +59,8 @@ public class AllAppsState extends LauncherState { @Override public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) { - return new ScaleAndTranslation(WORKSPACE_SCALE_FACTOR, NO_OFFSET, NO_OFFSET); + return new ScaleAndTranslation(launcher.getDeviceProfile().workspaceContentScale, NO_OFFSET, + NO_OFFSET); } @Override @@ -71,17 +71,22 @@ public class AllAppsState extends LauncherState { ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW .getWorkspaceScaleAndTranslation(launcher); return new ScaleAndTranslation( - WORKSPACE_SCALE_FACTOR, + launcher.getDeviceProfile().workspaceContentScale, overviewScaleAndTranslation.translationX, overviewScaleAndTranslation.translationY); } } @Override - protected float getDepthUnchecked(Context context) { - // The scrim fades in at approximately 50% of the swipe gesture. - // This means that the depth should be greater than 1, in order to fully zoom out. - return 2f; + protected + float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) { + if (context.getDeviceProfile().isTablet) { + return context.getDeviceProfile().bottomSheetDepth; + } else { + // The scrim fades in at approximately 50% of the swipe gesture. + // This means that the depth should be greater than 1, in order to fully zoom out. + return 2f; + } } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index b7330072d4..4150d40bea 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; +import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import android.content.Context; import android.graphics.Color; @@ -82,6 +83,11 @@ public class BackgroundAppState extends OverviewState { return false; } + @Override + public boolean showTaskThumbnailSplash() { + return true; + } + @Override protected float getDepthUnchecked(Context context) { return 1; @@ -96,6 +102,12 @@ public class BackgroundAppState extends OverviewState { return Color.TRANSPARENT; } + @Override + public boolean isTaskbarAlignedWithHotseat(Launcher launcher) { + if (ENABLE_SHELL_TRANSITIONS) return false; + return super.isTaskbarAlignedWithHotseat(launcher); + } + public static float[] getOverviewScaleAndOffsetForBackgroundState( BaseDraggingActivity activity) { return new float[] { diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java index 6427e0981a..6f07568cf5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -22,12 +22,10 @@ import android.content.Context; import android.graphics.Rect; import android.os.SystemProperties; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; -import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.Themes; import com.android.quickstep.util.LayoutUtils; @@ -39,6 +37,10 @@ import com.android.quickstep.views.TaskView; */ public class OverviewState extends LauncherState { + private static final int OVERVIEW_SLIDE_IN_DURATION = 380; + private static final int OVERVIEW_POP_IN_DURATION = 250; + private static final int OVERVIEW_EXIT_DURATION = 250; + protected static final Rect sTempRect = new Rect(); private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED @@ -59,8 +61,15 @@ public class OverviewState extends LauncherState { @Override public int getTransitionDuration(Context context, boolean isToState) { - // In gesture modes, overview comes in all the way from the side, so give it more time. - return DisplayController.getNavigationMode(context).hasGestures ? 380 : 250; + if (isToState) { + // In gesture modes, overview comes in all the way from the side, so give it more time. + return DisplayController.getNavigationMode(context).hasGestures + ? OVERVIEW_SLIDE_IN_DURATION + : OVERVIEW_POP_IN_DURATION; + } else { + // When exiting Overview, exit quickly. + return OVERVIEW_EXIT_DURATION; + } } @Override @@ -95,13 +104,7 @@ public class OverviewState extends LauncherState { @Override public boolean isTaskbarStashed(Launcher launcher) { - if (launcher instanceof BaseQuickstepLauncher) { - LauncherTaskbarUIController uiController = - ((BaseQuickstepLauncher) launcher).getTaskbarUIController(); - - return uiController != null && uiController.supportsVisualStashing(); - } - return super.isTaskbarStashed(launcher); + return true; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index 2d7fe69d3b..bcd722f4e3 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -22,16 +22,19 @@ import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; -import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; +import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE; +import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE; import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; @@ -39,20 +42,15 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y; import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE; -import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD; -import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD; -import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_OPAQUE_THRESHOLD; -import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_VISIBLE_THRESHOLD; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; -import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; -import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE; import android.animation.ValueAnimator; @@ -60,11 +58,12 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.Hotseat; import com.android.launcher3.LauncherState; import com.android.launcher3.Workspace; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.touch.AllAppsSwipeController; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DisplayController; import com.android.quickstep.util.RecentsAtomicAnimationFactory; +import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.views.RecentsView; /** @@ -94,9 +93,16 @@ public class QuickstepAtomicAnimationFactory extends public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState, StateAnimationConfig config) { RecentsView overview = mActivity.getOverviewPanel(); - if (toState == NORMAL && fromState == OVERVIEW) { + if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) { + if (fromState == OVERVIEW_SPLIT_SELECT) { + config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN, + clampToProgress(EMPHASIZED_ACCELERATE, 0, 0.4f)); + config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE, + clampToProgress(LINEAR, 0, 0.33f)); + } + config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f)); - config.setInterpolator(ANIM_SCRIM_FADE, LINEAR); + config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR, 0.33f, 1)); config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL); config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); @@ -105,8 +111,7 @@ public class QuickstepAtomicAnimationFactory extends // Overview is going offscreen, so keep it at its current scale and opacity. config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME); config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME); - config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, - clampToProgress(FAST_OUT_SLOW_IN, 0, 0.75f)); + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, EMPHASIZED_DECELERATE); config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME); } else { config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL); @@ -182,23 +187,24 @@ public class QuickstepAtomicAnimationFactory extends } config.duration = Math.max(config.duration, mHintToNormalDuration); } else if (fromState == ALL_APPS && toState == NORMAL) { - boolean isTablet = mActivity.getDeviceProfile().isTablet; - config.setInterpolator(ANIM_ALL_APPS_FADE, - isTablet ? FINAL_FRAME : Interpolators.clampToProgress(LINEAR, - 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD, - 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD)); - config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(LINEAR, - 1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD, - 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD)); - config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE); - if (!isTablet) { - config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT); - } + AllAppsSwipeController.applyAllAppsToNormalConfig(mActivity, config); } else if (fromState == NORMAL && toState == ALL_APPS) { - if (mActivity.getDeviceProfile().isTablet) { - config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_DECELERATE); - } - // TODO(b/231682175): centralize this setup in AllAppsSwipeController + AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config); + } else if (fromState == OVERVIEW && toState == OVERVIEW_SPLIT_SELECT) { + SplitAnimationTimings timings = mActivity.getDeviceProfile().isTablet + ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT + : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT; + config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, + timings.getActionsFadeStartOffset(), + timings.getActionsFadeEndOffset())); + } else if (fromState == NORMAL && toState == OVERVIEW_SPLIT_SELECT) { + // Splitting from Home is currently only available on tablets + SplitAnimationTimings timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT; + config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR, + timings.getScrimFadeInStartOffset(), + timings.getScrimFadeInEndOffset())); + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_0_75); + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_0_75); } } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java index e79d56b631..8babd3470b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java @@ -16,7 +16,10 @@ package com.android.launcher3.uioverrides.states; +import android.content.Context; + import com.android.launcher3.Launcher; +import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.views.RecentsView; /** @@ -38,4 +41,16 @@ public class SplitScreenSelectState extends OverviewState { RecentsView recentsView = launcher.getOverviewPanel(); return recentsView.getSplitSelectTranslation(); } + + @Override + public int getTransitionDuration(Context context, boolean isToState) { + boolean isTablet = ((Launcher) context).getDeviceProfile().isTablet; + if (isToState && isTablet) { + return SplitAnimationTimings.TABLET_ENTER_DURATION; + } else if (isToState && !isTablet) { + return SplitAnimationTimings.PHONE_ENTER_DURATION; + } else { + return SplitAnimationTimings.ABORT_DURATION; + } + } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index 34a6821ac5..30bb892c9a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -25,7 +25,6 @@ import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION; import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; -import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; @@ -106,7 +105,7 @@ public class NavBarToHomeTouchController implements TouchController, if (mStartState.overviewUi || mStartState == ALL_APPS) { return true; } - int typeToClose = ENABLE_ALL_APPS_EDU.get() ? TYPE_ALL & ~TYPE_ALL_APPS_EDU : TYPE_ALL; + int typeToClose = TYPE_ALL & ~TYPE_ALL_APPS_EDU; if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) { return true; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 8faabc9561..918b3c1bf8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -34,13 +34,13 @@ import android.graphics.PointF; import android.view.MotionEvent; import android.view.ViewConfiguration; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.taskbar.LauncherTaskbarUIController; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.MotionPauseDetector; @@ -114,7 +114,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch public void onDragStart(boolean start, float startDisplacement) { if (mLauncher.isInState(ALL_APPS)) { LauncherTaskbarUIController controller = - ((BaseQuickstepLauncher) mLauncher).getTaskbarUIController(); + ((QuickstepLauncher) mLauncher).getTaskbarUIController(); if (controller != null) { controller.setShouldDelayLauncherStateAnim(true); } @@ -151,7 +151,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch @Override public void onDragEnd(float velocity) { LauncherTaskbarUIController controller = - ((BaseQuickstepLauncher) mLauncher).getTaskbarUIController(); + ((QuickstepLauncher) mLauncher).getTaskbarUIController(); if (controller != null) { controller.setShouldDelayLauncherStateAnim(false); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 53dc9dd873..922679b5f4 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -39,7 +39,6 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_S import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS; import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT; import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; @@ -49,6 +48,7 @@ import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; +import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; @@ -56,11 +56,9 @@ import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.graphics.PointF; -import android.util.Log; import android.view.MotionEvent; import android.view.animation.Interpolator; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -69,6 +67,7 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.BaseSwipeDetector; import com.android.launcher3.touch.BothAxesSwipeDetector; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.TouchController; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SystemUiProxy; @@ -93,7 +92,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR; private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300; - private final BaseQuickstepLauncher mLauncher; + private final QuickstepLauncher mLauncher; private final BothAxesSwipeDetector mSwipeDetector; private final float mXRange; private final float mYRange; @@ -115,7 +114,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, private AnimatorPlaybackController mXOverviewAnim; private AnimatedFloat mYOverviewAnim; - public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) { + public NoButtonQuickSwitchTouchController(QuickstepLauncher launcher) { mLauncher = launcher; mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this); mRecentsView = mLauncher.getOverviewPanel(); @@ -227,11 +226,12 @@ public class NoButtonQuickSwitchTouchController implements TouchController, // Set RecentView's initial properties. RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]); ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, 1f); - Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators setContentAlpha=1"); + TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0); mRecentsView.setContentAlpha(1); mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress()); mLauncher.getActionsView().getVisibilityAlpha().setValue( (fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f); + mRecentsView.setTaskIconScaledDown(true); float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher); // As we drag right, animate the following properties: @@ -246,24 +246,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController, QUICK_SWITCH.getWorkspaceScrimColor(mLauncher), LINEAR); if (mRecentsView.getTaskViewCount() == 0) { xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR); - Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators from: 0 to: 1"); - xAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onStart"); - } - - @Override - public void onAnimationCancel(Animator animation) { - float alpha = mRecentsView == null ? -1 : CONTENT_ALPHA.get(mRecentsView); - Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onCancel, alpha=" + alpha); - } - - @Override - public void onAnimationEnd(Animator animation) { - Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onEnd"); - } - }); } mXOverviewAnim = xAnim.createPlaybackController(); mXOverviewAnim.dispatchOnStart(); @@ -321,6 +303,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, boolean verticalFling = mSwipeDetector.isFling(velocity.y); boolean noFling = !horizontalFling && !verticalFling; if (mMotionPauseDetector.isPaused() && noFling) { + // Going to Overview. cancelAnimations(); StateAnimationConfig config = new StateAnimationConfig(); @@ -331,6 +314,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, @Override public void onAnimationEnd(Animator animation) { onAnimationToStateCompleted(OVERVIEW); + // Animate the icon after onAnimationToStateCompleted() so it doesn't clobber. + mRecentsView.animateUpTaskIconScale(); } }); overviewAnim.start(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index e56c90c20c..9efbc34a17 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -21,21 +21,8 @@ import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; -import static com.android.launcher3.anim.Interpolators.INSTANT; -import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE; -import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE; import android.view.MotionEvent; -import android.view.animation.Interpolator; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; @@ -44,6 +31,7 @@ import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.AbstractStateChangeTouchController; +import com.android.launcher3.touch.AllAppsSwipeController; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.uioverrides.states.OverviewState; import com.android.quickstep.SystemUiProxy; @@ -58,53 +46,6 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr private static final String TAG = "PortraitStatesTouchCtrl"; - /** - * The progress at which all apps content will be fully visible. - */ - public static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f; - - /** - * Minimum clamping progress for fading in all apps content - */ - public static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f; - - /** - * Minimum clamping progress for fading in all apps scrim - */ - public static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f; - - /** - * Maximum clamping progress for opaque all apps scrim - */ - public static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f; - - // Custom timing for NORMAL -> ALL_APPS on phones only. - private static final float ALL_APPS_STATE_TRANSITION = 0.4f; - private static final float ALL_APPS_FULL_DEPTH_PROGRESS = 0.5f; - - // Custom interpolators for NORMAL -> ALL_APPS on phones only. - private static final Interpolator LINEAR_EARLY = - Interpolators.clampToProgress(LINEAR, 0f, ALL_APPS_STATE_TRANSITION); - private static final Interpolator STEP_TRANSITION = - Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION); - // The blur to and from All Apps is set to be complete when the interpolator is at 0.5. - public static final Interpolator BLUR = - Interpolators.clampToProgress( - Interpolators.mapToProgress(LINEAR, 0f, ALL_APPS_FULL_DEPTH_PROGRESS), - 0f, ALL_APPS_STATE_TRANSITION); - public static final Interpolator WORKSPACE_FADE = STEP_TRANSITION; - public static final Interpolator WORKSPACE_SCALE = LINEAR_EARLY; - public static final Interpolator HOTSEAT_FADE = STEP_TRANSITION; - public static final Interpolator HOTSEAT_SCALE = LINEAR_EARLY; - public static final Interpolator HOTSEAT_TRANSLATE = STEP_TRANSITION; - public static final Interpolator SCRIM_FADE = LINEAR_EARLY; - public static final Interpolator ALL_APPS_FADE = - Interpolators.clampToProgress(LINEAR, ALL_APPS_STATE_TRANSITION, 1f); - public static final Interpolator ALL_APPS_VERTICAL_PROGRESS = - Interpolators.clampToProgress( - Interpolators.mapToProgress(LINEAR, ALL_APPS_STATE_TRANSITION, 1f), - ALL_APPS_STATE_TRANSITION, 1f); - private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper; public PortraitStatesTouchController(Launcher l) { @@ -160,66 +101,15 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr return fromState; } - private StateAnimationConfig getNormalToAllAppsAnimation() { - StateAnimationConfig builder = new StateAnimationConfig(); - if (mLauncher.getDeviceProfile().isTablet) { - builder.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT); - builder.setInterpolator(ANIM_SCRIM_FADE, - Interpolators.clampToProgress(LINEAR, - ALL_APPS_SCRIM_VISIBLE_THRESHOLD, - ALL_APPS_SCRIM_OPAQUE_THRESHOLD)); - } else { - // TODO(b/231682175): centralize this setup in AllAppsSwipeController. - builder.setInterpolator(ANIM_DEPTH, BLUR); - builder.setInterpolator(ANIM_WORKSPACE_FADE, WORKSPACE_FADE); - builder.setInterpolator(ANIM_WORKSPACE_SCALE, WORKSPACE_SCALE); - builder.setInterpolator(ANIM_HOTSEAT_FADE, HOTSEAT_FADE); - builder.setInterpolator(ANIM_HOTSEAT_SCALE, HOTSEAT_SCALE); - builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, HOTSEAT_TRANSLATE); - builder.setInterpolator(ANIM_SCRIM_FADE, SCRIM_FADE); - builder.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_FADE); - builder.setInterpolator(ANIM_VERTICAL_PROGRESS, ALL_APPS_VERTICAL_PROGRESS); - } - return builder; - } - - private StateAnimationConfig getAllAppsToNormalAnimation() { - StateAnimationConfig builder = new StateAnimationConfig(); - if (mLauncher.getDeviceProfile().isTablet) { - builder.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME); - builder.setInterpolator(ANIM_SCRIM_FADE, - Interpolators.clampToProgress(LINEAR, - 1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD, - 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD)); - } else { - // These interpolators are the reverse of the ones used above, so swiping out of All - // Apps feels the same as swiping into it. - // TODO(b/231682175): centralize this setup in AllAppsSwipeController. - builder.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR)); - builder.setInterpolator(ANIM_WORKSPACE_FADE, Interpolators.reverse(WORKSPACE_FADE)); - builder.setInterpolator(ANIM_WORKSPACE_SCALE, Interpolators.reverse(WORKSPACE_SCALE)); - builder.setInterpolator(ANIM_HOTSEAT_FADE, Interpolators.reverse(HOTSEAT_FADE)); - builder.setInterpolator(ANIM_HOTSEAT_SCALE, Interpolators.reverse(HOTSEAT_SCALE)); - builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, - Interpolators.reverse(HOTSEAT_TRANSLATE)); - builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.reverse(SCRIM_FADE)); - builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.reverse(ALL_APPS_FADE)); - builder.setInterpolator(ANIM_VERTICAL_PROGRESS, - Interpolators.reverse(ALL_APPS_VERTICAL_PROGRESS)); - } - return builder; - } - @Override protected StateAnimationConfig getConfigForStates( LauncherState fromState, LauncherState toState) { - final StateAnimationConfig config; + final StateAnimationConfig config = new StateAnimationConfig(); + config.userControlled = true; if (fromState == NORMAL && toState == ALL_APPS) { - config = getNormalToAllAppsAnimation(); + AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mLauncher, config); } else if (fromState == ALL_APPS && toState == NORMAL) { - config = getAllAppsToNormalAnimation(); - } else { - config = new StateAnimationConfig(); + AllAppsSwipeController.applyAllAppsToNormalConfig(mLauncher, config); } return config; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index d1b0a9c4ba..56ac4c5b3a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -29,7 +29,6 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TR import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; @@ -37,7 +36,6 @@ import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHO import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; -import android.util.Log; import android.view.MotionEvent; import com.android.launcher3.Launcher; @@ -47,7 +45,7 @@ import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.AbstractStateChangeTouchController; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskUtils; import com.android.quickstep.views.RecentsView; @@ -114,7 +112,6 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll RECENTS_SCALE_PROPERTY.set(mOverviewPanel, QUICK_SWITCH.getOverviewScaleAndOffset(mLauncher)[0] * 0.85f); ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mOverviewPanel, 1f); - Log.d(BAD_STATE, "QuickSwitchTouchController initCurrentAnimation setContentAlpha=1"); mOverviewPanel.setContentAlpha(1); mCurrentAnimation = mLauncher.getStateManager() diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index ca7f633bbc..c49848a5b4 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -236,7 +236,8 @@ public abstract class TaskViewTouchController PendingAnimation pa; if (goingUp) { currentInterpolator = Interpolators.LINEAR; - pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged, + pa = new PendingAnimation(maxDuration); + mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged, true /* animateTaskView */, true /* removeTask */, maxDuration, false /* dismissingForSplitSelection*/); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java index e2747dfcbb..9f2c1d479f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java @@ -24,13 +24,11 @@ import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import android.animation.ValueAnimator; import android.os.SystemClock; -import android.util.Log; import android.view.MotionEvent; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.AbstractStateChangeTouchController; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.quickstep.SystemUiProxy; diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 76f7718765..f3630c187b 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -33,6 +33,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; +import static com.android.launcher3.uioverrides.QuickstepLauncher.ENABLE_PIP_KEEP_CLEAR_ALGORITHM; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; @@ -46,6 +47,9 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_SET; import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED; import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET; import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; @@ -58,6 +62,7 @@ import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; +import android.app.WindowConfiguration; import android.content.Context; import android.content.Intent; import android.graphics.Matrix; @@ -81,6 +86,7 @@ import android.window.PictureInPictureSurfaceTransaction; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import com.android.internal.util.LatencyTracker; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; @@ -99,6 +105,7 @@ import com.android.launcher3.util.WindowBounds; import com.android.quickstep.BaseActivityInterface.AnimationFactory; import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; +import com.android.quickstep.util.ActiveGestureErrorDetector; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; @@ -120,7 +127,6 @@ import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; -import com.android.systemui.shared.system.LatencyTrackerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -140,7 +146,7 @@ public abstract class AbsSwipeUpHandler, RecentsAnimationCallbacks.RecentsAnimationListener { private static final String TAG = "AbsSwipeUpHandler"; - private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null; + private static final ArrayList STATE_NAMES = new ArrayList<>(); protected final BaseActivityInterface mActivityInterface; protected final InputConsumerProxy mInputConsumerProxy; @@ -171,66 +177,68 @@ public abstract class AbsSwipeUpHandler, } }; - private static int getFlagForIndex(int index, String name) { + private static int FLAG_COUNT = 0; + private static int getNextStateFlag(String name) { if (DEBUG_STATES) { - STATE_NAMES[index] = name; + STATE_NAMES.add(name); } - return 1 << index; + int index = 1 << FLAG_COUNT; + FLAG_COUNT++; + return index; } // Launcher UI related states protected static final int STATE_LAUNCHER_PRESENT = - getFlagForIndex(0, "STATE_LAUNCHER_PRESENT"); + getNextStateFlag("STATE_LAUNCHER_PRESENT"); protected static final int STATE_LAUNCHER_STARTED = - getFlagForIndex(1, "STATE_LAUNCHER_STARTED"); + getNextStateFlag("STATE_LAUNCHER_STARTED"); protected static final int STATE_LAUNCHER_DRAWN = - getFlagForIndex(2, "STATE_LAUNCHER_DRAWN"); + getNextStateFlag("STATE_LAUNCHER_DRAWN"); // Called when the Launcher has connected to the touch interaction service (and the taskbar // ui controller is initialized) protected static final int STATE_LAUNCHER_BIND_TO_SERVICE = - getFlagForIndex(3, "STATE_LAUNCHER_BIND_TO_SERVICE"); + getNextStateFlag("STATE_LAUNCHER_BIND_TO_SERVICE"); // Internal initialization states private static final int STATE_APP_CONTROLLER_RECEIVED = - getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED"); + getNextStateFlag("STATE_APP_CONTROLLER_RECEIVED"); // Interaction finish states private static final int STATE_SCALED_CONTROLLER_HOME = - getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME"); + getNextStateFlag("STATE_SCALED_CONTROLLER_HOME"); private static final int STATE_SCALED_CONTROLLER_RECENTS = - getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS"); + getNextStateFlag("STATE_SCALED_CONTROLLER_RECENTS"); protected static final int STATE_HANDLER_INVALIDATED = - getFlagForIndex(7, "STATE_HANDLER_INVALIDATED"); + getNextStateFlag("STATE_HANDLER_INVALIDATED"); private static final int STATE_GESTURE_STARTED = - getFlagForIndex(8, "STATE_GESTURE_STARTED"); + getNextStateFlag("STATE_GESTURE_STARTED"); private static final int STATE_GESTURE_CANCELLED = - getFlagForIndex(9, "STATE_GESTURE_CANCELLED"); + getNextStateFlag("STATE_GESTURE_CANCELLED"); private static final int STATE_GESTURE_COMPLETED = - getFlagForIndex(10, "STATE_GESTURE_COMPLETED"); + getNextStateFlag("STATE_GESTURE_COMPLETED"); private static final int STATE_CAPTURE_SCREENSHOT = - getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT"); + getNextStateFlag("STATE_CAPTURE_SCREENSHOT"); protected static final int STATE_SCREENSHOT_CAPTURED = - getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED"); + getNextStateFlag("STATE_SCREENSHOT_CAPTURED"); private static final int STATE_SCREENSHOT_VIEW_SHOWN = - getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN"); + getNextStateFlag("STATE_SCREENSHOT_VIEW_SHOWN"); private static final int STATE_RESUME_LAST_TASK = - getFlagForIndex(14, "STATE_RESUME_LAST_TASK"); + getNextStateFlag("STATE_RESUME_LAST_TASK"); private static final int STATE_START_NEW_TASK = - getFlagForIndex(15, "STATE_START_NEW_TASK"); + getNextStateFlag("STATE_START_NEW_TASK"); private static final int STATE_CURRENT_TASK_FINISHED = - getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED"); + getNextStateFlag("STATE_CURRENT_TASK_FINISHED"); private static final int STATE_FINISH_WITH_NO_END = - getFlagForIndex(17, "STATE_FINISH_WITH_NO_END"); + getNextStateFlag("STATE_FINISH_WITH_NO_END"); private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED | STATE_LAUNCHER_BIND_TO_SERVICE; public static final long MAX_SWIPE_DURATION = 350; - public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS; public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f; private static final float SWIPE_DURATION_MULTIPLIER = @@ -298,9 +306,12 @@ public abstract class AbsSwipeUpHandler, mActivityInterface = gestureState.getActivityInterface(); mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit); mInputConsumerProxy = - new InputConsumerProxy(context, - () -> mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation(), - inputConsumer, () -> { + new InputConsumerProxy(context, /* rotationSupplier = */ () -> { + if (mRecentsView == null) { + return ROTATION_0; + } + return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation(); + }, inputConsumer, /* callback = */ () -> { endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */); endLauncherTransitionController(); }, new InputProxyHandlerFactory(mActivityInterface, mGestureState)); @@ -314,8 +325,29 @@ public abstract class AbsSwipeUpHandler, initStateCallbacks(); } + @Nullable + private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { + if (stateFlag == STATE_GESTURE_STARTED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_STARTED; + } else if (stateFlag == STATE_GESTURE_COMPLETED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_COMPLETED; + } else if (stateFlag == STATE_GESTURE_CANCELLED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_CANCELLED; + } else if (stateFlag == STATE_SCREENSHOT_CAPTURED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_SCREENSHOT_CAPTURED; + } else if (stateFlag == STATE_CAPTURE_SCREENSHOT) { + return ActiveGestureErrorDetector.GestureEvent.STATE_CAPTURE_SCREENSHOT; + } else if (stateFlag == STATE_HANDLER_INVALIDATED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED; + } else if (stateFlag == STATE_LAUNCHER_DRAWN) { + return ActiveGestureErrorDetector.GestureEvent.STATE_LAUNCHER_DRAWN; + } + return null; + } + private void initStateCallbacks() { - mStateCallback = new MultiStateCallback(STATE_NAMES); + mStateCallback = new MultiStateCallback( + STATE_NAMES.toArray(new String[0]), AbsSwipeUpHandler::getTrackedEventForState); mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, this::onLauncherPresentAndGestureStarted); @@ -442,7 +474,7 @@ public abstract class AbsSwipeUpHandler, }); setupRecentsViewUi(); - linkRecentsViewScroll(); + mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll); activity.runOnBindToTouchInteractionService(this::onLauncherBindToService); mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks); @@ -571,7 +603,7 @@ public abstract class AbsSwipeUpHandler, protected void notifyGestureAnimationStartToRecents() { Task[] runningTasks; - if (mIsSwipeForStagedSplit) { + if (mIsSwipeForSplit) { int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds(); runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds); } else { @@ -589,8 +621,8 @@ public abstract class AbsSwipeUpHandler, Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents", TraceHelper.FLAG_IGNORE_BINDERS); - LatencyTrackerCompat.logToggleRecents( - mContext, (int) (mLauncherFrameDrawnTime - mTouchTimeMs)); + LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS, + (int) (mLauncherFrameDrawnTime - mTouchTimeMs)); TraceHelper.INSTANCE.endSection(traceToken); // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the @@ -794,7 +826,6 @@ public abstract class AbsSwipeUpHandler, public void onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets) { super.onRecentsAnimationStart(controller, targets); - ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length); mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets); mRecentsAnimationController = controller; mRecentsAnimationTargets = targets; @@ -837,17 +868,14 @@ public abstract class AbsSwipeUpHandler, @Override public void onRecentsAnimationCanceled(HashMap thumbnailDatas) { - ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation"); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "cancelRecentsAnimation", + /* gestureEvent= */ CANCEL_RECENTS_ANIMATION); mActivityInitListener.unregister(); // Cache the recents animation controller so we can defer its cleanup to after having // properly cleaned up the screenshot without accidentally using it. mDeferredCleanupRecentsAnimationController = mRecentsAnimationController; mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); - - if (mRecentsAnimationTargets != null) { - setDividerShown(true /* shown */, false /* immediate */); - } - // Defer clearing the controller and the targets until after we've updated the state mRecentsAnimationController = null; mRecentsAnimationTargets = null; @@ -862,6 +890,7 @@ public abstract class AbsSwipeUpHandler, TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); if (mRecentsView != null) { + final View rv = mRecentsView; mRecentsView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() { boolean mHandled = false; @@ -877,8 +906,7 @@ public abstract class AbsSwipeUpHandler, InteractionJankMonitorWrapper.begin(mRecentsView, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME); - mRecentsView.post(() -> - mRecentsView.getViewTreeObserver().removeOnDrawListener(this)); + rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this)); } }); } @@ -1004,7 +1032,9 @@ public abstract class AbsSwipeUpHandler, } break; } - ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "onSettledOnEndTarget " + endTarget, + /* gestureEvent= */ ON_SETTLED_ON_END_TARGET); } /** @return Whether this was the task we were waiting to appear, and thus handled it. */ @@ -1021,77 +1051,90 @@ public abstract class AbsSwipeUpHandler, return false; } - private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, - boolean isFlingY, boolean isCancel) { + private GestureEndTarget calculateEndTarget( + PointF velocity, float endVelocity, boolean isFlingY, boolean isCancel) { + if (mGestureState.isHandlingAtomicEvent()) { - // Button mode, this is only used to go to recents + // Button mode, this is only used to go to recents. return RECENTS; } - final GestureEndTarget endTarget; - final boolean goingToNewTask; - if (mRecentsView != null) { - 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; - } else { - final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); - final int taskToLaunch = mRecentsView.getNextPage(); - goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex; - } - } else { - goingToNewTask = false; - } - final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; - final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources() - .getDimension(R.dimen.quickstep_fling_threshold_speed); - if (!isFlingY) { - if (isCancel) { - endTarget = LAST_TASK; - } else if (mDeviceState.isFullyGesturalNavMode()) { - if (goingToNewTask && isFlingX) { - // Flinging towards new task takes precedence over mIsMotionPaused (which only - // checks y-velocity). - endTarget = NEW_TASK; - } else if (mIsMotionPaused) { - endTarget = RECENTS; - } else if (goingToNewTask) { - endTarget = NEW_TASK; - } else { - endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME; - } - } else { - endTarget = reachedOverviewThreshold && mGestureStarted - ? RECENTS - : goingToNewTask - ? NEW_TASK - : LAST_TASK; - } - } else { - // If swiping at a diagonal, base end target on the faster velocity. - boolean isSwipeUp = endVelocity < 0; - boolean willGoToNewTaskOnSwipeUp = - goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity); - if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) { - endTarget = HOME; - } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) { - // If swiping at a diagonal, base end target on the faster velocity. - endTarget = NEW_TASK; - } else if (isSwipeUp) { - endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp - ? NEW_TASK : RECENTS; - } else { - endTarget = goingToNewTask ? NEW_TASK : LAST_TASK; - } + GestureEndTarget endTarget; + if (isCancel) { + endTarget = LAST_TASK; + } else if (isFlingY) { + endTarget = calculateEndTargetForFlingY(velocity, endVelocity); + } else { + endTarget = calculateEndTargetForNonFling(velocity); } - if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) { + if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) { return LAST_TASK; } + return endTarget; } + private GestureEndTarget calculateEndTargetForFlingY(PointF velocity, float endVelocity) { + // If swiping at a diagonal, base end target on the faster velocity direction. + final boolean willGoToNewTask = + isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity); + final boolean isSwipeUp = endVelocity < 0; + if (!isSwipeUp) { + final boolean isCenteredOnNewTask = + mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex(); + return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK; + } + + if (!mDeviceState.isFullyGesturalNavMode()) { + return (!hasReachedOverviewThreshold() && willGoToNewTask) ? NEW_TASK : RECENTS; + } + return willGoToNewTask ? NEW_TASK : HOME; + } + + private GestureEndTarget calculateEndTargetForNonFling(PointF velocity) { + final boolean isScrollingToNewTask = isScrollingToNewTask(); + final boolean reachedOverviewThreshold = hasReachedOverviewThreshold(); + if (!mDeviceState.isFullyGesturalNavMode()) { + return reachedOverviewThreshold && mGestureStarted + ? RECENTS + : (isScrollingToNewTask ? NEW_TASK : LAST_TASK); + } + + // Fully gestural mode. + final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources() + .getDimension(R.dimen.quickstep_fling_threshold_speed); + if (isScrollingToNewTask && isFlingX) { + // Flinging towards new task takes precedence over mIsMotionPaused (which only + // checks y-velocity). + return NEW_TASK; + } else if (mIsMotionPaused) { + return RECENTS; + } else if (isScrollingToNewTask) { + return NEW_TASK; + } else if (reachedOverviewThreshold) { + return HOME; + } + return LAST_TASK; + } + + private boolean isScrollingToNewTask() { + if (mRecentsView == null) { + return false; + } + 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. + return true; + } + int runningTaskIndex = mRecentsView.getRunningTaskIndex(); + return runningTaskIndex >= 0 && mRecentsView.getNextPage() != runningTaskIndex; + } + + private boolean hasReachedOverviewThreshold() { + return mCurrentShift.value > MIN_PROGRESS_FOR_OVERVIEW; + } + @UiThread private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, boolean isCancel) { @@ -1137,7 +1180,9 @@ public abstract class AbsSwipeUpHandler, mInputConsumerProxy.enable(); } if (endTarget == HOME) { - duration = HOME_DURATION; + duration = mActivity != null && mActivity.getDeviceProfile().isTaskbarPresent + ? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS + : StaggeredWorkspaceAnim.DURATION_MS; // Early detach the nav bar once the endTarget is determined as HOME if (mRecentsAnimationController != null) { mRecentsAnimationController.detachNavigationBarFromApp(true); @@ -1167,11 +1212,16 @@ public abstract class AbsSwipeUpHandler, duration = Math.max(duration, mRecentsView.getScroller().getDuration()); } } + } else if (endTarget == LAST_TASK && mRecentsView != null + && mRecentsView.getNextPage() != mRecentsView.getRunningTaskIndex()) { + mRecentsView.snapToPage(mRecentsView.getRunningTaskIndex(), Math.toIntExact(duration)); } // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() // or resumeLastTask(). if (mRecentsView != null) { + ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent + .SET_ON_PAGE_TRANSITION_END_CALLBACK); mRecentsView.setOnPageTransitionEndCallback( () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED)); } else { @@ -1280,15 +1330,18 @@ public abstract class AbsSwipeUpHandler, ? runningTaskTarget.taskInfo.launchCookies : new ArrayList<>(); boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent; + boolean hasValidLeash = runningTaskTarget != null + && runningTaskTarget.leash != null + && runningTaskTarget.leash.isValid(); boolean appCanEnterPip = !mDeviceState.isPipActive() - && runningTaskTarget != null + && hasValidLeash && runningTaskTarget.allowEnterPip && runningTaskTarget.taskInfo.pictureInPictureParams != null && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled(); HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip, runningTaskTarget); - mIsSwipingPipToHome = !mIsSwipeForStagedSplit && appCanEnterPip; + mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip; final RectFSpringAnim[] windowAnim; if (mIsSwipingPipToHome) { mSwipePipToHomeAnimator = createWindowAnimationToPip( @@ -1319,7 +1372,8 @@ public abstract class AbsSwipeUpHandler, if (windowAnimation == null) { continue; } - windowAnimation.start(mContext, velocityPxPerMs); + DeviceProfile dp = mActivity == null ? null : mActivity.getDeviceProfile(); + windowAnimation.start(mContext, dp, velocityPxPerMs); mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation); } homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y); @@ -1390,9 +1444,7 @@ public abstract class AbsSwipeUpHandler, } } - /** - * TODO(b/195473090) handle multiple task simulators (if needed) for PIP - */ + @Nullable private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory, RemoteAnimationTargetCompat runningTaskTarget, float startProgress) { // Directly animate the app to PiP (picture-in-picture) mode @@ -1411,12 +1463,27 @@ public abstract class AbsSwipeUpHandler, homeToWindowPositionMap.invert(windowToHomePositionMap); windowToHomePositionMap.mapRect(startRect); + final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat(); final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext) .startSwipePipToHome(taskInfo.topActivity, taskInfo.topActivityInfo, runningTaskTarget.taskInfo.pictureInPictureParams, homeRotation, - mDp.hotseatBarSizePx); + hotseatKeepClearArea); + if (destinationBounds == null) { + // No destination bounds returned from SystemUI, bail early. + return null; + } + final Rect appBounds = new Rect(); + final WindowConfiguration winConfig = taskInfo.configuration.windowConfiguration; + // Adjust the appBounds for TaskBar by using the calculated window crop Rect + // from TaskViewSimulator and fallback to the bounds in TaskInfo when it's originated + // from windowing modes other than full-screen. + if (winConfig.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) { + mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().round(appBounds); + } else { + appBounds.set(winConfig.getBounds()); + } final SwipePipToHomeAnimator.Builder builder = new SwipePipToHomeAnimator.Builder() .setContext(mContext) .setTaskId(runningTaskTarget.taskId) @@ -1424,7 +1491,7 @@ public abstract class AbsSwipeUpHandler, .setLeash(runningTaskTarget.leash) .setSourceRectHint( runningTaskTarget.taskInfo.pictureInPictureParams.getSourceRectHint()) - .setAppBounds(taskInfo.configuration.windowConfiguration.getBounds()) + .setAppBounds(appBounds) .setHomeToWindowPositionMap(homeToWindowPositionMap) .setStartBounds(startRect) .setDestinationBounds(destinationBounds) @@ -1469,6 +1536,35 @@ public abstract class AbsSwipeUpHandler, return swipePipToHomeAnimator; } + private Rect getKeepClearAreaForHotseat() { + Rect keepClearArea; + if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { + // make the height equal to hotseatBarSizePx only + keepClearArea = new Rect(0, 0, 0, mDp.hotseatBarSizePx); + return keepClearArea; + } + // the keep clear area in global screen coordinates, in pixels + if (mDp.isPhone) { + if (mDp.isSeascape()) { + // in seascape the Hotseat is on the left edge of the screen + keepClearArea = new Rect(0, 0, mDp.hotseatBarSizePx, mDp.heightPx); + } else if (mDp.isLandscape) { + // in landscape the Hotseat is on the right edge of the screen + keepClearArea = new Rect(mDp.widthPx - mDp.hotseatBarSizePx, 0, + mDp.widthPx, mDp.heightPx); + } else { + // in portrait mode the Hotseat is at the bottom of the screen + keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx, + mDp.widthPx, mDp.heightPx); + } + } else { + // large screens have Hotseat always at the bottom of the screen + keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx, + mDp.widthPx, mDp.heightPx); + } + return keepClearArea; + } + private void startInterceptingTouchesForGesture() { if (mRecentsAnimationController == null) { return; @@ -1556,7 +1652,10 @@ public abstract class AbsSwipeUpHandler, private void resumeLastTask() { if (mRecentsAnimationController != null) { mRecentsAnimationController.finish(false /* toRecents */, null); - ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "finishRecentsAnimation", + /* extras= */ false, + /* gestureEvent= */ FINISH_RECENTS_ANIMATION); } doLogGesture(LAST_TASK, null); reset(); @@ -1605,13 +1704,17 @@ public abstract class AbsSwipeUpHandler, * handler (in case of quick switch). */ private void cancelCurrentAnimation() { + ActiveGestureLog.INSTANCE.addLog( + "AbsSwipeUpHandler.cancelCurrentAnimation", + ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION); mCanceled = true; mCurrentShift.cancelAnimation(); // Cleanup when switching handlers mInputConsumerProxy.unregisterCallback(); mActivityInitListener.unregister(); - ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener); + TaskStackChangeListeners.getInstance().unregisterTaskStackListener( + mActivityRestartListener); mTaskSnapshot = null; } @@ -1675,10 +1778,6 @@ public abstract class AbsSwipeUpHandler, boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted; mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget()); - if (mRecentsAnimationTargets != null) { - setDividerShown(true /* shown */, true /* immediate */); - } - // Leave the pending invisible flag, as it may be used by wallpaper open animation. if (mActivity != null) { mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER); @@ -1759,7 +1858,10 @@ public abstract class AbsSwipeUpHandler, mRecentsAnimationController.finish(true /* toRecents */, () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } - ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "finishRecentsAnimation", + /* extras= */ true, + /* gestureEvent= */ FINISH_RECENTS_ANIMATION); } private void finishCurrentTransitionToHome() { @@ -1771,7 +1873,10 @@ public abstract class AbsSwipeUpHandler, finishRecentsControllerToHome( () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } - ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "finishRecentsAnimation", + /* extras= */ true, + /* gestureEvent= */ FINISH_RECENTS_ANIMATION); doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView()); } @@ -1792,12 +1897,13 @@ public abstract class AbsSwipeUpHandler, mSwipePipToHomeAnimator.getFinishTransaction(), mSwipePipToHomeAnimator.getContentOverlay()); mIsSwipingPipToHome = false; - } else if (mIsSwipeForStagedSplit) { + } else if (mIsSwipeForSplit) { // Transaction to hide the task to avoid flicker for entering PiP from split-screen. PictureInPictureSurfaceTransaction tx = new PictureInPictureSurfaceTransaction.Builder() .setAlpha(0f) .build(); + tx.setShouldDisableCanAffectSystemUiFlags(false); int[] taskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds(); for (int taskId : taskIds) { mRecentsAnimationController.setFinishTaskTransaction(taskId, @@ -1957,8 +2063,10 @@ public abstract class AbsSwipeUpHandler, if (handleTaskAppeared(appearedTaskTargets)) { mRecentsAnimationController.finish(false /* toRecents */, null /* onFinishComplete */); - mActivityInterface.onLaunchTaskSuccess(); - ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "finishRecentsAnimation", + /* extras= */ false, + /* gestureEvent= */ FINISH_RECENTS_ANIMATION); } } } diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java index 6c7a885a1f..a1665530a3 100644 --- a/quickstep/src/com/android/quickstep/AnimatedFloat.java +++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java @@ -133,4 +133,11 @@ public class AnimatedFloat { public boolean isAnimatingToValue(float endValue) { return isAnimating() && mEndValue != null && mEndValue == endValue; } + + /** + * Returns the value we are animating to, or {@code null} if we are not currently animating. + */ + public Float getEndValue() { + return mEndValue; + } } diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 2fcd286345..226b173ecd 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -55,14 +55,11 @@ import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.taskbar.TaskbarUIController; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; -import com.android.launcher3.util.WindowBounds; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.views.ScrimView; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; -import com.android.quickstep.util.SplitScreenBounds; 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.RemoteAnimationTargetCompat; @@ -186,14 +183,6 @@ public abstract class BaseActivityInterface STATE_NAMES = new ArrayList<>(); + private static final List STATE_NAMES = new ArrayList<>(); public static final GestureState DEFAULT_STATE = new GestureState(); private static int FLAG_COUNT = 0; - private static int getFlagForIndex(String name) { + private static int getNextStateFlag(String name) { if (DEBUG_STATES) { STATE_NAMES.add(name); } @@ -97,36 +103,36 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL // Called when the end target as been set public static final int STATE_END_TARGET_SET = - getFlagForIndex("STATE_END_TARGET_SET"); + getNextStateFlag("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"); + getNextStateFlag("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"); + getNextStateFlag("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"); + getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED"); // Called when the recents animation is canceled public static final int STATE_RECENTS_ANIMATION_CANCELED = - getFlagForIndex("STATE_RECENTS_ANIMATION_CANCELED"); + getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED"); // Called when the recents animation finishes public static final int STATE_RECENTS_ANIMATION_FINISHED = - getFlagForIndex("STATE_RECENTS_ANIMATION_FINISHED"); + getNextStateFlag("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"); + getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED"); // Called when RecentsView stops scrolling and settles on a TaskView. public static final int STATE_RECENTS_SCROLLING_FINISHED = - getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED"); + getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED"); // Needed to interact with the current activity private final Intent mHomeIntent; @@ -152,7 +158,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL mHomeIntent = componentObserver.getHomeIntent(); mOverviewIntent = componentObserver.getOverviewIntent(); mActivityInterface = componentObserver.getActivityInterface(); - mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0])); + mStateCallback = new MultiStateCallback( + STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); mGestureId = gestureId; } @@ -174,10 +181,23 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL mHomeIntent = new Intent(); mOverviewIntent = new Intent(); mActivityInterface = null; - mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0])); + mStateCallback = new MultiStateCallback( + STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); mGestureId = -1; } + @Nullable + private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { + if (stateFlag == STATE_END_TARGET_ANIMATION_FINISHED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED; + } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED; + } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED; + } + return null; + } + /** * @return whether the gesture state has the provided {@param stateMask} flags set. */ @@ -310,7 +330,23 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL public void setEndTarget(GestureEndTarget target, boolean isAtomic) { mEndTarget = target; mStateCallback.setState(STATE_END_TARGET_SET); - ActiveGestureLog.INSTANCE.addLog("setEndTarget " + mEndTarget); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "setEndTarget " + mEndTarget, + /* gestureEvent= */ SET_END_TARGET); + switch (mEndTarget) { + case HOME: + ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME); + break; + case NEW_TASK: + ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK); + break; + case LAST_TASK: + ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_LAST_TASK); + break; + case RECENTS: + default: + // No-Op + } if (isAtomic) { mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED); } diff --git a/quickstep/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java index 154848d224..227380664c 100644 --- a/quickstep/src/com/android/quickstep/ImageActionsApi.java +++ b/quickstep/src/com/android/quickstep/ImageActionsApi.java @@ -78,16 +78,17 @@ public class ImageActionsApi { addImageAndSendIntent(crop, intent, true, exceptionCallback); } - @UiThread private void addImageAndSendIntent(@Nullable Rect crop, Intent intent, boolean setData, @Nullable Runnable exceptionCallback) { - if (mBitmapSupplier.get() == null) { - Log.e(TAG, "No snapshot available, not starting share."); - return; - } - UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext, - mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> { + UI_HELPER_EXECUTOR.execute(() -> { + Bitmap bitmap = mBitmapSupplier.get(); + if (bitmap == null) { + Log.e(TAG, "No snapshot available, not starting share."); + return; + } + persistBitmapAndStartActivity(mContext, + bitmap, crop, intent, (uri, intentForUri) -> { intentForUri.addFlags(FLAG_GRANT_READ_URI_PERMISSION); if (setData) { intentForUri.setData(uri); @@ -95,7 +96,8 @@ public class ImageActionsApi { intentForUri.putExtra(EXTRA_STREAM, uri); } return new Intent[]{intentForUri}; - }, TAG, exceptionCallback)); + }, TAG, exceptionCallback); + }); } /** diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index c13b95f2ab..1127e2c8e7 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -33,7 +33,6 @@ import android.view.MotionEvent; 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.LauncherInitListener; @@ -44,8 +43,9 @@ import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; @@ -61,7 +61,7 @@ import java.util.function.Predicate; * {@link BaseActivityInterface} for the in-launcher recents. */ public final class LauncherActivityInterface extends - BaseActivityInterface { + BaseActivityInterface { public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface(); @@ -122,7 +122,7 @@ public final class LauncherActivityInterface extends notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) { @Override - protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity, + protected void createBackgroundToOverviewAnim(QuickstepLauncher activity, PendingAnimation pa) { super.createBackgroundToOverviewAnim(activity, pa); @@ -135,7 +135,7 @@ public final class LauncherActivityInterface extends } }; - BaseQuickstepLauncher launcher = factory.initBackgroundStateUI(); + QuickstepLauncher launcher = factory.initBackgroundStateUI(); // 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. launcher.getAppsView().reset(false /* animate */); @@ -159,14 +159,14 @@ public final class LauncherActivityInterface extends @Nullable @Override - public BaseQuickstepLauncher getCreatedActivity() { - return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); + public QuickstepLauncher getCreatedActivity() { + return QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); } @Nullable @Override public DepthController getDepthController() { - BaseQuickstepLauncher launcher = getCreatedActivity(); + QuickstepLauncher launcher = getCreatedActivity(); if (launcher == null) { return null; } @@ -176,7 +176,7 @@ public final class LauncherActivityInterface extends @Nullable @Override public LauncherTaskbarUIController getTaskbarController() { - BaseQuickstepLauncher launcher = getCreatedActivity(); + QuickstepLauncher launcher = getCreatedActivity(); if (launcher == null) { return null; } @@ -318,7 +318,7 @@ public final class LauncherActivityInterface extends } @Override - protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher, + protected int getOverviewScrimColorForState(QuickstepLauncher launcher, LauncherState state) { return state.getWorkspaceScrimColor(launcher); } diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java index fd9f922005..7a281dd2a7 100644 --- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java +++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java @@ -39,10 +39,10 @@ import android.window.BackEvent; import android.window.IOnBackInvokedCallback; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.quickstep.util.RectFSpringAnim; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -75,7 +75,7 @@ public class LauncherBackAnimationController { private final RectF mCancelRect = new RectF(); /** The current window position. */ private final RectF mCurrentRect = new RectF(); - private final BaseQuickstepLauncher mLauncher; + private final QuickstepLauncher mLauncher; private final int mWindowScaleMarginX; /** Max window translation in the Y axis. */ private final int mWindowMaxDeltaY; @@ -93,7 +93,7 @@ public class LauncherBackAnimationController { private IOnBackInvokedCallback mBackCallback; public LauncherBackAnimationController( - BaseQuickstepLauncher launcher, + QuickstepLauncher launcher, QuickstepTransitionManager quickstepTransitionManager) { mLauncher = launcher; mQuickstepTransitionManager = quickstepTransitionManager; diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java index 50d1244c81..36ca993f85 100644 --- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -35,10 +35,10 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ObjectWrapper; import com.android.launcher3.views.FloatingIconView; import com.android.launcher3.views.FloatingView; @@ -57,7 +57,7 @@ import java.util.ArrayList; * Temporary class to allow easier refactoring */ public class LauncherSwipeHandlerV2 extends - AbsSwipeUpHandler { + AbsSwipeUpHandler { public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, @@ -91,7 +91,7 @@ public class LauncherSwipeHandlerV2 extends mActivity.setHintUserWillBeActive(); } - if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) { + if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) { return new LauncherHomeAnimationFactory(); } if (workspaceView instanceof LauncherAppWidgetHostView) { diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java index b3875aef52..a68bea2cc0 100644 --- a/quickstep/src/com/android/quickstep/MultiStateCallback.java +++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java @@ -22,7 +22,12 @@ import android.os.Looper; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.config.FeatureFlags; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import java.util.ArrayList; import java.util.LinkedList; @@ -41,17 +46,29 @@ public class MultiStateCallback { private final SparseArray>> mStateChangeListeners = new SparseArray<>(); + @NonNull private final TrackedEventsMapper mTrackedEventsMapper; + private final String[] mStateNames; private int mState = 0; public MultiStateCallback(String[] stateNames) { + this(stateNames, stateFlag -> null); + } + + public MultiStateCallback( + String[] stateNames, + @NonNull TrackedEventsMapper trackedEventsMapper) { mStateNames = DEBUG_STATES ? stateNames : null; + mTrackedEventsMapper = trackedEventsMapper; } /** * Adds the provided state flags to the global state on the UI thread and executes any callbacks * as a result. + * + * Also tracks the provided gesture events for error detection. Each provided event must be + * associated with one provided state flag. */ public void setStateOnUiThread(int stateFlag) { if (Looper.myLooper() == Looper.getMainLooper()) { @@ -69,7 +86,9 @@ public class MultiStateCallback { Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding " + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState)); } - + if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) { + trackGestureEvents(stateFlag); + } final int oldState = mState; mState = mState | stateFlag; @@ -87,6 +106,26 @@ public class MultiStateCallback { notifyStateChangeListeners(oldState); } + private void trackGestureEvents(int stateFlags) { + for (int index = 0; (stateFlags >> index) != 0; index++) { + if ((stateFlags & (1 << index)) == 0) { + continue; + } + ActiveGestureErrorDetector.GestureEvent gestureEvent = + mTrackedEventsMapper.getTrackedEventForState(1 << index); + if (gestureEvent == null) { + continue; + } + if (gestureEvent.mLogEvent && gestureEvent.mTrackEvent) { + ActiveGestureLog.INSTANCE.addLog(gestureEvent.name(), gestureEvent); + } else if (gestureEvent.mLogEvent) { + ActiveGestureLog.INSTANCE.addLog(gestureEvent.name()); + } else if (gestureEvent.mTrackEvent) { + ActiveGestureLog.INSTANCE.trackEvent(gestureEvent); + } + } + } + /** * Adds the provided state flags to the global state and executes any change handlers * as a result. @@ -174,4 +213,7 @@ public class MultiStateCallback { return joiner.toString(); } + public interface TrackedEventsMapper { + @Nullable ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateflag); + } } diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java index 895cf89382..1b05fd272d 100644 --- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -32,9 +32,9 @@ import android.view.MotionEvent; import android.view.Surface; import com.android.launcher3.R; -import com.android.launcher3.ResourceUtils; +import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.window.CachedDisplayInfo; import java.io.PrintWriter; diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index dffdc5a641..875b72cb34 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -144,7 +144,7 @@ public class OverviewCommandHelper { RunnableList callbackList = null; if (taskView != null) { taskView.setEndQuickswitchCuj(true); - callbackList = taskView.launchTaskAnimated(); + callbackList = taskView.launchTasks(); } if (callbackList != null) { @@ -193,7 +193,20 @@ public class OverviewCommandHelper { } } - if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) { + final Runnable completeCallback = () -> { + if (cmd.type == TYPE_SHOW_NEXT_FOCUS) { + RecentsView rv = activityInterface.getVisibleRecentsView(); + // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS), + // the touch mode somehow is not change to false by the Android framework. + // The subsequent tab to go through tasks in overview can only be dispatched to + // focuses views, while focus can only be requested in + // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note, + // here we launch overview from home. + rv.getViewRootImpl().touchModeChanged(false); + } + scheduleNextTask(cmd); + }; + if (activityInterface.switchToRecentsIfVisible(completeCallback)) { // If successfully switched, wait until animation finishes return false; } @@ -227,14 +240,21 @@ public class OverviewCommandHelper { interactionHandler.onGestureCancelled(); cmd.removeListener(this); - RecentsView createdRecents = - activityInterface.getCreatedActivity().getOverviewPanel(); + T createdActivity = activityInterface.getCreatedActivity(); + if (createdActivity == null) { + return; + } + RecentsView createdRecents = createdActivity.getOverviewPanel(); if (createdRecents != null) { createdRecents.onRecentsAnimationComplete(); } } }; + RecentsView visibleRecentsView = activityInterface.getVisibleRecentsView(); + if (visibleRecentsView != null) { + visibleRecentsView.moveFocusedTaskToFront(); + } if (mTaskAnimationManager.isRecentsAnimationRunning()) { cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState); cmd.mActiveCallbacks.addListener(interactionHandler); @@ -264,6 +284,13 @@ public class OverviewCommandHelper { RecentsView rv = mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView(); if (rv != null) { + // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS), + // the touch mode somehow is not change to false by the Android framework. + // The subsequent tab to go through tasks in overview can only be dispatched to + // focuses views, while focus can only be requested in + // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note, + // here we launch overview with live tile. + rv.getViewRootImpl().touchModeChanged(false); // Ensure that recents view has focus so that it receives the followup key inputs TaskView taskView = rv.getNextTaskView(); if (taskView == null) { diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java index 0efe6666a8..9e3173c1f0 100644 --- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java +++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java @@ -20,11 +20,11 @@ import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_CHANGED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; -import static com.android.launcher3.Utilities.createHomeIntent; import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter; import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -33,8 +33,12 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.Bundle; import android.util.SparseIntArray; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.tracing.OverviewComponentObserverProto; import com.android.launcher3.tracing.TouchInteractionServiceProto; import com.android.launcher3.util.SimpleBroadcastReceiver; @@ -276,4 +280,34 @@ public final class OverviewComponentObserver { overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed()); serviceProto.setOverviewComponentObvserver(overviewComponentObserver); } + + /** + * Starts the intent for the current home activity. + */ + public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) { + RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context); + OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState); + Intent intent = observer.getHomeIntent(); + observer.onDestroy(); + deviceState.destroy(); + startHomeIntentSafely(context, intent, options); + } + + /** + * Starts the intent for the current home activity. + */ + public static void startHomeIntentSafely( + @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) { + try { + context.startActivity(homeIntent, options); + } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { + context.startActivity(createHomeIntent(), options); + } + } + + private static Intent createHomeIntent() { + return new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } } diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java index 528fb97983..b7cdecd115 100644 --- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -1,16 +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.content.res.Resources; import android.graphics.Rect; import android.os.Bundle; import androidx.annotation.Nullable; +import com.android.launcher3.R; import com.android.launcher3.testing.TestInformationHandler; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.TISBindHelper; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; public class QuickstepTestInformationHandler extends TestInformationHandler { @@ -72,6 +81,37 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { TestProtocol.REQUEST_HAS_TIS, true); return response; } + + case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING: + runOnTISBinder(tisBinder -> { + enableManualTaskbarStashing(tisBinder, true); + }); + return response; + + case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING: + runOnTISBinder(tisBinder -> { + enableManualTaskbarStashing(tisBinder, false); + }); + return response; + + case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED: + runOnTISBinder(tisBinder -> { + enableManualTaskbarStashing(tisBinder, true); + + // Allow null-pointer to catch illegal states. + tisBinder.getTaskbarManager().getCurrentActivityContext() + .unstashTaskbarIfStashed(); + + enableManualTaskbarStashing(tisBinder, false); + }); + return response; + + case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: { + final Resources resources = mContext.getResources(); + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, + resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size)); + return response; + } } return super.call(method, arg, extras); @@ -93,4 +133,30 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { protected boolean isLauncherInitialized() { return super.isLauncherInitialized() && TouchInteractionService.isInitialized(); } + + private void enableManualTaskbarStashing( + TouchInteractionService.TISBinder tisBinder, boolean enable) { + // Allow null-pointer to catch illegal states. + tisBinder.getTaskbarManager().getCurrentActivityContext().enableManualStashingDuringTests( + enable); + } + + /** + * Runs the given command on the UI thread, after ensuring we are connected to + * TouchInteractionService. + */ + protected void runOnTISBinder(Consumer connectionCallback) { + try { + CountDownLatch countDownLatch = new CountDownLatch(1); + TISBindHelper helper = MAIN_EXECUTOR.submit(() -> + new TISBindHelper(mContext, tisBinder -> { + connectionCallback.accept(tisBinder); + countDownLatch.countDown(); + })).get(); + countDownLatch.await(); + MAIN_EXECUTOR.execute(helper::onDestroy); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + } } diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 097850fd6f..6b616b1b07 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -20,6 +20,7 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.annotation.TargetApi; import android.app.ActivityManager; +import android.app.KeyguardManager; import android.os.Build; import android.os.Process; import android.os.RemoteException; @@ -27,14 +28,13 @@ import android.util.SparseBooleanArray; import androidx.annotation.VisibleForTesting; -import com.android.quickstep.util.GroupTask; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.SplitConfigurationOptions; +import com.android.quickstep.util.GroupTask; import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.util.GroupedRecentTaskInfo; -import com.android.wm.shell.util.StagedSplitBounds; +import com.android.wm.shell.util.SplitBounds; import java.io.PrintWriter; import java.util.ArrayList; @@ -49,7 +49,7 @@ public class RecentTasksList { private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0); - private final KeyguardManagerCompat mKeyguardManager; + private final KeyguardManager mKeyguardManager; private final LooperExecutor mMainThreadExecutor; private final SystemUiProxy mSysUiProxy; @@ -62,8 +62,12 @@ public class RecentTasksList { private TaskLoadResult mResultsBg = INVALID_RESULT; private TaskLoadResult mResultsUi = INVALID_RESULT; - public RecentTasksList(LooperExecutor mainThreadExecutor, - KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) { + private RecentsModel.RunningTasksListener mRunningTasksListener; + // Tasks are stored in order of least recently launched to most recently launched. + private ArrayList mRunningTasks; + + public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager, + SystemUiProxy sysUiProxy) { mMainThreadExecutor = mainThreadExecutor; mKeyguardManager = keyguardManager; mChangeId = 1; @@ -73,7 +77,26 @@ public class RecentTasksList { public void onRecentTasksChanged() throws RemoteException { mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged); } + + @Override + public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { + mMainThreadExecutor.execute(() -> { + RecentTasksList.this.onRunningTaskAppeared(taskInfo); + }); + } + + @Override + public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + mMainThreadExecutor.execute(() -> { + RecentTasksList.this.onRunningTaskVanished(taskInfo); + }); + } }); + // We may receive onRunningTaskAppeared events later for tasks which have already been + // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive + // onRunningTaskVanished for tasks not included in the returned list. These cases will be + // addressed when the tasks are added to/removed from mRunningTasks. + initRunningTasks(mSysUiProxy.getRunningTasks(Integer.MAX_VALUE)); } @VisibleForTesting @@ -154,6 +177,59 @@ public class RecentTasksList { mChangeId++; } + /** + * Registers a listener for running tasks + */ + public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) { + mRunningTasksListener = listener; + } + + /** + * Removes the previously registered running tasks listener + */ + public void unregisterRunningTasksListener() { + mRunningTasksListener = null; + } + + private void initRunningTasks(ArrayList runningTasks) { + // Tasks are retrieved in order of most recently launched/used to least recently launched. + mRunningTasks = new ArrayList<>(runningTasks); + Collections.reverse(mRunningTasks); + } + + /** + * Gets the set of running tasks. + */ + public ArrayList getRunningTasks() { + return mRunningTasks; + } + + private void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { + // Make sure this task is not already in the list + for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { + if (taskInfo.taskId == existingTask.taskId) { + return; + } + } + mRunningTasks.add(taskInfo); + if (mRunningTasksListener != null) { + mRunningTasksListener.onRunningTasksChanged(); + } + } + + private void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + // Find the task from the list of running tasks, if it exists + for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { + if (existingTask.taskId != taskInfo.taskId) continue; + + mRunningTasks.remove(existingTask); + if (mRunningTasksListener != null) { + mRunningTasksListener.onRunningTasksChanged(); + } + return; + } + } + /** * Loads and creates a list of all the recent tasks. */ @@ -178,8 +254,13 @@ public class RecentTasksList { TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size()); for (GroupedRecentTaskInfo rawTask : rawTasks) { - ActivityManager.RecentTaskInfo taskInfo1 = rawTask.mTaskInfo1; - ActivityManager.RecentTaskInfo taskInfo2 = rawTask.mTaskInfo2; + if (rawTask.getType() == GroupedRecentTaskInfo.TYPE_FREEFORM) { + GroupTask desktopTask = createDesktopTask(rawTask); + allTasks.add(desktopTask); + continue; + } + ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1(); + ActivityManager.RecentTaskInfo taskInfo2 = rawTask.getTaskInfo2(); Task.TaskKey task1Key = new Task.TaskKey(taskInfo1); Task task1 = loadKeysOnly ? new Task(task1Key) @@ -195,19 +276,29 @@ public class RecentTasksList { tmpLockedUsers.get(task2Key.userId) /* isLocked */); task2.setLastSnapshotData(taskInfo2); } - final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds = - convertSplitBounds(rawTask.mStagedSplitBounds); + final SplitConfigurationOptions.SplitBounds launcherSplitBounds = + convertSplitBounds(rawTask.getSplitBounds()); allTasks.add(new GroupTask(task1, task2, launcherSplitBounds)); } return allTasks; } - private SplitConfigurationOptions.StagedSplitBounds convertSplitBounds( - StagedSplitBounds shellSplitBounds) { + private GroupTask createDesktopTask(GroupedRecentTaskInfo taskInfo) { + // TODO(b/244348395): create a subclass of GroupTask for desktop tile + // We need a single task information as the primary task. Use the first task + Task.TaskKey key = new Task.TaskKey(taskInfo.getTaskInfo1()); + Task task = new Task(key); + task.desktopTile = true; + task.topActivity = key.sourceComponent; + return new GroupTask(task, null, null); + } + + private SplitConfigurationOptions.SplitBounds convertSplitBounds( + SplitBounds shellSplitBounds) { return shellSplitBounds == null ? null : - new SplitConfigurationOptions.StagedSplitBounds( + new SplitConfigurationOptions.SplitBounds( shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds, shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId); } @@ -234,8 +325,8 @@ public class RecentTasksList { mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId); writer.println(prefix + " rawTasks=["); for (GroupedRecentTaskInfo task : rawTasks) { - writer.println(prefix + " t1=" + task.mTaskInfo1.taskId - + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1")); + writer.println(prefix + " t1=" + task.getTaskInfo1().taskId + + " t2=" + (task.getTaskInfo2() != null ? task.getTaskInfo2().taskId : "-1")); } writer.println(prefix + " ]"); } diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 4f0b9767f9..c879494c7d 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -15,17 +15,13 @@ */ package com.android.quickstep; -import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; -import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; - import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION; import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY; -import static com.android.launcher3.Utilities.createHomeIntent; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; -import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; +import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely; import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; @@ -34,12 +30,12 @@ import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MOD import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.app.ActivityOptions; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.util.Log; import android.view.Display; import android.view.SurfaceControl.Transaction; import android.view.View; @@ -82,7 +78,6 @@ import com.android.quickstep.util.TISBindHelper; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -112,8 +107,6 @@ public final class RecentsActivity extends StatefulActivity { private @Nullable TaskbarManager mTaskbarManager; private @Nullable FallbackTaskbarUIController mTaskbarUIController; - private Configuration mOldConfig; - private StateManager mStateManager; // Strong refs to runners which are cleared when the activity is destroyed @@ -138,7 +131,7 @@ public final class RecentsActivity extends StatefulActivity { SplitSelectStateController controller = new SplitSelectStateController(this, mHandler, getStateManager(), - null /* depthController */); + /* depthController */ null, getStatsLogManager()); mDragLayer.recreateControllers(); mFallbackRecentsView.init(mActionsView, controller); @@ -165,7 +158,7 @@ public final class RecentsActivity extends StatefulActivity { @Override public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) { - onHandleConfigChanged(); + onHandleConfigurationChanged(); super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig); } @@ -175,11 +168,8 @@ public final class RecentsActivity extends StatefulActivity { ACTIVITY_TRACKER.handleNewIntent(this); } - /** - * Logic for when device configuration changes (rotation, screen size change, multi-window, - * etc.) - */ - protected void onHandleConfigChanged() { + @Override + protected void onHandleConfigurationChanged() { initDeviceProfile(); AbstractFloatingView.closeOpenViews(this, true, @@ -273,8 +263,10 @@ public final class RecentsActivity extends StatefulActivity { wrapper, RECENTS_LAUNCH_DURATION, RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION - STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread()); - final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper( - ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), + final ActivityOptions options = ActivityOptions.makeRemoteAnimation( + adapterCompat.getWrapped(), + adapterCompat.getRemoteTransition().getTransition()); + final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(options, onEndCallback); activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); activityOptions.options.setLaunchDisplayId( @@ -314,7 +306,6 @@ public final class RecentsActivity extends StatefulActivity { protected void onStart() { // Set the alpha to 1 before calling super, as it may get set back to 0 due to // onActivityStart callback. - Log.d(BAD_STATE, "RecentsActivity onStart mFallbackRecentsView.setContentAlpha(1)"); mFallbackRecentsView.setContentAlpha(1); super.onStart(); mFallbackRecentsView.updateLocusId(); @@ -341,7 +332,6 @@ public final class RecentsActivity extends StatefulActivity { mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER); - mOldConfig = new Configuration(getResources().getConfiguration()); initDeviceProfile(); setupViews(); @@ -350,16 +340,6 @@ public final class RecentsActivity extends StatefulActivity { ACTIVITY_TRACKER.handleCreate(this); } - @Override - public void onConfigurationChanged(Configuration newConfig) { - int diff = newConfig.diff(mOldConfig); - if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) { - onHandleConfigChanged(); - } - mOldConfig.setTo(newConfig); - super.onConfigurationChanged(newConfig); - } - @Override public void onStateSetEnd(RecentsState state) { super.onStateSetEnd(state); @@ -428,8 +408,10 @@ public final class RecentsActivity extends StatefulActivity { RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0, getIApplicationThread()); - startActivity(createHomeIntent(), - ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle()); + ActivityOptions options = ActivityOptions.makeRemoteAnimation( + adapterCompat.getWrapped(), + adapterCompat.getRemoteTransition().getTransition()); + startHomeIntentSafely(this, options.toBundle()); } private final RemoteAnimationFactory mAnimationToHomeFactory = diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java index 51ae56b96d..b233521ff2 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java @@ -16,7 +16,8 @@ package com.android.quickstep; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION; import android.graphics.Rect; import android.util.ArraySet; @@ -28,6 +29,8 @@ import androidx.annotation.UiThread; import com.android.launcher3.Utilities; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -97,18 +100,6 @@ public class RecentsAnimationCallbacks implements RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, Rect homeContentInsets, Rect minimizedHomeBounds) { - // Convert appTargets to type RemoteAnimationTarget for all apps except Home app - RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets) - .filter(remoteAnimationTarget -> - remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME) - .map(RemoteAnimationTargetCompat::unwrap) - .toArray(RemoteAnimationTarget[]::new); - - RemoteAnimationTarget[] nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(nonHomeApps); - - RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets, - wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets), - homeContentInsets, minimizedHomeBounds); mController = new RecentsAnimationController(animationController, mAllowMinimizeSplitScreen, this::onAnimationFinished); @@ -116,7 +107,18 @@ public class RecentsAnimationCallbacks implements Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), mController::finishAnimationToApp); } else { + final RemoteAnimationTarget[] nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy( + Arrays.stream(appTargets).map(RemoteAnimationTargetCompat::unwrap) + .toArray(RemoteAnimationTarget[]::new)); + final RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets, + wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets), + homeContentInsets, minimizedHomeBounds); + Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "RecentsAnimationCallbacks.onAnimationStart", + /* extras= */ targets.apps.length, + /* gestureEvent= */ START_RECENTS_ANIMATION); for (RecentsAnimationListener listener : getListeners()) { listener.onRecentsAnimationStart(mController, targets); } @@ -128,6 +130,9 @@ public class RecentsAnimationCallbacks implements @Override public final void onAnimationCanceled(HashMap thumbnailDatas) { Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "onRecentsAnimationCancelled", + /* gestureEvent= */ CANCEL_RECENTS_ANIMATION); for (RecentsAnimationListener listener : getListeners()) { listener.onRecentsAnimationCanceled(thumbnailDatas); } @@ -138,6 +143,8 @@ public class RecentsAnimationCallbacks implements @Override public void onTasksAppeared(RemoteAnimationTargetCompat[] apps) { Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { + ActiveGestureLog.INSTANCE.addLog("onTasksAppeared", + ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED); for (RecentsAnimationListener listener : getListeners()) { listener.onTasksAppeared(apps); } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index 2007ee1ebd..542c0d4b21 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -32,6 +32,8 @@ import androidx.annotation.UiThread; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RunnableList; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; @@ -104,8 +106,6 @@ public class RecentsAnimationController { } if (mSplitScreenMinimized != splitScreenMinimized) { mSplitScreenMinimized = splitScreenMinimized; - UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(context) - .setSplitScreenMinimized(splitScreenMinimized)); } } @@ -174,7 +174,12 @@ public class RecentsAnimationController { */ @UiThread public void cleanupScreenshot() { - UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot()); + UI_HELPER_EXECUTOR.execute(() -> { + ActiveGestureLog.INSTANCE.addLog( + "cleanupScreenshot", + ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT); + mController.cleanupScreenshot(); + }); } /** @@ -222,7 +227,6 @@ public class RecentsAnimationController { */ public void enableInputConsumer() { UI_HELPER_EXECUTOR.submit(() -> { - mController.hideCurrentInputMethod(); mController.setInputConsumerEnabled(true); }); } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index 4fb7e6ba06..e87fdad933 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -23,9 +23,9 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.launcher3.util.DisplayController.CHANGE_ALL; import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION; -import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON; -import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS; -import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS; +import static com.android.launcher3.util.NavigationMode.NO_BUTTON; +import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS; +import static com.android.launcher3.util.NavigationMode.TWO_BUTTONS; import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED; import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; @@ -61,12 +61,13 @@ import android.provider.Settings; import android.view.MotionEvent; import androidx.annotation.BinderThread; +import androidx.annotation.NonNull; import com.android.launcher3.Utilities; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.SettingsCache; import com.android.quickstep.TopTaskTracker.CachedTaskInfo; import com.android.quickstep.util.NavBarPosition; @@ -124,7 +125,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener { }; private int mGestureBlockingTaskId = -1; - private Region mExclusionRegion; + private @NonNull Region mExclusionRegion = new Region(); private SystemGestureExclusionListenerCompat mExclusionListener; public RecentsAnimationDeviceState(Context context) { @@ -162,6 +163,10 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener { @Override @BinderThread public void onExclusionChanged(Region region) { + if (region == null) { + // Don't think this is possible but just in case, don't let it be null. + region = new Region(); + } // Assignments are atomic, it should be safe on binder thread mExclusionRegion = region; } @@ -498,7 +503,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener { 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 + return mMode == NO_BUTTON && exclusionRegion.contains((int) event.getX(), (int) event.getY()); } @@ -575,19 +580,23 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener { && ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0); } + public String getSystemUiStateString() { + return QuickStepContract.getSystemUiStateString(mSystemUiStateFlags); + } + public void dump(PrintWriter pw) { pw.println("DeviceState:"); pw.println(" canStartSystemGesture=" + canStartSystemGesture()); pw.println(" systemUiFlags=" + mSystemUiStateFlags); - pw.println(" systemUiFlagsDesc=" - + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags)); + pw.println(" systemUiFlagsDesc=" + getSystemUiStateString()); pw.println(" assistantAvailable=" + mAssistantAvailable); pw.println(" assistantDisabled=" + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)); pw.println(" isUserUnlocked=" + mIsUserUnlocked); pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled); pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled); - pw.println(" deferredGestureRegion=" + mDeferredGestureRegion); + pw.println(" deferredGestureRegion=" + mDeferredGestureRegion.getBounds()); + pw.println(" exclusionRegion=" + mExclusionRegion.getBounds()); pw.println(" pipIsActive=" + mPipIsActive); mRotationTouchHelper.dump(pw); } diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 1634c0839f..305347414c 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -22,6 +22,7 @@ import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; import android.annotation.TargetApi; import android.app.ActivityManager; +import android.app.KeyguardManager; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.Intent; @@ -36,10 +37,10 @@ import com.android.launcher3.icons.IconProvider.IconChangeListener; import com.android.launcher3.util.Executors.SimpleThreadFactory; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -54,7 +55,8 @@ import java.util.function.Consumer; * Singleton class to load and manage recents model. */ @TargetApi(Build.VERSION_CODES.O) -public class RecentsModel implements IconChangeListener, TaskStackChangeListener { +public class RecentsModel implements IconChangeListener, TaskStackChangeListener, + TaskVisualsChangeListener { // We do not need any synchronization for this variable as its only written on UI thread. public static final MainThreadInitializedObject INSTANCE = @@ -73,10 +75,12 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener private RecentsModel(Context context) { mContext = context; mTaskList = new RecentTasksList(MAIN_EXECUTOR, - new KeyguardManagerCompat(context), SystemUiProxy.INSTANCE.get(context)); + context.getSystemService(KeyguardManager.class), + SystemUiProxy.INSTANCE.get(context)); IconProvider iconProvider = new IconProvider(context); mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider); + mIconCache.registerTaskVisualsChangeListener(this); mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR); TaskStackChangeListeners.getInstance().registerTaskStackListener(this); @@ -203,6 +207,13 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener } } + @Override + public void onTaskIconChanged(int taskId) { + for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) { + listener.onTaskIconChanged(taskId); + } + } + @Override public void onSystemIconStateChanged(String iconState) { mIconCache.clearCache(); @@ -228,18 +239,33 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener } /** - * Listener for receiving various task properties changes + * Registers a listener for running tasks */ - public interface TaskVisualsChangeListener { + public void registerRunningTasksListener(RunningTasksListener listener) { + mTaskList.registerRunningTasksListener(listener); + } - /** - * Called whn the task thumbnail changes - */ - Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData); + /** + * Removes the previously registered running tasks listener + */ + public void unregisterRunningTasksListener() { + mTaskList.unregisterRunningTasksListener(); + } + /** + * Gets the set of running tasks. + */ + public ArrayList getRunningTasks() { + return mTaskList.getRunningTasks(); + } + + /** + * Listener for receiving running tasks changes + */ + public interface RunningTasksListener { /** - * Called when the icon for a task changes + * Called when there's a change to running tasks */ - void onTaskIconChanged(String pkg, UserHandle user); + void onRunningTasksChanged(); } } diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java index b20d48806a..1bd808d13d 100644 --- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java @@ -17,8 +17,6 @@ package com.android.quickstep; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; -import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; - import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.ArrayList; @@ -114,10 +112,6 @@ public class RemoteAnimationTargets { } public void release() { - if (ENABLE_SHELL_TRANSITIONS) { - mReleaseChecks.clear(); - return; - } if (mReleased) { return; } diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java index c3ea25683d..7183c49ca2 100644 --- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java +++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java @@ -20,7 +20,7 @@ import android.content.Context; import androidx.annotation.Nullable; -import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; @@ -34,7 +34,7 @@ import java.util.ArrayList; */ public class RemoteTargetGluer { private RemoteTargetHandle[] mRemoteTargetHandles; - private StagedSplitBounds mStagedSplitBounds; + private SplitBounds mSplitBounds; /** * Use this constructor if remote targets are split-screen independent @@ -118,18 +118,18 @@ public class RemoteTargetGluer { // remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude, // vice versa - mStagedSplitBounds = new StagedSplitBounds( + mSplitBounds = new SplitBounds( topLeftTarget.startScreenSpaceBounds, bottomRightTarget.startScreenSpaceBounds, splitIds[0], splitIds[1]); mRemoteTargetHandles[0].mTransformParams.setTargetSet( createRemoteAnimationTargetsForTarget(targets, bottomRightTarget)); mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget, - mStagedSplitBounds); + mSplitBounds); mRemoteTargetHandles[1].mTransformParams.setTargetSet( createRemoteAnimationTargetsForTarget(targets, topLeftTarget)); mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(bottomRightTarget, - mStagedSplitBounds); + mSplitBounds); } return mRemoteTargetHandles; } @@ -173,8 +173,8 @@ public class RemoteTargetGluer { return mRemoteTargetHandles; } - public StagedSplitBounds getStagedSplitBounds() { - return mStagedSplitBounds; + public SplitBounds getSplitBounds() { + return mSplitBounds; } /** diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java index f1e20dbb04..f8b69666fe 100644 --- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java +++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java @@ -23,20 +23,20 @@ import static com.android.launcher3.util.DisplayController.CHANGE_ALL; import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION; import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS; -import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS; import android.content.Context; import android.content.res.Resources; import android.view.MotionEvent; import android.view.OrientationEventListener; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.DisplayController.NavigationMode; import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.NavigationMode; import com.android.quickstep.util.RecentsOrientedState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListener; diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index 966710854f..baeb514e18 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -17,7 +17,6 @@ package com.android.quickstep; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT; import android.animation.Animator; import android.content.Context; @@ -72,16 +71,14 @@ public abstract class SwipeUpAnimationLogic implements // How much further we can drag past recents, as a factor of mTransitionDragLength. protected float mDragLengthFactor = 1; - protected boolean mIsSwipeForStagedSplit; + protected boolean mIsSwipeForSplit; public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState) { mContext = context; mDeviceState = deviceState; mGestureState = gestureState; - - mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() && - TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1; + mIsSwipeForSplit = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1; mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface()); mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles(); diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 39d8b5447d..3b2df315b1 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -17,21 +17,25 @@ package com.android.quickstep; import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; -import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import android.app.ActivityManager; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.graphics.Bitmap; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; +import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; @@ -41,8 +45,10 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.IOnBackInvokedCallback; -import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.Info; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; + +import com.android.internal.logging.InstanceId; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.systemui.shared.recents.ISystemUiProxy; @@ -52,6 +58,8 @@ import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationCon import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController; import com.android.systemui.shared.system.smartspace.SmartspaceState; import com.android.wm.shell.back.IBackAnimation; +import com.android.wm.shell.desktopmode.IDesktopMode; +import com.android.wm.shell.floating.IFloatingTasks; import com.android.wm.shell.onehanded.IOneHanded; import com.android.wm.shell.pip.IPip; import com.android.wm.shell.pip.IPipAnimationListener; @@ -70,21 +78,25 @@ import java.util.Arrays; /** * Holds the reference to SystemUI. */ -public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayInfoChangeListener { +public class SystemUiProxy implements ISystemUiProxy { private static final String TAG = SystemUiProxy.class.getSimpleName(); public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(SystemUiProxy::new); + private static final int MSG_SET_SHELF_HEIGHT = 1; + private ISystemUiProxy mSystemUiProxy; private IPip mPip; private ISysuiUnlockAnimationController mSysuiUnlockAnimationController; private ISplitScreen mSplitScreen; + private IFloatingTasks mFloatingTasks; private IOneHanded mOneHanded; private IShellTransitions mShellTransitions; private IStartingWindow mStartingWindow; private IRecentTasks mRecentTasks; private IBackAnimation mBackAnimation; + private IDesktopMode mDesktopMode; private final DeathRecipient mSystemUiProxyDeathRecipient = () -> { MAIN_EXECUTOR.execute(() -> clearProxy()); }; @@ -96,7 +108,7 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI private IPipAnimationListener mPipAnimationListener; private ISplitScreenListener mSplitScreenListener; private IStartingWindowListener mStartingWindowListener; - private ILauncherUnlockAnimationController mPendingLauncherUnlockAnimationController; + private ILauncherUnlockAnimationController mLauncherUnlockAnimationController; private IRecentTasksListener mRecentTasksListener; private final ArrayList mRemoteTransitions = new ArrayList<>(); private IOnBackInvokedCallback mBackToLauncherCallback; @@ -104,24 +116,16 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI // Used to dedupe calls to SystemUI private int mLastShelfHeight; private boolean mLastShelfVisible; - private float mLastNavButtonAlpha; - private boolean mLastNavButtonAnimate; - private boolean mHasNavButtonAlphaBeenSet = false; - private Runnable mPendingSetNavButtonAlpha = null; + + private final Context mContext; + private final Handler mAsyncHandler; // TODO(141886704): Find a way to remove this private int mLastSystemUiStateFlags; public SystemUiProxy(Context context) { - DisplayController.INSTANCE.get(context).addChangeListener(this); - } - - @Override - public void onDisplayInfoChanged(Context context, Info info, int flags) { - if ((flags & CHANGE_NAVIGATION_MODE) != 0) { - // Whenever the nav mode changes, force reset the nav button alpha - setNavBarButtonAlpha(1f, false); - } + mContext = context; + mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync); } @Override @@ -164,20 +168,22 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen, - IOneHanded oneHanded, IShellTransitions shellTransitions, + IFloatingTasks floatingTasks, IOneHanded oneHanded, IShellTransitions shellTransitions, IStartingWindow startingWindow, IRecentTasks recentTasks, ISysuiUnlockAnimationController sysuiUnlockAnimationController, - IBackAnimation backAnimation) { + IBackAnimation backAnimation, IDesktopMode desktopMode) { unlinkToDeath(); mSystemUiProxy = proxy; mPip = pip; mSplitScreen = splitScreen; + mFloatingTasks = floatingTasks; mOneHanded = oneHanded; mShellTransitions = shellTransitions; mStartingWindow = startingWindow; mSysuiUnlockAnimationController = sysuiUnlockAnimationController; mRecentTasks = recentTasks; mBackAnimation = backAnimation; + mDesktopMode = desktopMode; linkToDeath(); // re-attach the listeners once missing due to setProxy has not been initialized yet. if (mPipAnimationListener != null && mPip != null) { @@ -189,10 +195,8 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI if (mStartingWindowListener != null && mStartingWindow != null) { setStartingWindowListener(mStartingWindowListener); } - if (mPendingLauncherUnlockAnimationController != null - && mSysuiUnlockAnimationController != null) { - setLauncherUnlockAnimationController(mPendingLauncherUnlockAnimationController); - mPendingLauncherUnlockAnimationController = null; + if (mSysuiUnlockAnimationController != null && mLauncherUnlockAnimationController != null) { + setLauncherUnlockAnimationController(mLauncherUnlockAnimationController); } for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) { registerRemoteTransition(mRemoteTransitions.get(i)); @@ -203,15 +207,10 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI if (mBackAnimation != null && mBackToLauncherCallback != null) { setBackToLauncherCallback(mBackToLauncherCallback); } - - if (mPendingSetNavButtonAlpha != null) { - mPendingSetNavButtonAlpha.run(); - mPendingSetNavButtonAlpha = null; - } } public void clearProxy() { - setProxy(null, null, null, null, null, null, null, null, null); + setProxy(null, null, null, null, null, null, null, null, null, null, null); } // TODO(141886704): Find a way to remove this @@ -270,43 +269,6 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } } - @Override - public Rect getNonMinimizedSplitScreenSecondaryBounds() { - if (mSystemUiProxy != null) { - try { - return mSystemUiProxy.getNonMinimizedSplitScreenSecondaryBounds(); - } catch (RemoteException e) { - Log.w(TAG, "Failed call getNonMinimizedSplitScreenSecondaryBounds", e); - } - } - return null; - } - - public float getLastNavButtonAlpha() { - return mLastNavButtonAlpha; - } - - @Override - public void setNavBarButtonAlpha(float alpha, boolean animate) { - boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0 - || animate != mLastNavButtonAnimate - || !mHasNavButtonAlphaBeenSet; - if (changed) { - if (mSystemUiProxy == null) { - mPendingSetNavButtonAlpha = () -> setNavBarButtonAlpha(alpha, animate); - } else { - mLastNavButtonAlpha = alpha; - mLastNavButtonAnimate = animate; - mHasNavButtonAlphaBeenSet = true; - try { - mSystemUiProxy.setNavBarButtonAlpha(alpha, animate); - } catch (RemoteException e) { - Log.w(TAG, "Failed call setNavBarButtonAlpha", e); - } - } - } - } - @Override public void onStatusBarMotionEvent(MotionEvent event) { if (mSystemUiProxy != null) { @@ -384,28 +346,6 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } } - @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 setSplitScreenMinimized", e); - } - } - } - @Override public void notifySwipeUpGestureStarted() { if (mSystemUiProxy != null) { @@ -513,12 +453,20 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI * Sets the shelf height. */ public void setShelfHeight(boolean visible, int shelfHeight) { + Message.obtain(mAsyncHandler, MSG_SET_SHELF_HEIGHT, + visible ? 1 : 0 , shelfHeight).sendToTarget(); + } + + @WorkerThread + private void setShelfHeightAsync(int visibleInt, int shelfHeight) { + boolean visible = visibleInt != 0; boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight; - if (mPip != null && changed) { + IPip pip = mPip; + if (pip != null && changed) { mLastShelfVisible = visible; mLastShelfHeight = shelfHeight; try { - mPip.setShelfHeight(visible, shelfHeight); + pip.setShelfHeight(visible, shelfHeight); } catch (RemoteException e) { Log.w(TAG, "Failed call setShelfHeight visible: " + visible + " height: " + shelfHeight, e); @@ -540,12 +488,17 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI mPipAnimationListener = listener; } + /** + * @return Destination bounds of auto-pip animation, {@code null} if the animation is not ready. + */ + @Nullable public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, int launcherRotation, int shelfHeight) { + PictureInPictureParams pictureInPictureParams, int launcherRotation, + Rect hotseatKeepClearArea) { if (mPip != null) { try { return mPip.startSwipePipToHome(componentName, activityInfo, - pictureInPictureParams, launcherRotation, shelfHeight); + pictureInPictureParams, launcherRotation, hotseatKeepClearArea); } catch (RemoteException e) { Log.w(TAG, "Failed call startSwipePipToHome", e); } @@ -598,11 +551,11 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI /** Start multiple tasks in split-screen simultaneously. */ public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio, - RemoteTransitionCompat remoteTransition) { + RemoteTransitionCompat remoteTransition, InstanceId instanceId) { if (mSystemUiProxy != null) { try { mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions, - sidePosition, splitRatio, remoteTransition.getTransition()); + sidePosition, splitRatio, remoteTransition.getTransition(), instanceId); } catch (RemoteException e) { Log.w(TAG, "Failed call startTask"); } @@ -614,11 +567,11 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI */ public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition, - float splitRatio, RemoteAnimationAdapter adapter) { + float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { if (mSystemUiProxy != null) { try { mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId, - sideOptions, sidePosition, splitRatio, adapter); + sideOptions, sidePosition, splitRatio, adapter, instanceId); } catch (RemoteException e) { Log.w(TAG, "Failed call startTasksWithLegacyTransition"); } @@ -628,23 +581,38 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio, - RemoteAnimationAdapter adapter) { + RemoteAnimationAdapter adapter, InstanceId instanceId) { if (mSystemUiProxy != null) { try { mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent, - taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter); + taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter, + instanceId); } catch (RemoteException e) { - Log.w(TAG, "Failed call startTasksWithLegacyTransition"); + Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition"); + } + } + } + + public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId, + Bundle mainOptions, Bundle sideOptions, + @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio, + RemoteAnimationAdapter adapter, InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId, + mainOptions, sideOptions, sidePosition, splitRatio, adapter, instanceId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition"); } } } public void startShortcut(String packageName, String shortcutId, int position, - Bundle options, UserHandle user) { + Bundle options, UserHandle user, InstanceId instanceId) { if (mSplitScreen != null) { try { mSplitScreen.startShortcut(packageName, shortcutId, position, options, - user); + user, instanceId); } catch (RemoteException e) { Log.w(TAG, "Failed call startShortcut"); } @@ -652,10 +620,10 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } public void startIntent(PendingIntent intent, Intent fillInIntent, int position, - Bundle options) { + Bundle options, InstanceId instanceId) { if (mSplitScreen != null) { try { - mSplitScreen.startIntent(intent, fillInIntent, position, options); + mSplitScreen.startIntent(intent, fillInIntent, position, options, instanceId); } catch (RemoteException e) { Log.w(TAG, "Failed call startIntent"); } @@ -700,6 +668,20 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI return null; } + // + // Floating tasks + // + + public void showFloatingTask(Intent intent) { + if (mFloatingTasks != null) { + try { + mFloatingTasks.showTask(intent); + } catch (RemoteException e) { + Log.w(TAG, "Launcher: Failed call showFloatingTask", e); + } + } + } + // // One handed // @@ -791,11 +773,11 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI controller.dispatchSmartspaceStateToSysui(); } } catch (RemoteException e) { - Log.w(TAG, "Failed call setStartingWindowListener", e); + Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e); } - } else { - mPendingLauncherUnlockAnimationController = controller; } + + mLauncherUnlockAnimationController = controller; } /** @@ -904,4 +886,44 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } return new ArrayList<>(); } + + /** + * Gets the set of running tasks. + */ + public ArrayList getRunningTasks(int numTasks) { + if (mRecentTasks != null + && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) { + try { + return new ArrayList<>(Arrays.asList(mRecentTasks.getRunningTasks(numTasks))); + } catch (RemoteException e) { + Log.w(TAG, "Failed call getRunningTasks", e); + } + } + return new ArrayList<>(); + } + + private boolean handleMessageAsync(Message msg) { + switch (msg.what) { + case MSG_SET_SHELF_HEIGHT: + setShelfHeightAsync(msg.arg1, msg.arg2); + return true; + } + + return false; + } + + // + // Desktop Mode + // + + /** Call shell to show all apps active on the desktop */ + public void showDesktopApps() { + if (mDesktopMode != null) { + try { + mDesktopMode.showDesktopApps(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call showDesktopApps", e); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java index 54f457d2d8..b9a1b0629f 100644 --- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -39,13 +39,12 @@ import com.android.quickstep.TopTaskTracker.CachedTaskInfo; import com.android.quickstep.views.RecentsView; 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.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.RemoteTransitionCompat; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; -import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener { @@ -156,30 +155,21 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) { RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0]; BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface(); - // Convert appTargets to type RemoteAnimationTarget for all apps except Home app - final ArrayList tmpNonHomeApps = new ArrayList<>(); - final ArrayList tmpHomeApps = new ArrayList<>(); + for (RemoteAnimationTargetCompat compat : appearedTaskTargets) { - if (compat.activityType != ACTIVITY_TYPE_HOME) { - tmpNonHomeApps.add(compat); - } else { - tmpHomeApps.add(compat); + if (compat.activityType == ACTIVITY_TYPE_HOME + && activityInterface.getCreatedActivity() instanceof RecentsActivity) { + // When receive opening home activity while recents is running, enter home + // and dismiss recents. + ((RecentsActivity) activityInterface.getCreatedActivity()).startHome(); + return; } } - RemoteAnimationTarget[] nonHomeApps = tmpNonHomeApps.stream() - .map(RemoteAnimationTargetCompat::unwrap) - .toArray(RemoteAnimationTarget[]::new); - RemoteAnimationTarget[] homeApps = tmpHomeApps.stream() - .map(RemoteAnimationTargetCompat::unwrap) - .toArray(RemoteAnimationTarget[]::new); - if (homeApps.length > 0 - && activityInterface.getCreatedActivity() instanceof RecentsActivity) { - ((RecentsActivity) activityInterface.getCreatedActivity()).startHome(); - return; - } - RemoteAnimationTarget[] nonAppTargets = - SystemUiProxy.INSTANCE.getNoCreate().onStartingSplitLegacy(nonHomeApps); + RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.getNoCreate() + .onStartingSplitLegacy(Arrays.stream(appearedTaskTargets) + .map(RemoteAnimationTargetCompat::unwrap) + .toArray(RemoteAnimationTarget[]::new)); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode() && activityInterface.getCreatedActivity() != null) { @@ -195,7 +185,10 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn } else if (nonAppTargets != null && nonAppTargets.length > 0) { TaskViewUtils.createSplitAuxiliarySurfacesAnimator( RemoteAnimationTargetCompat.wrap(nonAppTargets) /* nonApps */, - true /*shown*/, dividerAnimator -> dividerAnimator.start()); + true /*shown*/, dividerAnimator -> { + dividerAnimator.start(); + dividerAnimator.end(); + }); } if (mController != null) { if (mLastAppearedTaskTarget == null @@ -235,7 +228,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks, mController != null ? mController.getController() : null, mCtx.getIApplicationThread()); - final ActivityOptions options = ActivityOptionsCompat.makeRemoteTransition(transition); + final ActivityOptions options = ActivityOptions.makeRemoteTransition( + transition.getTransition()); // Allowing to pause Home if Home is top activity and Recents is not Home. So when user // start home when recents animation is playing, the home activity can be resumed again // to let the transition controller collect Home activity. diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 3803f0350b..dc60875db8 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -18,6 +18,7 @@ package com.android.quickstep; import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED; import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.content.Context; @@ -46,6 +47,7 @@ import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.Preconditions; import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.TaskKeyLruCache; +import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.system.PackageManagerWrapper; @@ -70,6 +72,9 @@ public class TaskIconCache implements DisplayInfoChangeListener { private BaseIconFactory mIconFactory; + @Nullable + public TaskVisualsChangeListener mTaskVisualsChangeListener = null; + public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) { mContext = context; mBgExecutor = bgExecutor; @@ -116,6 +121,7 @@ public class TaskIconCache implements DisplayInfoChangeListener { task.icon = result.icon; task.titleDescription = result.contentDescription; callback.accept(task); + dispatchIconUpdate(task.key.id); } }; mBgExecutor.execute(request); @@ -257,7 +263,8 @@ public class TaskIconCache implements DisplayInfoChangeListener { if (mIconFactory == null) { mIconFactory = new BaseIconFactory(mContext, DisplayController.INSTANCE.get(mContext).getInfo().getDensityDpi(), - mContext.getResources().getDimensionPixelSize(R.dimen.taskbar_icon_size)); + mContext.getResources().getDimensionPixelSize( + R.dimen.task_icon_cache_default_icon_size)); } return mIconFactory; } @@ -272,4 +279,18 @@ public class TaskIconCache implements DisplayInfoChangeListener { public Drawable icon; public String contentDescription = ""; } + + void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) { + mTaskVisualsChangeListener = newListener; + } + + void removeTaskVisualsChangeListener() { + mTaskVisualsChangeListener = null; + } + + void dispatchIconUpdate(int taskId) { + if (mTaskVisualsChangeListener != null) { + mTaskVisualsChangeListener.onTaskIconChanged(taskId); + } + } } diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java index 3ef1332a87..e10cab60e0 100644 --- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java @@ -23,7 +23,6 @@ import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBN import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED; import android.annotation.SuppressLint; -import android.app.ActivityManager; import android.content.Context; import android.graphics.Insets; import android.graphics.Matrix; @@ -37,17 +36,12 @@ import androidx.annotation.RequiresApi; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; -import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.ResourceBasedOverride; -import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.views.ActivityContext; -import com.android.quickstep.TaskShortcutFactory.SplitSelectSystemShortcut; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; @@ -66,7 +60,7 @@ import java.util.List; public class TaskOverlayFactory implements ResourceBasedOverride { public static List getEnabledShortcuts(TaskView taskView, - DeviceProfile deviceProfile, TaskIdAttributeContainer taskContainer) { + TaskIdAttributeContainer taskContainer) { final ArrayList shortcuts = new ArrayList<>(); final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext()); boolean hasMultipleTasks = taskView.getTaskIds()[1] != -1; @@ -75,17 +69,11 @@ public class TaskOverlayFactory implements ResourceBasedOverride { continue; } - SystemShortcut shortcut = menuOption.getShortcut(activity, taskContainer); - if (shortcut == null) { + List menuShortcuts = menuOption.getShortcuts(activity, taskContainer); + if (menuShortcuts == null) { continue; } - - if (menuOption == TaskShortcutFactory.SPLIT_SCREEN && - FeatureFlags.ENABLE_SPLIT_SELECT.get()) { - addSplitOptions(shortcuts, activity, taskView, deviceProfile); - } else { - shortcuts.add(shortcut); - } + shortcuts.addAll(menuShortcuts); } RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState(); boolean canLauncherRotate = orientedState.isRecentsActivityRotationAllowed(); @@ -94,61 +82,24 @@ public class TaskOverlayFactory implements ResourceBasedOverride { // Add overview actions to the menu when in in-place rotate landscape mode. if (!canLauncherRotate && isInLandscape) { // Add screenshot action to task menu. - SystemShortcut screenshotShortcut = TaskShortcutFactory.SCREENSHOT - .getShortcut(activity, taskContainer); - if (screenshotShortcut != null) { - shortcuts.add(screenshotShortcut); + List screenshotShortcuts = TaskShortcutFactory.SCREENSHOT + .getShortcuts(activity, taskContainer); + if (screenshotShortcuts != null) { + shortcuts.addAll(screenshotShortcuts); } // Add modal action only if display orientation is the same as the device orientation. if (orientedState.getDisplayRotation() == ROTATION_0) { - SystemShortcut modalShortcut = TaskShortcutFactory.MODAL - .getShortcut(activity, taskContainer); - if (modalShortcut != null) { - shortcuts.add(modalShortcut); + List modalShortcuts = TaskShortcutFactory.MODAL + .getShortcuts(activity, taskContainer); + if (modalShortcuts != null) { + shortcuts.addAll(modalShortcuts); } } } return shortcuts; } - - /** - * Does NOT add split options in the following scenarios: - * * The taskView to add split options is already showing split screen tasks - * * There aren't at least 2 tasks in overview to show split options for - * * Device is in "Lock task mode" - * * The taskView to show split options for is the focused task AND we haven't started - * scrolling in overview (if we haven't scrolled, there's a split overview action button so - * we don't need this menu option) - */ - private static void addSplitOptions(List outShortcuts, - BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) { - RecentsView recentsView = taskView.getRecentsView(); - PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); - int[] taskViewTaskIds = taskView.getTaskIds(); - boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 && - taskViewTaskIds[1] != -1; - boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2; - boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask(); - boolean isTaskInExpectedScrollPosition = - recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView)); - ActivityManager activityManager = - (ActivityManager) taskView.getContext().getSystemService(Context.ACTIVITY_SERVICE); - boolean isLockTaskMode = activityManager.isInLockTaskMode(); - - if (taskViewHasMultipleTasks || notEnoughTasksToSplit || isLockTaskMode || - (isFocusedTask && isTaskInExpectedScrollPosition)) { - return; - } - - List positions = - orientationHandler.getSplitPositionOptions(deviceProfile); - for (SplitPositionOption option : positions) { - outShortcuts.add(new SplitSelectSystemShortcut(activity, taskView, option)); - } - } - public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { return new TaskOverlay(thumbnailView); } @@ -170,7 +121,7 @@ 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 TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ TaskShortcutFactory.APP_INFO, - TaskShortcutFactory.SPLIT_SCREEN, + TaskShortcutFactory.SPLIT_SELECT, TaskShortcutFactory.PIN, TaskShortcutFactory.INSTALL, TaskShortcutFactory.FREE_FORM, diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index e807e26b08..eae79dfa19 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -16,11 +16,10 @@ package com.android.quickstep; -import static android.view.Display.DEFAULT_DISPLAY; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP; import android.app.Activity; import android.app.ActivityOptions; @@ -29,9 +28,16 @@ import android.graphics.Color; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManagerGlobal; import android.window.SplashScreen; +import androidx.annotation.Nullable; + import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; @@ -39,6 +45,7 @@ import com.android.launcher3.logging.StatsLogManager.LauncherEvent; import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut.AppInfo; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.quickstep.views.RecentsView; @@ -50,26 +57,38 @@ 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.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.Function; +import java.util.stream.Collectors; /** * Represents a system shortcut that can be shown for a recent task. */ public interface TaskShortcutFactory { - SystemShortcut getShortcut(BaseDraggingActivity activity, - TaskIdAttributeContainer taskContainer); + @Nullable + default List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + return null; + } default boolean showForSplitscreen() { return false; } + /** @return a singleton list if the provided shortcut is non-null, null otherwise */ + @Nullable + default List createSingletonShortcutList(@Nullable SystemShortcut shortcut) { + if (shortcut != null) { + return Collections.singletonList(shortcut); + } + return null; + } + TaskShortcutFactory APP_INFO = new TaskShortcutFactory() { @Override - public SystemShortcut getShortcut(BaseDraggingActivity activity, + public List getShortcuts(BaseDraggingActivity activity, TaskIdAttributeContainer taskContainer) { TaskView taskView = taskContainer.getTaskView(); AppInfo.SplitAccessibilityInfo accessibilityInfo = @@ -77,7 +96,8 @@ public interface TaskShortcutFactory { TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()), taskContainer.getA11yNodeId() ); - return new AppInfo(activity, taskContainer.getItemInfo(), taskView, accessibilityInfo); + return Collections.singletonList(new AppInfo(activity, taskContainer.getItemInfo(), + taskView, accessibilityInfo)); } @Override @@ -86,40 +106,10 @@ public interface TaskShortcutFactory { } }; - abstract class MultiWindowFactory implements TaskShortcutFactory { - - private final int mIconRes; - private final int mTextRes; - private final LauncherEvent mLauncherEvent; - - MultiWindowFactory(int iconRes, int textRes, LauncherEvent launcherEvent) { - mIconRes = iconRes; - mTextRes = textRes; - mLauncherEvent = launcherEvent; - } - - 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, - TaskIdAttributeContainer taskContainer) { - final Task task = taskContainer.getTask(); - if (!task.isDockable) { - return null; - } - if (!isAvailable(activity, task.key.displayId)) { - return null; - } - return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskContainer, this, - mLauncherEvent); - } - } - class SplitSelectSystemShortcut extends SystemShortcut { private final TaskView mTaskView; private final SplitPositionOption mSplitPositionOption; + public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView, SplitPositionOption option) { super(option.iconResId, option.textResId, target, taskView.getItemInfo(), taskView); @@ -133,19 +123,18 @@ public interface TaskShortcutFactory { } } - class MultiWindowSystemShortcut extends SystemShortcut { + class FreeformSystemShortcut extends SystemShortcut { + private static final String TAG = "FreeformSystemShortcut"; private Handler mHandler; private final RecentsView mRecentsView; private final TaskThumbnailView mThumbnailView; private final TaskView mTaskView; - private final MultiWindowFactory mFactory; private final LauncherEvent mLauncherEvent; - public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity, - TaskIdAttributeContainer taskContainer, MultiWindowFactory factory, - LauncherEvent launcherEvent) { + public FreeformSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer, LauncherEvent launcherEvent) { super(iconRes, textRes, activity, taskContainer.getItemInfo(), taskContainer.getTaskView()); mLauncherEvent = launcherEvent; @@ -153,55 +142,30 @@ public interface TaskShortcutFactory { mTaskView = taskContainer.getTaskView(); mRecentsView = activity.getOverviewPanel(); mThumbnailView = taskContainer.getThumbnailView(); - 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); + RecentsView rv = mTarget.getOverviewPanel(); + rv.switchToScreenshot(() -> { + rv.finishRecentsAnimation(true /* toHome */, () -> { + mTarget.returnToHomescreen(); + rv.getHandler().post(this::startActivity); + }); + }); + } - ActivityOptions options = mFactory.makeLaunchOptions(mTarget); + private void startActivity() { + final Task.TaskKey taskKey = mTaskView.getTask().key; + final int taskId = taskKey.id; + final ActivityOptions options = makeLaunchOptions(mTarget); if (options != null) { options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); } 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 @@ -233,82 +197,132 @@ public interface TaskShortcutFactory { taskId, thumbnail, taskBounds)); } }; - WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( + overridePendingAppTransitionMultiThumbFuture( future, animStartedListener, mHandler, true /* scaleUp */, taskKey.displayId); mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo()) .log(mLauncherEvent); } } - } - /** @Deprecated */ - TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory(R.drawable.ic_split_screen, - R.string.recent_task_option_split_screen, LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP) { - - @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 int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition( - activity.getDisplayId()); - if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) { - return null; + /** + * Overrides a pending app transition. + */ + private void overridePendingAppTransitionMultiThumbFuture( + AppTransitionAnimationSpecsFuture animationSpecFuture, Runnable animStartedCallback, + Handler animStartedCallbackHandler, boolean scaleUp, int displayId) { + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionMultiThumbFuture( + animationSpecFuture.getFuture(), + RecentsTransition.wrapStartedListener(animStartedCallbackHandler, + animStartedCallback), scaleUp, displayId); + } catch (RemoteException e) { + Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", + e); } - boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT; - return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft); } - @Override - protected boolean onActivityStarted(BaseDraggingActivity activity) { - return true; - } - }; - - TaskShortcutFactory FREE_FORM = new MultiWindowFactory(R.drawable.ic_split_screen, - R.string.recent_task_option_freeform, LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP) { - - @Override - protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { - return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity); - } - - @Override - protected ActivityOptions makeLaunchOptions(Activity activity) { - ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions(); + private ActivityOptions makeLaunchOptions(Activity activity) { + ActivityOptions activityOptions = ActivityOptions.makeBasic(); + activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); // Arbitrary bounds only because freeform is in dev mode right now - Rect r = new Rect(50, 50, 200, 200); + final View decorView = activity.getWindow().getDecorView(); + final WindowInsets insets = decorView.getRootWindowInsets(); + final Rect r = new Rect(0, 0, decorView.getWidth() / 2, decorView.getHeight() / 2); + r.offsetTo(insets.getSystemWindowInsetLeft() + 50, + insets.getSystemWindowInsetTop() + 50); activityOptions.setLaunchBounds(r); return activityOptions; } + } + /** + * Does NOT add split options in the following scenarios: + * * The taskView to add split options is already showing split screen tasks + * * There aren't at least 2 tasks in overview to show split options for + * * Split isn't supported by the task itself (non resizable activity) + * * We aren't currently in multi-window + * * The taskView to show split options for is the focused task AND we haven't started + * scrolling in overview (if we haven't scrolled, there's a split overview action button so + * we don't need this menu option) + */ + TaskShortcutFactory SPLIT_SELECT = new TaskShortcutFactory() { @Override - protected boolean onActivityStarted(BaseDraggingActivity activity) { - activity.returnToHomescreen(); - return true; + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + DeviceProfile deviceProfile = activity.getDeviceProfile(); + final Task task = taskContainer.getTask(); + final TaskView taskView = taskContainer.getTaskView(); + final RecentsView recentsView = taskView.getRecentsView(); + final PagedOrientationHandler orientationHandler = + recentsView.getPagedOrientationHandler(); + + int[] taskViewTaskIds = taskView.getTaskIds(); + boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 && + taskViewTaskIds[1] != -1; + boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2; + boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask(); + boolean isTaskInExpectedScrollPosition = + recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView)); + boolean isTaskSplitNotSupported = !task.isDockable; + boolean hideForExistingMultiWindow = activity.getDeviceProfile().isMultiWindowMode; + + if (taskViewHasMultipleTasks || + notEnoughTasksToSplit || + isTaskSplitNotSupported || + hideForExistingMultiWindow || + (isFocusedTask && isTaskInExpectedScrollPosition)) { + return null; + } + + return orientationHandler.getSplitPositionOptions(deviceProfile) + .stream() + .map((Function) option -> + new SplitSelectSystemShortcut(activity, taskView, option)) + .collect(Collectors.toList()); } }; - TaskShortcutFactory PIN = (activity, taskContainer) -> { - if (!SystemUiProxy.INSTANCE.get(activity).isActive()) { - return null; + TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() { + @Override + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + final Task task = taskContainer.getTask(); + if (!task.isDockable) { + return null; + } + if (!isAvailable(activity, task.key.displayId)) { + return null; + } + + return Collections.singletonList(new FreeformSystemShortcut(R.drawable.ic_split_screen, + R.string.recent_task_option_freeform, activity, taskContainer, + LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP)); } - if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { - return null; + + private boolean isAvailable(BaseDraggingActivity activity, int displayId) { + return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity) + && !SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false); } - if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { - // We shouldn't be able to pin while an app is locked. - return null; + }; + + TaskShortcutFactory PIN = new TaskShortcutFactory() { + @Override + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + 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 Collections.singletonList(new PinSystemShortcut(activity, taskContainer)); } - return new PinSystemShortcut(activity, taskContainer); }; class PinSystemShortcut extends SystemShortcut { @@ -335,26 +349,52 @@ public interface TaskShortcutFactory { } } - TaskShortcutFactory INSTALL = (activity, taskContainer) -> - InstantAppResolver.newInstance(activity).isInstantApp(activity, - taskContainer.getTask().getTopComponent().getPackageName()) - ? new SystemShortcut.Install(activity, taskContainer.getItemInfo(), - taskContainer.getTaskView()) : null; + TaskShortcutFactory INSTALL = new TaskShortcutFactory() { + @Override + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + return InstantAppResolver.newInstance(activity).isInstantApp(activity, + taskContainer.getTask().getTopComponent().getPackageName()) ? + Collections.singletonList(new SystemShortcut.Install(activity, + taskContainer.getItemInfo(), taskContainer.getTaskView())) : + null; + } + }; - TaskShortcutFactory WELLBEING = (activity, taskContainer) -> - WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo(), - taskContainer.getTaskView()); + TaskShortcutFactory WELLBEING = new TaskShortcutFactory() { + @Override + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + SystemShortcut wellbeingShortcut = + WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, + taskContainer.getItemInfo(), taskContainer.getTaskView()); + return createSingletonShortcutList(wellbeingShortcut); + } + }; - TaskShortcutFactory SCREENSHOT = (activity, taskContainer) -> - taskContainer.getThumbnailView().getTaskOverlay() + TaskShortcutFactory SCREENSHOT = new TaskShortcutFactory() { + @Override + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + SystemShortcut screenshotShortcut = taskContainer.getThumbnailView().getTaskOverlay() .getScreenshotShortcut(activity, taskContainer.getItemInfo(), taskContainer.getTaskView()); - - TaskShortcutFactory MODAL = (activity, taskContainer) -> { - if (ENABLE_OVERVIEW_SELECTIONS.get()) { - return taskContainer.getThumbnailView().getTaskOverlay().getModalStateSystemShortcut( - taskContainer.getItemInfo(), taskContainer.getTaskView()); + return createSingletonShortcutList(screenshotShortcut); + } + }; + + TaskShortcutFactory MODAL = new TaskShortcutFactory() { + @Override + public List getShortcuts(BaseDraggingActivity activity, + TaskIdAttributeContainer taskContainer) { + SystemShortcut modalStateSystemShortcut = + taskContainer.getThumbnailView().getTaskOverlay() + .getModalStateSystemShortcut( + taskContainer.getItemInfo(), taskContainer.getTaskView()); + if (ENABLE_OVERVIEW_SELECTIONS.get()) { + return createSingletonShortcutList(modalStateSystemShortcut); + } + return null; } - return null; }; } diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java index c9db1533ff..d7227780c6 100644 --- a/quickstep/src/com/android/quickstep/TaskUtils.java +++ b/quickstep/src/com/android/quickstep/TaskUtils.java @@ -18,6 +18,8 @@ package com.android.quickstep; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -25,6 +27,8 @@ import android.content.pm.PackageManager; import android.os.UserHandle; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageManagerHelper; @@ -47,16 +51,32 @@ public final class TaskUtils { * TODO: remove this once we switch to getting the icon and label from IconCache. */ public static CharSequence getTitle(Context context, Task task) { - UserHandle user = UserHandle.of(task.key.userId); + return getTitle(context, task.key.userId, task.getTopComponent().getPackageName()); + } + + public static CharSequence getTitle( + @NonNull Context context, + @UserIdInt @Nullable Integer userId, + @Nullable String packageName) { + if (userId == null || packageName == null) { + if (userId == null) { + Log.e(TAG, "Failed to get title; missing userId"); + } + if (packageName == null) { + Log.e(TAG, "Failed to get title; missing packageName"); + } + return ""; + } + UserHandle user = UserHandle.of(userId); ApplicationInfo applicationInfo = new PackageManagerHelper(context) - .getApplicationInfo(task.getTopComponent().getPackageName(), user, 0); + .getApplicationInfo(packageName, user, 0); if (applicationInfo == null) { - Log.e(TAG, "Failed to get title for task " + task); + Log.e(TAG, "Failed to get title for userId=" + userId + ", packageName=" + packageName); return ""; } PackageManager packageManager = context.getPackageManager(); return packageManager.getUserBadgedLabel( - applicationInfo.loadLabel(packageManager), user); + applicationInfo.loadLabel(packageManager), user); } public static ComponentKey getLaunchComponentKeyForTask(Task.TaskKey taskKey) { diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index db402aff85..a96524e186 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -36,15 +36,13 @@ import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.statehandlers.DepthController.DEPTH; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; +import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH; 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.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.PendingIntent; @@ -55,7 +53,6 @@ import android.graphics.Matrix.ScaleToFit; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; -import android.util.Log; import android.view.SurfaceControl; import android.view.View; import android.window.TransitionInfo; @@ -197,10 +194,10 @@ public final class TaskViewUtils { int taskIndex = recentsView.indexOfChild(v); Context context = v.getContext(); - DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); + BaseActivity baseActivity = BaseActivity.fromContext(context); + DeviceProfile dp = baseActivity.getDeviceProfile(); boolean showAsGrid = dp.isTablet; - boolean parallaxCenterAndAdjacentTask = - taskIndex != recentsView.getCurrentPage() && !showAsGrid; + boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage(); int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex); int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0; @@ -370,7 +367,7 @@ public final class TaskViewUtils { }); if (depthController != null) { - out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context), + out.setFloat(depthController, STATE_DEPTH, BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE_INTERPOLATOR); } } @@ -391,10 +388,39 @@ public final class TaskViewUtils { * device is considered in multiWindowMode and things like insets and stuff change * and calculations have to be adjusted in the animations for that */ - public static void composeRecentsSplitLaunchAnimator(int initialTaskId, - @Nullable PendingIntent initialTaskPendingIntent, int secondTaskId, + public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView, + @NonNull StateManager stateManager, @Nullable DepthController depthController, + int initialTaskId, @Nullable PendingIntent initialTaskPendingIntent, int secondTaskId, @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + if (launchingTaskView != null) { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finishCallback.run(); + } + }); + + final RemoteAnimationTargetCompat[] appTargets = + RemoteAnimationTargetCompat.wrapApps(transitionInfo, t, null /* leashMap */); + final RemoteAnimationTargetCompat[] wallpaperTargets = + RemoteAnimationTargetCompat.wrapNonApps( + transitionInfo, true /* wallpapers */, t, null /* leashMap */); + final RemoteAnimationTargetCompat[] nonAppTargets = + RemoteAnimationTargetCompat.wrapNonApps( + transitionInfo, false /* wallpapers */, t, null /* leashMap */); + final RecentsView recentsView = launchingTaskView.getRecentsView(); + composeRecentsLaunchAnimator(animatorSet, launchingTaskView, + appTargets, wallpaperTargets, nonAppTargets, + true, stateManager, + recentsView, depthController); + + t.apply(); + animatorSet.start(); + return; + } + // TODO: consider initialTaskPendingIntent TransitionInfo.Change splitRoot1 = null; TransitionInfo.Change splitRoot2 = null; @@ -468,7 +494,6 @@ public final class TaskViewUtils { animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); finishCallback.run(); } }); @@ -530,7 +555,6 @@ public final class TaskViewUtils { for (SurfaceControl leash: closingTargets) { t.hide(leash); } - super.onAnimationEnd(animation); finishCallback.run(); } }); @@ -568,39 +592,26 @@ public final class TaskViewUtils { Animator launcherAnim; final AnimatorListenerAdapter windowAnimEndListener; if (launcherClosing) { - Context context = v.getContext(); - DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); - launcherAnim = dp.isTablet - ? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0) - : recentsView.createAdjacentPageAnimForTaskLaunch(taskView); - if (dp.isTablet) { - Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator alpha=" + 0); - launcherAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onStart"); - } - - @Override - public void onAnimationCancel(Animator animation) { - float alpha = recentsView == null - ? -1 - : RecentsView.CONTENT_ALPHA.get(recentsView); - Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onCancel, alpha=" - + alpha); - } - - @Override - public void onAnimationEnd(Animator animation) { - Log.d(BAD_STATE, "TVU composeRecentsLaunchAnimator onEnd"); - } - }); + // Since Overview is in launcher, just opening overview sets willFinishToHome to true. + // Now that we are closing the launcher, we need to (re)set willFinishToHome back to + // false. Otherwise, RecentsAnimationController can't differentiate between closing + // overview to 3p home vs closing overview to app. + final RecentsAnimationController raController = + recentsView.getRecentsAnimationController(); + if (raController != null) { + raController.setWillFinishToHome(false); } + launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView); launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR); launcherAnim.setDuration(RECENTS_LAUNCH_DURATION); - // Make sure recents gets fixed up by resetting task alphas and scales, etc. windowAnimEndListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + recentsView.onTaskLaunchedInLiveTileMode(); + } + + // Make sure recents gets fixed up by resetting task alphas and scales, etc. @Override public void onAnimationEnd(Animator animation) { recentsView.finishRecentsAnimation(false /* toRecents */, () -> { @@ -657,7 +668,7 @@ public final class TaskViewUtils { for (int i = 0; i < nonApps.length; ++i) { final RemoteAnimationTargetCompat targ = nonApps[i]; final SurfaceControl leash = targ.leash; - if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null) { + if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) { auxiliarySurfaces.add(leash); hasSurfaceToAnimate = true; } @@ -670,16 +681,18 @@ public final class TaskViewUtils { dockFadeAnimator.addUpdateListener(valueAnimator -> { float progress = valueAnimator.getAnimatedFraction(); for (SurfaceControl leash : auxiliarySurfaces) { - t.setAlpha(leash, shown ? progress : 1 - progress); + if (leash != null && leash.isValid()) { + t.setAlpha(leash, shown ? progress : 1 - progress); + } } t.apply(); }); dockFadeAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); if (shown) { for (SurfaceControl leash : auxiliarySurfaces) { + t.setLayer(leash, Integer.MAX_VALUE); t.setAlpha(leash, 0); t.show(leash); } @@ -689,10 +702,11 @@ public final class TaskViewUtils { @Override public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); if (!shown) { for (SurfaceControl leash : auxiliarySurfaces) { - t.hide(leash); + if (leash != null && leash.isValid()) { + t.hide(leash); + } } t.apply(); } diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java index 723dc721a9..d4bf5c75aa 100644 --- a/quickstep/src/com/android/quickstep/TopTaskTracker.java +++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java @@ -20,9 +20,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.content.Intent.ACTION_CHOOSER; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; +import static android.view.Display.DEFAULT_DISPLAY; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; +import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; @@ -31,9 +33,9 @@ import androidx.annotation.UiThread; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SplitConfigurationOptions; +import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; import com.android.launcher3.util.SplitConfigurationOptions.StageType; -import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition; import com.android.launcher3.util.TraceHelper; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; @@ -63,8 +65,9 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta // Ordered list with first item being the most recent task. private final LinkedList mOrderedTaskList = new LinkedList<>(); - private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition(); - private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition(); + + private final SplitStageInfo mMainStagePosition = new SplitStageInfo(); + private final SplitStageInfo mSideStagePosition = new SplitStageInfo(); private int mPinnedTaskId = INVALID_TASK_ID; private TopTaskTracker(Context context) { @@ -84,6 +87,19 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta public void onTaskMovedToFront(RunningTaskInfo taskInfo) { mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId); mOrderedTaskList.addFirst(taskInfo); + + // Keep the home display's top running task in the first while adding a non-home + // display's task to the list, to avoid showing non-home display's task upon going to + // Recents animation. + if (taskInfo.displayId != DEFAULT_DISPLAY) { + final RunningTaskInfo topTaskOnHomeDisplay = mOrderedTaskList.stream() + .filter(rto -> rto.displayId == DEFAULT_DISPLAY).findFirst().orElse(null); + if (topTaskOnHomeDisplay != null) { + mOrderedTaskList.removeIf(rto -> rto.taskId == topTaskOnHomeDisplay.taskId); + mOrderedTaskList.addFirst(topTaskOnHomeDisplay); + } + } + if (mOrderedTaskList.size() >= HISTORY_SIZE) { // If we grow in size, remove the last taskInfo which is not part of the split task. Iterator itr = mOrderedTaskList.descendingIterator(); @@ -144,8 +160,8 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta mPinnedTaskId = INVALID_TASK_ID; } - private void resetTaskId(StagedSplitTaskPosition taskPosition) { - taskPosition.taskId = INVALID_TASK_ID; + private void resetTaskId(SplitStageInfo taskPosition) { + taskPosition.taskId = -1; } /** @@ -267,5 +283,16 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta } return result; } + + @UserIdInt + @Nullable + public Integer getUserId() { + return mTopTask == null ? null : mTopTask.userId; + } + + @Nullable + public String getPackageName() { + return mTopTask == null ? null : mTopTask.baseActivity.getPackageName(); + } } } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 61d36fb1f7..1999701f61 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -23,9 +23,13 @@ import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.quickstep.GestureState.DEFAULT_STATE; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; @@ -51,6 +55,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.Log; import android.view.Choreographer; import android.view.InputEvent; @@ -64,7 +69,6 @@ import androidx.annotation.UiThread; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; -import com.android.launcher3.ResourceUtils; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.provider.RestoreDbTask; @@ -72,7 +76,8 @@ import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.ResourceUtils; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.tracing.LauncherTraceProto; import com.android.launcher3.tracing.TouchInteractionServiceProto; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; @@ -93,9 +98,11 @@ import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer; import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer; import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.ActiveGestureLog.CompoundString; import com.android.quickstep.util.ProtoTracer; import com.android.quickstep.util.ProxyScreenStatusProvider; import com.android.quickstep.util.SplitScreenBounds; +import com.android.quickstep.util.ViewCapture; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -105,6 +112,8 @@ import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.wm.shell.back.IBackAnimation; +import com.android.wm.shell.desktopmode.IDesktopMode; +import com.android.wm.shell.floating.IFloatingTasks; import com.android.wm.shell.onehanded.IOneHanded; import com.android.wm.shell.pip.IPip; import com.android.wm.shell.recents.IRecentTasks; @@ -125,8 +134,14 @@ import java.util.function.Function; public class TouchInteractionService extends Service implements ProtoTraceable { + private static final String SUBSTRING_PREFIX = "; "; + private static final String NEWLINE_PREFIX = "\n\t\t\t-> "; + private static final String TAG = "TouchInteractionService"; + private static final boolean BUBBLES_HOME_GESTURE_ENABLED = + SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true); + 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"; @@ -156,6 +171,8 @@ public class TouchInteractionService extends Service IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP)); ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder( KEY_EXTRA_SHELL_SPLIT_SCREEN)); + IFloatingTasks floatingTasks = IFloatingTasks.Stub.asInterface(bundle.getBinder( + KEY_EXTRA_SHELL_FLOATING_TASKS)); IOneHanded onehanded = IOneHanded.Stub.asInterface( bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED)); IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface( @@ -169,10 +186,12 @@ public class TouchInteractionService extends Service bundle.getBinder(KEY_EXTRA_RECENT_TASKS)); IBackAnimation backAnimation = IBackAnimation.Stub.asInterface( bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION)); + IDesktopMode desktopMode = IDesktopMode.Stub.asInterface( + bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE)); MAIN_EXECUTOR.execute(() -> { SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip, - splitscreen, onehanded, shellTransitions, startingWindow, recentTasks, - launcherUnlockAnimationController, backAnimation); + splitscreen, floatingTasks, onehanded, shellTransitions, startingWindow, + recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode); TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()"); preloadOverview(true /* fromInit */); }); @@ -266,6 +285,18 @@ public class TouchInteractionService extends Service MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn); } + @BinderThread + @Override + public void onScreenTurningOn() { + MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOn); + } + + @BinderThread + @Override + public void onScreenTurningOff() { + MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOff); + } + /** * Preloads the Overview activity. * @@ -439,7 +470,8 @@ public class TouchInteractionService extends Service mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState); mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver, mTaskAnimationManager); - mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager); + mResetGestureInputConsumer = new ResetGestureInputConsumer( + mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext); mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); mInputConsumer.registerInputConsumer(); onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags()); @@ -603,8 +635,6 @@ public class TouchInteractionService extends Service mConsumer.onConsumerAboutToBeSwitched(); mGestureState = newGestureState; mConsumer = newConsumer(prevGestureState, mGestureState, event); - - ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + mConsumer.getName()); mUncheckedConsumer = mConsumer; } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode() && mDeviceState.canTriggerAssistantAction(event)) { @@ -612,8 +642,7 @@ public class TouchInteractionService extends Service // 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 = tryCreateAssistantInputConsumer( - InputConsumer.NO_OP, mGestureState, event); + mUncheckedConsumer = tryCreateAssistantInputConsumer(mGestureState, event); } else if (mDeviceState.canTriggerOneHandedAction(event)) { // Consume gesture event for triggering one handed feature. mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState, @@ -633,12 +662,17 @@ public class TouchInteractionService extends Service switch (event.getActionMasked()) { case ACTION_DOWN: case ACTION_UP: - ActiveGestureLog.INSTANCE.addLog("onMotionEvent(" - + (int) event.getRawX() + ", " + (int) event.getRawY() + ")", - event.getActionMasked()); + ActiveGestureLog.INSTANCE.addLog( + /* event= */ "onMotionEvent(" + (int) event.getRawX() + ", " + + (int) event.getRawY() + "): " + + MotionEvent.actionToString(event.getActionMasked()), + /* gestureEvent= */ event.getActionMasked() == ACTION_DOWN + ? MOTION_DOWN + : MOTION_UP); break; default: - ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked()); + ActiveGestureLog.INSTANCE.addLog("onMotionEvent: " + + MotionEvent.actionToString(event.getActionMasked())); break; } } @@ -660,118 +694,247 @@ public class TouchInteractionService extends Service ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate(); } - private InputConsumer tryCreateAssistantInputConsumer(InputConsumer base, + private InputConsumer tryCreateAssistantInputConsumer( GestureState gestureState, MotionEvent motionEvent) { - return mDeviceState.isGestureBlockedTask(gestureState.getRunningTask()) - ? base - : new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat, - mDeviceState, motionEvent); + return tryCreateAssistantInputConsumer( + InputConsumer.NO_OP, gestureState, motionEvent, CompoundString.NO_OP); + } + + private InputConsumer tryCreateAssistantInputConsumer( + InputConsumer base, + GestureState gestureState, + MotionEvent motionEvent, + CompoundString reasonString) { + if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) { + reasonString.append(SUBSTRING_PREFIX) + .append("is gesture-blocked task, using base input consumer"); + return base; + } else { + reasonString.append(SUBSTRING_PREFIX).append("using AssistantInputConsumer"); + return new AssistantInputConsumer( + this, gestureState, base, mInputMonitorCompat, mDeviceState, motionEvent); + } } public GestureState createGestureState(GestureState previousGestureState) { - GestureState gestureState = new GestureState(mOverviewComponentObserver, - ActiveGestureLog.INSTANCE.generateAndSetLogId()); + final GestureState gestureState; + TopTaskTracker.CachedTaskInfo taskInfo; if (mTaskAnimationManager.isRecentsAnimationRunning()) { - gestureState.updateRunningTask(previousGestureState.getRunningTask()); + gestureState = new GestureState(mOverviewComponentObserver, + ActiveGestureLog.INSTANCE.getLogId()); + taskInfo = previousGestureState.getRunningTask(); + gestureState.updateRunningTask(taskInfo); gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId()); gestureState.updatePreviouslyAppearedTaskIds( previousGestureState.getPreviouslyAppearedTaskIds()); } else { - gestureState.updateRunningTask( - TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false)); + gestureState = new GestureState(mOverviewComponentObserver, + ActiveGestureLog.INSTANCE.incrementLogId()); + taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false); + gestureState.updateRunningTask(taskInfo); } + // Log initial state for the gesture. + ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=") + .append(taskInfo == null ? "no running task" : taskInfo.getPackageName())); + ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=") + .append(mDeviceState.getSystemUiStateString())); return gestureState; } - private InputConsumer newConsumer(GestureState previousGestureState, - GestureState newGestureState, MotionEvent event) { + private InputConsumer newConsumer( + GestureState previousGestureState, GestureState newGestureState, MotionEvent event) { AnimatedFloat progressProxy = mSwipeUpProxyProvider.apply(mGestureState); if (progressProxy != null) { - return new ProgressDelegateInputConsumer(this, mTaskAnimationManager, - mGestureState, mInputMonitorCompat, progressProxy); + InputConsumer consumer = new ProgressDelegateInputConsumer( + this, mTaskAnimationManager, mGestureState, mInputMonitorCompat, progressProxy); + + logInputConsumerSelectionReason(consumer, newCompoundString( + "mSwipeUpProxyProvider has been set, using ProgressDelegateInputConsumer")); + + return consumer; } boolean canStartSystemGesture = mDeviceState.canStartSystemGesture(); if (!mDeviceState.isUserUnlocked()) { + CompoundString reasonString = newCompoundString("device locked"); + InputConsumer consumer; 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(newGestureState); + consumer = createDeviceLockedInputConsumer( + newGestureState, reasonString.append(SUBSTRING_PREFIX) + .append("can start system gesture")); } else { - return getDefaultInputConsumer(); + consumer = getDefaultInputConsumer( + reasonString.append(SUBSTRING_PREFIX) + .append("cannot start system gesture")); } + logInputConsumerSelectionReason(consumer, reasonString); + return consumer; } + CompoundString reasonString; + InputConsumer base; // 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) - : getDefaultInputConsumer(); + if (canStartSystemGesture || previousGestureState.isRecentsAnimationRunning()) { + reasonString = newCompoundString(canStartSystemGesture + ? "can start system gesture" : "recents animation was running") + .append(", trying to use base consumer"); + base = newBaseConsumer(previousGestureState, newGestureState, event, reasonString); + } else { + reasonString = newCompoundString( + "cannot start system gesture and recents animation was not running") + .append(", trying to use default input consumer"); + base = getDefaultInputConsumer(reasonString); + } if (mDeviceState.isGesturalNavMode()) { handleOrientationSetup(base); } if (mDeviceState.isFullyGesturalNavMode()) { + String reasonPrefix = "device is in gesture navigation mode"; if (mDeviceState.canTriggerAssistantAction(event)) { - base = tryCreateAssistantInputConsumer(base, newGestureState, event); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("gesture can trigger the assistant") + .append(", trying to use assistant input consumer"); + base = tryCreateAssistantInputConsumer(base, newGestureState, event, reasonString); } // If Taskbar is present, we listen for long press to unstash it. TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext(); if (tac != null) { + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("TaskbarActivityContext != null, using TaskbarStashInputConsumer"); base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac); } - // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles - // instead of going all the way home when a swipe up is detected. - // Notification panel can be expanded on top of expanded bubbles. Bubbles remain - // expanded in the back. Make sure swipe up is not passed to bubbles in this case. - if ((mDeviceState.isBubblesExpanded() && !mDeviceState.isNotificationPanelExpanded()) - || mDeviceState.isSystemUiDialogShowing()) { + if (mDeviceState.isBubblesExpanded()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("bubbles expanded"); + if (BUBBLES_HOME_GESTURE_ENABLED) { + reasonString.append(SUBSTRING_PREFIX) + .append("bubbles can handle the home gesture") + .append(", trying to use default input consumer"); + // Bubbles can handle home gesture itself. + base = getDefaultInputConsumer(reasonString); + } else { + // If Bubbles is expanded, use the overlay input consumer, which will close + // Bubbles instead of going all the way home when a swipe up is detected. + // Notification panel can be expanded on top of expanded bubbles. Bubbles remain + // expanded in the back. Make sure swipe up is not passed to bubbles in this + // case. + if (!mDeviceState.isNotificationPanelExpanded()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("using SysUiOverlayInputConsumer"); + base = new SysUiOverlayInputConsumer( + getBaseContext(), mDeviceState, mInputMonitorCompat); + } + } + } + + if (mDeviceState.isSystemUiDialogShowing()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("system dialog is showing, using SysUiOverlayInputConsumer"); base = new SysUiOverlayInputConsumer( getBaseContext(), mDeviceState, mInputMonitorCompat); } + + if (mDeviceState.isScreenPinningActive()) { + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("screen pinning is active, using ScreenPinnedInputConsumer"); // 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, newGestureState); } if (mDeviceState.canTriggerOneHandedAction(event)) { - base = new OneHandedModeInputConsumer(this, mDeviceState, base, - mInputMonitorCompat); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("gesture can trigger one handed mode") + .append(", using OneHandedModeInputConsumer"); + base = new OneHandedModeInputConsumer( + this, mDeviceState, base, mInputMonitorCompat); } if (mDeviceState.isAccessibilityMenuAvailable()) { - base = new AccessibilityInputConsumer(this, mDeviceState, base, - mInputMonitorCompat); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("accessibility menu is available") + .append(", using AccessibilityInputConsumer"); + base = new AccessibilityInputConsumer( + this, mDeviceState, base, mInputMonitorCompat); } } else { + String reasonPrefix = "device is not in gesture navigation mode"; if (mDeviceState.isScreenPinningActive()) { - base = getDefaultInputConsumer(); + reasonString = newCompoundString(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("screen pinning is active, trying to use default input consumer"); + base = getDefaultInputConsumer(reasonString); } if (mDeviceState.canTriggerOneHandedAction(event)) { - base = new OneHandedModeInputConsumer(this, mDeviceState, base, - mInputMonitorCompat); + reasonString.append(NEWLINE_PREFIX) + .append(reasonPrefix) + .append(SUBSTRING_PREFIX) + .append("gesture can trigger one handed mode") + .append(", using OneHandedModeInputConsumer"); + base = new OneHandedModeInputConsumer( + this, mDeviceState, base, mInputMonitorCompat); } } + logInputConsumerSelectionReason(base, reasonString); return base; } + private CompoundString newCompoundString(String substring) { + return new CompoundString(NEWLINE_PREFIX).append(substring); + } + + private void logInputConsumerSelectionReason( + InputConsumer consumer, CompoundString reasonString) { + if (!FeatureFlags.ENABLE_INPUT_CONSUMER_REASON_LOGGING.get()) { + ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + consumer.getName()); + return; + } + ActiveGestureLog.INSTANCE.addLog(new CompoundString("setInputConsumer: ") + .append(consumer.getName()) + .append(". reason(s):") + .append(reasonString)); + } + private void handleOrientationSetup(InputConsumer baseInputConsumer) { baseInputConsumer.notifyOrientationSetup(); } - private InputConsumer newBaseConsumer(GestureState previousGestureState, - GestureState gestureState, MotionEvent event) { + private InputConsumer newBaseConsumer( + GestureState previousGestureState, + GestureState gestureState, + MotionEvent event, + CompoundString reasonString) { if (mDeviceState.isKeyguardShowingOccluded()) { // This handles apps showing over the lockscreen (e.g. camera) - return createDeviceLockedInputConsumer(gestureState); + return createDeviceLockedInputConsumer( + gestureState, + reasonString.append(SUBSTRING_PREFIX) + .append("keyguard is showing occluded") + .append(", trying to use device locked input consumer")); } + reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded"); // Use overview input consumer for sharesheets on top of home. boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted() && gestureState.getRunningTask() != null @@ -785,23 +948,46 @@ public class TouchInteractionService extends Service forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask(); } + boolean previousGestureAnimatedToLauncher = + previousGestureState.isRunningAnimationToLauncher(); + // with shell-transitions, home is resumed during recents animation, so + // explicitly check against recents animation too. + boolean launcherResumedThroughShellTransition = + gestureState.getActivityInterface().isResumed() + && !previousGestureState.isRecentsAnimationRunning(); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && gestureState.getActivityInterface().isInLiveTileMode()) { return createOverviewInputConsumer( - previousGestureState, gestureState, event, forceOverviewInputConsumer); + previousGestureState, + gestureState, + event, + forceOverviewInputConsumer, + reasonString.append(SUBSTRING_PREFIX) + .append("is in live tile mode, trying to use overview input consumer")); } else if (gestureState.getRunningTask() == null) { - return getDefaultInputConsumer(); - } else if (previousGestureState.isRunningAnimationToLauncher() - || (gestureState.getActivityInterface().isResumed() - // with shell-transitions, home is resumed during recents animation, so - // explicitly check against recents animation too. - && !previousGestureState.isRecentsAnimationRunning()) + return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX) + .append("running task == null")); + } else if (previousGestureAnimatedToLauncher + || launcherResumedThroughShellTransition || forceOverviewInputConsumer) { return createOverviewInputConsumer( - previousGestureState, gestureState, event, forceOverviewInputConsumer); + previousGestureState, + gestureState, + event, + forceOverviewInputConsumer, + reasonString.append(SUBSTRING_PREFIX) + .append(previousGestureAnimatedToLauncher + ? "previous gesture animated to launcher" + : (launcherResumedThroughShellTransition + ? "launcher resumed through a shell transition" + : "forceOverviewInputConsumer == true")) + .append(", trying to use overview input consumer")); } else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) { - return getDefaultInputConsumer(); + return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX) + .append("is gesture-blocked task, trying to use default input consumer")); } else { + reasonString.append(SUBSTRING_PREFIX) + .append("using OtherActivityInputConsumer"); return createOtherActivityInputConsumer(gestureState, event); } } @@ -823,32 +1009,64 @@ public class TouchInteractionService extends Service mInputMonitorCompat, mInputEventReceiver, disableHorizontalSwipe, factory); } - private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) { + private InputConsumer createDeviceLockedInputConsumer( + GestureState gestureState, CompoundString reasonString) { if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) { - return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager, - gestureState, mInputMonitorCompat); + reasonString.append(SUBSTRING_PREFIX) + .append("device is in gesture nav mode and running task != null") + .append(", using DeviceLockedInputConsumer"); + return new DeviceLockedInputConsumer( + this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat); } else { - return getDefaultInputConsumer(); + return getDefaultInputConsumer(reasonString + .append(SUBSTRING_PREFIX) + .append(mDeviceState.isFullyGesturalNavMode() + ? "running task == null" : "device is not in gesture nav mode") + .append(", trying to use default input consumer")); } } - public InputConsumer createOverviewInputConsumer(GestureState previousGestureState, - GestureState gestureState, MotionEvent event, - boolean forceOverviewInputConsumer) { + public InputConsumer createOverviewInputConsumer( + GestureState previousGestureState, + GestureState gestureState, + MotionEvent event, + boolean forceOverviewInputConsumer, + CompoundString reasonString) { StatefulActivity activity = gestureState.getActivityInterface().getCreatedActivity(); if (activity == null) { - return getDefaultInputConsumer(); + return getDefaultInputConsumer( + reasonString.append(SUBSTRING_PREFIX) + .append("activity == null, trying to use default input consumer")); } - if (activity.getRootView().hasWindowFocus() - || previousGestureState.isRunningAnimationToLauncher() - || (ASSISTANT_GIVES_LAUNCHER_FOCUS.get() - && forceOverviewInputConsumer) - || (ENABLE_QUICKSTEP_LIVE_TILE.get() - && gestureState.getActivityInterface().isInLiveTileMode())) { + boolean hasWindowFocus = activity.getRootView().hasWindowFocus(); + boolean isPreviousGestureAnimatingToLauncher = + previousGestureState.isRunningAnimationToLauncher(); + boolean forcingOverviewInputConsumer = + ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && forceOverviewInputConsumer; + boolean isInLiveTileMode = ENABLE_QUICKSTEP_LIVE_TILE.get() + && gestureState.getActivityInterface().isInLiveTileMode(); + reasonString.append(SUBSTRING_PREFIX) + .append(hasWindowFocus + ? "activity has window focus" + : (isPreviousGestureAnimatingToLauncher + ? "previous gesture is still animating to launcher" + : (forcingOverviewInputConsumer + ? "assistant gives launcher focus and forcing focus" + : (isInLiveTileMode + ? "device is in live mode" + : "all overview focus conditions failed")))); + if (hasWindowFocus + || isPreviousGestureAnimatingToLauncher + || forcingOverviewInputConsumer + || isInLiveTileMode) { + reasonString.append(SUBSTRING_PREFIX) + .append("overview should have focus, using OverviewInputConsumer"); return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat, false /* startingInActivityBounds */); } else { + reasonString.append(SUBSTRING_PREFIX).append( + "overview shouldn't have focus, using OverviewWithoutFocusInputConsumer"); final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event); return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState, mInputMonitorCompat, disableHorizontalSwipe); @@ -876,13 +1094,21 @@ public class TouchInteractionService extends Service } } + private @NonNull InputConsumer getDefaultInputConsumer() { + return getDefaultInputConsumer(CompoundString.NO_OP); + } + /** * Returns the {@link ResetGestureInputConsumer} if user is unlocked, else NO_OP. */ - private @NonNull InputConsumer getDefaultInputConsumer() { + private @NonNull InputConsumer getDefaultInputConsumer(@NonNull CompoundString reasonString) { if (mResetGestureInputConsumer != null) { + reasonString.append(SUBSTRING_PREFIX).append( + "mResetGestureInputConsumer initialized, using ResetGestureInputConsumer"); return mResetGestureInputConsumer; } else { + reasonString.append(SUBSTRING_PREFIX).append( + "mResetGestureInputConsumer not initialized, using no-op input consumer"); // mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to // NO_OP until then (we never want these to be null). return InputConsumer.NO_OP; @@ -997,15 +1223,18 @@ public class TouchInteractionService extends Service pw.println("ProtoTrace:"); pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile()); if (createdOverviewActivity != null) { - createdOverviewActivity.getDeviceProfile().dump("", pw); + createdOverviewActivity.getDeviceProfile().dump(this, "", pw); } mTaskbarManager.dumpLogs("", pw); + + ViewCapture.INSTANCE.get(this).dump(pw, fd); } } private void printAvailableCommands(PrintWriter pw) { pw.println("Available commands:"); pw.println(" clear-touch-log: Clears the touch interaction log"); + pw.println(" print-gesture-log: only prints the ActiveGestureLog dump"); } private void onCommand(PrintWriter pw, LinkedList args) { @@ -1013,6 +1242,8 @@ public class TouchInteractionService extends Service case "clear-touch-log": ActiveGestureLog.INSTANCE.clear(); break; + case "print-gesture-log": + ActiveGestureLog.INSTANCE.dump("", pw); } } diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java index 1fef544e3e..b1320674e6 100644 --- a/quickstep/src/com/android/quickstep/ViewUtils.java +++ b/quickstep/src/com/android/quickstep/ViewUtils.java @@ -17,6 +17,7 @@ package com.android.quickstep; import android.graphics.HardwareRenderer; import android.os.Handler; +import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; @@ -45,12 +46,15 @@ public class ViewUtils { return new FrameHandler(view, onFinishRunnable, canceled).schedule(); } - private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback { + private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback, + ViewRootImpl.SurfaceChangedCallback { final ViewRootImpl mViewRoot; final Runnable mFinishCallback; final BooleanSupplier mCancelled; final Handler mHandler; + boolean mSurfaceCallbackRegistered = false; + boolean mFinished; int mDeferFrameCount = 1; @@ -61,6 +65,23 @@ public class ViewUtils { mHandler = new Handler(); } + @Override + public void surfaceCreated(SurfaceControl.Transaction t) { + // Do nothing + } + + @Override + public void surfaceReplaced(SurfaceControl.Transaction t) { + // Do nothing + } + + @Override + public void surfaceDestroyed() { + // If the root view is detached, then the app won't get any scheduled frames so we need + // to force-run any pending callbacks + finish(); + } + @Override public void onFrameDraw(long frame) { Utilities.postAsyncCallback(mHandler, this::onFrame); @@ -77,18 +98,35 @@ public class ViewUtils { return; } - if (mFinishCallback != null) { - mFinishCallback.run(); - } + finish(); } private boolean schedule() { if (mViewRoot != null && mViewRoot.getView() != null) { + if (!mSurfaceCallbackRegistered) { + mSurfaceCallbackRegistered = true; + mViewRoot.addSurfaceChangedCallback(this); + } mViewRoot.registerRtFrameCallback(this); mViewRoot.getView().invalidate(); return true; } return false; } + + private void finish() { + if (mFinished) { + return; + } + mFinished = true; + mDeferFrameCount = 0; + if (mFinishCallback != null) { + mFinishCallback.run(); + } + if (mViewRoot != null) { + mViewRoot.removeSurfaceChangedCallback(this); + mSurfaceCallbackRegistered = false; + } + } } } diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java index 3e01ed0b77..8a87f63aaf 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java @@ -22,7 +22,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.Utilities; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.TouchController; import com.android.quickstep.RecentsActivity; import com.android.quickstep.util.NavBarPosition; diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java index f68bbbce62..19a6c38901 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java @@ -33,6 +33,7 @@ import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION; import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION; import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; +import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA; import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL; import android.util.FloatProperty; @@ -77,6 +78,11 @@ public class FallbackRecentsStateController implements StateHandler mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL)); + setter.addEndListener(success -> { + if (!success) { + mRecentsView.reset(); + } + }); mRecentsView.updateEmptyMessage(); setProperties(toState, config, setter); @@ -105,14 +111,19 @@ public class FallbackRecentsStateController implements StateHandler taskViewsFloat = diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java index ab3201a27e..e32aaee532 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -15,7 +15,6 @@ */ package com.android.quickstep.fallback; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; import static com.android.quickstep.fallback.RecentsState.DEFAULT; import static com.android.quickstep.fallback.RecentsState.HOME; @@ -27,7 +26,6 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import androidx.annotation.Nullable; @@ -35,6 +33,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.popup.QuickstepSystemShortcut; import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.util.SplitConfigurationOptions; @@ -56,6 +55,8 @@ import java.util.ArrayList; public class FallbackRecentsView extends RecentsView implements StateListener { + private static final int TASK_DISMISS_DURATION = 150; + @Nullable private Task mHomeTask; @@ -107,8 +108,9 @@ public class FallbackRecentsView extends RecentsView setCurrentTask(-1)); AnimatorPlaybackController controller = pa.createPlaybackController(); controller.dispatchOnStart(); @@ -212,8 +214,9 @@ public class FallbackRecentsView extends RecentsView { + controller.finish(false /* toRecents */, null); + }); + } + } } diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java index 02ac48ebeb..6f35928a9f 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java @@ -29,13 +29,12 @@ import com.android.launcher3.Utilities; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; import com.android.quickstep.TaskUtils; -import com.android.quickstep.util.ActiveGestureLog; import com.android.systemui.shared.system.InputMonitorCompat; /** @@ -91,7 +90,6 @@ public class OverviewInputConsumer, T extends StatefulAct if (!mStartingInActivityBounds) { mActivityInterface.closeOverlay(); TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); - ActiveGestureLog.INSTANCE.addLog("startQuickstep"); } if (mInputMonitor != null) { TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java index 864e08da46..b70fe8e03e 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java @@ -15,12 +15,11 @@ */ package com.android.quickstep.inputconsumers; -import static com.android.launcher3.Utilities.createHomeIntent; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; +import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely; -import android.content.ActivityNotFoundException; import android.content.Context; import android.graphics.PointF; import android.view.MotionEvent; @@ -29,11 +28,10 @@ import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; 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; @@ -79,12 +77,7 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer, @Override public void onSwipeUp(boolean wasFling, PointF finalVelocity) { - try { - mContext.startActivity(mGestureState.getHomeIntent()); - } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { - mContext.startActivity(createHomeIntent()); - } - ActiveGestureLog.INSTANCE.addLog("startQuickstep"); + startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null); BaseActivity activity = BaseDraggingActivity.fromContext(mContext); int state = (mGestureState != null && mGestureState.getEndTarget() != null) ? mGestureState.getEndTarget().containerType diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java index 71dca663f0..b9b5e7c2b9 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java @@ -28,9 +28,11 @@ import android.content.Intent; import android.graphics.Point; import android.view.MotionEvent; +import androidx.annotation.Nullable; + import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.util.DisplayController; import com.android.quickstep.AnimatedFloat; @@ -41,6 +43,7 @@ import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.TaskAnimationManager; +import com.android.quickstep.util.ActiveGestureErrorDetector; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputMonitorCompat; @@ -99,7 +102,8 @@ public class ProgressDelegateInputConsumer implements InputConsumer, mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize; // Init states - mStateCallback = new MultiStateCallback(STATE_NAMES); + mStateCallback = new MultiStateCallback( + STATE_NAMES, ProgressDelegateInputConsumer::getTrackedEventForState); mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, this::endRemoteAnimation); mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_FLING_FINISHED, @@ -109,6 +113,14 @@ public class ProgressDelegateInputConsumer implements InputConsumer, mSwipeDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false); } + @Nullable + private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { + if (stateFlag == STATE_HANDLER_INVALIDATED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED; + } + return null; + } + @Override public int getType() { return TYPE_PROGRESS_DELEGATE; diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java index d34b40bf0c..349f4d2f2f 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java @@ -17,18 +17,25 @@ package com.android.quickstep.inputconsumers; import android.view.MotionEvent; +import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.quickstep.InputConsumer; import com.android.quickstep.TaskAnimationManager; +import java.util.function.Supplier; + /** * A NO_OP input consumer which also resets any pending gesture */ public class ResetGestureInputConsumer implements InputConsumer { private final TaskAnimationManager mTaskAnimationManager; + private final Supplier mActivityContextSupplier; - public ResetGestureInputConsumer(TaskAnimationManager taskAnimationManager) { + public ResetGestureInputConsumer( + TaskAnimationManager taskAnimationManager, + Supplier activityContextSupplier) { mTaskAnimationManager = taskAnimationManager; + mActivityContextSupplier = activityContextSupplier; } @Override @@ -40,7 +47,9 @@ public class ResetGestureInputConsumer implements InputConsumer { public void onMotionEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN && mTaskAnimationManager.isRecentsAnimationRunning()) { - mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */); + TaskbarActivityContext tac = mActivityContextSupplier.get(); + mTaskAnimationManager.finishRunningRecentsAnimation( + /* toHome= */ tac != null && !tac.isInApp()); } } } diff --git a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java index 878f132531..4806ac1d6e 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java @@ -23,7 +23,7 @@ import android.util.Log; import android.view.MotionEvent; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.quickstep.InputConsumer; import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.util.TriggerSwipeUpTouchTracker; diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java index 5680170a8f..8ad17cb711 100644 --- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java +++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java @@ -19,6 +19,7 @@ import static com.android.launcher3.Utilities.mapBoundToRange; import static com.android.launcher3.Utilities.mapRange; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely; import android.animation.Animator; import android.app.Activity; @@ -82,6 +83,8 @@ public class AllSetActivity extends Activity { private static final int MAX_SWIPE_DURATION = 350; + private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f; + private TISBindHelper mTISBindHelper; private TISBinder mBinder; @@ -144,54 +147,64 @@ public class AllSetActivity extends Activity { } private void runOnUiHelperThread(Runnable runnable) { + if (!isResumed() + || getContentViewAlphaForSwipeProgress() <= ANIMATION_PAUSE_ALPHA_THRESHOLD) { + return; + } Executors.UI_HELPER_EXECUTOR.execute(runnable); } private void startBackgroundAnimation() { - if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported( - VibrationEffect.Composition.PRIMITIVE_THUD)) { - if (mBackgroundAnimatorListener == null) { - mBackgroundAnimatorListener = - new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect())); - } - - @Override - public void onAnimationRepeat(Animator animation) { - runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect())); - } - - @Override - public void onAnimationEnd(Animator animation) { - runOnUiHelperThread(mVibrator::cancel); - } - - @Override - public void onAnimationCancel(Animator animation) { - runOnUiHelperThread(mVibrator::cancel); - } - }; - } - mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener); + if (!Utilities.ATLEAST_S || mVibrator == null) { + return; } - mAnimatedBackground.playAnimation(); - } + boolean supportsThud = mVibrator.areAllPrimitivesSupported( + VibrationEffect.Composition.PRIMITIVE_THUD); - /** - * Sets up the vibration effect for the next round of animation. The parameters vary between - * different illustrations. - */ - private VibrationEffect getVibrationEffect() { - return VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, 50) - .compose(); + if (!supportsThud && !mVibrator.areAllPrimitivesSupported( + VibrationEffect.Composition.PRIMITIVE_TICK)) { + return; + } + if (mBackgroundAnimatorListener == null) { + VibrationEffect vibrationEffect = VibrationEffect.startComposition() + .addPrimitive(supportsThud + ? VibrationEffect.Composition.PRIMITIVE_THUD + : VibrationEffect.Composition.PRIMITIVE_TICK, + /* scale= */ 1.0f, + /* delay= */ 50) + .compose(); + + mBackgroundAnimatorListener = + new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect)); + } + + @Override + public void onAnimationRepeat(Animator animation) { + runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect)); + } + + @Override + public void onAnimationEnd(Animator animation) { + runOnUiHelperThread(mVibrator::cancel); + } + + @Override + public void onAnimationCancel(Animator animation) { + runOnUiHelperThread(mVibrator::cancel); + } + }; + } + mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener); + mAnimatedBackground.playAnimation(); } @Override protected void onResume() { super.onResume(); + maybeResumeOrPauseBackgroundAnimation(); if (mBinder != null) { mBinder.getTaskbarManager().setSetupUIVisible(true); mBinder.setSwipeUpProxy(this::createSwipeUpProxy); @@ -210,6 +223,7 @@ public class AllSetActivity extends Activity { protected void onPause() { super.onPause(); clearBinderOverride(); + maybeResumeOrPauseBackgroundAnimation(); if (mSwipeProgress.value >= 1) { finishAndRemoveTask(); } @@ -244,10 +258,25 @@ public class AllSetActivity extends Activity { return mSwipeProgress; } + private float getContentViewAlphaForSwipeProgress() { + return Utilities.mapBoundToRange( + mSwipeProgress.value, 0, HINT_BOTTOM_FACTOR, 1, 0, LINEAR); + } + + private void maybeResumeOrPauseBackgroundAnimation() { + boolean shouldPlayAnimation = + getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD + && isResumed(); + if (mAnimatedBackground.isAnimating() && !shouldPlayAnimation) { + mAnimatedBackground.pauseAnimation(); + } else if (!mAnimatedBackground.isAnimating() && shouldPlayAnimation) { + mAnimatedBackground.resumeAnimation(); + } + } + private void onSwipeProgressUpdate() { mBackground.setProgress(mSwipeProgress.value); - float alpha = Utilities.mapBoundToRange( - mSwipeProgress.value, 0, HINT_BOTTOM_FACTOR, 1, 0, LINEAR); + float alpha = getContentViewAlphaForSwipeProgress(); mContentView.setAlpha(alpha); mContentView.setTranslationY((alpha - 1) * mSwipeUpShift); @@ -259,12 +288,7 @@ public class AllSetActivity extends Activity { mLauncherStartAnim.setPlayFraction(Utilities.mapBoundToRange( mSwipeProgress.value, 0, 1, 0, 1, FAST_OUT_SLOW_IN)); } - - if (alpha == 0f) { - mAnimatedBackground.pauseAnimation(); - } else if (!mAnimatedBackground.isAnimating()) { - mAnimatedBackground.resumeAnimation(); - } + maybeResumeOrPauseBackgroundAnimation(); } /** @@ -284,7 +308,7 @@ public class AllSetActivity extends Activity { @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { if (action == AccessibilityAction.ACTION_CLICK.getId()) { - startActivity(Utilities.createHomeIntent()); + startHomeIntentSafely(AllSetActivity.this, null); finish(); return true; } diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java index d059d828f8..8660d871cc 100644 --- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java @@ -29,7 +29,7 @@ import android.view.ViewGroup.LayoutParams; import androidx.annotation.Nullable; -import com.android.launcher3.ResourceUtils; +import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.Utilities; import com.android.launcher3.util.DisplayController; diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java index b2b2f59773..437572b380 100644 --- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java @@ -41,7 +41,7 @@ import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.R; -import com.android.launcher3.ResourceUtils; +import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.anim.Interpolators; import com.android.quickstep.util.VibratorWrapper; diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java index f9818600cf..e7bf7e2d08 100644 --- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java +++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java @@ -44,10 +44,10 @@ import android.view.ViewConfiguration; import androidx.annotation.Nullable; import com.android.launcher3.R; -import com.android.launcher3.ResourceUtils; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.util.NavBarPosition; import com.android.quickstep.util.TriggerSwipeUpTouchTracker; diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java index b70c411196..fa7bc04b30 100644 --- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -358,7 +358,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { }; RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory)[0]; - windowAnim.start(mContext, velocityPxPerMs); + windowAnim.start(mContext, mDp, velocityPxPerMs); return windowAnim; } } diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java index bd0250d27d..2ccdfa3b36 100644 --- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java +++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java @@ -40,15 +40,14 @@ import android.util.Xml; import com.android.launcher3.AutoInstallsLayout; import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.model.DeviceGridState; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.DisplayController.NavigationMode; import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.SettingsCache; import org.xmlpull.v1.XmlPullParser; @@ -179,11 +178,9 @@ public class SettingsChangeLogger implements logger::log); SharedPreferences prefs = getPrefs(mContext); - if (FeatureFlags.ENABLE_THEMED_ICONS.get()) { - logger.log(prefs.getBoolean(KEY_THEMED_ICONS, false) - ? LAUNCHER_THEMED_ICON_ENABLED - : LAUNCHER_THEMED_ICON_DISABLED); - } + logger.log(prefs.getBoolean(KEY_THEMED_ICONS, false) + ? LAUNCHER_THEMED_ICON_ENABLED + : LAUNCHER_THEMED_ICON_DISABLED); mLoggablePrefs.forEach((key, lp) -> logger.log(() -> prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff)); diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index 45c80366f4..37a28e554b 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -336,8 +336,9 @@ public class StatsLogCompatManager extends StatsLogManager { appState.getModel().enqueueModelUpdateTask( new BaseModelUpdateTask() { @Override - public void execute(LauncherAppState app, BgDataModel dataModel, - AllAppsList apps) { + public void execute(@NonNull final LauncherAppState app, + @NonNull final BgDataModel dataModel, + @NonNull final AllAppsList apps) { FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container); write(event, applyOverwrites(mItemInfo.buildProto(folderInfo))); } diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java new file mode 100644 index 0000000000..53e0c2b631 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import android.util.ArraySet; + +import androidx.annotation.NonNull; + +import java.io.PrintWriter; +import java.util.List; +import java.util.Set; + +/** + * Utility class for tracking gesture navigation events as they happen, then detecting and reporting + * known issues at log dump time. + */ +public class ActiveGestureErrorDetector { + + /** + * Enums associated to gesture navigation events. + */ + public enum GestureEvent { + MOTION_DOWN, MOTION_UP, SET_END_TARGET, SET_END_TARGET_HOME, SET_END_TARGET_LAST_TASK, + SET_END_TARGET_NEW_TASK, ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION, + FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK, + CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, + + /** + * These GestureEvents are specifically associated to state flags that get set in + * {@link com.android.quickstep.MultiStateCallback}. If a state flag needs to be tracked + * for error detection, an enum should be added here and that state flag-enum pair should + * be added to the state flag's container class' {@code getTrackedEventForState} method. + */ + STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, + STATE_END_TARGET_ANIMATION_FINISHED, STATE_RECENTS_SCROLLING_FINISHED, + STATE_CAPTURE_SCREENSHOT, STATE_SCREENSHOT_CAPTURED, STATE_HANDLER_INVALIDATED, + STATE_RECENTS_ANIMATION_CANCELED, STATE_LAUNCHER_DRAWN(true, false); + + public final boolean mLogEvent; + public final boolean mTrackEvent; + + GestureEvent() { + this(false, true); + } + + GestureEvent(boolean logEvent, boolean trackEvent) { + mLogEvent = logEvent; + mTrackEvent = trackEvent; + } + } + + private ActiveGestureErrorDetector() {} + + protected static void analyseAndDump( + @NonNull String prefix, + @NonNull PrintWriter writer, + List eventLogs) { + writer.println(prefix + "ActiveGestureErrorDetector:"); + for (int i = 0; i < eventLogs.size(); i++) { + ActiveGestureLog.EventLog eventLog = eventLogs.get(i); + if (eventLog == null) { + continue; + } + int gestureId = eventLog.logId; + writer.println(prefix + "\tError messages for gesture ID: " + gestureId); + + boolean errorDetected = false; + // Use a Set since the order is inherently checked in the loop. + final Set encounteredEvents = new ArraySet<>(); + // Set flags and check order of operations. + for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) { + GestureEvent gestureEvent = eventEntry.getGestureEvent(); + if (gestureEvent == null) { + continue; + } + encounteredEvents.add(gestureEvent); + switch (gestureEvent) { + case MOTION_UP: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.MOTION_DOWN), + /* errorMessage= */ prefix + "\t\tMotion up detected before/without" + + " motion down.", + writer); + break; + case ON_SETTLED_ON_END_TARGET: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.SET_END_TARGET), + /* errorMessage= */ prefix + "\t\tonSettledOnEndTarget called " + + "before/without setEndTarget.", + writer); + break; + case FINISH_RECENTS_ANIMATION: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), + /* errorMessage= */ prefix + "\t\tfinishRecentsAnimation called " + + "before/without startRecentsAnimation.", + writer); + break; + case CANCEL_RECENTS_ANIMATION: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), + /* errorMessage= */ prefix + "\t\tcancelRecentsAnimation called " + + "before/without startRecentsAnimation.", + writer); + break; + case CLEANUP_SCREENSHOT: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), + /* errorMessage= */ prefix + "\t\trecents activity screenshot was " + + "cleaned up before/without STATE_SCREENSHOT_CAPTURED " + + "being set.", + writer); + break; + case SCROLLER_ANIMATION_ABORTED: + errorDetected |= printErrorIfTrue( + encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME) + && !encounteredEvents.contains( + GestureEvent.ON_SETTLED_ON_END_TARGET), + /* errorMessage= */ prefix + "\t\trecents view scroller animation " + + "aborted after setting end target HOME, but before" + + " settling on end target.", + writer); + break; + case TASK_APPEARED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.SET_END_TARGET_LAST_TASK) + && !encounteredEvents.contains( + GestureEvent.SET_END_TARGET_NEW_TASK), + /* errorMessage= */ prefix + "\t\tonTasksAppeared called " + + "before/without setting end target to last or new task", + writer); + break; + case STATE_GESTURE_COMPLETED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.MOTION_UP), + /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_COMPLETED set " + + "before/without motion up.", + writer); + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), + /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_COMPLETED set " + + "before/without STATE_GESTURE_STARTED.", + writer); + break; + case STATE_GESTURE_CANCELLED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.MOTION_UP), + /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_CANCELLED set " + + "before/without motion up.", + writer); + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), + /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_CANCELLED set " + + "before/without STATE_GESTURE_STARTED.", + writer); + break; + case STATE_SCREENSHOT_CAPTURED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT), + /* errorMessage= */ prefix + "\t\tSTATE_SCREENSHOT_CAPTURED set " + + "before/without STATE_CAPTURE_SCREENSHOT.", + writer); + break; + case STATE_RECENTS_SCROLLING_FINISHED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains( + GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_SCROLLING_FINISHED " + + "set before/without calling " + + "setOnPageTransitionEndCallback.", + writer); + break; + case STATE_RECENTS_ANIMATION_CANCELED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains( + GestureEvent.START_RECENTS_ANIMATION), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED " + + "set before/without startRecentsAnimation.", + writer); + break; + case MOTION_DOWN: + case SET_END_TARGET: + case SET_END_TARGET_HOME: + case START_RECENTS_ANIMATION: + case SET_ON_PAGE_TRANSITION_END_CALLBACK: + case CANCEL_CURRENT_ANIMATION: + case STATE_GESTURE_STARTED: + case STATE_END_TARGET_ANIMATION_FINISHED: + case STATE_CAPTURE_SCREENSHOT: + case STATE_HANDLER_INVALIDATED: + case STATE_LAUNCHER_DRAWN: + default: + // No-Op + } + } + + // Check that all required events were found. + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.MOTION_DOWN), + /* errorMessage= */ prefix + "\t\tMotion down never detected.", + writer); + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.MOTION_UP), + /* errorMessage= */ prefix + "\t\tMotion up never detected.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) + && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), + /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but " + + "onSettledOnEndTarget wasn't.", + writer); + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) + && !encounteredEvents.contains( + GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED), + /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but " + + "STATE_END_TARGET_ANIMATION_FINISHED was never set.", + writer); + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) + && !encounteredEvents.contains( + GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), + /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but " + + "STATE_RECENTS_SCROLLING_FINISHED was never set.", + writer); + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED) + && encounteredEvents.contains( + GestureEvent.STATE_RECENTS_SCROLLING_FINISHED) + && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), + /* errorMessage= */ prefix + "\t\tSTATE_END_TARGET_ANIMATION_FINISHED and " + + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget " + + "wasn't called.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.START_RECENTS_ANIMATION) + && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION) + && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION), + /* errorMessage= */ prefix + "\t\tstartRecentsAnimation was called, but " + + "finishRecentsAnimation and cancelRecentsAnimation weren't.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED) + && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED) + && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED), + /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_STARTED was set, but " + + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.STATE_CAPTURE_SCREENSHOT) + && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), + /* errorMessage= */ prefix + "\t\tSTATE_CAPTURE_SCREENSHOT was set, but " + + "STATE_SCREENSHOT_CAPTURED wasn't.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK) + && !encounteredEvents.contains( + GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), + /* errorMessage= */ prefix + "\t\tsetOnPageTransitionEndCallback called, but " + + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ !encounteredEvents.contains( + GestureEvent.CANCEL_CURRENT_ANIMATION) + && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED), + /* errorMessage= */ prefix + "\t\tAbsSwipeUpHandler.cancelCurrentAnimation " + + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.STATE_RECENTS_ANIMATION_CANCELED) + && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED was set but " + + "the task screenshot wasn't cleaned up.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.SET_END_TARGET_LAST_TASK) + && !encounteredEvents.contains(GestureEvent.TASK_APPEARED), + /* errorMessage= */ prefix + "\t\tend target set to last task, but " + + "onTaskAppeared wasn't called.", + writer); + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.SET_END_TARGET_NEW_TASK) + && !encounteredEvents.contains(GestureEvent.TASK_APPEARED), + /* errorMessage= */ prefix + "\t\tend target set to new task, but " + + "onTaskAppeared wasn't called.", + writer); + + if (!errorDetected) { + writer.println(prefix + "\t\tNo errors detected."); + } + } + } + + private static boolean printErrorIfTrue( + boolean condition, String errorMessage, PrintWriter writer) { + if (!condition) { + return false; + } + writer.println(errorMessage); + return true; + } +} diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java index fabfc4bb51..23fdd58877 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java @@ -15,15 +15,26 @@ */ package com.android.quickstep.util; -import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import com.android.launcher3.logging.EventLogArray; -import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.config.FeatureFlags; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Objects; /** * A log to keep track of the active gesture. */ -public class ActiveGestureLog extends EventLogArray { +public class ActiveGestureLog { + + private static final int MAX_GESTURES_TRACKED = 10; public static final ActiveGestureLog INSTANCE = new ActiveGestureLog(); @@ -33,7 +44,306 @@ public class ActiveGestureLog extends EventLogArray { */ public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID"; + private static final int TYPE_ONE_OFF = 0; + private static final int TYPE_FLOAT = 1; + private static final int TYPE_INTEGER = 2; + private static final int TYPE_BOOL_TRUE = 3; + private static final int TYPE_BOOL_FALSE = 4; + private static final int TYPE_INPUT_CONSUMER = 5; + private static final int TYPE_GESTURE_EVENT = 6; + + private final EventLog[] logs; + private int nextIndex; + private int mCurrentLogId = 100; + private ActiveGestureLog() { - super("touch_interaction_log", 40); + this.logs = new EventLog[MAX_GESTURES_TRACKED]; + this.nextIndex = 0; + } + + /** + * Track the given event for error detection. + * + * @param gestureEvent GestureEvent representing an event during the current gesture's + * execution. + */ + public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { + addLog(TYPE_GESTURE_EVENT, "", 0, CompoundString.NO_OP, gestureEvent); + } + + public void addLog(String event) { + addLog(event, null); + } + + public void addLog(String event, int extras) { + addLog(event, extras, null); + } + + public void addLog(String event, boolean extras) { + addLog(event, extras, null); + } + + public void addLog(CompoundString compoundString) { + addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString, null); + } + + /** + * Adds a log and track the associated event for error detection. + * + * @param gestureEvent GestureEvent representing the event being logged. + */ + public void addLog( + String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { + addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP, gestureEvent); + } + + public void addLog( + String event, + int extras, + @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { + addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP, gestureEvent); + } + + public void addLog( + String event, + boolean extras, + @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { + addLog( + extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, + event, + 0, + CompoundString.NO_OP, + gestureEvent); + } + + private void addLog( + int type, + String event, + float extras, + CompoundString compoundString, + @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { + EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length]; + if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) { + EventLog eventLog = new EventLog(mCurrentLogId); + EventEntry eventEntry = new EventEntry(); + + eventEntry.update(type, event, extras, compoundString, gestureEvent); + eventLog.eventEntries.add(eventEntry); + logs[nextIndex] = eventLog; + nextIndex = (nextIndex + 1) % logs.length; + return; + } + + // Update the last EventLog + List lastEventEntries = lastEventLog.eventEntries; + EventEntry lastEntry = lastEventEntries.size() > 0 + ? lastEventEntries.get(lastEventEntries.size() - 1) : null; + + // Update the last EventEntry if it's a duplicate + if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) { + lastEntry.duplicateCount++; + return; + } + EventEntry eventEntry = new EventEntry(); + + eventEntry.update(type, event, extras, compoundString, gestureEvent); + lastEventEntries.add(eventEntry); + } + + public void clear() { + Arrays.fill(logs, null); + } + + public void dump(String prefix, PrintWriter writer) { + writer.println(prefix + "ActiveGestureLog history:"); + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ ", Locale.US); + Date date = new Date(); + ArrayList eventLogs = new ArrayList<>(); + + for (int i = 0; i < logs.length; i++) { + EventLog eventLog = logs[(nextIndex + i) % logs.length]; + if (eventLog == null) { + continue; + } + eventLogs.add(eventLog); + writer.println(prefix + "\tLogs for logId: " + eventLog.logId); + + for (EventEntry eventEntry : eventLog.eventEntries) { + date.setTime(eventEntry.time); + + StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date)) + .append(eventEntry.event); + switch (eventEntry.type) { + case TYPE_BOOL_FALSE: + msg.append(": false"); + break; + case TYPE_BOOL_TRUE: + msg.append(": true"); + break; + case TYPE_FLOAT: + msg.append(": ").append(eventEntry.extras); + break; + case TYPE_INTEGER: + msg.append(": ").append((int) eventEntry.extras); + break; + case TYPE_INPUT_CONSUMER: + msg.append(eventEntry.mCompoundString); + break; + case TYPE_GESTURE_EVENT: + continue; + default: // fall out + } + if (eventEntry.duplicateCount > 0) { + msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events"); + } + writer.println(msg); + } + } + + if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) { + ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLogs); + } + } + + /** + * Increments and returns the current log ID. This should be used every time a new log trace + * is started. + */ + public int incrementLogId() { + return mCurrentLogId++; + } + + /** Returns the current log ID. This should be used when a log trace is being reused. */ + public int getLogId() { + return mCurrentLogId; + } + + private boolean isEntrySame( + EventEntry entry, + int type, + String event, + float extras, + CompoundString compoundString, + ActiveGestureErrorDetector.GestureEvent gestureEvent) { + return entry != null + && entry.type == type + && entry.event.equals(event) + && Float.compare(entry.extras, extras) == 0 + && entry.mCompoundString.equals(compoundString) + && entry.gestureEvent == gestureEvent; + } + + /** A single event entry. */ + protected static class EventEntry { + + private int type; + private String event; + private float extras; + @NonNull private CompoundString mCompoundString; + private ActiveGestureErrorDetector.GestureEvent gestureEvent; + private long time; + private int duplicateCount; + + private EventEntry() {} + + @Nullable + protected ActiveGestureErrorDetector.GestureEvent getGestureEvent() { + return gestureEvent; + } + + private void update( + int type, + String event, + float extras, + @NonNull CompoundString compoundString, + ActiveGestureErrorDetector.GestureEvent gestureEvent) { + this.type = type; + this.event = event; + this.extras = extras; + this.mCompoundString = compoundString; + this.gestureEvent = gestureEvent; + time = System.currentTimeMillis(); + duplicateCount = 0; + } + } + + /** An entire log of entries associated with a single log ID */ + protected static class EventLog { + + protected final List eventEntries = new ArrayList<>(); + protected final int logId; + + private EventLog(int logId) { + this.logId = logId; + } + } + + /** A buildable string stored as an array for memory efficiency. */ + public static class CompoundString { + + public static final CompoundString NO_OP = new CompoundString(); + + private final List mSubstrings; + + private final boolean mIsNoOp; + + private CompoundString() { + this(null); + } + + public CompoundString(String substring) { + mIsNoOp = substring == null; + if (mIsNoOp) { + mSubstrings = null; + return; + } + mSubstrings = new ArrayList<>(); + mSubstrings.add(substring); + } + + public CompoundString append(CompoundString substring) { + if (mIsNoOp) { + return this; + } + mSubstrings.addAll(substring.mSubstrings); + + return this; + } + + public CompoundString append(String substring) { + if (mIsNoOp) { + return this; + } + mSubstrings.add(substring); + + return this; + } + + @Override + public String toString() { + if (mIsNoOp) { + return "ERROR: cannot use No-Op compound string"; + } + StringBuilder sb = new StringBuilder(); + for (String substring : mSubstrings) { + sb.append(substring); + } + + return sb.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(mIsNoOp, mSubstrings); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CompoundString)) { + return false; + } + CompoundString other = (CompoundString) obj; + return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings); + } } } diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java new file mode 100644 index 0000000000..b7b7825d78 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +/** + * Utility class containing methods to help manage animations, interpolators, and timings. + */ +public class AnimUtils { + /** + * Fetches device-specific timings for the Overview > Split animation + * (splitscreen initiated from Overview). + */ + public static SplitAnimationTimings getDeviceOverviewToSplitTimings(boolean isTablet) { + return isTablet + ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT + : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT; + } + + /** + * Fetches device-specific timings for the Split > Confirm animation + * (splitscreen confirmed by selecting a second app). + */ + public static SplitAnimationTimings getDeviceSplitToConfirmTimings(boolean isTablet) { + return isTablet + ? SplitAnimationTimings.TABLET_SPLIT_TO_CONFIRM + : SplitAnimationTimings.PHONE_SPLIT_TO_CONFIRM; + } +} diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java new file mode 100644 index 0000000000..29ae9a13ca --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import android.app.WallpaperManager; +import android.os.IBinder; +import android.util.FloatProperty; +import android.view.AttachedSurfaceControl; +import android.view.SurfaceControl; + +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.MultiPropertyFactory; +import com.android.systemui.shared.system.BlurUtils; + +/** + * Utility class for applying depth effect + */ +public class BaseDepthController { + + private static final FloatProperty DEPTH = + new FloatProperty("depth") { + @Override + public void setValue(BaseDepthController depthController, float depth) { + depthController.setDepth(depth); + } + + @Override + public Float get(BaseDepthController depthController) { + return depthController.mDepth; + } + }; + + private static final MultiPropertyFactory DEPTH_PROPERTY_FACTORY = + new MultiPropertyFactory<>("depthProperty", DEPTH, Float::max); + + private static final int DEPTH_INDEX_STATE_TRANSITION = 1; + private static final int DEPTH_INDEX_WIDGET = 2; + + /** Property to set the depth for state transition. */ + public static final FloatProperty STATE_DEPTH = + DEPTH_PROPERTY_FACTORY.get(DEPTH_INDEX_STATE_TRANSITION); + /** Property to set the depth for widget picker. */ + public static final FloatProperty WIDGET_DEPTH = + DEPTH_PROPERTY_FACTORY.get(DEPTH_INDEX_WIDGET); + + protected final Launcher mLauncher; + + /** + * Blur radius when completely zoomed out, in pixels. + */ + protected final int mMaxBlurRadius; + protected final WallpaperManager mWallpaperManager; + protected boolean mCrossWindowBlursEnabled; + + /** + * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in. + * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float) + */ + protected float mDepth; + + protected SurfaceControl mSurface; + + // Hints that there is potentially content behind Launcher and that we shouldn't optimize by + // marking the launcher surface as opaque. Only used in certain Launcher states. + private boolean mHasContentBehindLauncher; + /** + * Last blur value, in pixels, that was applied. + * For debugging purposes. + */ + protected int mCurrentBlur; + /** + * If we requested early wake-up offsets to SurfaceFlinger. + */ + protected boolean mInEarlyWakeUp; + + public BaseDepthController(Launcher activity) { + mLauncher = activity; + mMaxBlurRadius = activity.getResources().getInteger(R.integer.max_depth_blur_radius); + mWallpaperManager = activity.getSystemService(WallpaperManager.class); + } + + protected void setCrossWindowBlursEnabled(boolean isEnabled) { + mCrossWindowBlursEnabled = isEnabled; + applyDepthAndBlur(); + } + + public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) { + mHasContentBehindLauncher = hasContentBehindLauncher; + } + + protected void applyDepthAndBlur() { + float depth = mDepth; + IBinder windowToken = mLauncher.getRootView().getWindowToken(); + if (windowToken != null) { + mWallpaperManager.setWallpaperZoomOut(windowToken, depth); + } + + if (!BlurUtils.supportsBlursOnWindows()) { + return; + } + if (mSurface == null || !mSurface.isValid()) { + return; + } + boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque(); + boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg; + + mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg + ? 0 : (int) (depth * mMaxBlurRadius); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction() + .setBackgroundBlurRadius(mSurface, mCurrentBlur) + .setOpaque(mSurface, isSurfaceOpaque); + + // Set early wake-up flags when we know we're executing an expensive operation, this way + // SurfaceFlinger will adjust its internal offsets to avoid jank. + boolean wantsEarlyWakeUp = depth > 0 && depth < 1; + if (wantsEarlyWakeUp && !mInEarlyWakeUp) { + transaction.setEarlyWakeupStart(); + mInEarlyWakeUp = true; + } else if (!wantsEarlyWakeUp && mInEarlyWakeUp) { + transaction.setEarlyWakeupEnd(); + mInEarlyWakeUp = false; + } + + AttachedSurfaceControl rootSurfaceControl = + mLauncher.getRootView().getRootSurfaceControl(); + if (rootSurfaceControl != null) { + rootSurfaceControl.applyTransactionOnDraw(transaction); + } + } + + protected void setDepth(float depth) { + depth = Utilities.boundToRange(depth, 0, 1); + // Round out the depth to dedupe frequent, non-perceptable updates + int depthI = (int) (depth * 256); + float depthF = depthI / 256f; + if (Float.compare(mDepth, depthF) == 0) { + return; + } + mDepth = depthF; + applyDepthAndBlur(); + } + + /** + * Sets the specified app target surface to apply the blur to. + */ + protected void setSurface(SurfaceControl surface) { + if (mSurface != surface) { + mSurface = surface; + applyDepthAndBlur(); + } + } +} diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java index e2563e398d..f30d00c722 100644 --- a/quickstep/src/com/android/quickstep/util/GroupTask.java +++ b/quickstep/src/com/android/quickstep/util/GroupTask.java @@ -19,7 +19,7 @@ package com.android.quickstep.util; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; import com.android.systemui.shared.recents.model.Task; /** @@ -29,13 +29,14 @@ import com.android.systemui.shared.recents.model.Task; public class GroupTask { public @NonNull Task task1; public @Nullable Task task2; - public @Nullable StagedSplitBounds mStagedSplitBounds; + public @Nullable + SplitBounds mSplitBounds; public GroupTask(@NonNull Task t1, @Nullable Task t2, - @Nullable StagedSplitBounds stagedSplitBounds) { + @Nullable SplitBounds splitBounds) { task1 = t1; task2 = t2; - mStagedSplitBounds = stagedSplitBounds; + mSplitBounds = splitBounds; } public GroupTask(@NonNull GroupTask group) { @@ -43,7 +44,7 @@ public class GroupTask { task2 = group.task2 != null ? new Task(group.task2) : null; - mStagedSplitBounds = group.mStagedSplitBounds; + mSplitBounds = group.mSplitBounds; } public boolean containsTask(int taskId) { diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java index 63d5b0dd50..9fe24dec66 100644 --- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java +++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java @@ -43,7 +43,6 @@ import android.net.Uri; import android.util.Log; import android.view.View; -import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import androidx.core.content.FileProvider; @@ -86,67 +85,70 @@ public class ImageActionUtils { * Launch the activity to share image for overview sharing. This is to share cropped bitmap * with specific share targets (with shortcutInfo and appTarget) rendered in overview. */ - @UiThread public static void shareImage(Context context, Supplier bitmapSupplier, RectF rectF, ShortcutInfo shortcutInfo, AppTarget appTarget, String tag) { - if (bitmapSupplier.get() == null) { - return; - } - Rect crop = new Rect(); - rectF.round(crop); - Intent intent = new Intent(); - Uri uri = getImageUri(bitmapSupplier.get(), crop, context, tag); - ClipData clipdata = new ClipData(new ClipDescription("content", - new String[]{"image/png"}), - new ClipData.Item(uri)); - intent.setAction(Intent.ACTION_SEND) - .setComponent(new ComponentName(appTarget.getPackageName(), appTarget.getClassName())) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(FLAG_GRANT_READ_URI_PERMISSION) - .setType("image/png") - .putExtra(Intent.EXTRA_STREAM, uri) - .putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId()) - .setClipData(clipdata); + UI_HELPER_EXECUTOR.execute(() -> { + Bitmap bitmap = bitmapSupplier.get(); + if (bitmap == null) { + return; + } + Rect crop = new Rect(); + rectF.round(crop); + Intent intent = new Intent(); + Uri uri = getImageUri(bitmap, crop, context, tag); + ClipData clipdata = new ClipData(new ClipDescription("content", + new String[]{"image/png"}), + new ClipData.Item(uri)); + intent.setAction(Intent.ACTION_SEND) + .setComponent( + new ComponentName(appTarget.getPackageName(), appTarget.getClassName())) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(FLAG_GRANT_READ_URI_PERMISSION) + .setType("image/png") + .putExtra(Intent.EXTRA_STREAM, uri) + .putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId()) + .setClipData(clipdata); - if (context.getUserId() != appTarget.getUser().getIdentifier()) { - intent.prepareToLeaveUser(context.getUserId()); - intent.fixUris(context.getUserId()); - context.startActivityAsUser(intent, appTarget.getUser()); - } else { - context.startActivity(intent); - } + if (context.getUserId() != appTarget.getUser().getIdentifier()) { + intent.prepareToLeaveUser(context.getUserId()); + intent.fixUris(context.getUserId()); + context.startActivityAsUser(intent, appTarget.getUser()); + } else { + context.startActivity(intent); + } + }); } /** * Launch the activity to share image. */ - @UiThread public static void startShareActivity(Context context, Supplier bitmapSupplier, Rect crop, Intent intent, String tag) { - if (bitmapSupplier.get() == null) { - Log.e(tag, "No snapshot available, not starting share."); - return; - } - - UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(context, - bitmapSupplier.get(), crop, intent, ImageActionUtils::getShareIntentForImageUri, - tag)); + UI_HELPER_EXECUTOR.execute(() -> { + Bitmap bitmap = bitmapSupplier.get(); + if (bitmap == null) { + Log.e(tag, "No snapshot available, not starting share."); + return; + } + persistBitmapAndStartActivity(context, bitmap, crop, intent, + ImageActionUtils::getShareIntentForImageUri, tag); + }); } /** * Launch the activity to share image with shared element transition. */ - @UiThread public static void startShareActivity(Context context, Supplier bitmapSupplier, Rect crop, Intent intent, String tag, View sharedElement) { - if (bitmapSupplier.get() == null) { - Log.e(tag, "No snapshot available, not starting share."); - return; - } - - UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(context, - bitmapSupplier.get(), crop, intent, ImageActionUtils::getShareIntentForImageUri, - tag, sharedElement)); + UI_HELPER_EXECUTOR.execute(() -> { + Bitmap bitmap = bitmapSupplier.get(); + if (bitmap == null) { + Log.e(tag, "No snapshot available, not starting share."); + return; + } + persistBitmapAndStartActivity(context, bitmap, + crop, intent, ImageActionUtils::getShareIntentForImageUri, tag, sharedElement); + }); } /** diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index d0856bed20..f7136a5b68 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -23,7 +23,7 @@ import android.view.ViewGroup; import com.android.launcher3.DeviceProfile; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.DisplayController; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; import com.android.quickstep.LauncherActivityInterface; public class LayoutUtils { diff --git a/quickstep/src/com/android/quickstep/util/LogUtils.kt b/quickstep/src/com/android/quickstep/util/LogUtils.kt new file mode 100644 index 0000000000..bad8506eec --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/LogUtils.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util + +import android.util.Pair +import com.android.internal.logging.InstanceIdSequence +import com.android.launcher3.logging.InstanceId + +object LogUtils { + /** + * @return a [Pair] of two InstanceIds but with different types, one that can be used by framework + * (if needing to pass through an intent or such) and one used in Launcher + */ + @JvmStatic + fun getShellShareableInstanceId(): + Pair { + val internalInstanceId = InstanceIdSequence(InstanceId.INSTANCE_ID_MAX).newInstanceId() + val launcherInstanceId = InstanceId(internalInstanceId.id) + return Pair(internalInstanceId, launcherInstanceId) + } +} diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java index b83e26e5de..69ed2f8028 100644 --- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java +++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java @@ -17,13 +17,14 @@ package com.android.quickstep.util; import android.content.Context; import android.content.res.Resources; +import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import com.android.launcher3.Alarm; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.compat.AccessibilityManagerCompat; -import com.android.launcher3.testing.TestProtocol; /** * Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is @@ -31,6 +32,8 @@ import com.android.launcher3.testing.TestProtocol; */ public class MotionPauseDetector { + private static final String TAG = "MotionPauseDetector"; + // The percentage of the previous speed that determines whether this is a rapid deceleration. // The bigger this number, the easier it is to trigger the first pause. private static final float RAPID_DECELERATION_FACTOR = 0.6f; @@ -43,6 +46,12 @@ public class MotionPauseDetector { */ private static final long HARDER_TRIGGER_TIMEOUT = 400; + /** + * When running in a test harness, if no motion is added for this amount of time, assume the + * motion has paused. (We use an increased timeout since sometimes test devices can be slow.) + */ + private static final long TEST_HARNESS_TRIGGER_TIMEOUT = 2000; + private final float mSpeedVerySlow; private final float mSpeedSlow; private final float mSpeedSomewhatFast; @@ -85,7 +94,8 @@ public class MotionPauseDetector { mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast); mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast); mForcePauseTimeout = new Alarm(); - mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */)); + mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */, + "Force pause timeout after " + alarm.getLastSetTimeout() + "ms" /* reason */)); mMakePauseHarderToTrigger = makePauseHarderToTrigger; mVelocityProvider = new SystemVelocityProvider(axis); } @@ -102,7 +112,7 @@ public class MotionPauseDetector { */ public void setDisallowPause(boolean disallowPause) { mDisallowPause = disallowPause; - updatePaused(mIsPaused); + updatePaused(mIsPaused, "Set disallowPause=" + disallowPause); } /** @@ -119,9 +129,11 @@ public class MotionPauseDetector { * @param pointerIndex Index for the pointer being tracked in the motion event */ public void addPosition(MotionEvent ev, int pointerIndex) { - long timeoutMs = TestProtocol.sForcePauseTimeout != null - ? TestProtocol.sForcePauseTimeout - : mMakePauseHarderToTrigger ? HARDER_TRIGGER_TIMEOUT : FORCE_PAUSE_TIMEOUT; + long timeoutMs = Utilities.IS_RUNNING_IN_TEST_HARNESS + ? TEST_HARNESS_TRIGGER_TIMEOUT + : mMakePauseHarderToTrigger + ? HARDER_TRIGGER_TIMEOUT + : FORCE_PAUSE_TIMEOUT; mForcePauseTimeout.setAlarm(timeoutMs); float newVelocity = mVelocityProvider.addMotionEvent(ev, ev.getPointerId(pointerIndex)); if (mPreviousVelocity != null) { @@ -134,21 +146,27 @@ public class MotionPauseDetector { float speed = Math.abs(velocity); float previousSpeed = Math.abs(prevVelocity); boolean isPaused; + String isPausedReason = ""; if (mIsPaused) { // Continue to be paused until moving at a fast speed. isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast; + isPausedReason = "Was paused, but started moving at a fast speed"; } else { if (velocity < 0 != prevVelocity < 0) { // We're just changing directions, not necessarily stopping. isPaused = false; + isPausedReason = "Velocity changed directions"; } else { isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow; + isPausedReason = "Pause requires back to back slow speeds"; if (!isPaused && !mHasEverBeenPaused) { // We want to be more aggressive about detecting the first pause to ensure it // feels as responsive as possible; getting two very slow speeds back to back // takes too long, so also check for a rapid deceleration. boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR; isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast; + isPausedReason = "Didn't have back to back slow speeds, checking for rapid" + + " deceleration on first pause only"; } if (mMakePauseHarderToTrigger) { if (speed < mSpeedSlow) { @@ -156,22 +174,31 @@ public class MotionPauseDetector { mSlowStartTime = time; } isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT; + isPausedReason = "Maintained slow speed for sufficient duration when making" + + " pause harder to trigger"; } else { mSlowStartTime = 0; isPaused = false; + isPausedReason = "Intentionally making pause harder to trigger"; } } } } - updatePaused(isPaused); + updatePaused(isPaused, isPausedReason); } - private void updatePaused(boolean isPaused) { + private void updatePaused(boolean isPaused, String reason) { if (mDisallowPause) { + reason = "Disallow pause; otherwise, would have been " + isPaused + " due to " + reason; isPaused = false; } if (mIsPaused != isPaused) { mIsPaused = isPaused; + String logString = "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason; + if (Utilities.IS_RUNNING_IN_TEST_HARNESS) { + Log.d(TAG, logString); + } + ActiveGestureLog.INSTANCE.addLog(logString); boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused; if (mIsPaused) { AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext); diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java index 527a6d2d9a..59c82633d2 100644 --- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java +++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java @@ -15,12 +15,12 @@ */ package com.android.quickstep.util; -import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON; +import static com.android.launcher3.util.NavigationMode.NO_BUTTON; import android.view.Surface; import com.android.launcher3.util.DisplayController.Info; -import com.android.launcher3.util.DisplayController.NavigationMode; +import com.android.launcher3.util.NavigationMode; /** * Utility class to check nav bar position. diff --git a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java new file mode 100644 index 0000000000..e189a66ee4 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.EMPHASIZED; +import static com.android.launcher3.anim.Interpolators.INSTANT; + +import android.view.animation.Interpolator; + +/** + * Timings for the Overview > OverviewSplitSelect animation. + */ +abstract class OverviewToSplitTimings implements SplitAnimationTimings { + // Overwritten by device-specific timings + abstract public int getPlaceholderFadeInStart(); + abstract public int getPlaceholderFadeInEnd(); + abstract public int getPlaceholderIconFadeInStart(); + abstract public int getPlaceholderIconFadeInEnd(); + abstract public int getStagedRectSlideStart(); + abstract public int getStagedRectSlideEnd(); + abstract public int getGridSlideStart(); + abstract public int getGridSlideStagger(); + abstract public int getGridSlideDuration(); + + // Common timings + public int getIconFadeStart() { return 0; } + public int getIconFadeEnd() { return 83; } + public int getActionsFadeStart() { return 0; } + public int getActionsFadeEnd() { return 83; } + public int getInstructionsContainerFadeInStart() { return 167; } + public int getInstructionsContainerFadeInEnd() { return 250; } + public int getInstructionsTextFadeInStart() { return 217; } + public int getInstructionsTextFadeInEnd() { return 300; } + public int getInstructionsUnfoldStart() { return 167; } + public int getInstructionsUnfoldEnd() { return 500; } + public Interpolator getGridSlidePrimaryInterpolator() { return EMPHASIZED; } + public Interpolator getGridSlideSecondaryInterpolator() { return INSTANT; } + + abstract public int getDuration(); + abstract public Interpolator getStagedRectXInterpolator(); + abstract public Interpolator getStagedRectYInterpolator(); + abstract public Interpolator getStagedRectScaleXInterpolator(); + abstract public Interpolator getStagedRectScaleYInterpolator(); + + public float getGridSlideStartOffset() { + return (float) getGridSlideStart() / getDuration(); + } + public float getGridSlideStaggerOffset() { + return (float) getGridSlideStagger() / getDuration(); + } + public float getGridSlideDurationOffset() { + return (float) getGridSlideDuration() / getDuration(); + } + public float getActionsFadeStartOffset() { + return (float) getActionsFadeStart() / getDuration(); + } + public float getActionsFadeEndOffset() { + return (float) getActionsFadeEnd() / getDuration(); + } + public float getIconFadeStartOffset() { + return (float) getIconFadeStart() / getDuration(); + } + public float getIconFadeEndOffset() { + return (float) getIconFadeEnd() / getDuration(); + } + public float getInstructionsContainerFadeInStartOffset() { + return (float) getInstructionsContainerFadeInStart() / getDuration(); + } + public float getInstructionsContainerFadeInEndOffset() { + return (float) getInstructionsContainerFadeInEnd() / getDuration(); + } + public float getInstructionsTextFadeInStartOffset() { + return (float) getInstructionsTextFadeInStart() / getDuration(); + } + public float getInstructionsTextFadeInEndOffset() { + return (float) getInstructionsTextFadeInEnd() / getDuration(); + } + public float getInstructionsUnfoldStartOffset() { + return (float) getInstructionsUnfoldStart() / getDuration(); + } + public float getInstructionsUnfoldEndOffset() { + return (float) getInstructionsUnfoldEnd() / getDuration(); + } +} diff --git a/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java new file mode 100644 index 0000000000..f1dde53d12 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.EMPHASIZED; + +import android.view.animation.Interpolator; + +/** + * Timings for the Overview > OverviewSplitSelect animation on phones. + */ +public class PhoneOverviewToSplitTimings + extends OverviewToSplitTimings implements SplitAnimationTimings { + public int getPlaceholderFadeInStart() { return 0; } + public int getPlaceholderFadeInEnd() { return 133; } + public int getPlaceholderIconFadeInStart() { return 83; } + public int getPlaceholderIconFadeInEnd() { return 167; } + public int getStagedRectSlideStart() { return 0; } + public int getStagedRectSlideEnd() { return 333; } + public int getGridSlideStart() { return 100; } + public int getGridSlideStagger() { return 0; } + public int getGridSlideDuration() { return 417; } + + public int getDuration() { return PHONE_ENTER_DURATION; } + public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; } + public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; } + public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; } + public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; } +} diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java new file mode 100644 index 0000000000..9e7351a2de --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.EMPHASIZED; + +import android.view.animation.Interpolator; + +/** + * Timings for the OverviewSplitSelect > confirmed animation on phones. + */ +public class PhoneSplitToConfirmTimings + extends SplitToConfirmTimings implements SplitAnimationTimings { + public int getPlaceholderFadeInStart() { return 0; } + public int getPlaceholderFadeInEnd() { return 133; } + public int getPlaceholderIconFadeInStart() { return 50; } + public int getPlaceholderIconFadeInEnd() { return 133; } + public int getStagedRectSlideStart() { return 0; } + public int getStagedRectSlideEnd() { return 333; } + + public int getDuration() { return PHONE_CONFIRM_DURATION; } + public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; } + public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; } + public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; } + public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; } +} diff --git a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java index 3777c659e3..8f79ccf450 100644 --- a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java +++ b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java @@ -39,6 +39,16 @@ public class ProxyScreenStatusProvider implements ScreenStatusProvider { mListeners.forEach(ScreenListener::onScreenTurnedOn); } + /** Called when the screen is starting to turn on. */ + public void onScreenTurningOn() { + mListeners.forEach(ScreenListener::onScreenTurningOn); + } + + /** Called when the screen is starting to turn off. */ + public void onScreenTurningOff() { + mListeners.forEach(ScreenListener::onScreenTurningOff); + } + @Override public void addCallback(@NonNull ScreenListener listener) { mListeners.add(listener); diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java index fb32581508..e928b27751 100644 --- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java +++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java @@ -21,7 +21,7 @@ import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON; +import static com.android.launcher3.util.NavigationMode.NO_BUTTON; import android.content.SharedPreferences; @@ -29,7 +29,6 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.appprediction.AppsDividerView; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.statemanager.StateManager.StateListener; @@ -88,8 +87,7 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs }); } - if (DisplayController.getNavigationMode(launcher) == NO_BUTTON - && FeatureFlags.ENABLE_ALL_APPS_EDU.get()) { + if (DisplayController.getNavigationMode(launcher) == NO_BUTTON) { stateManager.addStateListener(new StateListener() { private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3; diff --git a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java index edaa32637a..1d008da5b3 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java +++ b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java @@ -15,13 +15,10 @@ */ package com.android.quickstep.util; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.util.Log; import androidx.dynamicanimation.animation.DynamicAnimation; @@ -30,8 +27,6 @@ import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; import com.android.launcher3.statemanager.StatefulActivity; import com.android.quickstep.views.RecentsView; -import java.util.Arrays; - public class RecentsAtomicAnimationFactory extends AtomicAnimationFactory { @@ -53,27 +48,6 @@ public class RecentsAtomicAnimationFactory 0) { - scale = scale * dp.widthPx / fullWidth; - } - if (scale == 1) { - outPivot.set(fullWidth / 2, fullHeight / 2); - } else if (dp.isMultiWindowMode) { - float denominator = 1 / (scale - 1); - // Ensure that the task aligns to right bottom for the root view - float y = (scale * taskView.bottom - fullHeight) * denominator; - float x = (scale * taskView.right - fullWidth) * denominator; - outPivot.set(x, y); + outPivot.set(taskView.centerX(), taskView.centerY()); } else { float factor = scale / (scale - 1); outPivot.set(taskView.left * factor, taskView.top * factor); diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java index c4909de510..68739ba458 100644 --- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java +++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java @@ -214,7 +214,7 @@ public class RectFSpringAnim extends ReleaseCheck { * @param context The activity context. * @param velocityPxPerMs Velocity of swipe in px/ms. */ - public void start(Context context, PointF velocityPxPerMs) { + public void start(Context context, @Nullable DeviceProfile profile, PointF velocityPxPerMs) { // Only tell caller that we ended if both x and y animations have ended. OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> { mRectXAnimEnded = true; @@ -252,7 +252,13 @@ public class RectFSpringAnim extends ReleaseCheck { 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); + + // Increase the stiffness for devices where we want the window size to transform quicker. + boolean shouldUseHigherStiffness = profile != null + && (profile.isLandscape || profile.isTablet); + float stiffness = shouldUseHigherStiffness + ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness) + : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness); mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS) .setSpring(new SpringForce(1f) diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java new file mode 100644 index 0000000000..2966fbb5db --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.LINEAR; + +import android.view.animation.Interpolator; + +/** + * An interface that supports the centralization of timing information for splitscreen animations. + */ +public interface SplitAnimationTimings { + int TABLET_ENTER_DURATION = 866; + int TABLET_CONFIRM_DURATION = 383; + + int PHONE_ENTER_DURATION = 517; + int PHONE_CONFIRM_DURATION = 333; + + int ABORT_DURATION = 500; + + SplitAnimationTimings TABLET_OVERVIEW_TO_SPLIT = new TabletOverviewToSplitTimings(); + SplitAnimationTimings TABLET_HOME_TO_SPLIT = new TabletHomeToSplitTimings(); + SplitAnimationTimings TABLET_SPLIT_TO_CONFIRM = new TabletSplitToConfirmTimings(); + + SplitAnimationTimings PHONE_OVERVIEW_TO_SPLIT = new PhoneOverviewToSplitTimings(); + SplitAnimationTimings PHONE_SPLIT_TO_CONFIRM = new PhoneSplitToConfirmTimings(); + + // Shared methods + int getDuration(); + int getPlaceholderFadeInStart(); + int getPlaceholderFadeInEnd(); + int getPlaceholderIconFadeInStart(); + int getPlaceholderIconFadeInEnd(); + int getStagedRectSlideStart(); + int getStagedRectSlideEnd(); + Interpolator getStagedRectXInterpolator(); + Interpolator getStagedRectYInterpolator(); + Interpolator getStagedRectScaleXInterpolator(); + Interpolator getStagedRectScaleYInterpolator(); + default float getPlaceholderFadeInStartOffset() { + return (float) getPlaceholderFadeInStart() / getDuration(); + } + default float getPlaceholderFadeInEndOffset() { + return (float) getPlaceholderFadeInEnd() / getDuration(); + } + default float getPlaceholderIconFadeInStartOffset() { + return (float) getPlaceholderIconFadeInStart() / getDuration(); + } + default float getPlaceholderIconFadeInEndOffset() { + return (float) getPlaceholderIconFadeInEnd() / getDuration(); + } + default float getStagedRectSlideStartOffset() { + return (float) getStagedRectSlideStart() / getDuration(); + } + default float getStagedRectSlideEndOffset() { + return (float) getStagedRectSlideEnd() / getDuration(); + } + + // Defaults for OverviewToSplit + default float getGridSlideStartOffset() { return 0; } + default float getGridSlideStaggerOffset() { return 0; } + default float getGridSlideDurationOffset() { return 0; } + default float getActionsFadeStartOffset() { return 0; } + default float getActionsFadeEndOffset() { return 0; } + default float getIconFadeStartOffset() { return 0; } + default float getIconFadeEndOffset() { return 0; } + default float getInstructionsContainerFadeInStartOffset() { return 0; } + default float getInstructionsContainerFadeInEndOffset() { return 0; } + default float getInstructionsTextFadeInStartOffset() { return 0; } + default float getInstructionsTextFadeInEndOffset() { return 0; } + default float getInstructionsUnfoldStartOffset() { return 0; } + default float getInstructionsUnfoldEndOffset() { return 0; } + default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; } + default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; } + + // Defaults for HomeToSplit + default float getScrimFadeInStartOffset() { return 0; } + default float getScrimFadeInEndOffset() { return 0; } + + // Defaults for SplitToConfirm + default float getInstructionsFadeStartOffset() { return 0; } + default float getInstructionsFadeEndOffset() { return 0; } +} + diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 250235925e..efbe783125 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -31,17 +31,28 @@ import android.app.ActivityThread; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; import android.os.Handler; import android.os.IBinder; +import android.os.UserHandle; import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; import android.view.RemoteAnimationAdapter; import android.view.SurfaceControl; import android.window.TransitionInfo; import androidx.annotation.Nullable; +import com.android.internal.logging.InstanceId; +import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager; +import com.android.launcher3.testing.TestLogging; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; import com.android.quickstep.SystemUiProxy; @@ -63,26 +74,34 @@ import java.util.function.Consumer; * and is in the process of either a) selecting a second app or b) exiting intention to invoke split */ public class SplitSelectStateController { + private static final String TAG = "SplitSelectStateCtor"; private final Context mContext; private final Handler mHandler; + private StatsLogManager mStatsLogManager; private final SystemUiProxy mSystemUiProxy; private final StateManager mStateManager; private final DepthController mDepthController; private @StagePosition int mStagePosition; + private ItemInfo mItemInfo; private Intent mInitialTaskIntent; private int mInitialTaskId = INVALID_TASK_ID; private int mSecondTaskId = INVALID_TASK_ID; private String mSecondTaskPackageName; private boolean mRecentsAnimationRunning; + @Nullable + private UserHandle mUser; /** If not null, this is the TaskView we want to launch from */ @Nullable private GroupedTaskView mLaunchingTaskView; + /** Represents where split is intended to be invoked from. */ + private StatsLogManager.EventEnum mSplitEvent; public SplitSelectStateController(Context context, Handler handler, StateManager stateManager, - DepthController depthController) { + DepthController depthController, StatsLogManager statsLogManager) { mContext = context; mHandler = handler; + mStatsLogManager = statsLogManager; mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext); mStateManager = stateManager; mDepthController = depthController; @@ -91,16 +110,25 @@ public class SplitSelectStateController { /** * To be called after first task selected */ - public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition) { + public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition, + StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) { mInitialTaskId = taskId; - mStagePosition = stagePosition; - mInitialTaskIntent = null; + setInitialData(stagePosition, splitEvent, itemInfo); } - public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition) { + public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition, + @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent) { mInitialTaskIntent = intent; + mUser = itemInfo.user; + mItemInfo = itemInfo; + setInitialData(stagePosition, splitEvent, itemInfo); + } + + private void setInitialData(@StagePosition int stagePosition, + StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) { + mItemInfo = itemInfo; mStagePosition = stagePosition; - mInitialTaskId = INVALID_TASK_ID; + mSplitEvent = splitEvent; } /** @@ -118,11 +146,22 @@ public class SplitSelectStateController { } else { fillInIntent = null; } - final PendingIntent pendingIntent = - mInitialTaskIntent == null ? null : PendingIntent.getActivity(mContext, 0, - mInitialTaskIntent, FLAG_MUTABLE); + + final PendingIntent pendingIntent = mInitialTaskIntent == null ? null : (mUser != null + ? PendingIntent.getActivityAsUser(mContext, 0, mInitialTaskIntent, + FLAG_MUTABLE, null /* options */, mUser) + : PendingIntent.getActivity(mContext, 0, mInitialTaskIntent, FLAG_MUTABLE)); + + Pair instanceIds = + LogUtils.getShellShareableInstanceId(); launchTasks(mInitialTaskId, pendingIntent, fillInIntent, mSecondTaskId, mStagePosition, - callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO); + callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO, + instanceIds.first); + + mStatsLogManager.logger() + .withItemInfo(mItemInfo) + .withInstanceId(instanceIds.second) + .log(mSplitEvent); } @@ -158,7 +197,7 @@ public class SplitSelectStateController { public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition, Consumer callback, boolean freezeTaskList, float splitRatio) { launchTasks(taskId1, null /* taskPendingIntent */, null /* fillInIntent */, taskId2, - stagePosition, callback, freezeTaskList, splitRatio); + stagePosition, callback, freezeTaskList, splitRatio, null); } /** @@ -167,10 +206,16 @@ public class SplitSelectStateController { * fill in intent with a taskId2 are set. * @param taskPendingIntent is null when split is initiated from Overview * @param stagePosition representing location of task1 + * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that create + * a split instance, null for cases that bring existing instaces to the + * foreground (quickswitch, launching previous pairs from overview) */ public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent, @Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition, - Consumer callback, boolean freezeTaskList, float splitRatio) { + Consumer callback, boolean freezeTaskList, float splitRatio, + @Nullable InstanceId shellInstanceId) { + TestLogging.recordEvent( + TestProtocol.SEQUENCE_MAIN, "launchSplitTasks"); // Assume initial task is for top/left part of screen final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT ? new int[]{taskId1, taskId2} @@ -182,8 +227,9 @@ public class SplitSelectStateController { mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio, new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR, - ActivityThread.currentActivityThread().getApplicationThread())); - // TODO: handle intent + task with shell transition + ActivityThread.currentActivityThread().getApplicationThread()), + shellInstanceId); + // TODO(b/237635859): handle intent/shortcut + task with shell transition } else { RemoteSplitLaunchAnimationRunner animationRunner = new RemoteSplitLaunchAnimationRunner(taskId1, taskPendingIntent, taskId2, @@ -200,11 +246,19 @@ public class SplitSelectStateController { if (taskPendingIntent == null) { mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(), taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, - splitRatio, adapter); + splitRatio, adapter, shellInstanceId); } else { - mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent, - fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */, - stagePosition, splitRatio, adapter); + final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent, + taskPendingIntent.getCreatorUserHandle()); + if (shortcutInfo != null) { + mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId2, + mainOpts.toBundle(), null /* sideOptions */, stagePosition, splitRatio, + adapter, shellInstanceId); + } else { + mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent, + fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */, + stagePosition, splitRatio, adapter, shellInstanceId); + } } } } @@ -213,10 +267,36 @@ public class SplitSelectStateController { return mStagePosition; } + public StatsLogManager.EventEnum getSplitEvent() { + return mSplitEvent; + } + public void setRecentsAnimationRunning(boolean running) { this.mRecentsAnimationRunning = running; } + @Nullable + private ShortcutInfo getShortcutInfo(Intent intent, UserHandle userHandle) { + if (intent == null || intent.getPackage() == null) { + return null; + } + + final String shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID); + if (shortcutId == null) { + return null; + } + + try { + final Context context = mContext.createPackageContextAsUser( + intent.getPackage(), 0 /* flags */, userHandle); + return new ShortcutInfo.Builder(context, shortcutId).build(); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage()); + } + + return null; + } + /** * Requires Shell Transitions */ @@ -238,8 +318,9 @@ public class SplitSelectStateController { @Override public void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { - TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTaskId, - mInitialTaskPendingIntent, mSecondTaskId, info, t, () -> { + TaskViewUtils.composeRecentsSplitLaunchAnimator(mLaunchingTaskView, mStateManager, + mDepthController, mInitialTaskId, mInitialTaskPendingIntent, mSecondTaskId, + info, t, () -> { finishCallback.run(); if (mSuccessCallback != null) { mSuccessCallback.accept(true); @@ -309,6 +390,8 @@ public class SplitSelectStateController { mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; mRecentsAnimationRunning = false; mLaunchingTaskView = null; + mItemInfo = null; + mSplitEvent = null; } /** @@ -330,4 +413,8 @@ public class SplitSelectStateController { private boolean isInitialTaskIntentSet() { return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null); } + + public int getInitialTaskId() { + return mInitialTaskId; + } } diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java new file mode 100644 index 0000000000..3026e98aa9 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import android.view.animation.Interpolator; + +/** + * Timings for the OverviewSplitSelect > confirmed animation. + */ +abstract class SplitToConfirmTimings implements SplitAnimationTimings { + // Overwritten by device-specific timings + abstract public int getPlaceholderFadeInStart(); + abstract public int getPlaceholderFadeInEnd(); + abstract public int getPlaceholderIconFadeInStart(); + abstract public int getPlaceholderIconFadeInEnd(); + abstract public int getStagedRectSlideStart(); + abstract public int getStagedRectSlideEnd(); + + // Common timings + public int getInstructionsFadeStart() { return 0; } + public int getInstructionsFadeEnd() { return 67; } + + abstract public int getDuration(); + abstract public Interpolator getStagedRectXInterpolator(); + abstract public Interpolator getStagedRectYInterpolator(); + abstract public Interpolator getStagedRectScaleXInterpolator(); + abstract public Interpolator getStagedRectScaleYInterpolator(); + + public float getInstructionsFadeStartOffset() { + return (float) getInstructionsFadeStart() / getDuration(); + } + public float getInstructionsFadeEndOffset() { + return (float) getInstructionsFadeEnd() / getDuration(); + } +} diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 32e08ffa8d..eec8582756 100644 --- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -35,12 +35,12 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Hotseat; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Workspace; @@ -48,6 +48,7 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DynamicResource; import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.ResourceProvider; @@ -60,10 +61,10 @@ import com.android.systemui.plugins.ResourceProvider; public class StaggeredWorkspaceAnim { private static final int APP_CLOSE_ROW_START_DELAY_MS = 10; - // How long it takes to fade in each staggered row. - private static final int ALPHA_DURATION_MS = 250; // Should be used for animations running alongside this StaggeredWorkspaceAnim. public static final int DURATION_MS = 250; + public static final int DURATION_TASKBAR_MS = + QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION; private static final float MAX_VELOCITY_PX_PER_S = 22f; @@ -91,16 +92,20 @@ public class StaggeredWorkspaceAnim { mSpringTransY = transFactor * launcher.getResources() .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y); + DeviceProfile grid = launcher.getDeviceProfile(); + long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS; if (staggerWorkspace) { - DeviceProfile grid = launcher.getDeviceProfile(); Workspace workspace = launcher.getWorkspace(); Hotseat hotseat = launcher.getHotseat(); - // Hotseat and QSB takes up two additional rows. - int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2); + boolean staggerHotseat = !grid.isVerticalBarLayout() && !grid.isTaskbarPresent; + boolean staggerQsb = + !grid.isVerticalBarLayout() && !(grid.isTaskbarPresent && grid.isQsbInline); + int totalRows = grid.inv.numRows + (staggerHotseat ? 1 : 0) + (staggerQsb ? 1 : 0); // Add animation for all the visible workspace pages - workspace.forEachVisiblePage(page -> addAnimationForPage((CellLayout) page, totalRows)); + workspace.forEachVisiblePage( + page -> addAnimationForPage((CellLayout) page, totalRows, duration)); boolean workspaceClipChildren = workspace.getClipChildren(); boolean workspaceClipToPadding = workspace.getClipToPadding(); @@ -119,23 +124,34 @@ public class StaggeredWorkspaceAnim { View child = hotseatIcons.getChildAt(i); CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); - addStaggeredAnimationForView(child, lp.cellY + 1, totalRows); + addStaggeredAnimationForView(child, lp.cellY + 1, totalRows, duration); } } else { final int hotseatRow, qsbRow; if (grid.isTaskbarPresent) { - qsbRow = grid.inv.numRows + 1; - hotseatRow = grid.inv.numRows + 2; + if (grid.isQsbInline) { + qsbRow = grid.inv.numRows + 1; + hotseatRow = grid.inv.numRows + 1; + } else { + qsbRow = grid.inv.numRows + 1; + hotseatRow = grid.inv.numRows + 2; + } } else { hotseatRow = grid.inv.numRows + 1; qsbRow = grid.inv.numRows + 2; } - for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) { - View child = hotseatIcons.getChildAt(i); - addStaggeredAnimationForView(child, hotseatRow, totalRows); - } - addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows); + // Do not stagger hotseat as a whole when taskbar is present, and stagger QSB only + // if it's not inline. + if (staggerHotseat) { + for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) { + View child = hotseatIcons.getChildAt(i); + addStaggeredAnimationForView(child, hotseatRow, totalRows, duration); + } + } + if (staggerQsb) { + addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows, duration); + } } mAnimators.addListener(new AnimatorListenerAdapter() { @@ -153,19 +169,19 @@ public class StaggeredWorkspaceAnim { mAnimators.addListener(forEndCallback(launcher::resumeExpensiveViewUpdates)); if (animateOverviewScrim) { - PendingAnimation pendingAnimation = new PendingAnimation(DURATION_MS); + PendingAnimation pendingAnimation = new PendingAnimation(duration); launcher.getWorkspace().getStateTransitionAnimation() .setScrim(pendingAnimation, NORMAL, new StateAnimationConfig()); mAnimators.play(pendingAnimation.buildAnim()); } - addDepthAnimationForState(launcher, NORMAL, DURATION_MS); + addDepthAnimationForState(launcher, NORMAL, duration); mAnimators.play(launcher.getRootView().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f) - .setDuration(DURATION_MS)); + .setDuration(duration)); } - private void addAnimationForPage(CellLayout page, int totalRows) { + private void addAnimationForPage(CellLayout page, int totalRows, long duration) { ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets(); boolean pageClipChildren = page.getClipChildren(); @@ -178,7 +194,7 @@ public class StaggeredWorkspaceAnim { for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) { View child = itemsContainer.getChildAt(i); CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); - addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows); + addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows, duration); } mAnimators.addListener(new AnimatorListenerAdapter() { @@ -231,8 +247,9 @@ public class StaggeredWorkspaceAnim { * @param v A view on the workspace. * @param row The bottom-most row that contains the view. * @param totalRows Total number of rows. + * @param duration duration of the animation */ - private void addStaggeredAnimationForView(View v, int row, int totalRows) { + private void addStaggeredAnimationForView(View v, int row, int totalRows, long duration) { if (mIgnoredView != null && mIgnoredView == v) return; // Invert the rows, because we stagger starting from the bottom of the screen. int invertedRow = totalRows - row; @@ -266,7 +283,7 @@ public class StaggeredWorkspaceAnim { v.setAlpha(0); ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); alpha.setInterpolator(LINEAR); - alpha.setDuration(ALPHA_DURATION_MS); + alpha.setDuration(duration); alpha.setStartDelay(startDelay); alpha.addListener(new AnimatorListenerAdapter() { @Override @@ -278,11 +295,11 @@ public class StaggeredWorkspaceAnim { } private void addDepthAnimationForState(Launcher launcher, LauncherState state, long duration) { - if (!(launcher instanceof BaseQuickstepLauncher)) { + if (!(launcher instanceof QuickstepLauncher)) { return; } PendingAnimation builder = new PendingAnimation(duration); - DepthController depthController = ((BaseQuickstepLauncher) launcher).getDepthController(); + DepthController depthController = ((QuickstepLauncher) launcher).getDepthController(); depthController.setStateWithAnimation(state, new StateAnimationConfig(), builder); mAnimators.play(builder.buildAnim()); } diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java index b222f51e50..74e4acc421 100644 --- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java +++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java @@ -253,7 +253,7 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY); } else { return mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mAppBounds, - bounds, insets); + bounds, insets, progress); } } @@ -279,7 +279,10 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { // get the final leash operations but do not apply to the leash. final SurfaceControl.Transaction tx = PipSurfaceTransactionHelper.newSurfaceControlTransaction(); - return onAnimationUpdate(tx, new RectF(mDestinationBounds), END_PROGRESS); + final PictureInPictureSurfaceTransaction pipTx = + onAnimationUpdate(tx, new RectF(mDestinationBounds), END_PROGRESS); + pipTx.setShouldDisableCanAffectSystemUiFlags(true); + return pipTx; } private RotatedPosition getRotatedPosition(float progress) { diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java index 9bb3d56556..5dc461363b 100644 --- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java +++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java @@ -15,12 +15,20 @@ */ package com.android.quickstep.util; -import android.content.Context; -import android.hardware.display.DisplayManager; -import android.view.Display; +import static android.view.Display.DEFAULT_DISPLAY; +import android.content.Context; +import android.util.ArrayMap; +import android.view.Surface; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import com.android.launcher3.util.WindowBounds; +import com.android.launcher3.util.window.CachedDisplayInfo; import com.android.launcher3.util.window.WindowManagerProxy; +import java.util.Set; + /** * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher */ @@ -31,23 +39,23 @@ public class SystemWindowManagerProxy extends WindowManagerProxy { } @Override - protected String getDisplayId(Display display) { - return display.getUniqueId(); + public int getRotation(Context displayInfoContext) { + return displayInfoContext.getResources().getConfiguration().windowConfiguration + .getRotation(); } @Override - public boolean isInternalDisplay(Display display) { - return display.getType() == Display.TYPE_INTERNAL; - } - - @Override - public int getRotation(Context context) { - return context.getResources().getConfiguration().windowConfiguration.getRotation(); - } - - @Override - protected Display[] getDisplays(Context context) { - return context.getSystemService(DisplayManager.class).getDisplays( - DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); + public ArrayMap estimateInternalDisplayBounds( + Context displayInfoContext) { + ArrayMap result = new ArrayMap<>(); + WindowManager windowManager = displayInfoContext.getSystemService(WindowManager.class); + Set possibleMaximumWindowMetrics = + windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); + for (WindowMetrics windowMetrics : possibleMaximumWindowMetrics) { + CachedDisplayInfo info = getDisplayInfo(windowMetrics, Surface.ROTATION_0); + WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info); + result.put(info, bounds); + } + return result; } } diff --git a/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java new file mode 100644 index 0000000000..bf8612a797 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.LINEAR; + +import android.view.animation.Interpolator; + +/** + * Timings for the Home > OverviewSplitSelect animation on tablets. + */ +public class TabletHomeToSplitTimings + extends TabletOverviewToSplitTimings implements SplitAnimationTimings { + @Override + public Interpolator getStagedRectXInterpolator() { return LINEAR; } + @Override + public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; } + @Override + public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; } + + public int getScrimFadeInStart() { return 0; } + public int getScrimFadeInEnd() { return 167; } + + public float getScrimFadeInStartOffset() { + return (float) getScrimFadeInStart() / getDuration(); + } + public float getScrimFadeInEndOffset() { + return (float) getScrimFadeInEnd() / getDuration(); + } +} diff --git a/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java new file mode 100644 index 0000000000..cbf46bfcb8 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.DEACCEL_2; + +import android.view.animation.Interpolator; + +/** + * Timings for the Overview > OverviewSplitSelect animation on tablets. + */ +public class TabletOverviewToSplitTimings + extends OverviewToSplitTimings implements SplitAnimationTimings { + public int getPlaceholderFadeInStart() { return 0; } + public int getPlaceholderFadeInEnd() { return 133; } + public int getPlaceholderIconFadeInStart() { return 167; } + public int getPlaceholderIconFadeInEnd() { return 250; } + public int getStagedRectSlideStart() { return 0; } + public int getStagedRectSlideEnd() { return 417; } + public int getGridSlideStart() { return 67; } + public int getGridSlideStagger() { return 16; } + public int getGridSlideDuration() { return 500; } + + public int getDuration() { return TABLET_ENTER_DURATION; } + public Interpolator getStagedRectXInterpolator() { return DEACCEL_2; } + public Interpolator getStagedRectYInterpolator() { return DEACCEL_2; } + public Interpolator getStagedRectScaleXInterpolator() { return DEACCEL_2; } + public Interpolator getStagedRectScaleYInterpolator() { return DEACCEL_2; } +} diff --git a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java new file mode 100644 index 0000000000..3ea8466148 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.LINEAR; + +import android.view.animation.Interpolator; + +/** + * Timings for the OverviewSplitSelect > confirmed animation on tablets. + */ +public class TabletSplitToConfirmTimings + extends SplitToConfirmTimings implements SplitAnimationTimings { + public int getPlaceholderFadeInStart() { return 0; } + public int getPlaceholderFadeInEnd() { return 133; } + public int getPlaceholderIconFadeInStart() { return 167; } + public int getPlaceholderIconFadeInEnd() { return 250; } + public int getStagedRectSlideStart() { return 0; } + public int getStagedRectSlideEnd() { return 383; } + + public int getDuration() { return TABLET_CONFIRM_DURATION; } + public Interpolator getStagedRectXInterpolator() { return LINEAR; } + public Interpolator getStagedRectYInterpolator() { return LINEAR; } + public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; } + public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; } +} diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 5212755ec7..1a026fc31d 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -15,6 +15,8 @@ */ package com.android.quickstep.util; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.states.RotationHelper.deltaRotation; import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE; @@ -22,9 +24,9 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition; +import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation; -import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; import android.animation.TimeInterpolator; import android.content.Context; @@ -40,7 +42,7 @@ import androidx.annotation.NonNull; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.BaseActivityInterface; @@ -100,8 +102,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { // Cached calculations private boolean mLayoutValid = false; private int mOrientationStateId; - private StagedSplitBounds mStagedSplitBounds; - private boolean mDrawsBelowRecents; + private SplitBounds mSplitBounds; + private Boolean mDrawsBelowRecents = null; private boolean mIsGridTask; private int mTaskRectTranslationX; private int mTaskRectTranslationY; @@ -152,13 +154,13 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } Rect fullTaskSize; - if (mStagedSplitBounds != null) { + if (mSplitBounds != null) { // The task rect changes according to the staged split task sizes, but recents // fullscreen scale and pivot remains the same since the task fits into the existing // sized task space bounds fullTaskSize = new Rect(mTaskRect); mOrientationState.getOrientationHandler() - .setSplitTaskSwipeRect(mDp, mTaskRect, mStagedSplitBounds, mStagePosition); + .setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition); mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); } else { fullTaskSize = mTaskRect; @@ -180,10 +182,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { * * @param splitInfo set to {@code null} when not in staged split mode */ - public void setPreview(RemoteAnimationTargetCompat runningTarget, StagedSplitBounds splitInfo) { + public void setPreview(RemoteAnimationTargetCompat runningTarget, SplitBounds splitInfo) { setPreview(runningTarget); - mStagedSplitBounds = splitInfo; - if (mStagedSplitBounds == null) { + mSplitBounds = splitInfo; + if (mSplitBounds == null) { mStagePosition = STAGE_POSITION_UNDEFINED; return; } @@ -390,10 +392,16 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { .withWindowCrop(mTmpCropRect) .withCornerRadius(getCurrentCornerRadius()); - if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) { - // When relativeLayer = 0, it reverts the surfaces back to the original order. - builder.withRelativeLayerTo(params.getRecentsSurface(), - mDrawsBelowRecents ? Integer.MIN_VALUE : 0); + // If mDrawsBelowRecents is unset, no reordering will be enforced. + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mDrawsBelowRecents != null) { + // In legacy transitions, the animation leashes remain in same hierarchy in the + // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will + // conflict with layers that WM core positions (ie. the input consumers). For shell + // transitions, the animation leashes are reparented to an animation container so we + // can bump layers as needed. + builder.withLayer(mDrawsBelowRecents + ? Integer.MIN_VALUE + 1 + : ENABLE_SHELL_TRANSITIONS ? Integer.MAX_VALUE : 0); } } diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java new file mode 100644 index 0000000000..66bff730bf --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import android.os.UserHandle; + +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.ThumbnailData; + +/** + * Listener for receiving various task properties changes + */ +public interface TaskVisualsChangeListener { + + /** + * Called when the task thumbnail changes + */ + default Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { + return null; + } + + /** + * Called when the icon for a task changes + */ + default void onTaskIconChanged(String pkg, UserHandle user) {} + + /** + * Called when the icon for a task changes + */ + default void onTaskIconChanged(int taskId) {} +} diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java index 75d6001afd..a7f25d40ef 100644 --- a/quickstep/src/com/android/quickstep/util/TransformParams.java +++ b/quickstep/src/com/android/quickstep/util/TransformParams.java @@ -139,10 +139,12 @@ public class TransformParams { public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) { RemoteAnimationTargets targets = mTargetSet; - SurfaceParams[] surfaceParams = new SurfaceParams[targets.unfilteredApps.length]; + final int appLength = targets.unfilteredApps.length; + final int wallpaperLength = targets.wallpapers != null ? targets.wallpapers.length : 0; + SurfaceParams[] surfaceParams = new SurfaceParams[appLength + wallpaperLength]; mRecentsSurface = getRecentsSurface(targets); - for (int i = 0; i < targets.unfilteredApps.length; i++) { + for (int i = 0; i < appLength; i++) { RemoteAnimationTargetCompat app = targets.unfilteredApps[i]; SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash); @@ -166,6 +168,12 @@ public class TransformParams { } surfaceParams[i] = builder.build(); } + // always put wallpaper layer to bottom. + for (int i = 0; i < wallpaperLength; i++) { + RemoteAnimationTargetCompat wallpaper = targets.wallpapers[i]; + surfaceParams[appLength + i] = new SurfaceParams.Builder(wallpaper.leash) + .withLayer(Integer.MIN_VALUE).build(); + } return surfaceParams; } diff --git a/quickstep/src/com/android/quickstep/util/ViewCapture.java b/quickstep/src/com/android/quickstep/util/ViewCapture.java new file mode 100644 index 0000000000..cfcfce0b6a --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/ViewCapture.java @@ -0,0 +1,527 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.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.launcher3.util.Executors.createAndStartNewLooper; + +import static java.util.stream.Collectors.toList; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.Trace; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Base64OutputStream; +import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewGroup; +import android.view.ViewTreeObserver.OnDrawListener; +import android.view.Window; + +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; + +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.LooperExecutor; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.SafeCloseable; +import com.android.launcher3.view.ViewCaptureData.ExportedData; +import com.android.launcher3.view.ViewCaptureData.FrameData; +import com.android.launcher3.view.ViewCaptureData.ViewNode; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.zip.GZIPOutputStream; + +/** + * Utility class for capturing view data every frame + */ +public class ViewCapture { + + private static final String TAG = "ViewCapture"; + + // These flags are copies of two private flags in the View class. + private static final int PFLAG_INVALIDATED = 0x80000000; + private static final int PFLAG_DIRTY_MASK = 0x00200000; + + // Number of frames to keep in memory + private static final int MEMORY_SIZE = 2000; + // Initial size of the reference pool. This is at least be 5 * total number of views in + // Launcher. This allows the first free frames avoid object allocation during view capture. + private static final int INIT_POOL_SIZE = 300; + + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(ViewCapture::new); + + private final List mListeners = new ArrayList<>(); + + private final Context mContext; + private final LooperExecutor mExecutor; + + // Pool used for capturing view tree on the UI thread. + private ViewRef mPool = new ViewRef(); + + private ViewCapture(Context context) { + mContext = context; + if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) { + Looper looper = createAndStartNewLooper("ViewCapture", + Process.THREAD_PRIORITY_FOREGROUND); + mExecutor = new LooperExecutor(looper); + mExecutor.execute(this::initPool); + } else { + mExecutor = UI_HELPER_EXECUTOR; + } + } + + @UiThread + private void addToPool(ViewRef start, ViewRef end) { + end.next = mPool; + mPool = start; + } + + @WorkerThread + private void initPool() { + ViewRef start = new ViewRef(); + ViewRef current = start; + + for (int i = 0; i < INIT_POOL_SIZE; i++) { + current.next = new ViewRef(); + current = current.next; + } + + ViewRef finalCurrent = current; + MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent)); + } + + /** + * Attaches the ViewCapture to the provided window and returns a handle to detach the listener + */ + public SafeCloseable startCapture(Window window) { + String title = window.getAttributes().getTitle().toString(); + String name = TextUtils.isEmpty(title) ? window.toString() : title; + return startCapture(window.getDecorView(), name); + } + + /** + * Attaches the ViewCapture to the provided window and returns a handle to detach the listener + */ + public SafeCloseable startCapture(View view, String name) { + if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) { + return () -> { }; + } + + WindowListener listener = new WindowListener(view, name); + mExecutor.execute(() -> MAIN_EXECUTOR.execute(listener::attachToRoot)); + mListeners.add(listener); + return () -> { + mListeners.remove(listener); + listener.destroy(); + }; + } + + /** + * Dumps all the active view captures + */ + public void dump(PrintWriter writer, FileDescriptor out) { + if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) { + return; + } + ViewIdProvider idProvider = new ViewIdProvider(mContext.getResources()); + + // Collect all the tasks first so that all the tasks are posted on the executor + List>> tasks = mListeners.stream() + .map(l -> Pair.create(l.name, mExecutor.submit(() -> l.dumpToProto(idProvider)))) + .collect(toList()); + + tasks.forEach(pair -> { + writer.println(); + writer.println(" ContinuousViewCapture:"); + writer.println(" window " + pair.first + ":"); + writer.println(" pkg:" + mContext.getPackageName()); + writer.print(" data:"); + writer.flush(); + try (OutputStream os = new FileOutputStream(out)) { + ExportedData data = pair.second.get(); + OutputStream encodedOS = new GZIPOutputStream(new Base64OutputStream(os, + Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP)); + data.writeTo(encodedOS); + encodedOS.close(); + os.flush(); + } catch (Exception e) { + Log.e(TAG, "Error capturing proto", e); + } + writer.println(); + writer.println("--end--"); + }); + } + + private class WindowListener implements OnDrawListener { + + private final View mRoot; + public final String name; + + private final Handler mHandler; + private final ViewRef mViewRef = new ViewRef(); + + private int mFrameIndexBg = -1; + private boolean mIsFirstFrame = true; + private final long[] mFrameTimesBg = new long[MEMORY_SIZE]; + private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE]; + + private boolean mDestroyed = false; + + WindowListener(View view, String name) { + mRoot = view; + this.name = name; + mHandler = new Handler(mExecutor.getLooper(), this::captureViewPropertiesBg); + } + + @Override + public void onDraw() { + Trace.beginSection("view_capture"); + captureViewTree(mRoot, mViewRef); + Message m = Message.obtain(mHandler); + m.obj = mViewRef.next; + mHandler.sendMessage(m); + mIsFirstFrame = false; + Trace.endSection(); + } + + /** + * Captures the View property on the background thread, and transfer all the ViewRef objects + * back to the pool + */ + @WorkerThread + private boolean captureViewPropertiesBg(Message msg) { + ViewRef viewRefStart = (ViewRef) msg.obj; + long time = msg.getWhen(); + if (viewRefStart == null) { + return false; + } + mFrameIndexBg++; + if (mFrameIndexBg >= MEMORY_SIZE) { + mFrameIndexBg = 0; + } + mFrameTimesBg[mFrameIndexBg] = time; + + ViewPropertyRef recycle = mNodesBg[mFrameIndexBg]; + + ViewPropertyRef resultStart = null; + ViewPropertyRef resultEnd = null; + + ViewRef viewRefEnd = viewRefStart; + while (viewRefEnd != null) { + ViewPropertyRef propertyRef = recycle; + if (propertyRef == null) { + propertyRef = new ViewPropertyRef(); + } else { + recycle = recycle.next; + propertyRef.next = null; + } + + ViewPropertyRef copy = null; + if (viewRefEnd.childCount < 0) { + copy = findInLastFrame(viewRefEnd.view.hashCode()); + viewRefEnd.childCount = (copy != null) ? copy.childCount : 0; + } + viewRefEnd.transferTo(propertyRef); + + if (resultStart == null) { + resultStart = propertyRef; + resultEnd = resultStart; + } else { + resultEnd.next = propertyRef; + resultEnd = resultEnd.next; + } + + if (copy != null) { + int pending = copy.childCount; + while (pending > 0) { + copy = copy.next; + pending = pending - 1 + copy.childCount; + + propertyRef = recycle; + if (propertyRef == null) { + propertyRef = new ViewPropertyRef(); + } else { + recycle = recycle.next; + propertyRef.next = null; + } + + copy.transferTo(propertyRef); + + resultEnd.next = propertyRef; + resultEnd = resultEnd.next; + } + } + + if (viewRefEnd.next == null) { + // The compiler will complain about using a non-final variable from + // an outer class in a lambda if we pass in viewRefEnd directly. + final ViewRef finalViewRefEnd = viewRefEnd; + MAIN_EXECUTOR.execute(() -> addToPool(viewRefStart, finalViewRefEnd)); + break; + } + viewRefEnd = viewRefEnd.next; + } + mNodesBg[mFrameIndexBg] = resultStart; + return true; + } + + private ViewPropertyRef findInLastFrame(int hashCode) { + int lastFrameIndex = (mFrameIndexBg == 0) ? MEMORY_SIZE - 1 : mFrameIndexBg - 1; + ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex]; + while (viewPropertyRef != null && viewPropertyRef.hashCode != hashCode) { + viewPropertyRef = viewPropertyRef.next; + } + return viewPropertyRef; + } + + void attachToRoot() { + if (mRoot.isAttachedToWindow()) { + mRoot.getViewTreeObserver().addOnDrawListener(this); + } else { + mRoot.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + if (!mDestroyed) { + mRoot.getViewTreeObserver().addOnDrawListener(WindowListener.this); + } + mRoot.removeOnAttachStateChangeListener(this); + } + + @Override + public void onViewDetachedFromWindow(View v) { } + }); + } + } + + void destroy() { + mRoot.getViewTreeObserver().removeOnDrawListener(this); + mDestroyed = true; + } + + @WorkerThread + private ExportedData dumpToProto(ViewIdProvider idProvider) { + ExportedData.Builder dataBuilder = ExportedData.newBuilder(); + ArrayList classList = new ArrayList<>(); + + int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE; + for (int i = size - 1; i >= 0; i--) { + int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE; + ViewNode.Builder nodeBuilder = ViewNode.newBuilder(); + mNodesBg[index].toProto(idProvider, classList, nodeBuilder); + dataBuilder.addFrameData(FrameData.newBuilder() + .setNode(nodeBuilder) + .setTimestamp(mFrameTimesBg[index])); + } + return dataBuilder + .addAllClassname(classList.stream().map(Class::getName).collect(toList())) + .build(); + } + + private ViewRef captureViewTree(View view, ViewRef start) { + ViewRef ref; + if (mPool != null) { + ref = mPool; + mPool = mPool.next; + ref.next = null; + } else { + ref = new ViewRef(); + } + ref.view = view; + start.next = ref; + if (view instanceof ViewGroup) { + ViewGroup parent = (ViewGroup) view; + // If a view has not changed since the last frame, we will copy + // its children from the last processed frame's data. + if ((view.mPrivateFlags & (PFLAG_INVALIDATED | PFLAG_DIRTY_MASK)) == 0 + && !mIsFirstFrame) { + // A negative child count is the signal to copy this view from the last frame. + ref.childCount = -parent.getChildCount(); + return ref; + } + ViewRef result = ref; + int childCount = ref.childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + result = captureViewTree(parent.getChildAt(i), result); + } + return result; + } else { + ref.childCount = 0; + return ref; + } + } + } + + private static class ViewPropertyRef { + // We store reference in memory to avoid generating and storing too many strings + public Class clazz; + public int hashCode; + public int childCount = 0; + + public int id; + public int left, top, right, bottom; + public int scrollX, scrollY; + + public float translateX, translateY; + public float scaleX, scaleY; + public float alpha; + public float elevation; + + public int visibility; + public boolean willNotDraw; + public boolean clipChildren; + + public ViewPropertyRef next; + + public void transferTo(ViewPropertyRef out) { + out.clazz = this.clazz; + out.hashCode = this.hashCode; + out.childCount = this.childCount; + out.id = this.id; + out.left = this.left; + out.top = this.top; + out.right = this.right; + out.bottom = this.bottom; + out.scrollX = this.scrollX; + out.scrollY = this.scrollY; + out.scaleX = this.scaleX; + out.scaleY = this.scaleY; + out.translateX = this.translateX; + out.translateY = this.translateY; + out.alpha = this.alpha; + out.visibility = this.visibility; + out.willNotDraw = this.willNotDraw; + out.clipChildren = this.clipChildren; + out.next = this.next; + out.elevation = this.elevation; + } + + /** + * Converts the data to the proto representation and returns the next property ref + * at the end of the iteration. + * @return + */ + public ViewPropertyRef toProto(ViewIdProvider idProvider, ArrayList classList, + ViewNode.Builder outBuilder) { + int classnameIndex = classList.indexOf(clazz); + if (classnameIndex < 0) { + classnameIndex = classList.size(); + classList.add(clazz); + } + outBuilder + .setClassnameIndex(classnameIndex) + .setHashcode(hashCode) + .setId(idProvider.getName(id)) + .setLeft(left) + .setTop(top) + .setWidth(right - left) + .setHeight(bottom - top) + .setTranslationX(translateX) + .setTranslationY(translateY) + .setScaleX(scaleX) + .setScaleY(scaleY) + .setAlpha(alpha) + .setVisibility(visibility) + .setWillNotDraw(willNotDraw) + .setElevation(elevation) + .setClipChildren(clipChildren); + + ViewPropertyRef result = next; + for (int i = 0; (i < childCount) && (result != null); i++) { + ViewNode.Builder childBuilder = ViewNode.newBuilder(); + result = result.toProto(idProvider, classList, childBuilder); + outBuilder.addChildren(childBuilder); + } + return result; + } + } + + private static class ViewRef { + public View view; + public int childCount = 0; + public ViewRef next; + + public void transferTo(ViewPropertyRef out) { + out.childCount = this.childCount; + + View view = this.view; + this.view = null; + + out.clazz = view.getClass(); + out.hashCode = view.hashCode(); + out.id = view.getId(); + out.left = view.getLeft(); + out.top = view.getTop(); + out.right = view.getRight(); + out.bottom = view.getBottom(); + out.scrollX = view.getScrollX(); + out.scrollY = view.getScrollY(); + + out.translateX = view.getTranslationX(); + out.translateY = view.getTranslationY(); + out.scaleX = view.getScaleX(); + out.scaleY = view.getScaleY(); + out.alpha = view.getAlpha(); + out.elevation = view.getElevation(); + + out.visibility = view.getVisibility(); + out.willNotDraw = view.willNotDraw(); + } + } + + private static final class ViewIdProvider { + + private final SparseArray mNames = new SparseArray<>(); + private final Resources mRes; + + ViewIdProvider(Resources res) { + mRes = res; + } + + String getName(int id) { + String name = mNames.get(id); + if (name == null) { + if (id >= 0) { + try { + name = mRes.getResourceTypeName(id) + '/' + mRes.getResourceEntryName(id); + } catch (Resources.NotFoundException e) { + name = "id/" + "0x" + Integer.toHexString(id).toUpperCase(); + } + } else { + name = "NO_ID"; + } + mNames.put(id, name); + } + return name; + } + } +} diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java index 5eb543e40d..34fa7f1764 100644 --- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java +++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java @@ -32,7 +32,6 @@ import android.animation.ObjectAnimator; import android.util.FloatProperty; import android.view.View; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.Hotseat; import com.android.launcher3.Launcher; import com.android.launcher3.R; @@ -41,6 +40,7 @@ import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DynamicResource; import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.ResourceProvider; @@ -84,9 +84,9 @@ public class WorkspaceRevealAnim { } // Add depth controller animation. - if (launcher instanceof BaseQuickstepLauncher) { + if (launcher instanceof QuickstepLauncher) { PendingAnimation depthBuilder = new PendingAnimation(DURATION_MS); - DepthController depth = ((BaseQuickstepLauncher) launcher).getDepthController(); + DepthController depth = ((QuickstepLauncher) launcher).getDepthController(); depth.setStateWithAnimation(NORMAL, new StateAnimationConfig(), depthBuilder); mAnimators.play(depthBuilder.buildAnim()); } diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java index 79b15c713a..96504afcd4 100644 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -53,7 +53,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; import com.android.systemui.shared.recents.model.Task; import java.lang.annotation.Retention; @@ -103,7 +103,7 @@ public final class DigitalWellBeingToast { */ private float mModalOffset = 0f; @Nullable - private StagedSplitBounds mStagedSplitBounds; + private SplitBounds mSplitBounds; private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN; private float mSplitOffsetTranslationY; private float mSplitOffsetTranslationX; @@ -164,9 +164,9 @@ public final class DigitalWellBeingToast { }); } - public void setSplitConfiguration(StagedSplitBounds stagedSplitBounds) { - mStagedSplitBounds = stagedSplitBounds; - if (mStagedSplitBounds == null + public void setSplitConfiguration(SplitBounds splitBounds) { + mSplitBounds = splitBounds; + if (mSplitBounds == null || !mActivity.getDeviceProfile().isTablet || mTaskView.isFocusedTask()) { mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN; @@ -180,11 +180,11 @@ public final class DigitalWellBeingToast { } // For landscape grid, for 30% width we only show icon, otherwise show icon and time - if (mTask.key.id == mStagedSplitBounds.leftTopTaskId) { - mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ? + if (mTask.key.id == mSplitBounds.leftTopTaskId) { + mSplitBannerConfig = mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE; } else { - mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ? + mSplitBannerConfig = mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE; } } @@ -321,7 +321,7 @@ public final class DigitalWellBeingToast { PagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler(); Pair translations = orientationHandler .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(), - mTaskView.getMeasuredHeight(), mStagedSplitBounds, deviceProfile, + mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile, mTaskView.getThumbnails(), mTask.key.id, mBanner); mSplitOffsetTranslationX = translations.first; mSplitOffsetTranslationY = translations.second; @@ -385,4 +385,12 @@ public final class DigitalWellBeingToast { mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint); mBanner.setLayerPaint(layerPaint); } + + void setBannerVisibility(int visibility) { + if (mBanner == null) { + return; + } + + mBanner.setVisibility(visibility); + } } diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java index c980d1eac2..dc1ae520a3 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java @@ -1,8 +1,8 @@ package com.android.quickstep.views; -import static com.android.launcher3.anim.Interpolators.ACCEL; -import static com.android.launcher3.anim.Interpolators.DEACCEL_3; +import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.clampToProgress; import android.animation.ValueAnimator; import android.content.Context; @@ -13,15 +13,16 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.FloatProperty; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.Nullable; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseActivity; import com.android.launcher3.InsettableFrameLayout; -import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; @@ -29,7 +30,9 @@ import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.views.BaseDragLayer; +import com.android.quickstep.util.AnimUtils; import com.android.quickstep.util.MultiValueUpdateListener; +import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.util.TaskCornerRadius; import com.android.systemui.shared.system.QuickStepContract; @@ -39,7 +42,8 @@ import com.android.systemui.shared.system.QuickStepContract; * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself. * * Can then animate the taskview using - * {@link #addAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} + * {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} or + * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} * giving a starting and ending bounds. Currently this is set to use the split placeholder view, * but it could be generified. * @@ -47,6 +51,29 @@ import com.android.systemui.shared.system.QuickStepContract; */ public class FloatingTaskView extends FrameLayout { + public static final FloatProperty PRIMARY_TRANSLATE_OFFSCREEN = + new FloatProperty("floatingTaskPrimaryTranslateOffscreen") { + @Override + public void setValue(FloatingTaskView view, float translation) { + ((RecentsView) view.mActivity.getOverviewPanel()).getPagedOrientationHandler() + .setFloatingTaskPrimaryTranslation( + view, + translation, + view.mActivity.getDeviceProfile() + ); + } + + @Override + public Float get(FloatingTaskView view) { + return ((RecentsView) view.mActivity.getOverviewPanel()) + .getPagedOrientationHandler() + .getFloatingTaskPrimaryTranslation( + view, + view.mActivity.getDeviceProfile() + ); + } + }; + private FloatingTaskThumbnailView mThumbnailView; private SplitPlaceholderView mSplitPlaceholderView; private RectF mStartingPosition; @@ -98,7 +125,7 @@ public class FloatingTaskView extends FrameLayout { RecentsView recentsView = launcher.getOverviewPanel(); mOrientationHandler = recentsView.getPagedOrientationHandler(); - mStagePosition = recentsView.getSplitPlaceholder().getActiveSplitStagePosition(); + mStagePosition = recentsView.getSplitSelectController().getActiveSplitStagePosition(); mSplitPlaceholderView.setIcon(icon, mContext.getResources().getDimensionPixelSize(R.dimen.split_placeholder_icon_size)); mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated()); @@ -110,13 +137,19 @@ public class FloatingTaskView extends FrameLayout { */ public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher, View originalView, @Nullable Bitmap thumbnail, Drawable icon, RectF positionOut) { - final BaseDragLayer dragLayer = launcher.getDragLayer(); - ViewGroup parent = (ViewGroup) dragLayer.getParent(); + final ViewGroup dragLayer = launcher.getDragLayer(); final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater() - .inflate(R.layout.floating_split_select_view, parent, false); + .inflate(R.layout.floating_split_select_view, dragLayer, false); floatingView.init(launcher, originalView, thumbnail, icon, positionOut); - parent.addView(floatingView); + // Add this animating view underneath the existing open task menu view (if there is one) + View openTaskView = AbstractFloatingView.getOpenView(launcher, TYPE_TASK_MENU); + int openTaskViewIndex = dragLayer.indexOfChild(openTaskView); + if (openTaskViewIndex == -1) { + // Add to top if not + openTaskViewIndex = dragLayer.getChildCount(); + } + dragLayer.addView(floatingView, openTaskViewIndex - 1); return floatingView; } @@ -125,7 +158,7 @@ public class FloatingTaskView extends FrameLayout { Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), originalView, viewBounds, false /* ignoreTransform */, null /* recycle */, mStartingPosition); - final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams( + final BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams( Math.round(mStartingPosition.width()), Math.round(mStartingPosition.height())); initPosition(mStartingPosition, lp); @@ -177,8 +210,50 @@ public class FloatingTaskView extends FrameLayout { layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); } - public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds, - boolean fadeWithThumbnail, boolean isStagedTask) { + /** + * Animates a FloatingTaskThumbnailView and its overlapping SplitPlaceholderView when a split + * is staged. + */ + public void addStagingAnimation(PendingAnimation animation, RectF startingBounds, + Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) { + boolean isTablet = mActivity.getDeviceProfile().isTablet; + boolean splittingFromOverview = fadeWithThumbnail; + SplitAnimationTimings timings; + + if (isTablet && splittingFromOverview) { + timings = SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT; + } else if (!isTablet && splittingFromOverview) { + timings = SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT; + } else { + // Splitting from Home is currently only available on tablets + timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT; + } + + addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask, + timings); + } + + /** + * Animates the FloatingTaskThumbnailView and SplitPlaceholderView for the two thumbnails + * when a split is confirmed. + */ + public void addConfirmAnimation(PendingAnimation animation, RectF startingBounds, + Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) { + SplitAnimationTimings timings = + AnimUtils.getDeviceSplitToConfirmTimings(mActivity.getDeviceProfile().isTablet); + + addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask, + timings); + } + + /** + * Sets up and builds a split staging animation. + * Called by {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} and + * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}. + */ + public void addAnimation(PendingAnimation animation, RectF startingBounds, + Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask, + SplitAnimationTimings timings) { mFullscreenParams.setIsStagedTask(isStagedTask); final BaseDragLayer dragLayer = mActivity.getDragLayer(); int[] dragLayerBounds = new int[2]; @@ -192,26 +267,47 @@ public class FloatingTaskView extends FrameLayout { RectF floatingTaskViewBounds = new RectF(); if (fadeWithThumbnail) { - animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT, - 0, 1, ACCEL); - animation.addFloat(mThumbnailView, LauncherAnimUtils.VIEW_ALPHA, - 1, 0, DEACCEL_3); + // This code block runs for the placeholder view during Overview > OverviewSplitSelect + // and for the selected (secondary) thumbnail during OverviewSplitSelect > Confirmed + + // FloatingTaskThumbnailView: thumbnail fades out to transparent + animation.setViewAlpha(mThumbnailView, 0, clampToProgress(LINEAR, + timings.getPlaceholderFadeInStartOffset(), + timings.getPlaceholderFadeInEndOffset())); + + // SplitPlaceholderView: gray background fades in at same time, then new icon fades in + fadeInSplitPlaceholder(animation, timings); } else if (isStagedTask) { - // Fade in the placeholder view when split is initiated from homescreen / all apps - // icons. + // This code block runs for the placeholder view during Normal > OverviewSplitSelect + // and for the placeholder (primary) thumbnail during OverviewSplitSelect > Confirmed + + // Fade in the placeholder view during Normal > OverviewSplitSelect if (mSplitPlaceholderView.getAlpha() == 0) { - animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT, - 0.3f, 1, ACCEL); + mSplitPlaceholderView.getIconView().setAlpha(0); + fadeInSplitPlaceholder(animation, timings); } + + // No-op for placeholder during OverviewSplitSelect > Confirmed, alpha should be set } MultiValueUpdateListener listener = new MultiValueUpdateListener() { - final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR); - final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR); + // SplitPlaceholderView: rectangle translates and stretches to new position + final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, + clampToProgress(timings.getStagedRectXInterpolator(), + timings.getStagedRectSlideStartOffset(), + timings.getStagedRectSlideEndOffset())); + final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, + clampToProgress(timings.getStagedRectYInterpolator(), + timings.getStagedRectSlideStartOffset(), + timings.getStagedRectSlideEndOffset())); final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0, - animDuration, LINEAR); + animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(), + timings.getStagedRectSlideStartOffset(), + timings.getStagedRectSlideEndOffset())); final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0, - animDuration, LINEAR); + animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(), + timings.getStagedRectSlideStartOffset(), + timings.getStagedRectSlideEndOffset())); @Override public void onUpdate(float percent, boolean initOnly) { // Calculate the icon position. @@ -223,9 +319,19 @@ public class FloatingTaskView extends FrameLayout { update(floatingTaskViewBounds, percent); } }; + transitionAnimator.addUpdateListener(listener); } + void fadeInSplitPlaceholder(PendingAnimation animation, SplitAnimationTimings timings) { + animation.setViewAlpha(mSplitPlaceholderView, 1, clampToProgress(LINEAR, + timings.getPlaceholderFadeInStartOffset(), + timings.getPlaceholderFadeInEndOffset())); + animation.setViewAlpha(mSplitPlaceholderView.getIconView(), 1, clampToProgress(LINEAR, + timings.getPlaceholderIconFadeInStartOffset(), + timings.getPlaceholderIconFadeInEndOffset())); + } + void drawRoundedRect(Canvas canvas, Paint paint) { if (mFullscreenParams == null) { return; @@ -246,12 +352,16 @@ public class FloatingTaskView extends FrameLayout { * offscreen). */ void centerIconView(IconView iconView, float onScreenRectCenterX, float onScreenRectCenterY) { - mOrientationHandler.updateStagedSplitIconParams(iconView, onScreenRectCenterX, + mOrientationHandler.updateSplitIconParams(iconView, onScreenRectCenterX, onScreenRectCenterY, mFullscreenParams.mScaleX, mFullscreenParams.mScaleY, iconView.getDrawableWidth(), iconView.getDrawableHeight(), mActivity.getDeviceProfile(), mStagePosition); } + public int getStagePosition() { + return mStagePosition; + } + private static class SplitOverlayProperties { private final float finalTaskViewScaleX; diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index 244a794562..3a5f606467 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -1,5 +1,6 @@ package com.android.quickstep.views; +import static com.android.launcher3.AbstractFloatingView.getAnyView; import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; @@ -13,11 +14,12 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.RunnableList; -import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; import com.android.launcher3.util.TransformingTouchDelegate; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskIconCache; @@ -53,7 +55,7 @@ public class GroupedTaskView extends TaskView { private CancellableTask mIconLoadRequest2; private final float[] mIcon2CenterCoords = new float[2]; private TransformingTouchDelegate mIcon2TouchDelegate; - @Nullable private StagedSplitBounds mSplitBoundsConfig; + @Nullable private SplitBounds mSplitBoundsConfig; private final DigitalWellBeingToast mDigitalWellBeingToast2; public GroupedTaskView(Context context) { @@ -78,7 +80,7 @@ public class GroupedTaskView extends TaskView { } public void bind(Task primary, Task secondary, RecentsOrientedState orientedState, - @Nullable StagedSplitBounds splitBoundsConfig) { + @Nullable SplitBounds splitBoundsConfig) { super.bind(primary, orientedState); mSecondaryTask = secondary; mTaskIdContainer[1] = secondary.key.id; @@ -126,8 +128,8 @@ public class GroupedTaskView extends TaskView { } } - public void updateSplitBoundsConfig(StagedSplitBounds stagedSplitBounds) { - mSplitBoundsConfig = stagedSplitBounds; + public void updateSplitBoundsConfig(SplitBounds splitBounds) { + mSplitBoundsConfig = splitBounds; invalidate(); } @@ -162,6 +164,21 @@ public class GroupedTaskView extends TaskView { } } + @Override + protected boolean showTaskMenuWithContainer(IconView iconView) { + boolean showedTaskMenu = super.showTaskMenuWithContainer(iconView); + if (iconView == mIconView2 && showedTaskMenu && !mActivity.getDeviceProfile().isTablet) { + // Adjust the position of the secondary task's menu view (only on phones) + TaskMenuView taskMenuView = getAnyView(mActivity, AbstractFloatingView.TYPE_TASK_MENU); + DeviceProfile deviceProfile = mActivity.getDeviceProfile(); + getRecentsView().getPagedOrientationHandler() + .setSecondaryTaskMenuPosition(mSplitBoundsConfig, this, + deviceProfile, mTaskIdAttributeContainer[0].getThumbnailView(), + taskMenuView); + } + return showedTaskMenu; + } + @Nullable @Override public RunnableList launchTaskAnimated() { @@ -174,7 +191,7 @@ public class GroupedTaskView extends TaskView { // Callbacks run from remote animation when recents animation not currently running InteractionJankMonitorWrapper.begin(this, InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView"); - recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/, + recentsView.getSplitSelectController().launchTasks(this /*groupedTaskView*/, success -> { endCallback.executeAllAndDestroy(); InteractionJankMonitorWrapper.end( @@ -189,7 +206,7 @@ public class GroupedTaskView extends TaskView { @Override public void launchTask(@NonNull Consumer callback, boolean freezeTaskList) { - getRecentsView().getSplitPlaceholder().launchTasks(mTask.key.id, mSecondaryTask.key.id, + getRecentsView().getSplitSelectController().launchTasks(mTask.key.id, mSecondaryTask.key.id, STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, getSplitRatio()); } @@ -213,11 +230,12 @@ public class GroupedTaskView extends TaskView { } @Override - protected int getChildTaskIndexAtPosition(PointF position) { - if (isCoordInView(mIconView2, position) || isCoordInView(mSnapshotView2, position)) { + protected int getLastSelectedChildTaskIndex() { + if (isCoordInView(mIconView2, mLastTouchDownPosition) + || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) { return 1; } - return super.getChildTaskIndexAtPosition(position); + return super.getLastSelectedChildTaskIndex(); } private boolean isCoordInView(View v, PointF position) { @@ -250,8 +268,7 @@ public class GroupedTaskView extends TaskView { @Override public void setOverlayEnabled(boolean overlayEnabled) { - super.setOverlayEnabled(overlayEnabled); - mSnapshotView2.setOverlayEnabled(overlayEnabled); + // Intentional no-op to prevent setting smart actions overlay on thumbnails } @Override @@ -313,4 +330,33 @@ public class GroupedTaskView extends TaskView { mSnapshotView2.setDimAlpha(amount); mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount); } + + @Override + protected void applyThumbnailSplashAlpha() { + super.applyThumbnailSplashAlpha(); + mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha); + } + + /** + * Sets visibility for thumbnails and associated elements (DWB banners). + * IconView is unaffected. + * + * When setting INVISIBLE, sets the visibility for the last selected child task. + * When setting VISIBLE (as a reset), sets the visibility for both tasks. + */ + @Override + void setThumbnailVisibility(int visibility) { + if (visibility == VISIBLE) { + mSnapshotView.setVisibility(visibility); + mDigitalWellBeingToast.setBannerVisibility(visibility); + mSnapshotView2.setVisibility(visibility); + mDigitalWellBeingToast2.setBannerVisibility(visibility); + } else if (getLastSelectedChildTaskIndex() == 0) { + mSnapshotView.setVisibility(visibility); + mDigitalWellBeingToast.setBannerVisibility(visibility); + } else { + mSnapshotView2.setVisibility(visibility); + mDigitalWellBeingToast2.setBannerVisibility(visibility); + } + } } diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index 306ebd73c8..bb8506d26f 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -21,24 +21,24 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; import static com.android.launcher3.LauncherState.SPRING_LOADED; -import static com.android.launcher3.testing.TestProtocol.BAD_STATE; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import android.view.Surface; import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; +import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.popup.QuickstepSystemShortcut; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.util.PendingSplitSelectInfo; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.util.SplitSelectStateController; @@ -47,7 +47,7 @@ import com.android.quickstep.util.SplitSelectStateController; * {@link RecentsView} used in Launcher activity */ @TargetApi(Build.VERSION_CODES.O) -public class LauncherRecentsView extends RecentsView +public class LauncherRecentsView extends RecentsView implements StateListener { public LauncherRecentsView(Context context) { @@ -67,7 +67,6 @@ public class LauncherRecentsView extends RecentsView extends FrameLayo return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA); } + public AlphaProperty getShareTargetAlpha() { + return mMultiValueAlpha.getProperty(INDEX_SHARE_TARGET_ALPHA); + } + /** * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar. */ @@ -202,10 +230,13 @@ public class OverviewActionsView extends FrameLayo if (mDp == null) { return; } - boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent && !mDp.isGestureMode; - if (alignFor3ButtonTaskbar) { + boolean largeScreenLandscape = mDp.isTablet && !mDp.isTwoPanels && mDp.isLandscape; + // If in 3-button mode, shift action buttons to accommodate 3-button layout. + // (Special exception for landscape tablets, where there is enough room and we don't need to + // shift the action buttons.) + if (mDp.areNavButtonsInline && !largeScreenLandscape) { // Add extra horizontal spacing - int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext()); + int additionalPadding = mDp.hotseatBarEndOffset; if (isLayoutRtl()) { setPadding(mInsets.left + additionalPadding, 0, mInsets.right, 0); } else { @@ -233,10 +264,6 @@ public class OverviewActionsView extends FrameLayo return 0; } - if (mDp.isVerticalBarLayout()) { - return mDp.getInsets().bottom; - } - if (!mDp.isGestureMode && mDp.isTaskbarPresent) { return mDp.getOverviewActionsClaimedSpaceBelow(); } @@ -254,12 +281,6 @@ public class OverviewActionsView extends FrameLayo mTaskSize.set(taskSize); updateVerticalMargin(DisplayController.getNavigationMode(getContext())); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - dp.isVerticalBarLayout() ? 0 : dp.overviewActionsButtonSpacing, - ViewGroup.LayoutParams.MATCH_PARENT); - params.weight = dp.isVerticalBarLayout() ? 1 : 0; - findViewById(R.id.action_split_space).setLayoutParams(params); - requestLayout(); mSplitButton.setCompoundDrawablesWithIntrinsicBounds( @@ -267,12 +288,17 @@ public class OverviewActionsView extends FrameLayo 0, 0, 0); } - public void setSplitButtonVisible(boolean visible) { + /** + * Shows/hides the "Split" button based on the status of mHiddenFlags. + */ + public void updateSplitButtonVisibility() { if (mSplitButton == null) { return; } - - mSplitButton.setVisibility(visible ? VISIBLE : GONE); - findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE); + boolean shouldBeVisible = mSplitButtonDisabledFlags == 0 + // and neither of these flags are active + && (mHiddenFlags & (HIDDEN_SPLIT_SCREEN | HIDDEN_SPLIT_SELECT_ACTIVE)) == 0; + mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE); + findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE); } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 2360396cf7..7ad0e48ccb 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -34,15 +34,19 @@ import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_2; +import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75; import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; -import static com.android.launcher3.statehandlers.DepthController.DEPTH; +import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH; import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; @@ -51,10 +55,13 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA; +import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET; +import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SCREEN; +import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -128,6 +135,7 @@ import com.android.launcher3.anim.SpringProperty; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.cache.HandlerRunnable; +import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.popup.QuickstepSystemShortcut; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.BaseState; @@ -140,8 +148,8 @@ import com.android.launcher3.util.IntSet; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.ResourceBasedOverride.Overrides; import com.android.launcher3.util.RunnableList; +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; -import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; import com.android.launcher3.util.Themes; import com.android.launcher3.util.TranslateEdgeEffect; import com.android.launcher3.util.ViewPool; @@ -151,7 +159,6 @@ import com.android.quickstep.GestureState; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; -import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.RemoteTargetGluer; import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; @@ -162,13 +169,18 @@ import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.ViewUtils; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.AnimUtils; import com.android.quickstep.util.GroupTask; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; +import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.util.SplitScreenBounds; import com.android.quickstep.util.SplitSelectStateController; import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TaskViewSimulator; +import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.VibratorWrapper; import com.android.systemui.plugins.ResourceProvider; @@ -362,6 +374,10 @@ public abstract class RecentsView RECENTS_GRID_PROGRESS = new FloatProperty("recentsGrid") { @Override @@ -375,6 +391,23 @@ public abstract class RecentsView TASK_THUMBNAIL_SPLASH_ALPHA = + new FloatProperty("taskThumbnailSplashAlpha") { + @Override + public void setValue(RecentsView view, float taskThumbnailSplashAlpha) { + view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); + } + + @Override + public Float get(RecentsView view) { + return view.mTaskThumbnailSplashAlpha; + } + }; + // OverScroll constants private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; @@ -465,6 +498,7 @@ public abstract class RecentsView taskGroups) { if (mPendingAnimation != null) { mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups)); @@ -1445,11 +1489,11 @@ public abstract class RecentsView endState = mSizeStrategy.stateFromGestureEndTarget(endTarget); + if (endState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) { TaskView runningTaskView = getRunningTaskView(); float runningTaskPrimaryGridTranslation = 0; if (runningTaskView != null) { @@ -2209,6 +2255,13 @@ public abstract class RecentsView setCurrentPage(getRunningTaskIndex())); setRunningTaskViewShowScreenshot(false); setRunningTaskHidden(runningTaskTileHidden); // Update task size after setting current task. @@ -2658,6 +2711,18 @@ public abstract class RecentsView secondaryViewTranslate = - taskView.getSecondaryDissmissTranslationProperty(); + taskView.getSecondaryDismissTranslationProperty(); int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor(); @@ -2745,33 +2810,58 @@ public abstract class RecentsView { if (success) { - mSplitToast.show(); InteractionJankMonitorWrapper.end( InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER); } else { + // If transition to split select was interrupted, clean up to prevent glitches + resetFromSplitSelectionState(); InteractionJankMonitorWrapper.cancel( InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER); } @@ -2788,17 +2878,16 @@ public abstract class RecentsView= dismissed index and in the same row as the // dismissed index or next focused index. Offset successive task dismissal // durations for a staggered effect. - float animationStartProgress = Utilities.boundToRange( - INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET - * ++distanceFromDismissedTask, 0f, - dismissTranslationInterpolationEnd); + distanceFromDismissedTask++; + boolean isStagingFocusedTask = + isFocusedTaskDismissed && nextFocusedTaskView == null; + int staggerColumn = isStagingFocusedTask + ? (int) Math.ceil(distanceFromDismissedTask / 2f) + : distanceFromDismissedTask; + // Set timings based on if user is initiating splitscreen on the focused task, + // or splitting/dismissing some other task. + float animationStartProgress = isStagingFocusedTask + ? Utilities.boundToRange( + splitTimings.getGridSlideStartOffset() + + (splitTimings.getGridSlideStaggerOffset() + * staggerColumn), + 0f, + dismissTranslationInterpolationEnd) + : Utilities.boundToRange( + INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + * staggerColumn, 0f, dismissTranslationInterpolationEnd); + float animationEndProgress = isStagingFocusedTask + ? Utilities.boundToRange( + splitTimings.getGridSlideStartOffset() + + (splitTimings.getGridSlideStaggerOffset() * staggerColumn) + + splitTimings.getGridSlideDurationOffset(), + 0f, + dismissTranslationInterpolationEnd) + : dismissTranslationInterpolationEnd; + Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR; + if (taskView == nextFocusedTaskView) { // Enlarge the task to be focused next, and translate into focus position. float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width(); @@ -3062,7 +3199,7 @@ public abstract class RecentsView 1); + mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive()); + mActionsView.updateSplitButtonFlags(FLAG_IS_NOT_TABLET, + !mActivity.getDeviceProfile().isTablet); + mActionsView.updateSplitButtonFlags(FLAG_SINGLE_TASK, getTaskViewCount() <= 1); + mActionsView.updateSplitButtonVisibility(); } /** @@ -3485,8 +3623,10 @@ public abstract class RecentsView { mSplitSelectStateController.launchSplitTasks( aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()); InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER); }); - if (containerTaskView.containsMultipleTasks()) { - // If we are launching from a child task, then only hide the thumbnail itself - mSecondSplitHiddenView = thumbnailView; - } else { - mSecondSplitHiddenView = containerTaskView; - } - mSecondSplitHiddenView.setVisibility(INVISIBLE); + + mSecondSplitHiddenView = containerTaskView; + mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE); + InteractionJankMonitorWrapper.begin(this, InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected"); + + // Fade out all other views underneath placeholders + ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0); + pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT); pendingAnimation.buildAnim().start(); return true; } @@ -4099,19 +4260,20 @@ public abstract class RecentsView UNFOLD = + new FloatProperty("SplitInstructionsUnfold") { + @Override + public void setValue(SplitInstructionsView splitInstructionsView, float v) { + splitInstructionsView.setScaleY(v); + } + + @Override + public Float get(SplitInstructionsView splitInstructionsView) { + return splitInstructionsView.getScaleY(); + } + }; + + public SplitInstructionsView(Context context) { + this(context, null); + } + + public SplitInstructionsView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = (StatefulActivity) context; + } + + static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) { + ViewGroup dragLayer = launcher.getDragLayer(); + final SplitInstructionsView splitInstructionsView = + (SplitInstructionsView) launcher.getLayoutInflater().inflate( + R.layout.split_instructions_view, + dragLayer, + false + ); + + splitInstructionsView.mTextView = splitInstructionsView.findViewById( + R.id.split_instructions_text); + + // Since textview overlays base view, and we sometimes manipulate the alpha of each + // simultaneously, force overlapping rendering to false prevents redrawing of pixels, + // improving performance at the cost of some accuracy. + splitInstructionsView.forceHasOverlappingRendering(false); + + dragLayer.addView(splitInstructionsView); + return splitInstructionsView; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + ensureProperRotation(); + } + + void ensureProperRotation() { + ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler() + .setSplitInstructionsParams( + this, + mLauncher.getDeviceProfile(), + getMeasuredHeight(), + getMeasuredWidth(), + getThreeButtonNavShift() + ); + } + + // In some cases, when user is using 3-button nav, there isn't enough room for both the + // 3-button nav and a centered SplitInstructionsView. This function will return an int that will + // be used to shift the SplitInstructionsView over a bit so that everything looks well-spaced. + // In many cases, this will return 0, since we don't need to shift it away from the center. + int getThreeButtonNavShift() { + DeviceProfile dp = mLauncher.getDeviceProfile(); + if ((DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS) + && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))) { + int navButtonWidth = getResources().getDimensionPixelSize( + R.dimen.taskbar_nav_buttons_size); + int extraMargin = getResources().getDimensionPixelSize( + R.dimen.taskbar_contextual_button_margin); + // Explanation: The 3-button nav for non-phones sits on one side of the screen, taking + // up 3 buttons + a side margin worth of space. Our splitInstructionsView starts in the + // center of the screen and we want to center it in the remaining space, therefore we + // want to shift it over by half the 3-button layout's width. + // If the user is using an RtL layout, we shift it the opposite way. + return -((3 * navButtonWidth + extraMargin) / 2) * (isLayoutRtl() ? -1 : 1); + } else { + return 0; + } + } + + public AppCompatTextView getTextView() { + return mTextView; + } +} diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java index 28080d477f..08004dcf1f 100644 --- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java +++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java @@ -22,7 +22,6 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.FloatProperty; import android.util.TypedValue; import android.widget.FrameLayout; @@ -33,20 +32,6 @@ public class SplitPlaceholderView extends FrameLayout { private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Rect mTempRect = new Rect(); - public static final FloatProperty ALPHA_FLOAT = - new FloatProperty("SplitViewAlpha") { - @Override - public void setValue(SplitPlaceholderView splitPlaceholderView, float v) { - splitPlaceholderView.setVisibility(v != 0 ? VISIBLE : GONE); - splitPlaceholderView.setAlpha(v); - } - - @Override - public Float get(SplitPlaceholderView splitPlaceholderView) { - return splitPlaceholderView.getAlpha(); - } - }; - @Nullable private IconView mIconView; diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java index 3803f1b426..681574573e 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java @@ -16,9 +16,6 @@ package com.android.quickstep.views; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; -import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA; import android.animation.Animator; @@ -157,23 +154,6 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange mTaskContainer.getThumbnailView(), overscrollShift, deviceProfile)); setY(pagedOrientationHandler.getTaskMenuY( adjustedY, mTaskContainer.getThumbnailView(), overscrollShift)); - - // TODO(b/193432925) temporary menu placement for split screen task menus - TaskIdAttributeContainer[] taskIdAttributeContainers = - mTaskView.getTaskIdAttributeContainers(); - if (taskIdAttributeContainers[0].getStagePosition() != STAGE_POSITION_UNDEFINED) { - if (mTaskContainer.getStagePosition() != STAGE_POSITION_BOTTOM_OR_RIGHT) { - return; - } - Rect r = new Rect(); - mTaskContainer.getThumbnailView().getBoundsOnScreen(r); - if (deviceProfile.isLandscape) { - setX(r.left); - } else { - setY(r.top); - - } - } } public void onRotationChanged() { @@ -232,8 +212,7 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange private void addMenuOptions(TaskIdAttributeContainer taskContainer) { mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask())); mTaskName.setOnClickListener(v -> close(true)); - TaskOverlayFactory.getEnabledShortcuts(mTaskView, mActivity.getDeviceProfile(), - taskContainer) + TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer) .forEach(this::addMenuOption); } @@ -245,17 +224,9 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams(); mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp, menuOptionView, mActivity.getDeviceProfile()); - menuOptionView.setOnClickListener(view -> { - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - RecentsView recentsView = mTaskView.getRecentsView(); - recentsView.switchToScreenshot(null, - () -> recentsView.finishRecentsAnimation(true /* toRecents */, - false /* shouldPip */, - () -> menuOption.onClick(view))); - } else { - menuOption.onClick(view); - } - }); + // Set an onClick listener on each menu option. The onClick method is responsible for + // ending LiveTile mode on the thumbnail if needed. + menuOptionView.setOnClickListener(menuOption::onClick); mOptionLayout.addView(menuOptionView); } diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt index 06a579300a..b586ac37b0 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt +++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt @@ -164,7 +164,7 @@ class TaskMenuViewWithArrow : ArrowPopup { private fun addMenuOptions() { // Add the options TaskOverlayFactory - .getEnabledShortcuts(taskView, mActivityContext.deviceProfile, taskContainer) + .getEnabledShortcuts(taskView, taskContainer) .forEach { this.addMenuOption(it) } // Add the spaces between items diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index d8120ff255..d7a8599eae 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -16,11 +16,11 @@ package com.android.quickstep.views; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; import android.content.Context; import android.graphics.Bitmap; @@ -36,12 +36,14 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; +import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Property; import android.view.Surface; import android.view.View; +import android.widget.ImageView; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -50,8 +52,10 @@ import androidx.core.graphics.ColorUtils; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SystemUiController; +import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags; import com.android.quickstep.TaskOverlayFactory.TaskOverlay; import com.android.quickstep.views.TaskView.FullscreenDrawParams; import com.android.systemui.shared.recents.model.Task; @@ -63,6 +67,7 @@ import com.android.systemui.shared.recents.model.ThumbnailData; public class TaskThumbnailView extends View { private static final MainThreadInitializedObject TEMP_PARAMS = new MainThreadInitializedObject<>(FullscreenDrawParams::new); + private static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f; public static final Property DIM_ALPHA = new FloatProperty("dimAlpha") { @@ -82,6 +87,7 @@ public class TaskThumbnailView extends View { private TaskOverlay mOverlay; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mSplashBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mClearPaint = new Paint(); private final Paint mDimmingPaintAfterClearing = new Paint(); private final int mDimColor; @@ -90,6 +96,8 @@ public class TaskThumbnailView extends View { private final Rect mPreviewRect = new Rect(); private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper(); private TaskView.FullscreenDrawParams mFullscreenParams; + private ImageView mSplashView; + private Drawable mSplashViewDrawable; @Nullable private Task mTask; @@ -100,6 +108,8 @@ public class TaskThumbnailView extends View { /** How much this thumbnail is dimmed, 0 not dimmed at all, 1 totally dimmed. */ private float mDimAlpha = 0f; + /** Controls visibility of the splash view, 0 is transparent, 255 fully opaque. */ + private int mSplashAlpha = 0; private boolean mOverlayEnabled; @@ -115,6 +125,7 @@ public class TaskThumbnailView extends View { super(context, attrs, defStyleAttr); mPaint.setFilterBitmap(true); mBackgroundPaint.setColor(Color.WHITE); + mSplashBackgroundPaint.setColor(Color.WHITE); mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mActivity = BaseActivity.fromContext(context); // Initialize with placeholder value. It is overridden later by TaskView @@ -134,6 +145,8 @@ public class TaskThumbnailView extends View { int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000; mPaint.setColor(color); mBackgroundPaint.setColor(color); + mSplashBackgroundPaint.setColor(color); + updateSplashView(mTask.icon); } /** @@ -151,6 +164,9 @@ public class TaskThumbnailView extends View { boolean thumbnailWasNull = mThumbnailData == null; mThumbnailData = (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null; + if (mTask != null) { + updateSplashView(mTask.icon); + } if (refreshNow) { refresh(thumbnailWasNull && mThumbnailData != null); } @@ -201,6 +217,18 @@ public class TaskThumbnailView extends View { updateThumbnailPaintFilter(); } + /** + * Sets the alpha of the splash view. + */ + public void setSplashAlpha(float splashAlpha) { + mSplashAlpha = (int) (Utilities.boundToRange(splashAlpha, 0f, 1f) * 255); + if (mSplashViewDrawable != null) { + mSplashViewDrawable.setAlpha(mSplashAlpha); + } + mSplashBackgroundPaint.setAlpha(mSplashAlpha); + invalidate(); + } + public TaskOverlay getTaskOverlay() { if (mOverlay == null) { mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this); @@ -237,16 +265,13 @@ public class TaskThumbnailView extends View { boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect); DeviceProfile dp = mActivity.getDeviceProfile(); - int leftInset = TaskView.clipLeft(dp) ? Math.round(boundsInBitmapSpace.left) : 0; - int topInset = TaskView.clipTop(dp) ? Math.round(boundsInBitmapSpace.top) : 0; - int rightInset = TaskView.clipRight(dp) ? Math.round( - bitmapRect.right - boundsInBitmapSpace.right) : 0; - int bottomInset = TaskView.clipBottom(dp) + int bottomInset = dp.isTablet ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0; - return Insets.of(leftInset, topInset, rightInset, bottomInset); + return Insets.of(0, 0, 0, bottomInset); } + @SystemUiControllerFlags public int getSysUiStatusNavFlags() { if (mThumbnailData != null) { int flags = 0; @@ -261,6 +286,12 @@ public class TaskThumbnailView extends View { return 0; } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateSplashView(mSplashViewDrawable); + } + @Override protected void onDraw(Canvas canvas) { RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets; @@ -311,6 +342,17 @@ public class TaskThumbnailView extends View { } canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint); + + // Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios. + if (shouldShowSplashView()) { + if (mSplashView != null) { + canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius, + cornerRadius, mSplashBackgroundPaint); + + mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1); + mSplashView.draw(canvas); + } + } } public TaskView getTaskView() { @@ -325,6 +367,78 @@ public class TaskThumbnailView extends View { } } + /** + * Determine if the splash should be shown over top of the thumbnail. + * + *

We want to show the splash if the aspect ratio or rotation of the thumbnail would be + * different from the task. + */ + boolean shouldShowSplashView() { + return isThumbnailAspectRatioDifferentFromThumbnailData() + || isThumbnailRotationDifferentFromTask(); + } + + private void updateSplashView(Drawable icon) { + if (icon == null || icon.getConstantState() == null) { + return; + } + mSplashViewDrawable = icon.getConstantState().newDrawable().mutate(); + mSplashViewDrawable.setAlpha(mSplashAlpha); + ImageView imageView = mSplashView == null ? new ImageView(getContext()) : mSplashView; + imageView.setImageDrawable(mSplashViewDrawable); + + imageView.setScaleType(ImageView.ScaleType.MATRIX); + Matrix matrix = new Matrix(); + + float drawableWidth = mSplashViewDrawable.getIntrinsicWidth(); + float drawableHeight = mSplashViewDrawable.getIntrinsicHeight(); + float viewWidth = getMeasuredWidth(); + float viewCenterX = viewWidth / 2f; + float viewHeight = getMeasuredHeight(); + float viewCenterY = viewHeight / 2f; + float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f; + float centeredDrawableTop = (viewHeight - drawableHeight) / 2f; + float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale(); + float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null + ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen(); + float scale = nonGridScale * recentsMaxScale; + + // Center the image in the view. + matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop); + // Apply scale transformation after translation, pivoting around center of view. + matrix.postScale(scale, scale, viewCenterX, viewCenterY); + + imageView.setImageMatrix(matrix); + mSplashView = imageView; + } + + private boolean isThumbnailAspectRatioDifferentFromThumbnailData() { + if (mThumbnailData == null || mThumbnailData.thumbnail == null) { + return false; + } + + float thumbnailViewAspect = getWidth() / (float) getHeight(); + float thumbnailDataAspect = + mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight(); + + return Utilities.isRelativePercentDifferenceGreaterThan(thumbnailViewAspect, + thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT); + } + + private boolean isThumbnailRotationDifferentFromTask() { + RecentsView recents = getTaskView().getRecentsView(); + if (recents == null || mThumbnailData == null) { + return false; + } + + if (recents.getPagedOrientationHandler() == PagedOrientationHandler.PORTRAIT) { + int currentRotation = recents.getPagedViewOrientedState().getRecentsActivityRotation(); + return (currentRotation - mThumbnailData.rotation) % 2 != 0; + } else { + return recents.getPagedOrientationHandler().getRotation() != mThumbnailData.rotation; + } + } + /** * Potentially re-init the task overlay. Be cautious when calling this as the overlay may * do processing on initialization. @@ -433,27 +547,17 @@ public class TaskThumbnailView extends View { int thumbnailRotation = thumbnailData.rotation; int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation); RectF thumbnailClipHint = new RectF(); - if (TaskView.clipLeft(dp)) { - thumbnailClipHint.left = thumbnailData.insets.left; - } - if (TaskView.clipRight(dp)) { - thumbnailClipHint.right = thumbnailData.insets.right; - } - if (TaskView.clipTop(dp)) { - thumbnailClipHint.top = thumbnailData.insets.top; - } - if (TaskView.clipBottom(dp)) { - thumbnailClipHint.bottom = thumbnailData.insets.bottom; - } + float canvasScreenRatio = canvasWidth / (float) dp.widthPx; + float scaledTaskbarSize = dp.taskbarSize * canvasScreenRatio; + thumbnailClipHint.bottom = dp.isTablet ? scaledTaskbarSize : 0; float scale = thumbnailData.scale; final float thumbnailScale; // Landscape vs portrait change. // Note: Disable rotation in grid layout. - boolean windowingModeSupportsRotation = !dp.isMultiWindowMode - && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN - && !dp.isTablet; + boolean windowingModeSupportsRotation = + thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !dp.isTablet; isOrientationDifferent = isOrientationChange(deltaRotate) && windowingModeSupportsRotation; if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) { @@ -475,8 +579,9 @@ public class TaskThumbnailView extends View { float availableAspect = isRotated ? availableHeight / availableWidth : availableWidth / availableHeight; - boolean isAspectLargelyDifferent = Utilities.isRelativePercentDifferenceGreaterThan( - canvasAspect, availableAspect, 0.1f); + boolean isAspectLargelyDifferent = + Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect, + availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT); if (isRotated && isAspectLargelyDifferent) { // Do not rotate thumbnail if it would not improve fit isRotated = false; @@ -485,18 +590,10 @@ public class TaskThumbnailView extends View { if (isAspectLargelyDifferent) { // Crop letterbox insets if insets isn't already clipped - if (!TaskView.clipLeft(dp)) { - thumbnailClipHint.left = thumbnailData.letterboxInsets.left; - } - if (!TaskView.clipRight(dp)) { - thumbnailClipHint.right = thumbnailData.letterboxInsets.right; - } - if (!TaskView.clipTop(dp)) { - thumbnailClipHint.top = thumbnailData.letterboxInsets.top; - } - if (!TaskView.clipBottom(dp)) { - thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom; - } + thumbnailClipHint.left = thumbnailData.letterboxInsets.left; + thumbnailClipHint.right = thumbnailData.letterboxInsets.right; + thumbnailClipHint.top = thumbnailData.letterboxInsets.top; + thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom; availableWidth = surfaceWidth - (thumbnailClipHint.left + thumbnailClipHint.right); availableHeight = surfaceHeight @@ -559,44 +656,15 @@ public class TaskThumbnailView extends View { thumbnailScale = targetW / (croppedWidth * scale); } - Rect splitScreenInsets = dp.getInsets(); if (!isRotated) { - // No Rotation - if (dp.isMultiWindowMode) { - mClippedInsets.offsetTo(splitScreenInsets.left * scale, - splitScreenInsets.top * scale); - } else { - mClippedInsets.offsetTo(thumbnailClipHint.left * scale, - thumbnailClipHint.top * scale); - } mMatrix.setTranslate( -thumbnailClipHint.left * scale, -thumbnailClipHint.top * scale); } else { - setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds, dp); + setThumbnailRotation(deltaRotate, thumbnailBounds); } - final float widthWithInsets; - final float heightWithInsets; - if (isOrientationDifferent) { - widthWithInsets = thumbnailBounds.height() * thumbnailScale; - heightWithInsets = thumbnailBounds.width() * thumbnailScale; - } else { - widthWithInsets = thumbnailBounds.width() * thumbnailScale; - heightWithInsets = thumbnailBounds.height() * thumbnailScale; - } - mClippedInsets.left *= thumbnailScale; - mClippedInsets.top *= thumbnailScale; - - if (dp.isMultiWindowMode) { - mClippedInsets.right = splitScreenInsets.right * scale * thumbnailScale; - mClippedInsets.bottom = splitScreenInsets.bottom * scale * thumbnailScale; - } else { - mClippedInsets.right = Math.max(0, - widthWithInsets - mClippedInsets.left - canvasWidth); - mClippedInsets.bottom = Math.max(0, - heightWithInsets - mClippedInsets.top - canvasHeight); - } + mClippedInsets.set(0, 0, 0, scaledTaskbarSize); mMatrix.postScale(thumbnailScale, thumbnailScale); mIsOrientationChanged = isOrientationDifferent; @@ -617,44 +685,32 @@ public class TaskThumbnailView extends View { return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270; } - private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale, - Rect thumbnailPosition, DeviceProfile dp) { - float newLeftInset = 0; - float newTopInset = 0; + private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) { float translateX = 0; float translateY = 0; mMatrix.setRotate(90 * deltaRotate); switch (deltaRotate) { /* Counter-clockwise */ case Surface.ROTATION_90: - newLeftInset = thumbnailInsets.bottom; - newTopInset = thumbnailInsets.left; translateX = thumbnailPosition.height(); break; case Surface.ROTATION_270: - newLeftInset = thumbnailInsets.top; - newTopInset = thumbnailInsets.right; translateY = thumbnailPosition.width(); break; case Surface.ROTATION_180: - newLeftInset = -thumbnailInsets.top; - newTopInset = -thumbnailInsets.left; translateX = thumbnailPosition.width(); translateY = thumbnailPosition.height(); break; } - mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale); mMatrix.postTranslate(translateX, translateY); - if (TaskView.useFullThumbnail(dp)) { - mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top); - } } /** * Insets to used for clipping the thumbnail (in case it is drawing outside its own space) */ public RectF getInsetsToDrawInFullscreen(DeviceProfile dp) { - return TaskView.useFullThumbnail(dp) ? mClippedInsets : EMPTY_RECT_F; + return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps + ? mClippedInsets : EMPTY_RECT_F; } } } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index d58bb7c719..a0f195ce26 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -18,8 +18,8 @@ package com.android.quickstep.views; import static android.view.Display.DEFAULT_DISPLAY; import static android.widget.Toast.LENGTH_SHORT; +import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR; -import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; import static com.android.launcher3.Utilities.comp; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; @@ -32,6 +32,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; +import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -43,12 +44,12 @@ import android.annotation.IdRes; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; -import android.graphics.Outline; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Handler; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; @@ -57,7 +58,6 @@ import android.view.MotionEvent; import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; -import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.FrameLayout; @@ -67,7 +67,6 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; @@ -77,7 +76,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.ComponentKey; @@ -97,13 +96,13 @@ import com.android.quickstep.TaskUtils; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.RecentsOrientedState; +import com.android.quickstep.util.SplitSelectStateController; import com.android.quickstep.util.TaskCornerRadius; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -139,41 +138,6 @@ public class TaskView extends FrameLayout implements Reusable { /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */ public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f; - /** - * Should the TaskView display clip off the left inset in RecentsView. - */ - public static boolean clipLeft(DeviceProfile deviceProfile) { - return false; - } - - /** - * Should the TaskView display clip off the top inset in RecentsView. - */ - public static boolean clipTop(DeviceProfile deviceProfile) { - return false; - } - - /** - * Should the TaskView display clip off the right inset in RecentsView. - */ - public static boolean clipRight(DeviceProfile deviceProfile) { - return false; - } - - /** - * Should the TaskView display clip off the bottom inset in RecentsView. - */ - public static boolean clipBottom(DeviceProfile deviceProfile) { - return deviceProfile.isTablet; - } - - /** - * Should the TaskView scale down to fit whole thumbnail in fullscreen. - */ - public static boolean useFullThumbnail(DeviceProfile deviceProfile) { - return deviceProfile.isTablet && !deviceProfile.isTaskbarPresentInApps; - } - private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f; private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f; @@ -362,8 +326,6 @@ public class TaskView extends FrameLayout implements Reusable { } }; - private final TaskOutlineProvider mOutlineProvider; - @Nullable protected Task mTask; protected TaskThumbnailView mSnapshotView; @@ -371,6 +333,7 @@ public class TaskView extends FrameLayout implements Reusable { protected final DigitalWellBeingToast mDigitalWellBeingToast; private float mFullscreenProgress; private float mGridProgress; + protected float mTaskThumbnailSplashAlpha; private float mNonGridScale = 1; private float mDismissScale = 1; protected final FullscreenDrawParams mCurrentFullscreenParams; @@ -426,11 +389,10 @@ public class TaskView extends FrameLayout implements Reusable { private final float[] mIconCenterCoords = new float[2]; - private final PointF mLastTouchDownPosition = new PointF(); + protected final PointF mLastTouchDownPosition = new PointF(); private boolean mIsClickableAsLiveTile = true; - public TaskView(Context context) { this(context, null); } @@ -446,10 +408,6 @@ public class TaskView extends FrameLayout implements Reusable { mCurrentFullscreenParams = new FullscreenDrawParams(context); mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this); - - mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams, - mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx); - setOutlineProvider(mOutlineProvider); } public void setTaskViewId(int id) { @@ -605,6 +563,18 @@ public class TaskView extends FrameLayout implements Reusable { @Override public boolean dispatchTouchEvent(MotionEvent ev) { + RecentsView recentsView = getRecentsView(); + if (recentsView == null || mTask == null) { + return false; + } + SplitSelectStateController splitSelectStateController = + recentsView.getSplitSelectController(); + if (splitSelectStateController.isSplitSelectActive() && + splitSelectStateController.getInitialTaskId() == mTask.key.id) { + // Prevent taps on the this taskview if it's being animated into split select state + return false; + } + if (ev.getAction() == MotionEvent.ACTION_DOWN) { mLastTouchDownPosition.set(ev.getX(), ev.getY()); } @@ -628,16 +598,16 @@ public class TaskView extends FrameLayout implements Reusable { * second app. {@code false} otherwise */ private boolean confirmSecondSplitSelectApp() { - int index = getChildTaskIndexAtPosition(mLastTouchDownPosition); + int index = getLastSelectedChildTaskIndex(); TaskIdAttributeContainer container = mTaskIdAttributeContainer[index]; return getRecentsView().confirmSplitSelect(this, container.getTask(), container.getIconView(), container.getThumbnailView()); } /** - * Returns the task under the given position in the local coordinates of this task view. + * Returns the task index of the last selected child task (0 or 1). */ - protected int getChildTaskIndexAtPosition(PointF position) { + protected int getLastSelectedChildTaskIndex() { return 0; } @@ -691,13 +661,16 @@ public class TaskView extends FrameLayout implements Reusable { TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); // Indicate success once the system has indicated that the transition has started - ActivityOptions opts = ActivityOptionsCompat.makeCustomAnimation( - getContext(), 0, 0, () -> callback.accept(true), MAIN_EXECUTOR.getHandler()); + ActivityOptions opts = makeCustomAnimation(getContext(), 0, 0, + () -> callback.accept(true), MAIN_EXECUTOR.getHandler()); opts.setLaunchDisplayId( getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId()); if (freezeTaskList) { - ActivityOptionsCompat.setFreezeRecentTasksList(opts); + opts.setFreezeRecentTasksReordering(); } + // TODO(b/202826469): Replace setSplashScreenStyle with setDisableStartingWindow. + opts.setSplashScreenStyle(mSnapshotView.shouldShowSplashView() + ? SPLASH_SCREEN_STYLE_SOLID_COLOR : opts.getSplashScreenStyle()); Task.TaskKey key = mTask.key; UI_HELPER_EXECUTOR.execute(() -> { if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) { @@ -715,21 +688,37 @@ public class TaskView extends FrameLayout implements Reusable { } } + /** + * Returns ActivityOptions for overriding task transition animation. + */ + private ActivityOptions makeCustomAnimation(Context context, int enterResId, + int exitResId, final Runnable callback, final Handler callbackHandler) { + return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId, + callbackHandler, + elapsedRealTime -> { + if (callback != null) { + callbackHandler.post(callback); + } + }, null /* finishedListener */); + } + /** * Launch of the current task (both live and inactive tasks) with an animation. */ - public void launchTasks() { + public RunnableList launchTasks() { RecentsView recentsView = getRecentsView(); RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles; + RunnableList runnableList = new RunnableList(); + if (mTask != null && mTask.desktopTile) { + // clicked on desktop + SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps(); + return runnableList; + } if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) { if (!mIsClickableAsLiveTile) { - return; + return runnableList; } - // Reset the minimized state since we force-toggled the minimized state when entering - // overview, but never actually finished the recents animation - SystemUiProxy.INSTANCE.get(getContext()).setSplitScreenMinimized(false); - mIsClickableAsLiveTile = false; RemoteAnimationTargets targets; if (remoteTargetHandles.length == 1) { @@ -754,7 +743,7 @@ public class TaskView extends FrameLayout implements Reusable { // here, try to launch the task as a non live tile task. launchTaskAnimated(); mIsClickableAsLiveTile = true; - return; + return runnableList; } AnimatorSet anim = new AnimatorSet(); @@ -779,13 +768,24 @@ public class TaskView extends FrameLayout implements Reusable { launchTaskAnimated(); } mIsClickableAsLiveTile = true; + runEndCallback(); + } + + @Override + public void onAnimationCancel(Animator animation) { + runEndCallback(); + } + + private void runEndCallback() { + runnableList.executeAllAndDestroy(); } }); anim.start(); recentsView.onTaskLaunchedInLiveTileMode(); } else { - launchTaskAnimated(); + return launchTaskAnimated(); } + return runnableList; } /** @@ -912,10 +912,9 @@ public class TaskView extends FrameLayout implements Reusable { int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; int taskIconHeight = deviceProfile.overviewTaskIconSizePx; - int taskMargin = isGridTask ? deviceProfile.overviewTaskMarginGridPx - : deviceProfile.overviewTaskMarginPx; - int taskIconMargin = thumbnailTopMargin - taskIconHeight - taskMargin; - orientationHandler.setTaskIconParams(iconParams, taskIconMargin, taskIconHeight, + int taskMargin = deviceProfile.overviewTaskMarginPx; + + orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight, thumbnailTopMargin, isRtl); iconParams.width = iconParams.height = taskIconHeight; mIconView.setLayoutParams(iconParams); @@ -1102,6 +1101,18 @@ public class TaskView extends FrameLayout implements Reusable { return scale; } + /** + * Updates alpha of task thumbnail splash on swipe up/down. + */ + public void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) { + mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha; + applyThumbnailSplashAlpha(); + } + + protected void applyThumbnailSplashAlpha() { + mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha); + } + private void setSplitSelectTranslationX(float x) { mSplitSelectTranslationX = x; applyTranslationX(); @@ -1250,7 +1261,7 @@ public class TaskView extends FrameLayout implements Reusable { DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); } - public FloatProperty getSecondaryDissmissTranslationProperty() { + public FloatProperty getSecondaryDismissTranslationProperty() { return getPagedOrientationHandler().getSecondaryValue( DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); } @@ -1289,33 +1300,6 @@ public class TaskView extends FrameLayout implements Reusable { mEndQuickswitchCuj = endQuickswitchCuj; } - private static final class TaskOutlineProvider extends ViewOutlineProvider { - - private int mMarginTop; - private FullscreenDrawParams mFullscreenParams; - - TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin) { - mMarginTop = topMargin; - mFullscreenParams = fullscreenParams; - } - - public void updateParams(FullscreenDrawParams params, int topMargin) { - mFullscreenParams = params; - mMarginTop = topMargin; - } - - @Override - public void getOutline(View view, Outline outline) { - RectF insets = mFullscreenParams.mCurrentDrawnInsets; - float scale = mFullscreenParams.mScale; - outline.setRoundRect(0, - (int) (mMarginTop * scale), - (int) ((insets.left + view.getWidth() + insets.right) * scale), - (int) ((insets.top + view.getHeight() + insets.bottom) * scale), - mFullscreenParams.mCurrentDrawnCornerRadius); - } - } - private int getExpectedViewHeight(View view) { int expectedHeight; int h = view.getLayoutParams().height; @@ -1343,7 +1327,7 @@ public class TaskView extends FrameLayout implements Reusable { continue; } for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, - mActivity.getDeviceProfile(), taskContainer)) { + taskContainer)) { info.addAction(s.createAccessibilityAction(context)); } } @@ -1381,7 +1365,7 @@ public class TaskView extends FrameLayout implements Reusable { continue; } for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, - mActivity.getDeviceProfile(), taskContainer)) { + taskContainer)) { if (s.hasHandlerForAction(action)) { s.onClick(this); return true; @@ -1421,11 +1405,6 @@ public class TaskView extends FrameLayout implements Reusable { mSnapshotView.getTaskOverlay().setFullscreenProgress(progress); updateSnapshotRadius(); - - mOutlineProvider.updateParams( - mCurrentFullscreenParams, - mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx); - invalidateOutline(); } protected void updateSnapshotRadius() { @@ -1537,8 +1516,8 @@ public class TaskView extends FrameLayout implements Reusable { } public void initiateSplitSelect(SplitPositionOption splitPositionOption) { - AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU); - getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition); + getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition, + getLogEventForPosition(splitPositionOption.stagePosition)); } /** @@ -1556,6 +1535,19 @@ public class TaskView extends FrameLayout implements Reusable { return display != null ? display.getDisplayId() : DEFAULT_DISPLAY; } + /** + * Sets visibility for the thumbnail and associated elements (DWB banners and action chips). + * IconView is unaffected. + */ + void setThumbnailVisibility(int visibility) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child != mIconView) { + child.setVisibility(visibility); + } + } + } + /** * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. */ @@ -1584,17 +1576,14 @@ public class TaskView extends FrameLayout implements Reusable { RectF insets = pph.getInsetsToDrawInFullscreen(dp); float currentInsetsLeft = insets.left * fullscreenProgress; + float currentInsetsTop = insets.top * fullscreenProgress; float currentInsetsRight = insets.right * fullscreenProgress; - float insetsBottom = insets.bottom; - if (dp.isTaskbarPresentInApps) { - insetsBottom = Math.max(0, insetsBottom - dp.taskbarSize); - } - mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress, - currentInsetsRight, insetsBottom * fullscreenProgress); - float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius; + float currentInsetsBottom = insets.bottom * fullscreenProgress; + mCurrentDrawnInsets.set( + currentInsetsLeft, currentInsetsTop, currentInsetsRight, currentInsetsBottom); mCurrentDrawnCornerRadius = - Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius) + Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius) / parentScale / taskViewScale; // We scaled the thumbnail to fit the content (excluding insets) within task view width. diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java index d8be30728e..4eec319f4a 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Handler; +import android.view.View; import androidx.test.runner.AndroidJUnit4; @@ -58,6 +59,8 @@ public class TaskbarNavButtonControllerTest { TaskbarControllers mockTaskbarControllers; @Mock TaskbarActivityContext mockTaskbarActivityContext; + @Mock + View mockView; private TaskbarNavButtonController mNavButtonController; @@ -76,110 +79,110 @@ public class TaskbarNavButtonControllerTest { @Test public void testPressBack() { - mNavButtonController.onButtonClick(BUTTON_BACK); + mNavButtonController.onButtonClick(BUTTON_BACK, mockView); verify(mockSystemUiProxy, times(1)).onBackPressed(); } @Test public void testPressImeSwitcher() { - mNavButtonController.onButtonClick(BUTTON_IME_SWITCH); + mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView); verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed(); } @Test public void testPressA11yShortClick() { - mNavButtonController.onButtonClick(BUTTON_A11Y); + mNavButtonController.onButtonClick(BUTTON_A11Y, mockView); verify(mockSystemUiProxy, times(1)) .notifyAccessibilityButtonClicked(DISPLAY_ID); } @Test public void testPressA11yLongClick() { - mNavButtonController.onButtonLongClick(BUTTON_A11Y); + mNavButtonController.onButtonLongClick(BUTTON_A11Y, mockView); verify(mockSystemUiProxy, times(1)).notifyAccessibilityButtonLongClicked(); } @Test public void testLongPressHome() { - mNavButtonController.onButtonLongClick(BUTTON_HOME); + mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); verify(mockSystemUiProxy, times(1)).startAssistant(any()); } @Test public void testPressHome() { - mNavButtonController.onButtonClick(BUTTON_HOME); + mNavButtonController.onButtonClick(BUTTON_HOME, mockView); verify(mockCommandHelper, times(1)).addCommand(TYPE_HOME); } @Test public void testPressRecents() { - mNavButtonController.onButtonClick(BUTTON_RECENTS); + mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); verify(mockCommandHelper, times(1)).addCommand(TYPE_TOGGLE); } @Test public void testPressRecentsWithScreenPinned() { mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); - mNavButtonController.onButtonClick(BUTTON_RECENTS); + mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView); verify(mockCommandHelper, times(0)).addCommand(TYPE_TOGGLE); } @Test public void testLongPressBackRecentsNotPinned() { - mNavButtonController.onButtonLongClick(BUTTON_RECENTS); - mNavButtonController.onButtonLongClick(BUTTON_BACK); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView); + mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView); verify(mockSystemUiProxy, times(0)).stopScreenPinning(); } @Test public void testLongPressBackRecentsPinned() { mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); - mNavButtonController.onButtonLongClick(BUTTON_RECENTS); - mNavButtonController.onButtonLongClick(BUTTON_BACK); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView); + mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView); verify(mockSystemUiProxy, times(1)).stopScreenPinning(); } @Test public void testLongPressBackRecentsTooLongPinned() { mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); - mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView); try { Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5); } catch (InterruptedException e) { e.printStackTrace(); } - mNavButtonController.onButtonLongClick(BUTTON_BACK); + mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView); verify(mockSystemUiProxy, times(0)).stopScreenPinning(); } @Test public void testLongPressBackRecentsMultipleAttemptPinned() { mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); - mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView); try { Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5); } catch (InterruptedException e) { e.printStackTrace(); } - mNavButtonController.onButtonLongClick(BUTTON_BACK); + mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView); verify(mockSystemUiProxy, times(0)).stopScreenPinning(); // Try again w/in threshold - mNavButtonController.onButtonLongClick(BUTTON_RECENTS); - mNavButtonController.onButtonLongClick(BUTTON_BACK); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView); + mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView); verify(mockSystemUiProxy, times(1)).stopScreenPinning(); } @Test public void testLongPressHomeScreenPinned() { mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING); - mNavButtonController.onButtonLongClick(BUTTON_HOME); + mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); verify(mockSystemUiProxy, times(0)).startAssistant(any()); } @Test public void testNoCallsToNullLogger() { - mNavButtonController.onButtonClick(BUTTON_HOME); + mNavButtonController.onButtonClick(BUTTON_HOME, mockView); verify(mockStatsLogManager, times(0)).logger(); verify(mockStatsLogger, times(0)).log(any()); } @@ -187,9 +190,9 @@ public class TaskbarNavButtonControllerTest { @Test public void testNoCallsAfterNullingOut() { mNavButtonController.init(mockTaskbarControllers); - mNavButtonController.onButtonClick(BUTTON_HOME); + mNavButtonController.onButtonClick(BUTTON_HOME, mockView); mNavButtonController.onDestroy(); - mNavButtonController.onButtonClick(BUTTON_HOME); + mNavButtonController.onButtonClick(BUTTON_HOME, mockView); verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP); verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS); } @@ -197,7 +200,7 @@ public class TaskbarNavButtonControllerTest { @Test public void testLogOnTap() { mNavButtonController.init(mockTaskbarControllers); - mNavButtonController.onButtonClick(BUTTON_HOME); + mNavButtonController.onButtonClick(BUTTON_HOME, mockView); verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP); verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS); } @@ -205,7 +208,7 @@ public class TaskbarNavButtonControllerTest { @Test public void testLogOnLongpress() { mNavButtonController.init(mockTaskbarControllers); - mNavButtonController.onButtonLongClick(BUTTON_HOME); + mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView); verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS); verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_HOME_BUTTON_TAP); } @@ -213,11 +216,11 @@ public class TaskbarNavButtonControllerTest { @Test public void testBackOverviewLogOnLongpress() { mNavButtonController.init(mockTaskbarControllers); - mNavButtonController.onButtonLongClick(BUTTON_RECENTS); + mNavButtonController.onButtonLongClick(BUTTON_RECENTS, mockView); verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS); verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP); - mNavButtonController.onButtonLongClick(BUTTON_BACK); + mNavButtonController.onButtonLongClick(BUTTON_BACK, mockView); verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS); verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_BACK_BUTTON_TAP); } diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java index 09f0858618..f190e27237 100644 --- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java +++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java @@ -35,7 +35,7 @@ import org.junit.rules.TestRule; * Base class for all instrumentation tests that deal with Quickstep. */ public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest { - static final boolean ENABLE_SHELL_TRANSITIONS = + public static final boolean ENABLE_SHELL_TRANSITIONS = SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); @Override protected TestRule getRulesInsideActivityMonitor() { diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt new file mode 100644 index 0000000000..3967f75fa6 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep + +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.DeviceProfileBaseTest +import com.android.launcher3.util.WindowBounds +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class HotseatWidthCalculationTest : DeviceProfileBaseTest() { + + /** + * This is a case when after setting the hotseat, the space needs to be recalculated + * but it doesn't need to change QSB width or remove icons + */ + @Test + fun distribute_border_space_when_space_is_enough_portrait() { + initializeVarsForTablet(isGestureMode = false) + windowBounds = WindowBounds(Rect(0, 0, 1800, 2560), Rect(0, 104, 0, 0)) + val dp = newDP() + dp.isTaskbarPresentInApps = true + + assertThat(dp.hotseatBarEndOffset).isEqualTo(558) + assertThat(dp.numShownHotseatIcons).isEqualTo(6) + assertThat(dp.hotseatBorderSpace).isEqualTo(69) + + assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(176) + assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(558) + + assertThat(dp.isQsbInline).isFalse() + assertThat(dp.hotseatQsbWidth).isEqualTo(1445) + } + + /** + * This is a case when after setting the hotseat, and recalculating spaces + * it still needs to remove icons for everything to fit + */ + @Test + fun decrease_num_of_icons_when_not_enough_space_portrait() { + initializeVarsForTablet(isGestureMode = false) + windowBounds = WindowBounds(Rect(0, 0, 1300, 2560), Rect(0, 104, 0, 0)) + val dp = newDP() + dp.isTaskbarPresentInApps = true + + assertThat(dp.hotseatBarEndOffset).isEqualTo(558) + assertThat(dp.numShownHotseatIcons).isEqualTo(4) + assertThat(dp.hotseatBorderSpace).isEqualTo(76) + + assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(122) + assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(558) + + assertThat(dp.isQsbInline).isFalse() + assertThat(dp.hotseatQsbWidth).isEqualTo(1058) + } + + /** + * This is a case when after setting the hotseat, the space needs to be recalculated + * but it doesn't need to change QSB width or remove icons + */ + @Test + fun distribute_border_space_when_space_is_enough_landscape() { + initializeVarsForTwoPanel(isGestureMode = false, isLandscape = true) + val dp = newDP() + dp.isTaskbarPresentInApps = true + + assertThat(dp.hotseatBarEndOffset).isEqualTo(744) + assertThat(dp.numShownHotseatIcons).isEqualTo(6) + assertThat(dp.hotseatBorderSpace).isEqualTo(83) + + assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(106) + assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(744) + + assertThat(dp.isQsbInline).isFalse() + assertThat(dp.hotseatQsbWidth).isEqualTo(1467) + } + + /** + * This is a case when the hotseat spans a certain amount of columns + * and the nav buttons push the hotseat to the side, but not enough to change the border space. + */ + @Test + fun nav_buttons_dont_interfere_with_required_hotseat_width() { + initializeVarsForTablet(isGestureMode = false, isLandscape = true) + inv?.apply { + hotseatColumnSpan = IntArray(4) { 4 } + inlineQsb = BooleanArray(4) { false } + } + val dp = newDP() + dp.isTaskbarPresentInApps = true + + assertThat(dp.hotseatBarEndOffset).isEqualTo(705) + assertThat(dp.numShownHotseatIcons).isEqualTo(6) + assertThat(dp.hotseatBorderSpace).isEqualTo(108) + + assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(631) + assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705) + + assertThat(dp.isQsbInline).isFalse() + assertThat(dp.hotseatQsbWidth).isEqualTo(1227) + } + + /** + * This is a case when after setting the hotseat, the QSB width needs to be changed to fit + */ + @Test + fun decrease_qsb_when_not_enough_space_landscape() { + initializeVarsForTablet(isGestureMode = false, isLandscape = true) + windowBounds = WindowBounds(Rect(0, 0, 2460, 1600), Rect(0, 104, 0, 0)) + val dp = newDP() + dp.isTaskbarPresentInApps = true + + assertThat(dp.hotseatBarEndOffset).isEqualTo(705) + assertThat(dp.numShownHotseatIcons).isEqualTo(6) + assertThat(dp.hotseatBorderSpace).isEqualTo(36) + + assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(884) + assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705) + + assertThat(dp.isQsbInline).isTrue() + assertThat(dp.hotseatQsbWidth).isEqualTo(559) + } + + /** + * This is a case when after setting the hotseat, changing QSB width, and recalculating spaces + * it still needs to remove icons for everything to fit + */ + @Test + fun decrease_num_of_icons_when_not_enough_space_landscape() { + initializeVarsForTablet(isGestureMode = false, isLandscape = true) + windowBounds = WindowBounds(Rect(0, 0, 2260, 1600), Rect(0, 104, 0, 0)) + val dp = newDP() + dp.isTaskbarPresentInApps = true + + assertThat(dp.hotseatBarEndOffset).isEqualTo(705) + assertThat(dp.numShownHotseatIcons).isEqualTo(5) + assertThat(dp.hotseatBorderSpace).isEqualTo(56) + + assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(801) + assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705) + + assertThat(dp.isQsbInline).isTrue() + assertThat(dp.hotseatQsbWidth).isEqualTo(480) + } +} diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java index 9e5d9585af..9c240f08c7 100644 --- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java +++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java @@ -19,7 +19,7 @@ package com.android.quickstep; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; -import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON; +import static com.android.launcher3.util.NavigationMode.NO_BUTTON; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -35,14 +35,13 @@ import android.graphics.Rect; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Size; -import android.view.Display; import android.view.MotionEvent; import android.view.Surface; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.launcher3.ResourceUtils; +import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.RotationUtils; import com.android.launcher3.util.WindowBounds; @@ -290,15 +289,17 @@ public class OrientationTouchTransformerTest { private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) { Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight()); RotationUtils.rotateSize(displaySize, rotation); - CachedDisplayInfo cdi = new CachedDisplayInfo(displaySize, rotation); - WindowBounds wm = new WindowBounds( + CachedDisplayInfo cachedDisplayInfo = new CachedDisplayInfo(displaySize, rotation); + WindowBounds windowBounds = new WindowBounds( new Rect(0, 0, displaySize.x, displaySize.y), new Rect()); WindowManagerProxy wmProxy = mock(WindowManagerProxy.class); - doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any()); - doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any()); + doReturn(cachedDisplayInfo).when(wmProxy).getDisplayInfo(any()); + doReturn(windowBounds).when(wmProxy).getRealBounds(any(), any()); + ArrayMap internalDisplayBounds = new ArrayMap<>(); + doReturn(internalDisplayBounds).when(wmProxy).estimateInternalDisplayBounds(any()); return new DisplayController.Info( - getApplicationContext(), mock(Display.class), wmProxy, new ArrayMap<>()); + getApplicationContext(), wmProxy, new ArrayMap<>()); } private float generateTouchRegionHeight(Size screenSize, int rotation) { diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java index 4e497163b5..ed5526f410 100644 --- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java +++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java @@ -26,12 +26,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.KeyguardManager; import androidx.test.filters.SmallTest; import com.android.launcher3.util.LooperExecutor; import com.android.quickstep.util.GroupTask; -import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.wm.shell.util.GroupedRecentTaskInfo; import org.junit.Before; @@ -56,8 +56,8 @@ public class RecentTasksListTest { public void setup() { MockitoAnnotations.initMocks(this); LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class); - KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class); - mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat, + KeyguardManager mockKeyguardManager = mock(KeyguardManager.class); + mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager, mockSystemUiProxy); } @@ -70,7 +70,7 @@ public class RecentTasksListTest { @Test public void loadTasksInBackground_onlyKeys_noValidTaskDescription() { - GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo( + GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks( new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null); when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); @@ -90,8 +90,8 @@ public class RecentTasksListTest { task1.taskDescription = new ActivityManager.TaskDescription(taskDescription); ActivityManager.RecentTaskInfo task2 = new ActivityManager.RecentTaskInfo(); task2.taskDescription = new ActivityManager.TaskDescription(); - GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo( - task1, task2, null); + GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(task1, task2, + null); when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index 6ec62695af..401b967d0d 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -16,8 +16,6 @@ package com.android.quickstep; -import android.content.Intent; - import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -43,7 +41,7 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { // b/143488140 mLauncher.goHome(); // Start an activity where the gestures start. - startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); + startTestActivity(2); } private void runTest(String... eventSequence) { diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index 4bf247c75d..42e9be32e3 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -179,6 +179,35 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { actionsView.clickAndDismissScreenshot(); } + @Test + @PortraitLandscape + public void testSplitFromOverview() { + assumeTrue(!mLauncher.isTablet()); + + startTestActivity(2); + startTestActivity(3); + + mLauncher.goHome().switchToOverview().getCurrentTask() + .tapMenu() + .tapSplitMenuItem() + .getTestActivityTask(2) + .open(); + } + + @Test + @PortraitLandscape + public void testSplitFromOverviewForTablet() { + assumeTrue(mLauncher.isTablet()); + + startTestActivity(2); + startTestActivity(3); + + mLauncher.goHome().switchToOverview().getOverviewActions() + .clickSplit() + .getTestActivityTask(2) + .open(); + } + private int getCurrentOverviewPage(Launcher launcher) { return launcher.getOverviewPanel().getCurrentPage(); } @@ -198,7 +227,9 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @NavigationModeSwitch @PortraitLandscape + @ScreenRecord // b/238461765 public void testSwitchToOverview() throws Exception { + startTestAppsWithCheck(); assertNotNull("Workspace.switchToOverview() returned null", mLauncher.goHome().switchToOverview()); assertTrue("Launcher internal state didn't switch to Overview", diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java index 1df9c02ee8..9337cb55bc 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java @@ -67,6 +67,13 @@ public class TaplTestsTaskbar extends AbstractQuickStepTest { mLauncher.getLaunchedAppState().showTaskbar(); } + @Test + public void testHideTaskbarPersistsOnRecreate() { + getTaskbar().hide(); + mLauncher.recreateTaskbar(); + mLauncher.getLaunchedAppState().assertTaskbarHidden(); + } + @Test public void testLaunchApp() throws Exception { getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE); diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt b/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt new file mode 100644 index 0000000000..cf3c8c9b8d --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep + +import android.graphics.Rect +import android.graphics.RectF +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.DeviceProfileBaseTest +import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper +import com.android.systemui.shared.recents.model.ThumbnailData +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +/** + * Test for TaskThumbnailView class. + */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class TaskThumbnailViewTest : DeviceProfileBaseTest() { + + private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java) + + private val mPreviewPositionHelper = PreviewPositionHelper() + + @Test + fun getInsetsToDrawInFullscreen_clipTaskbarSizeFromBottomForTablets() { + initializeVarsForTablet() + val dp = newDP() + val previewRect = Rect(0, 0, 100, 100) + val canvasWidth = dp.widthPx / 2 + val canvasHeight = dp.heightPx / 2 + val currentRotation = 0 + val isRtl = false + + mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth, + canvasHeight, dp, currentRotation, isRtl) + + val expectedClippedInsets = RectF(0f, 0f, 0f, dp.taskbarSize / 2f) + assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp)) + .isEqualTo(expectedClippedInsets) + } + + @Test + fun getInsetsToDrawInFullscreen_doNotClipTaskbarSizeFromBottomForPhones() { + initializeVarsForPhone() + val dp = newDP() + val previewRect = Rect(0, 0, 100, 100) + val canvasWidth = dp.widthPx / 2 + val canvasHeight = dp.heightPx / 2 + val currentRotation = 0 + val isRtl = false + + mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth, + canvasHeight, dp, currentRotation, isRtl) + + val expectedClippedInsets = RectF(0f, 0f, 0f, 0f) + assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp)) + .isEqualTo(expectedClippedInsets) + } +} \ No newline at end of file diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java index 7d414f4910..190b002d09 100644 --- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java +++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java @@ -23,8 +23,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.util.ArrayMap; -import android.util.Pair; -import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; @@ -36,6 +34,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.NavigationMode; import com.android.launcher3.util.ReflectionHelpers; import com.android.launcher3.util.RotationUtils; import com.android.launcher3.util.WindowBounds; @@ -148,7 +147,7 @@ public class TaskViewSimulatorTest { int rotation = mDisplaySize.x > mDisplaySize.y ? Surface.ROTATION_90 : Surface.ROTATION_0; CachedDisplayInfo cdi = - new CachedDisplayInfo("test-display", mDisplaySize, rotation , new Rect()); + new CachedDisplayInfo(mDisplaySize, rotation, new Rect()); WindowBounds wm = new WindowBounds( new Rect(0, 0, mDisplaySize.x, mDisplaySize.y), mDisplayInsets); @@ -164,15 +163,16 @@ public class TaskViewSimulatorTest { } WindowManagerProxy wmProxy = mock(WindowManagerProxy.class); - doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any()); - doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any()); + doReturn(cdi).when(wmProxy).getDisplayInfo(any()); + doReturn(wm).when(wmProxy).getRealBounds(any(), any()); + doReturn(NavigationMode.NO_BUTTON).when(wmProxy).getNavigationMode(any()); - ArrayMap> perDisplayBoundsCache = + ArrayMap perDisplayBoundsCache = new ArrayMap<>(); - perDisplayBoundsCache.put(cdi.id, Pair.create(cdi.normalize(), allBounds)); + perDisplayBoundsCache.put(cdi.normalize(), allBounds); DisplayController.Info mockInfo = new Info( - helper.sandboxContext, mock(Display.class), wmProxy, perDisplayBoundsCache); + helper.sandboxContext, wmProxy, perDisplayBoundsCache); DisplayController controller = DisplayController.INSTANCE.get(helper.sandboxContext); diff --git a/res/drawable/bg_work_apps_paused_action_button.xml b/res/drawable/bg_work_apps_paused_action_button.xml new file mode 100644 index 0000000000..74d469322a --- /dev/null +++ b/res/drawable/bg_work_apps_paused_action_button.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_all_apps_bg_hand.xml b/res/drawable/ic_all_apps_bg_hand.xml deleted file mode 100644 index 7f3fe14fb1..0000000000 --- a/res/drawable/ic_all_apps_bg_hand.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/ic_all_apps_bg_icon_1.xml b/res/drawable/ic_all_apps_bg_icon_1.xml deleted file mode 100644 index d226ac6000..0000000000 --- a/res/drawable/ic_all_apps_bg_icon_1.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/res/drawable/ic_all_apps_bg_icon_2.xml b/res/drawable/ic_all_apps_bg_icon_2.xml deleted file mode 100644 index 5966d9969f..0000000000 --- a/res/drawable/ic_all_apps_bg_icon_2.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/res/drawable/ic_all_apps_bg_icon_3.xml b/res/drawable/ic_all_apps_bg_icon_3.xml deleted file mode 100644 index b18f8bc473..0000000000 --- a/res/drawable/ic_all_apps_bg_icon_3.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - diff --git a/res/drawable/ic_all_apps_bg_icon_4.xml b/res/drawable/ic_all_apps_bg_icon_4.xml deleted file mode 100644 index 8eb4d90126..0000000000 --- a/res/drawable/ic_all_apps_bg_icon_4.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/ic_block_shadow.xml b/res/drawable/ic_block_shadow.xml deleted file mode 100644 index 045fe8d4c5..0000000000 --- a/res/drawable/ic_block_shadow.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/res/drawable/ic_remove_shadow.xml b/res/drawable/ic_remove_shadow.xml deleted file mode 100644 index 48abc10b3b..0000000000 --- a/res/drawable/ic_remove_shadow.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/res/drawable/ic_setup_shadow.xml b/res/drawable/ic_setup_shadow.xml deleted file mode 100644 index 10aeee6e02..0000000000 --- a/res/drawable/ic_setup_shadow.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/res/drawable/page_indicator.xml b/res/drawable/page_indicator.xml new file mode 100644 index 0000000000..c0ccc49250 --- /dev/null +++ b/res/drawable/page_indicator.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/work_apps_toggle_background.xml b/res/drawable/work_apps_toggle_background.xml index a47c8fef7c..6ad6c821db 100644 --- a/res/drawable/work_apps_toggle_background.xml +++ b/res/drawable/work_apps_toggle_background.xml @@ -13,16 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - - - + @@ -32,4 +24,4 @@ android:right="@dimen/work_profile_footer_padding" /> - + diff --git a/res/layout/all_apps_bottom_sheet_background.xml b/res/layout/all_apps_bottom_sheet_background.xml index 12b6b7bb55..3e47690197 100644 --- a/res/layout/all_apps_bottom_sheet_background.xml +++ b/res/layout/all_apps_bottom_sheet_background.xml @@ -22,7 +22,7 @@ + android:layout_height="@dimen/bottom_sheet_handle_area_height" /> @@ -28,7 +31,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/all_apps_tabs_button_horizontal_padding" - android:layout_marginVertical="@dimen/all_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/all_apps_tabs_background" android:text="@string/all_apps_personal_tab" @@ -41,7 +43,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginStart="@dimen/all_apps_tabs_button_horizontal_padding" - android:layout_marginVertical="@dimen/all_apps_tabs_vertical_padding" android:layout_weight="1" android:background="@drawable/all_apps_tabs_background" android:text="@string/all_apps_work_tab" diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml deleted file mode 100644 index 6f2dd3d21e..0000000000 --- a/res/layout/all_apps_search_market.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml index 82b0b8d74e..95ebd94d0e 100644 --- a/res/layout/hotseat.xml +++ b/res/layout/hotseat.xml @@ -21,4 +21,5 @@ android:layout_height="match_parent" android:theme="@style/HomeScreenElementTheme" android:importantForAccessibility="no" + android:preferKeepClear="true" launcher:containerType="hotseat" /> \ No newline at end of file diff --git a/res/layout/search_container_all_apps.xml b/res/layout/search_container_all_apps.xml index e1646ba43e..b46298cfdb 100644 --- a/res/layout/search_container_all_apps.xml +++ b/res/layout/search_container_all_apps.xml @@ -26,6 +26,7 @@ android:gravity="center" android:hint="@string/all_apps_search_bar_hint" android:imeOptions="actionSearch|flagNoExtractUi" + android:importantForAutofill="no" android:inputType="text|textNoSuggestions|textCapWords" android:maxLines="1" android:padding="8dp" diff --git a/res/layout/search_results_rv_layout.xml b/res/layout/search_results_rv_layout.xml index 567cb5f4fc..9127521090 100644 --- a/res/layout/search_results_rv_layout.xml +++ b/res/layout/search_results_rv_layout.xml @@ -18,7 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/search_results_list_view" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:clipToPadding="false" android:descendantFocusability="afterDescendants" android:focusable="true" /> diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml index 635db141dd..4be2e456ac 100644 --- a/res/layout/secondary_launcher.xml +++ b/res/layout/secondary_launcher.xml @@ -18,6 +18,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/drag_layer" + android:clipChildren="false" android:padding="@dimen/dynamic_grid_edge_margin"> - - -