Merge "Revert "Fixing testOverview test"" into sc-dev
diff --git a/Android.bp b/Android.bp
index 002f6fe..df0a96f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -44,6 +44,7 @@
"src/com/android/launcher3/ResourceUtils.java",
"src/com/android/launcher3/testing/TestProtocol.java",
],
+ resource_dirs: [ ],
manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
}
@@ -94,27 +95,35 @@
min_sdk_version: "28",
}
+// Library with all the dependencies for building Launcher3
+android_library {
+ name: "Launcher3ResLib",
+ srcs: [ ],
+ resource_dirs: ["res"],
+ static_libs: [
+ "LauncherPluginLib",
+ "launcher_quickstep_log_protos_lite",
+ "androidx-constraintlayout_constraintlayout",
+ "androidx.recyclerview_recyclerview",
+ "androidx.dynamicanimation_dynamicanimation",
+ "androidx.fragment_fragment",
+ "androidx.preference_preference",
+ "androidx.slice_slice-view",
+ "androidx.cardview_cardview",
+ "iconloader_base",
+ ],
+ manifest: "AndroidManifest-common.xml",
+ sdk_version: "current",
+ min_sdk_version: "26",
+}
+
//
// Build rule for Launcher3 dependencies lib.
//
android_library {
name: "Launcher3CommonDepsLib",
- static_libs: [
- "androidx.recyclerview_recyclerview",
- "androidx.dynamicanimation_dynamicanimation",
- "androidx.preference_preference",
- "androidx.slice_slice-view",
- "iconloader_base",
- "LauncherPluginLib",
- "launcher_quickstep_log_protos_lite"
- ],
- srcs: [
- "src_build_config/**/*.java",
- ],
- resource_dirs: ["res"],
- optimize: {
- enabled: false,
- },
+ srcs: ["src_build_config/**/*.java"],
+ static_libs: ["Launcher3ResLib"],
sdk_version: "current",
min_sdk_version: "26",
manifest: "AndroidManifest-common.xml",
@@ -164,22 +173,68 @@
],
}
-//
-// Launcher Robolectric test target.
-//
-java_library {
- name: "Launcher3TestCommon",
- libs: [
- "Launcher3CommonDepsLib",
+// Library with all the dependencies for building quickstep
+android_library {
+ name: "QuickstepResLib",
+ srcs: [ ],
+ resource_dirs: [
+ "quickstep/res",
],
+ static_libs: [
+ "Launcher3ResLib",
+ "SystemUISharedLib",
+ "SystemUI-statsd",
+ ],
+ manifest: "quickstep/AndroidManifest.xml",
+ min_sdk_version: "28",
+}
+
+
+// Source code used for test helpers
+filegroup {
+ name: "launcher-src-ext-tests",
+ srcs: ["ext_tests/src/**/*.java"],
+}
+
+// Common source files used to build launcher
+filegroup {
+ name: "launcher-src-no-build-config",
srcs: [
"src/**/*.java",
"src_shortcuts_overrides/**/*.java",
- "src_ui_overrides/**/*.java",
- "ext_tests/src/**/*.java",
- "tests/src_common/**/*.java",
+ "quickstep/src/**/*.java",
],
- target_sdk_version: "29",
- sdk_version: "current",
- min_sdk_version: "26",
}
+
+// Proguard files for Launcher3
+filegroup {
+ name: "launcher-proguard-rules",
+ srcs: ["proguard.flags"],
+}
+
+
+// Library with all the dependencies for building Launcher Go
+android_library {
+ name: "LauncherGoResLib",
+ srcs: [
+ "src/**/*.java",
+ "quickstep/src/**/*.java",
+ "go/src/**/*.java",
+ "go/quickstep/src/**/*.java",
+ ],
+ resource_dirs: [
+ "go/res",
+ "go/quickstep/res",
+ ],
+ static_libs: [
+ "Launcher3CommonDepsLib",
+ "QuickstepResLib",
+ ],
+ manifest: "quickstep/AndroidManifest-launcher.xml",
+ additional_manifests: [
+ "go/AndroidManifest.xml",
+ "AndroidManifest-common.xml",
+ ],
+ min_sdk_version: "29",
+}
+
diff --git a/Android.mk b/Android.mk
index 89870a6..54a80b7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,9 +78,7 @@
$(call all-java-files-under, quickstep/src) \
$(call all-java-files-under, src_shortcuts_overrides)
-LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/quickstep/res \
- $(LOCAL_PATH)/quickstep/overview_ui_overrides/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
LOCAL_PROGUARD_ENABLED := disabled
@@ -109,9 +107,7 @@
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/quickstep/res \
- $(LOCAL_PATH)/quickstep/overview_ui_overrides/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
@@ -148,10 +144,9 @@
$(call all-java-files-under, go/quickstep/src)
LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/quickstep/res \
- $(LOCAL_PATH)/go/res \
$(LOCAL_PATH)/go/quickstep/res \
- $(LOCAL_PATH)/go/quickstep/overview_ui_overrides/res
+ $(LOCAL_PATH)/go/res \
+ $(LOCAL_PATH)/quickstep/res
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PROGUARD_ENABLED := full
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 4fd2e40..4e72260 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -116,8 +116,7 @@
android:theme="@style/AppItemActivityTheme"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
- android:exported="true"
- android:label="@string/action_add_to_workspace" >
+ android:exported="true">
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
<action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
diff --git a/OWNERS b/OWNERS
index 1d6ad8c..daad057 100644
--- a/OWNERS
+++ b/OWNERS
@@ -4,6 +4,13 @@
# People who can approve changes for submission
#
+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
diff --git a/SharedLibWrapper/build.gradle b/SharedLibWrapper/build.gradle
deleted file mode 100644
index 674e38a..0000000
--- a/SharedLibWrapper/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply plugin: 'java'
-
-final String ANDROID_TOP = "${rootDir}/../../.."
-final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
-
-sourceSets {
- main {
- java.srcDirs = ["${ANDROID_TOP}/frameworks/lib/systemui/SharedLibWrapper/src"]
- }
-}
-
-sourceCompatibility = 1.8
-
-dependencies {
- implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
- compileOnly fileTree(dir: "$ANDROID_TOP/prebuilts/fullsdk-${org.gradle.internal.os.OperatingSystem.current().isMacOsX() ? "darwin" : "linux"}/platforms/${COMPILE_SDK}", include: 'android.jar')
-}
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index f36439d..2671604 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -24,6 +24,8 @@
<uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
+ <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
+
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:fullBackupOnly="true"
diff --git a/go/OWNERS b/go/OWNERS
new file mode 100644
index 0000000..903b3c4
--- /dev/null
+++ b/go/OWNERS
@@ -0,0 +1,2 @@
+rajekumar@google.com
+spivack@google.com
diff --git a/go/quickstep/overview_ui_overrides/res/values/config.xml b/go/quickstep/overview_ui_overrides/res/values/config.xml
deleted file mode 100644
index ec21a01..0000000
--- a/go/quickstep/overview_ui_overrides/res/values/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2021 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<resources>
- <string name="task_overlay_factory_class" translatable="false">
- com.android.quickstep.TaskOverlayFactoryGo</string>
-</resources>
\ No newline at end of file
diff --git a/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml b/go/quickstep/res/layout/overview_actions_container.xml
similarity index 92%
rename from go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
rename to go/quickstep/res/layout/overview_actions_container.xml
index b438da3..866eac6 100644
--- a/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
+++ b/go/quickstep/res/layout/overview_actions_container.xml
@@ -14,17 +14,18 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!-- NOTE! don't add dimensions for margins / gravity to root view in this file, they need to be
+ loaded at runtime. -->
<com.android.quickstep.views.GoOverviewActionsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="@dimen/overview_actions_height"
- android:layout_gravity="center_horizontal|bottom">
+ android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/action_buttons"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal">
<Space
diff --git a/go/quickstep/res/values/config.xml b/go/quickstep/res/values/config.xml
index 9dca137..402bf9a 100644
--- a/go/quickstep/res/values/config.xml
+++ b/go/quickstep/res/values/config.xml
@@ -20,5 +20,7 @@
<string name="niu_actions_package" translatable="false"/>
<!-- Feature Flags -->
- <bool name="enable_niu_actions">false</bool>
+ <bool name="enable_niu_actions">true</bool>
+
+ <string name="task_overlay_factory_class" translatable="false">com.android.quickstep.TaskOverlayFactoryGo</string>
</resources>
\ No newline at end of file
diff --git a/go/quickstep/res/values/strings.xml b/go/quickstep/res/values/strings.xml
index 71e2f3a..61c8cd9 100644
--- a/go/quickstep/res/values/strings.xml
+++ b/go/quickstep/res/values/strings.xml
@@ -10,5 +10,5 @@
<!-- Label for a button that translates a screenshot of the current app. [CHAR_LIMIT=40] -->
<string name="action_translate">Translate</string>
<!-- Label for a button that triggers Search on a screenshot of the current app. [CHAR_LIMIT=40] -->
- <string name="action_search">Search</string>
+ <string name="action_search">Lens</string>
</resources>
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index b102a39..350e0d1 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -20,13 +20,17 @@
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
import android.annotation.SuppressLint;
+import android.app.assist.AssistContent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Matrix;
import android.os.SystemClock;
import android.text.TextUtils;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.R;
+import com.android.quickstep.util.AssistContentRequester;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.systemui.shared.recents.model.Task;
@@ -40,6 +44,8 @@
public static final String ACTION_TRANSLATE = "com.android.quickstep.ACTION_TRANSLATE";
public static final String ACTION_SEARCH = "com.android.quickstep.ACTION_SEARCH";
public static final String ELAPSED_NANOS = "niu_actions_elapsed_realtime_nanos";
+ public static final String ACTIONS_URL = "niu_actions_app_url";
+ private static final String TAG = "TaskOverlayFactoryGo";
// Empty constructor required for ResourceBasedOverride
public TaskOverlayFactoryGo(Context context) {}
@@ -56,8 +62,8 @@
* @param <T> The type of View in which the overlay will be placed
*/
public static final class TaskOverlayGo<T extends OverviewActionsView> extends TaskOverlay {
-
- private String mPackageName;
+ private String mNIUPackageName;
+ private String mWebUrl;
private TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
super(taskThumbnailView);
@@ -70,29 +76,57 @@
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
boolean rotated) {
getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
- mPackageName =
+ mNIUPackageName =
mApplicationContext.getResources().getString(R.string.niu_actions_package);
- if (thumbnail == null || TextUtils.isEmpty(mPackageName)) {
+ if (thumbnail == null || TextUtils.isEmpty(mNIUPackageName)) {
return;
}
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
- boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+ boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
+
+ int taskId = task.key.id;
+ AssistContentRequester contentRequester =
+ new AssistContentRequester(mApplicationContext);
+ contentRequester.requestAssistContent(taskId, this::onAssistContentReceived);
}
- private void sendNIUIntent(String actionType) {
+ /** Provide Assist Content to the overlay. */
+ @VisibleForTesting
+ public void onAssistContentReceived(AssistContent assistContent) {
+ mWebUrl = assistContent.getWebUri() != null
+ ? assistContent.getWebUri().toString() : null;
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ mWebUrl = null;
+ }
+
+ /**
+ * Creates and sends an Intent corresponding to the button that was clicked
+ */
+ @VisibleForTesting
+ public void sendNIUIntent(String actionType) {
Intent intent = createNIUIntent(actionType);
mImageApi.shareAsDataWithExplicitIntent(/* crop */ null, intent);
}
private Intent createNIUIntent(String actionType) {
- return new Intent(actionType)
+ Intent intent = new Intent(actionType)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
- .setPackage(mPackageName)
+ .setPackage(mNIUPackageName)
.putExtra(ELAPSED_NANOS, SystemClock.elapsedRealtimeNanos());
+
+ if (mWebUrl != null) {
+ intent.putExtra(ACTIONS_URL, mWebUrl);
+ }
+
+ return intent;
}
protected class OverlayUICallbacksGoImpl extends OverlayUICallbacksImpl
@@ -128,6 +162,11 @@
}
}
}
+
+ @VisibleForTesting
+ public void setImageActionsAPI(ImageActionsApi imageActionsApi) {
+ mImageApi = imageActionsApi;
+ }
}
/**
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
new file mode 100644
index 0000000..38c9919
--- /dev/null
+++ b/quickstep/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
+filegroup {
+ name: "launcher3-quickstep-robolectric-src",
+ path: "robolectric_tests",
+ srcs: ["robolectric_tests/src/**/*.java"],
+}
diff --git a/quickstep/recents_ui_overrides/src/REMOVED.txt b/quickstep/recents_ui_overrides/src/REMOVED.txt
deleted file mode 100644
index c3a3eaf..0000000
--- a/quickstep/recents_ui_overrides/src/REMOVED.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Temp file to prevent build breakage.
-Will be removed in followup cl.
\ No newline at end of file
diff --git a/quickstep/res/drawable/task_menu_bg.xml b/quickstep/res/drawable/task_menu_bg.xml
index 7334d98..a60defc 100644
--- a/quickstep/res/drawable/task_menu_bg.xml
+++ b/quickstep/res/drawable/task_menu_bg.xml
@@ -14,25 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:gravity="bottom">
- <!-- Shadow -->
- <shape>
- <gradient android:angle="270"
- android:endColor="@android:color/transparent"
- android:startColor="#26000000" />
- <size android:height="@dimen/task_card_menu_shadow_height" />
- </shape>
- </item>
- <item android:bottom="@dimen/task_card_menu_shadow_height">
- <!-- Background -->
- <shape>
- <corners
- android:topLeftRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp" />
- <solid android:color="?attr/popupColorPrimary" />
- </shape>
- </item>
-</layer-list>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="?attr/popupColorPrimary" />
+</shape>
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index cd64a94..55400a7 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -19,6 +19,14 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
+ <com.android.quickstep.views.SplitPlaceholderView
+ android:id="@+id/split_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_placeholder_size"
+ android:background="@android:color/white"
+ android:alpha=".8"
+ android:visibility="gone" />
+
<com.android.quickstep.fallback.RecentsDragLayer
android:id="@+id/drag_layer"
android:layout_width="match_parent"
diff --git a/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
similarity index 88%
rename from quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
rename to quickstep/res/layout/overview_actions_container.xml
index d15a2d2..b652252 100644
--- a/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -14,16 +14,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!-- NOTE! don't add dimensions for margins / gravity to root view in this file, they need to be
+ loaded at runtime. -->
<com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="@dimen/overview_actions_height"
- android:layout_gravity="center_horizontal|bottom">
+ android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/action_buttons"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal">
<Space
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 34ff91d..1ee726e 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -21,6 +21,5 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/recents_clear_all"
- android:textColor="?attr/workspaceTextColor"
- android:textSize="14sp"
- android:translationY="@dimen/task_thumbnail_half_top_margin" />
\ No newline at end of file
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index fe57e9b..d7bcd9e 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -15,6 +15,13 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.android.quickstep.views.SplitPlaceholderView
+ android:id="@+id/split_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_placeholder_size"
+ android:background="@android:color/darker_gray"
+ android:visibility="gone" />
+
<com.android.quickstep.views.LauncherRecentsView
android:id="@+id/overview_panel"
android:layout_width="match_parent"
@@ -22,7 +29,6 @@
android:accessibilityPaneTitle="@string/accessibility_recent_apps"
android:clipChildren="false"
android:clipToPadding="false"
- android:theme="@style/HomeScreenElementTheme"
android:visibility="invisible" />
<include
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 0f9a6aa..7e5b85c 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -13,6 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!-- NOTE! don't add dimensions for margins / paddings / sizes that change per orientation to this
+ file, they need to be loaded at runtime. -->
<com.android.quickstep.views.TaskView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
@@ -24,8 +26,7 @@
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/task_thumbnail_top_margin"/>
+ android:layout_height="match_parent"/>
<com.android.quickstep.views.IconView
android:id="@+id/icon"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index b124b33..84e2304 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -25,7 +25,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/taskbar_background"
- android:gravity="center"
- android:animateLayoutChanges="true"/>
+ android:gravity="center"/>
</com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
index 6e1aa1e..87649f7 100644
--- a/quickstep/res/layout/taskbar_divider.xml
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -18,6 +18,4 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/taskbar_divider_thickness"
android:layout_height="@dimen/taskbar_divider_height"
- android:layout_marginStart="@dimen/taskbar_icon_spacing"
- android:layout_marginEnd="@dimen/taskbar_icon_spacing"
android:background="@color/taskbar_divider" />
\ No newline at end of file
diff --git a/quickstep/res/layout/scrim_view.xml b/quickstep/res/layout/taskbar_view.xml
similarity index 68%
rename from quickstep/res/layout/scrim_view.xml
rename to quickstep/res/layout/taskbar_view.xml
index 2cc37f9..34a88ea 100644
--- a/quickstep/res/layout/scrim_view.xml
+++ b/quickstep/res/layout/taskbar_view.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +13,14 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.ShelfScrimView
+
+<com.android.launcher3.taskbar.TaskbarView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/taskbar_view"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/scrim_view" />
\ No newline at end of file
+ android:layout_height="@dimen/taskbar_size"
+ android:background="@android:color/transparent"
+ android:layout_gravity="bottom"
+ android:gravity="center"
+ android:visibility="gone" />
+
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index c03eaa2..7cb01f6 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -16,4 +16,6 @@
-->
<resources>
<dimen name="task_card_menu_horizontal_padding">24dp</dimen>
+
+ <dimen name="overview_task_margin">8dp</dimen>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index be66104..b2ff770 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -15,6 +15,7 @@
-->
<resources>
<string name="overscroll_plugin_factory_class" translatable="false" />
+ <string name="task_overlay_factory_class" translatable="false"/>
<!-- Activities which block home gesture -->
<string-array name="gesture_blocking_activities" translatable="false">
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9773366..3e6c78f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -15,21 +15,30 @@
-->
<resources>
-
- <dimen name="task_thumbnail_top_margin">24dp</dimen>
- <dimen name="task_thumbnail_half_top_margin">12dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
- <dimen name="task_icon_top_margin">-16dp</dimen>
+ <dimen name="task_thumbnail_icon_size_grid">32dp</dimen>
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
+ <dimen name="overview_proactive_row_height">48dp</dimen>
+ <dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
+
+ <dimen name="overview_minimum_next_prev_size">48dp</dimen>
+ <dimen name="overview_task_margin">16dp</dimen>
+
<!-- Overrideable in overlay that provides the Overview Actions. -->
- <dimen name="overview_actions_height">66dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">16dp</dimen>
+ <dimen name="overview_actions_height">48dp</dimen>
+ <dimen name="overview_actions_bottom_margin_gesture">12dp</dimen>
<dimen name="overview_actions_bottom_margin_three_button">8dp</dimen>
<dimen name="overview_actions_horizontal_margin">16dp</dimen>
- <dimen name="recents_row_spacing">48dp</dimen>
+ <dimen name="overview_grid_top_margin">77dp</dimen>
+ <dimen name="overview_grid_bottom_margin">90dp</dimen>
+ <dimen name="overview_grid_side_margin">54dp</dimen>
+ <dimen name="overview_grid_row_spacing">42dp</dimen>
+ <dimen name="overview_grid_focus_vertical_margin">130dp</dimen>
+ <dimen name="split_placeholder_size">110dp</dimen>
+
<dimen name="recents_page_spacing">16dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
@@ -59,10 +68,6 @@
<dimen name="task_card_menu_option_vertical_padding">8dp</dimen>
<dimen name="task_card_menu_shadow_height">3dp</dimen>
<dimen name="task_card_menu_horizontal_padding">0dp</dimen>
- <dimen name="portrait_task_card_horz_space_big_overview">132dp</dimen>
- <dimen name="portrait_modal_task_card_horz_space">60dp</dimen>
- <dimen name="landscape_task_card_horz_space">200dp</dimen>
- <dimen name="multi_window_task_card_horz_space">100dp</dimen>
<!-- Copied from framework resource:
docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
<dimen name="multi_window_task_divider_size">10dp</dimen>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index 605774d..705ec9d 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -17,8 +17,6 @@
<!-- Class overrides for launcher with quickstep. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
-
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
<string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 9a4487c..2a06830 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -22,8 +22,6 @@
<string name="derived_app_name" translatable="false">Quickstep</string>
<!-- Options for recent tasks -->
- <!-- Title for an option to enter split screen mode for a given app -->
- <string name="recent_task_option_split_screen">Split screen</string>
<!-- Title for an option to keep an app pinned to the screen until it is unpinned -->
<string name="recent_task_option_pin">Pin</string>
<!-- Title for an option to enter freeform mode for a given app -->
@@ -61,7 +59,7 @@
<string name="all_apps_prediction_tip">Your predicted apps</string>
<!-- Content description for a close button. [CHAR LIMIT=NONE] -->
- <string name="gesture_tutorial_close_button_content_description" translatable="false">Close</string>
+ <string name="gesture_tutorial_close_button_content_description" translatable="false">Close</string>
<!-- Hotseat educational strings for users who don't qualify for migration -->
<string name="hotseat_edu_title_migrate">Get app suggestions on the bottom row of your Home screen</string>
@@ -93,70 +91,61 @@
<!-- content description for hotseat items -->
<string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
- <!-- primary educational text shown for first time search users -->
- <string name="search_edu_primary">Search your phone for apps, people, settings and more!</string>
- <!-- secondary educational text shown for first time search users -->
- <string name="search_edu_secondary">Tap keyboard search button to launch the first search
- result.</string>
-
- <!-- Dismiss button string for search education view -->
- <string name="search_edu_dismiss">Got it.</string>
-
- <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
- <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
- <!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
- <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge" translatable="false">Start at the right edge and swipe toward the middle</string>
- <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
- <string name="back_gesture_feedback_swipe_too_far_from_right_edge" translatable="false">Make sure you swipe from the far right edge</string>
- <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
- <string name="back_gesture_feedback_cancelled_right_edge" translatable="false">Make sure you swipe straight to the left and let go</string>
-
- <!-- Title shown during interactive part of Back gesture tutorial for left edge. [CHAR LIMIT=30] -->
- <string name="back_gesture_tutorial_playground_title_swipe_inward_left_edge" translatable="false">Try the other side</string>
- <!-- Subtitle shown during interactive parts of Back gesture tutorial for left edge. [CHAR LIMIT=60] -->
- <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge" translatable="false">That\'s it! Now try swiping from the left edge.</string>
<!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
- <string name="back_gesture_feedback_swipe_too_far_from_left_edge" translatable="false">Make sure you swipe from the far left edge</string>
+ <string name="back_gesture_feedback_swipe_too_far_from_left_edge">Make sure you swipe from the far-left edge.</string>
<!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
- <string name="back_gesture_feedback_cancelled_left_edge" translatable="false">Make sure you swipe straight to the right and let go</string>
-
+ <string name="back_gesture_feedback_cancelled_left_edge">Make sure you swipe from the left edge to the middle of the screen and let go.</string>
+ <!-- Feedback shown after completing the left back gesture before continuing on to the right edge. [CHAR LIMIT=60] -->
+ <string name="back_gesture_feedback_complete_left_edge">That\'s it! Now try swiping from the right edge.</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_swipe_too_far_from_right_edge">Make sure you swipe from the far-right edge.</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_cancelled_right_edge">Make sure you swipe from the right edge to the middle of the screen and let go.</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_complete">You completed the go back gesture. Next up, learn how to go Home.</string>
<!-- Feedback shown during interactive parts of Back gesture tutorial when the gesture is within the nav bar region. [CHAR LIMIT=100] -->
- <string name="back_gesture_feedback_swipe_in_nav_bar" translatable="false">Make sure you don\'t swipe too close to the bottom of the screen</string>
+ <string name="back_gesture_feedback_swipe_in_nav_bar">Make sure you don\'t swipe too close to the bottom of the screen.</string>
<!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
- <string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
-
- <!-- Title shown during interactive part of Home gesture tutorial. [CHAR LIMIT=30] -->
- <string name="home_gesture_tutorial_playground_title" translatable="false">Tutorial: Go Home</string>
- <!-- Subtitle shown during interactive parts of Home gesture tutorial. [CHAR LIMIT=60] -->
- <string name="home_gesture_tutorial_playground_subtitle" translatable="false">Try swiping upward from the bottom edge of the screen</string>
+ <string name="back_gesture_tutorial_confirm_subtitle">To change the sensitivity of the back gesture, go to Settings</string>
<!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
- <string name="home_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
- <!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
- <string name="home_gesture_feedback_overview_detected" translatable="false">Make sure you don\'t pause before letting go</string>
- <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
- <string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
+ <!-- Introduction title for the Back gesture tutorial. [CHAR LIMIT=100] -->
+ <string name="back_gesture_intro_title">Swipe to go back</string>
+ <!-- Introduction subtitle for the Back gesture tutorial. [CHAR LIMIT=100] -->
+ <string name="back_gesture_intro_subtitle">To go back to the last screen, swipe from the left or right edge to the middle of the screen.</string>
- <!-- Title shown during interactive part of Overview gesture tutorial. [CHAR LIMIT=30] -->
- <string name="overview_gesture_tutorial_playground_title" translatable="false">Tutorial: Switch Apps</string>
- <!-- Subtitle shown during interactive parts of Overview gesture tutorial. [CHAR LIMIT=60] -->
- <string name="overview_gesture_tutorial_playground_subtitle" translatable="false">Swipe up from the bottom of the screen and hold</string>
+ <string name="home_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
+ <!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
+ <string name="home_gesture_feedback_overview_detected">Make sure you don\'t pause before letting go.</string>
+ <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
+ <string name="home_gesture_feedback_wrong_swipe_direction">Make sure you swipe straight up.</string>
+ <string name="home_gesture_feedback_complete">You completed the go Home gesture. Next up, learn how to switch apps.</string>
+ <!-- Introduction title for the Home gesture tutorial. [CHAR LIMIT=100] -->
+ <string name="home_gesture_intro_title">Swipe to go home</string>
+ <!-- Introduction subtitle for the Home gesture tutorial. [CHAR LIMIT=100] -->
+ <string name="home_gesture_intro_subtitle">Swipe up from the bottom of your screen. This gesture always takes you to the Home screen.</string>
+
<!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
- <string name="overview_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
+ <string name="overview_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
<!-- Feedback shown during interactive parts of Overview gesture tutorial when the Home gesture is detected. [CHAR LIMIT=100] -->
- <string name="overview_gesture_feedback_home_detected" translatable="false">Try holding the window for longer before releasing</string>
+ <string name="overview_gesture_feedback_home_detected">Try holding the window for longer before releasing.</string>
<!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
- <string name="overview_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up and pause</string>
+ <string name="overview_gesture_feedback_wrong_swipe_direction">Make sure you swipe straight up, then pause.</string>
+ <string name="overview_gesture_feedback_complete">You completed the switch apps gesture. You\'re ready to use your phone!</string>
+ <!-- Introduction title for the Overview gesture tutorial. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_intro_title">Swipe to switch apps</string>
+ <!-- Introduction subtitle for the Overview gesture tutorial. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_intro_subtitle">Swipe up from the bottom of your screen, hold, then release.</string>
<!-- Title shown during interactive part of Assistant gesture tutorial. [CHAR LIMIT=30] -->
<string name="assistant_gesture_tutorial_playground_title" translatable="false">Tutorial: Assistant</string>
<!-- Subtitle shown during interactive parts of Assistant gesture tutorial. [CHAR LIMIT=60] -->
<string name="assistant_gesture_tutorial_playground_subtitle" translatable="false">Try swiping diagonally from a bottom corner of the screen</string>
<!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture is started too far from the corner. [CHAR LIMIT=100] -->
- <string name="assistant_gesture_feedback_swipe_too_far_from_corner" translatable="false">Make sure you swipe from a bottom corner of the screen</string>
+ <string name="assistant_gesture_feedback_swipe_too_far_from_corner" translatable="false">Make sure you swipe from a bottom corner of the screen.</string>
<!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go diagonally enough. [CHAR LIMIT=100] -->
- <string name="assistant_gesture_feedback_swipe_not_diagonal" translatable="false">Make sure you swipe diagonally</string>
+ <string name="assistant_gesture_feedback_swipe_not_diagonal" translatable="false">Make sure you swipe diagonally.</string>
<!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go far enough. [CHAR LIMIT=100] -->
- <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further</string>
+ <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further.</string>
<!-- Title shown in sandbox mode part of gesture tutorial. [CHAR LIMIT=30] -->
<string name="sandbox_mode_title" translatable="false">Sandbox Mode</string>
@@ -174,11 +163,19 @@
<string name="sandbox_mode_back_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the left/right edge of the screen</string>
<!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
- <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
- <!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
- <string name="gesture_tutorial_action_button_label_done" translatable="false">Done</string>
+ <string name="gesture_tutorial_confirm_title">All set</string>
+ <!-- Button text shown on a button on the feedback popup to proceed to the next tutorial step. [CHAR LIMIT=14] -->
+ <string name="gesture_tutorial_action_button_label_next">Next</string>
+ <!-- Button text shown on a button on the feedback popup to complete the tutorial. [CHAR LIMIT=14] -->
+ <string name="gesture_tutorial_action_button_label_done">Done</string>
<!-- Button text shown on a button to go to Settings. [CHAR LIMIT=14] -->
- <string name="gesture_tutorial_action_button_label_settings" translatable="false">Settings</string>
+ <string name="gesture_tutorial_action_button_label_settings">Settings</string>
+ <!-- Feedback title to try again. [CHAR LIMIT=30] -->
+ <string name="gesture_tutorial_try_again">Try again</string>
+ <!-- Feedback title for a successful gesture. [CHAR LIMIT=30] -->
+ <string name="gesture_tutorial_nice">Nice!</string>
+ <!-- Feedback subtext displaying the current step and the total number of steps for the tutorial. [CHAR LIMIT=30] -->
+ <string name="gesture_tutorial_step">Tutorial <xliff:g id="current">%1$d</xliff:g>/<xliff:g id="total">%2$d</xliff:g></string>
<!-- ******* Overview ******* -->
<!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
@@ -187,4 +184,14 @@
<string name="action_screenshot">Screenshot</string>
<!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
<string name="blocked_by_policy">This action isn\'t allowed by the app or your organization</string>
+
+ <!-- ******* Skip tutorial dialog ******* -->
+ <!-- Title for the dialog that allows the user to skip the gesture navigation tutorial. [CHAR_LIMIT=40] -->
+ <string name="skip_tutorial_dialog_title">Skip navigation tutorial?</string>
+ <!-- Subtitle for the dialog that allows the user to skip the gesture navigation tutorial. [CHAR_LIMIT=40] -->
+ <string name="skip_tutorial_dialog_subtitle">You can find this later in the Tips app</string>
+ <!-- Button text shown on a button on the tutorial skip dialog to return to the tutorial. [CHAR LIMIT=14] -->
+ <string name="gesture_tutorial_action_button_label_cancel">Cancel</string>
+ <!-- Button text shown on a button on the tutorial skip dialog to exit the tutorial. [CHAR LIMIT=14] -->
+ <string name="gesture_tutorial_action_button_label_skip">Skip</string>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index df089f6..e56397a 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -81,7 +81,7 @@
parent="@android:style/Widget.DeviceDefault.Button.Borderless">
<item name="android:textColor">@color/overview_button</item>
<item name="android:drawableTint">@color/overview_button</item>
- <item name="android:tint">?attr/workspaceTextColor</item>
+ <item name="android:tint">?android:attr/textColorPrimary</item>
<item name="android:drawablePadding">8dp</item>
<item name="android:textAllCaps">false</item>
</style>
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index eca27b5..e771962 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -247,10 +247,7 @@
}
@Test
- @Ignore("There's too much that goes into needing to mock a real motion event so the "
- + "transforms in native code get applied correctly. Once that happens then maybe we can"
- + " write slightly more complex unit tests")
- public void applyTransform_taskNotFrozen_90Rotate_inTwoRegions() {
+ public void applyTransform_taskNotFrozen_90Rotate_withTwoRegions() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
mTouchTransformer.enableMultipleRegions(true, mInfo);
mTouchTransformer
@@ -262,6 +259,7 @@
// Portrait point in landscape orientation axis
MotionEvent inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 10);
mTouchTransformer.transform(inRegion1_down);
+ // no-op
mTouchTransformer.transform(inRegion2);
assertTrue(mTouchTransformer.touchInValidSwipeRegions(
inRegion1_down.getX(), inRegion1_down.getY()));
@@ -269,9 +267,19 @@
assertFalse(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
mTouchTransformer.transform(inRegion1_up);
+ }
- // Set the new region with this MotionEvent.ACTION_DOWN
- inRegion2 = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
+ @Test
+ public void applyTransform_90Rotate_inRotatedRegion() {
+ // Create regions for both 0 Rotation and 90 Rotation
+ mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.enableMultipleRegions(true, mInfo);
+ mTouchTransformer
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ // Portrait point in landscape orientation axis
+ float x1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0);
+ // bottom of screen, from landscape perspective right side of screen
+ MotionEvent inRegion2 = generateAndTransformMotionEvent(MotionEvent.ACTION_DOWN, x1, 370);
assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
}
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index 7049af0..9df9ab1 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -37,6 +37,7 @@
@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
+@org.junit.Ignore
public class RecentsActivityTest {
@Test
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 688f323..88079ae 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -17,6 +17,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -29,6 +31,7 @@
import com.android.launcher3.shadows.LShadowDisplay;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.LauncherActivityInterface;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import org.hamcrest.Description;
@@ -162,7 +165,7 @@
@Override
public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
- proxy.onBuildTargetParams(builder, null, this);
+ proxy.onBuildTargetParams(builder, mock(RemoteAnimationTargetCompat.class), this);
return new SurfaceParams[] {builder.build()};
}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 56d25f2..e9ded8a 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -21,11 +21,11 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_SIZE;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
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.ActivityOptions;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
@@ -46,7 +46,9 @@
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarController;
import com.android.launcher3.taskbar.TaskbarStateHandler;
+import com.android.launcher3.taskbar.TaskbarView;
import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.RecentsModel;
@@ -57,8 +59,10 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -73,12 +77,13 @@
implements NavigationModeChangeListener {
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).setBackButtonAlpha(
+ (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setNavBarButtonAlpha(
Float.intBitsToFloat(arg1), arg2 != 0);
private OverviewActionsView mActionsView;
@@ -86,20 +91,21 @@
private @Nullable TaskbarController mTaskbarController;
private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
// Will be updated when dragging from taskbar.
- private DragOptions mWorkspaceDragOptions = new DragOptions();
+ private @Nullable DragOptions mNextWorkspaceDragOptions = null;
+ private SplitPlaceholderView mSplitPlaceholderView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
addMultiWindowModeChangedListener(mDepthController);
}
@Override
public void onDestroy() {
- SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+ mAppTransitionManager.onActivityDestroyed();
+ SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
if (mTaskbarController != null) {
mTaskbarController.cleanup();
}
@@ -107,6 +113,10 @@
super.onDestroy();
}
+ public QuickstepTransitionManager getAppTransitionManager() {
+ return mAppTransitionManager;
+ }
+
@Override
public void onNavigationModeChanged(Mode newMode) {
getDragLayer().recreateControllers();
@@ -206,9 +216,17 @@
SysUINavigationMode.INSTANCE.get(this).updateMode();
mActionsView = findViewById(R.id.overview_actions_view);
- ((RecentsView) getOverviewPanel()).init(mActionsView);
+ mSplitPlaceholderView = findViewById(R.id.split_placeholder);
+ RecentsView overviewPanel = (RecentsView) getOverviewPanel();
+ mSplitPlaceholderView.init(
+ new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this))
+ );
+ overviewPanel.init(mActionsView, mSplitPlaceholderView);
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
+ mAppTransitionManager = new QuickstepTransitionManager(this);
+ mAppTransitionManager.registerRemoteAnimations();
+
addTaskbarIfNecessary();
addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
}
@@ -227,9 +245,10 @@
mTaskbarController = null;
}
if (mDeviceProfile.isTaskbarPresent) {
+ TaskbarView taskbarViewOnHome = (TaskbarView) mHotseat.getTaskbarView();
TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
mTaskbarController = new TaskbarController(this,
- taskbarActivityContext.getTaskbarContainerView());
+ taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome);
mTaskbarController.init();
}
}
@@ -238,6 +257,10 @@
return (T) mActionsView;
}
+ public SplitPlaceholderView getSplitPlaceholderView() {
+ return mSplitPlaceholderView;
+ }
+
@Override
protected void closeOpenViews(boolean animate) {
super.closeOpenViews(animate);
@@ -270,19 +293,29 @@
return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v);
}
- @Override
- public DragOptions getDefaultWorkspaceDragOptions() {
- return mWorkspaceDragOptions;
+ public boolean supportsAdaptiveIconAnimation(View clickedView) {
+ return mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+ && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
+ && !isViewInTaskbar(clickedView);
}
- public void setWorkspaceDragOptions(DragOptions dragOptions) {
- mWorkspaceDragOptions = dragOptions;
+ @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) {
- QuickstepAppTransitionManagerImpl appTransitionManager =
- (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
+ QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
@Override
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
@@ -308,6 +341,14 @@
}
@Override
+ public float getNormalTaskbarScale() {
+ if (mTaskbarController != null) {
+ return mTaskbarController.getTaskbarScaleOnHome();
+ }
+ return super.getNormalTaskbarScale();
+ }
+
+ @Override
public void onDragLayerHierarchyChanged() {
onLauncherStateOrFocusChanged();
}
@@ -351,8 +392,10 @@
*/
private void onLauncherStateOrFocusChanged() {
boolean shouldBackButtonBeHidden = shouldBackButtonBeHidden(getStateManager().getState());
- UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
- shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+ if (SysUINavigationMode.getMode(this) == TWO_BUTTONS) {
+ UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
+ shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+ }
if (getDragLayer() != null) {
getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
}
@@ -374,10 +417,14 @@
}
@Override
- public ActivityOptions getActivityLaunchOptions(View v) {
- ActivityOptions activityOptions = super.getActivityLaunchOptions(v);
- if (activityOptions != null && mLastTouchUpTime > 0) {
- ActivityOptionsCompat.setLauncherSourceInfo(activityOptions, mLastTouchUpTime);
+ public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+ ActivityOptionsWrapper activityOptions =
+ mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+ ? mAppTransitionManager.getActivityLaunchOptions(this, v)
+ : super.getActivityLaunchOptions(v);
+ if (mLastTouchUpTime > 0) {
+ ActivityOptionsCompat.setLauncherSourceInfo(
+ activityOptions.options, mLastTouchUpTime);
}
return activityOptions;
}
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 588d676..be98157 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
@@ -29,6 +30,7 @@
import android.os.Handler;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -37,8 +39,6 @@
@TargetApi(Build.VERSION_CODES.P)
public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
- private static final String TAG = "LauncherAnimationRunner";
-
private final Handler mHandler;
private final boolean mStartAtFrontOfQueue;
private AnimationResult mAnimationResult;
@@ -66,10 +66,7 @@
Runnable runnable) {
Runnable r = () -> {
finishExistingAnimation();
- mAnimationResult = new AnimationResult(() -> {
- UI_HELPER_EXECUTOR.execute(runnable);
- mAnimationResult = null;
- });
+ mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);
onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,
mAnimationResult);
};
@@ -126,37 +123,60 @@
public static final class AnimationResult {
- private final Runnable mFinishRunnable;
+ private final Runnable mSyncFinishRunnable;
+ private final Runnable mASyncFinishRunnable;
private AnimatorSet mAnimator;
+ private Runnable mOnCompleteCallback;
private boolean mFinished = false;
private boolean mInitialized = false;
- private AnimationResult(Runnable finishRunnable) {
- mFinishRunnable = finishRunnable;
+ private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) {
+ mSyncFinishRunnable = syncFinishRunnable;
+ mASyncFinishRunnable = asyncFinishRunnable;
}
@UiThread
private void finish() {
if (!mFinished) {
- mFinishRunnable.run();
+ mSyncFinishRunnable.run();
+ UI_HELPER_EXECUTOR.execute(() -> {
+ mASyncFinishRunnable.run();
+ if (mOnCompleteCallback != null) {
+ MAIN_EXECUTOR.execute(mOnCompleteCallback);
+ }
+ });
mFinished = true;
}
}
@UiThread
public void setAnimation(AnimatorSet animation, Context context) {
+ setAnimation(animation, context, null);
+
+ }
+
+ /**
+ * Sets the animation to play for this app launch
+ */
+ @UiThread
+ public void setAnimation(AnimatorSet animation, Context context,
+ @Nullable Runnable onCompleteCallback) {
if (mInitialized) {
throw new IllegalStateException("Animation already initialized");
}
mInitialized = true;
mAnimator = animation;
+ mOnCompleteCallback = onCompleteCallback;
if (mAnimator == null) {
finish();
} else if (mFinished) {
// Animation callback was already finished, skip the animation.
mAnimator.start();
mAnimator.end();
+ if (mOnCompleteCallback != null) {
+ mOnCompleteCallback.run();
+ }
} else {
// Start the animation
mAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
deleted file mode 100644
index ae4bd96..0000000
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.quickstep.TaskViewUtils;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
- * {@link RecentsView}.
- */
-public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
-
- public LauncherAppTransitionManagerImpl(Context context) {
- super(context);
- }
-
- @Override
- protected boolean isLaunchingFromRecents(@NonNull View v,
- @Nullable RemoteAnimationTargetCompat[] targets) {
- return mLauncher.getStateManager().getState().overviewUi
- && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
- }
-
- @Override
- protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
- @NonNull RemoteAnimationTargetCompat[] appTargets,
- @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
- TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
- launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
- mLauncher.getDepthController());
- }
-
- @Override
- protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, float[] alphas,
- float[] trans) {
- RecentsView overview = mLauncher.getOverviewPanel();
- ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
- RecentsView.CONTENT_ALPHA, alphas);
- alpha.setDuration(CONTENT_ALPHA_DURATION);
- alpha.setInterpolator(LINEAR);
- anim.play(alpha);
- overview.setFreezeViewVisibility(true);
-
- ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
- transY.setInterpolator(AGGRESSIVE_EASE);
- transY.setDuration(CONTENT_TRANSLATION_DURATION);
- anim.play(transY);
-
- return () -> {
- overview.setFreezeViewVisibility(false);
- overview.setTranslationY(0);
- mLauncher.getStateManager().reapplyState();
- };
- }
-}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 7fb0d43..5fc79f0 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -46,8 +46,8 @@
@Override
public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
if (mRemoteAnimationProvider != null) {
- QuickstepAppTransitionManagerImpl appTransitionManager =
- (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
+ QuickstepTransitionManager appTransitionManager =
+ ((BaseQuickstepLauncher) 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/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
similarity index 78%
rename from quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
rename to quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c4b6961..009ca27 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -16,6 +16,9 @@
package com.android.launcher3;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -24,7 +27,6 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
@@ -34,6 +36,7 @@
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -44,7 +47,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
-import android.app.ActivityOptions;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -59,27 +61,32 @@
import android.os.SystemProperties;
import android.util.Pair;
import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.config.FeatureFlags;
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.util.ActivityOptionsWrapper;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
+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.InteractionJankMonitorWrapper;
@@ -91,14 +98,14 @@
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.LinkedHashMap;
/**
- * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
- * home and/or all-apps. Not used for 3p launchers.
+ * Manages the opening and closing app transitions from Launcher
*/
-@SuppressWarnings("unused")
-public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
- implements OnDeviceProfileChangeListener {
+public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
private static final String TAG = "QuickstepTransition";
@@ -109,8 +116,8 @@
public static final int STATUS_BAR_TRANSITION_DURATION = 120;
/**
- * Since our animations decelerate heavily when finishing, we want to start status bar animations
- * x ms before the ending.
+ * Since our animations decelerate heavily when finishing, we want to start status bar
+ * animations x ms before the ending.
*/
public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
@@ -132,6 +139,15 @@
private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
(long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
+ public static final int ANIMATION_NAV_FADE_IN_DURATION = 266;
+ public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133;
+ public static final long ANIMATION_DELAY_NAV_FADE_IN =
+ APP_LAUNCH_DURATION - ANIMATION_NAV_FADE_IN_DURATION;
+ public static final Interpolator NAV_FADE_IN_INTERPOLATOR =
+ new PathInterpolator(0f, 0f, 0f, 1f);
+ public static final Interpolator NAV_FADE_OUT_INTERPOLATOR =
+ new PathInterpolator(0.2f, 0f, 1f, 1f);
+
private static final long CROP_DURATION = 375;
private static final long RADIUS_DURATION = 375;
@@ -142,8 +158,7 @@
public static final int CONTENT_ALPHA_DURATION = 217;
protected static final int CONTENT_TRANSLATION_DURATION = 350;
- // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
- public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
+ private static final int MAX_NUM_TASKS = 5;
protected final BaseQuickstepLauncher mLauncher;
@@ -180,7 +195,10 @@
}
};
- public QuickstepAppTransitionManagerImpl(Context context) {
+ // Will never be larger than MAX_NUM_TASKS
+ private LinkedHashMap<Integer, Integer> mTypeForTaskId;
+
+ public QuickstepTransitionManager(Context context) {
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mDragLayer = mLauncher.getDragLayer();
mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
@@ -194,6 +212,23 @@
mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
mLauncher.addOnDeviceProfileChangeListener(this);
+
+ if (supportsSSplashScreen()) {
+ mTypeForTaskId = new LinkedHashMap<Integer, Integer>(MAX_NUM_TASKS) {
+ @Override
+ protected boolean removeEldestEntry(Entry<Integer, Integer> entry) {
+ return size() > MAX_NUM_TASKS;
+ }
+ };
+
+ SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
+ new IStartingWindowListener.Stub() {
+ @Override
+ public void onTaskLaunching(int taskId, int supportedType) {
+ mTypeForTaskId.put(taskId, supportedType);
+ }
+ });
+ }
}
@Override
@@ -201,37 +236,29 @@
mDeviceProfile = dp;
}
- @Override
- public boolean supportsAdaptiveIconAnimation(View clickedView) {
- return hasControlRemoteAppTransitionPermission()
- && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
- && !mLauncher.isViewInTaskbar(clickedView);
- }
-
/**
* @return ActivityOptions with remote animations that controls how the window of the opening
* targets are displayed.
*/
- @Override
- public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
- if (hasControlRemoteAppTransitionPermission()) {
- boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
- mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v);
- RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
- mAppLaunchRunner, true /* startAtFrontOfQueue */);
+ public ActivityOptionsWrapper getActivityLaunchOptions(Launcher launcher, View v) {
+ boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
+ RunnableList onEndCallback = new RunnableList();
+ mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v, onEndCallback);
+ RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
+ mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
- // Note that this duration is a guess as we do not know if the animation will be a
- // recents launch or not for sure until we know the opening app targets.
- long duration = fromRecents
- ? RECENTS_LAUNCH_DURATION
- : APP_LAUNCH_DURATION;
+ // Note that this duration is a guess as we do not know if the animation will be a
+ // recents launch or not for sure until we know the opening app targets.
+ long duration = fromRecents
+ ? RECENTS_LAUNCH_DURATION
+ : APP_LAUNCH_DURATION;
- long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
- - STATUS_BAR_TRANSITION_PRE_DELAY;
- return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
- runner, duration, statusBarTransitionDelay));
- }
- return super.getActivityLaunchOptions(launcher, v);
+ long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
+ - STATUS_BAR_TRANSITION_PRE_DELAY;
+ RemoteAnimationAdapterCompat adapterCompat =
+ new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay);
+ return new ActivityOptionsWrapper(
+ ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
}
/**
@@ -244,8 +271,11 @@
* @param targets apps that are opening/closing
* @return true if the app is launching from recents, false if it most likely is not
*/
- protected abstract boolean isLaunchingFromRecents(@NonNull View v,
- @Nullable RemoteAnimationTargetCompat[] targets);
+ protected boolean isLaunchingFromRecents(@NonNull View v,
+ @Nullable RemoteAnimationTargetCompat[] targets) {
+ return mLauncher.getStateManager().getState().overviewUi
+ && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
+ }
/**
* Composes the animations for a launch from the recents list.
@@ -255,9 +285,14 @@
* @param appTargets the apps that are opening/closing
* @param launcherClosing true if the launcher app is closing
*/
- protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+ protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
- @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing) {
+ TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
+ nonAppTargets, launcherClosing, mLauncher.getStateManager(),
+ mLauncher.getOverviewPanel(), mLauncher.getDepthController());
+ }
private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTargetCompat[] targets) {
boolean isAllOpeningTargetTrs = true;
@@ -282,14 +317,17 @@
private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
boolean launcherClosing) {
// Set the state animation first so that any state listeners are called
// before our internal listeners.
mLauncher.getStateManager().setCurrentAnimation(anim);
- Rect windowTargetBounds = getWindowTargetBounds(appTargets);
- anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds,
- areAllTargetsTranslucent(appTargets)));
+ final int rotationChange = getRotationChange(appTargets);
+ // Note: the targetBounds are relative to the launcher
+ Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange);
+ anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, nonAppTargets,
+ windowTargetBounds, areAllTargetsTranslucent(appTargets), rotationChange));
if (launcherClosing) {
Pair<AnimatorSet, Runnable> launcherContentAnimator =
getLauncherContentAnimator(true /* isAppOpening */,
@@ -318,24 +356,43 @@
* In multiwindow mode, we need to get the final size of the opening app window target to help
* figure out where the floating view should animate to.
*/
- private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets) {
- Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
- if (mLauncher.isInMultiWindowMode()) {
- for (RemoteAnimationTargetCompat target : appTargets) {
- if (target.mode == MODE_OPENING) {
- bounds.set(target.screenSpaceBounds);
- if (target.localBounds != null) {
- bounds.set(target.localBounds);
- } else {
- bounds.offsetTo(target.position.x, target.position.y);
- }
- return bounds;
- }
+ private Rect getWindowTargetBounds(@NonNull RemoteAnimationTargetCompat[] appTargets,
+ int rotationChange) {
+ RemoteAnimationTargetCompat target = null;
+ for (RemoteAnimationTargetCompat t : appTargets) {
+ if (t.mode != MODE_OPENING) continue;
+ target = t;
+ break;
+ }
+ if (target == null) return new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
+ final Rect bounds = new Rect(target.screenSpaceBounds);
+ if (target.localBounds != null) {
+ bounds.set(target.localBounds);
+ } else {
+ bounds.offsetTo(target.position.x, target.position.y);
+ }
+ if (rotationChange != 0) {
+ if ((rotationChange % 2) == 1) {
+ // undoing rotation, so our "original" parent size is actually flipped
+ Utilities.rotateBounds(bounds, mDeviceProfile.heightPx, mDeviceProfile.widthPx,
+ 4 - rotationChange);
+ } else {
+ Utilities.rotateBounds(bounds, mDeviceProfile.widthPx, mDeviceProfile.heightPx,
+ 4 - rotationChange);
}
}
return bounds;
}
+ private int getOpeningTaskId(RemoteAnimationTargetCompat[] appTargets) {
+ for (RemoteAnimationTargetCompat target : appTargets) {
+ if (target.mode == MODE_OPENING) {
+ return target.taskId;
+ }
+ }
+ return -1;
+ }
+
public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
CancellationSignal cancellationSignal) {
mRemoteAnimationProvider = animationProvider;
@@ -393,9 +450,6 @@
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
};
} else if (mLauncher.isInState(OVERVIEW)) {
- AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
- allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
} else {
mDragLayerAlpha.setValue(alphas[0]);
@@ -446,8 +500,27 @@
* @param trans the translation Y values to animator to over time
* @return listener to run when the animation ends
*/
- protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
- float[] alphas, float[] trans);
+ protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
+ float[] alphas, float[] trans) {
+ RecentsView overview = mLauncher.getOverviewPanel();
+ ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+ RecentsView.CONTENT_ALPHA, alphas);
+ alpha.setDuration(CONTENT_ALPHA_DURATION);
+ alpha.setInterpolator(LINEAR);
+ anim.play(alpha);
+ overview.setFreezeViewVisibility(true);
+
+ ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+ transY.setInterpolator(AGGRESSIVE_EASE);
+ transY.setDuration(CONTENT_TRANSLATION_DURATION);
+ anim.play(transY);
+
+ return () -> {
+ overview.setFreezeViewVisibility(false);
+ overview.setTranslationY(0);
+ mLauncher.getStateManager().reapplyState();
+ };
+ }
/**
* @return Animator that controls the window of the opening targets from app icons.
@@ -455,7 +528,8 @@
private Animator getOpeningWindowAnimators(View v,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
- Rect windowTargetBounds, boolean appTargetsAreTranslucent) {
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ Rect windowTargetBounds, boolean appTargetsAreTranslucent, int rotationChange) {
RectF launcherIconBounds = new RectF();
FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
!appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);
@@ -463,16 +537,28 @@
Matrix matrix = new Matrix();
RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
- wallpaperTargets, MODE_OPENING);
+ wallpaperTargets, nonAppTargets, MODE_OPENING);
SurfaceTransactionApplier surfaceApplier =
new SurfaceTransactionApplier(floatingView);
openingTargets.addReleaseCheck(surfaceApplier);
+ RemoteAnimationTargetCompat navBarTarget = openingTargets.getNavBarRemoteAnimationTarget();
int[] dragLayerBounds = new int[2];
mDragLayer.getLocationOnScreen(dragLayerBounds);
+ final boolean hasSplashScreen;
+ if (supportsSSplashScreen()) {
+ int taskId = getOpeningTaskId(appTargets);
+ int type = mTypeForTaskId.getOrDefault(taskId, STARTING_WINDOW_TYPE_NONE);
+ mTypeForTaskId.remove(taskId);
+ hasSplashScreen = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+ } else {
+ hasSplashScreen = false;
+ }
+
AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,
- windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1]);
+ windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],
+ hasSplashScreen);
int left = (int) (prop.cropCenterXStart - prop.cropWidthStart / 2);
int top = (int) (prop.cropCenterYStart - prop.cropHeightStart / 2);
int right = (int) (left + prop.cropWidthStart);
@@ -530,6 +616,11 @@
FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0,
CROP_DURATION, EXAGGERATED_EASE);
+ FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
+ NAV_FADE_OUT_INTERPOLATOR);
+ FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+
@Override
public void onUpdate(float percent) {
// Calculate the size of the scaled icon.
@@ -544,6 +635,10 @@
final int windowCropWidth = crop.width();
final int windowCropHeight = crop.height();
+ if (rotationChange != 0) {
+ Utilities.rotateBounds(crop, mDeviceProfile.widthPx,
+ mDeviceProfile.heightPx, rotationChange);
+ }
// Scale the size of the icon to match the size of the window crop.
float scaleX = iconWidth / windowCropWidth;
@@ -562,7 +657,7 @@
Utilities.scaleRectFAboutCenter(tmpRectF, mIconScaleToFitScreen.value);
float windowTransX0 = tmpRectF.left - offsetX;
float windowTransY0 = tmpRectF.top - offsetY;
- if (ENABLE_SHELL_STARTING_SURFACE) {
+ if (hasSplashScreen) {
windowTransX0 -= crop.left * scale;
windowTransY0 -= crop.top * scale;
}
@@ -583,7 +678,20 @@
if (target.mode == MODE_OPENING) {
matrix.setScale(scale, scale);
- matrix.postTranslate(windowTransX0, windowTransY0);
+ if (rotationChange == 1) {
+ matrix.postTranslate(windowTransY0,
+ mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth));
+ } else if (rotationChange == 2) {
+ matrix.postTranslate(
+ mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth),
+ mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight));
+ } else if (rotationChange == 3) {
+ matrix.postTranslate(
+ mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight),
+ windowTransX0);
+ } else {
+ matrix.postTranslate(windowTransX0, windowTransY0);
+ }
floatingView.update(floatingIconBounds, mIconAlpha.value, percent, 0f,
mWindowRadius.value * scale, true /* isOpening */);
@@ -592,17 +700,25 @@
.withAlpha(1f - mIconAlpha.value)
.withCornerRadius(mWindowRadius.value)
.withShadowRadius(mShadowRadius.value);
- } else {
+ } else if (target.mode == MODE_CLOSING) {
if (target.localBounds != null) {
final Rect localBounds = target.localBounds;
tmpPos.set(target.localBounds.left, target.localBounds.top);
} else {
tmpPos.set(target.position.x, target.position.y);
}
-
- matrix.setTranslate(tmpPos.x, tmpPos.y);
final Rect crop = new Rect(target.screenSpaceBounds);
crop.offsetTo(0, 0);
+
+ if ((rotationChange % 2) == 1) {
+ int tmp = crop.right;
+ crop.right = crop.bottom;
+ crop.bottom = tmp;
+ tmp = tmpPos.x;
+ tmpPos.x = tmpPos.y;
+ tmpPos.y = tmp;
+ }
+ matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
.withWindowCrop(crop)
.withAlpha(1f);
@@ -610,6 +726,21 @@
params[i] = builder.build();
}
surfaceApplier.scheduleApply(params);
+
+ if (navBarTarget != null) {
+ final SurfaceParams.Builder navBuilder =
+ new SurfaceParams.Builder(navBarTarget.leash);
+ if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(windowTransX0, windowTransY0);
+ navBuilder.withMatrix(matrix)
+ .withWindowCrop(crop)
+ .withAlpha(mNavFadeIn.value);
+ } else {
+ navBuilder.withAlpha(mNavFadeOut.value);
+ }
+ surfaceApplier.scheduleApply(navBuilder.build());
+ }
}
});
@@ -638,7 +769,6 @@
/**
* Registers remote animations used when closing apps to home screen.
*/
- @Override
public void registerRemoteAnimations() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
@@ -650,7 +780,7 @@
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
new RemoteAnimationAdapterCompat(
- new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
+ new WrappedLauncherAnimationRunner<>(mHandler, mWallpaperOpenRunner,
false /* startAtFrontOfQueue */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
@@ -659,7 +789,8 @@
definition.addRemoteAnimation(
WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
new RemoteAnimationAdapterCompat(
- new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner,
+ new WrappedLauncherAnimationRunner<>(
+ mHandler, mKeyguardGoingAwayRunner,
true /* startAtFrontOfQueue */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
}
@@ -671,7 +802,6 @@
/**
* Registers remote animations used when closing apps to home screen.
*/
- @Override
public void registerRemoteTransitions() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
@@ -679,18 +809,20 @@
if (hasControlRemoteAppTransitionPermission()) {
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
- new WrappedLauncherAnimationRunner<>(mWallpaperOpenTransitionRunner,
+ new WrappedLauncherAnimationRunner<>(mHandler, mWallpaperOpenTransitionRunner,
false /* startAtFrontOfQueue */));
mLauncherOpenTransition.addHomeOpenCheck();
SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
}
}
- /**
- * Unregisters all remote animations.
- */
- @Override
- public void unregisterRemoteAnimations() {
+ public void onActivityDestroyed() {
+ unregisterRemoteAnimations();
+ unregisterRemoteTransitions();
+ SystemUiProxy.INSTANCE.getNoCreate().setStartingWindowListener(null);
+ }
+
+ private void unregisterRemoteAnimations() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
@@ -705,8 +837,7 @@
}
}
- @Override
- public void unregisterRemoteTransitions() {
+ private void unregisterRemoteTransitions() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
@@ -759,14 +890,26 @@
return unlockAnimator;
}
+ private static int getRotationChange(RemoteAnimationTargetCompat[] appTargets) {
+ int rotationChange = 0;
+ for (RemoteAnimationTargetCompat target : appTargets) {
+ if (Math.abs(target.rotationChange) > Math.abs(rotationChange)) {
+ rotationChange = target.rotationChange;
+ }
+ }
+ return rotationChange;
+ }
+
/**
* Animator that controls the transformations of the windows the targets that are closing.
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
+ final int rotationChange = getRotationChange(appTargets);
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
Matrix matrix = new Matrix();
Point tmpPos = new Point();
+ Rect tmpRect = new Rect();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
float windowCornerRadius = mDeviceProfile.isMultiWindowMode
@@ -793,26 +936,32 @@
tmpPos.set(target.position.x, target.position.y);
}
+ final Rect crop = new Rect(target.screenSpaceBounds);
+ crop.offsetTo(0, 0);
if (target.mode == MODE_CLOSING) {
+ tmpRect.set(target.screenSpaceBounds);
+ if ((rotationChange % 2) != 0) {
+ final int right = crop.right;
+ crop.right = crop.bottom;
+ crop.bottom = right;
+ }
matrix.setScale(mScale.value, mScale.value,
- target.screenSpaceBounds.centerX(),
- target.screenSpaceBounds.centerY());
+ tmpRect.centerX(),
+ tmpRect.centerY());
matrix.postTranslate(0, mDy.value);
matrix.postTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
+ .withWindowCrop(crop)
.withAlpha(mAlpha.value)
.withCornerRadius(windowCornerRadius)
.withShadowRadius(mShadowRadius.value);
- } else {
+ } else if (target.mode == MODE_OPENING) {
matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
+ .withWindowCrop(crop)
.withAlpha(1f);
}
- final Rect crop = new Rect(target.screenSpaceBounds);
- crop.offsetTo(0, 0);
- params[i] = builder
- .withWindowCrop(crop)
- .build();
+ params[i] = builder.build();
}
surfaceApplier.scheduleApply(params);
}
@@ -821,7 +970,16 @@
return closingAnimator;
}
- private boolean hasControlRemoteAppTransitionPermission() {
+ private boolean supportsSSplashScreen() {
+ return hasControlRemoteAppTransitionPermission()
+ && Utilities.ATLEAST_S
+ && ENABLE_SHELL_STARTING_SURFACE;
+ }
+
+ /**
+ * Returns true if we have permission to control remote app transisions
+ */
+ public boolean hasControlRemoteAppTransitionPermission() {
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
}
@@ -861,11 +1019,6 @@
}
@Override
- public Handler getHandler() {
- return mHandler;
- }
-
- @Override
public void onCreateAnimation(int transit,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
@@ -955,15 +1108,12 @@
private final Handler mHandler;
private final View mV;
+ private final RunnableList mOnEndCallback;
- AppLaunchAnimationRunner(Handler handler, View v) {
+ AppLaunchAnimationRunner(Handler handler, View v, RunnableList onEndCallback) {
mHandler = handler;
mV = v;
- }
-
- @Override
- public Handler getHandler() {
- return mHandler;
+ mOnEndCallback = onEndCallback;
}
@Override
@@ -973,19 +1123,18 @@
RemoteAnimationTargetCompat[] nonAppTargets,
LauncherAnimationRunner.AnimationResult result) {
AnimatorSet anim = new AnimatorSet();
-
boolean launcherClosing =
launcherIsATargetWithMode(appTargets, MODE_CLOSING);
final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);
final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV);
if (launchingFromRecents) {
- composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
+ composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
launcherClosing);
} else if (launchingFromTaskbar) {
// TODO
} else {
- composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
+ composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
launcherClosing);
}
@@ -998,7 +1147,7 @@
anim.addListener(mForceInvisibleListener);
}
- result.setAnimation(anim, mLauncher);
+ result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy);
}
}
@@ -1030,7 +1179,8 @@
public final float iconAlphaStart;
AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds,
- RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop) {
+ RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop,
+ boolean hasSplashScreen) {
// Scale the app icon to take up the entire screen. This simplifies the math when
// animating the app window position / scale.
float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width());
@@ -1063,7 +1213,7 @@
alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
: APP_LAUNCH_ALPHA_DOWN_DURATION;
- if (ENABLE_SHELL_STARTING_SURFACE) {
+ if (hasSplashScreen) {
iconAlphaStart = 0;
// TOOD: Share value from shell when available.
diff --git a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
index 03cc28e..16727ec 100644
--- a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
+++ b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import android.os.Handler;
-
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
@@ -25,7 +23,7 @@
* implementation.
*/
public interface WrappedAnimationRunnerImpl {
- Handler getHandler();
+
void onCreateAnimation(int transit,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
diff --git a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
index 1e1631b..e319275 100644
--- a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import android.os.Handler;
+
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.lang.ref.WeakReference;
@@ -40,8 +42,9 @@
extends LauncherAnimationRunner {
private WeakReference<R> mImpl;
- public WrappedLauncherAnimationRunner(R animationRunnerImpl, boolean startAtFrontOfQueue) {
- super(animationRunnerImpl.getHandler(), startAtFrontOfQueue);
+ public WrappedLauncherAnimationRunner(
+ Handler handler, R animationRunnerImpl, boolean startAtFrontOfQueue) {
+ super(handler, startAtFrontOfQueue);
mImpl = new WeakReference<>(animationRunnerImpl);
}
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 66b1a86..d17a5ae 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -16,7 +16,6 @@
package com.android.launcher3.appprediction;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.ALL_APPS;
import android.annotation.TargetApi;
@@ -30,7 +29,6 @@
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.Interpolator;
import androidx.annotation.ColorInt;
import androidx.core.content.ContextCompat;
@@ -41,7 +39,6 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.Themes;
@@ -287,13 +284,6 @@
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- // Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(this, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
- }
-
- @Override
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
setTranslationY(scroll);
mIsScrolledOut = isScrolledOut;
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 83234e3..cc3ccea 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -16,20 +16,14 @@
package com.android.launcher3.appprediction;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.IntProperty;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
@@ -44,7 +38,6 @@
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
@@ -53,8 +46,6 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.util.Themes;
-import com.android.quickstep.AnimatedFloat;
import java.util.ArrayList;
import java.util.List;
@@ -63,22 +54,6 @@
public class PredictionRowView extends LinearLayout implements
OnDeviceProfileChangeListener, FloatingHeaderRow {
- private static final IntProperty<PredictionRowView> TEXT_ALPHA =
- new IntProperty<PredictionRowView>("textAlpha") {
- @Override
- public void setValue(PredictionRowView view, int alpha) {
- view.setTextAlpha(alpha);
- }
-
- @Override
- public Integer get(PredictionRowView view) {
- return view.mIconLastSetTextAlpha;
- }
- };
-
- private static final Interpolator ALPHA_FACTOR_INTERPOLATOR =
- (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
-
private final Launcher mLauncher;
private int mNumPredictedAppsPerRow;
@@ -88,21 +63,9 @@
// The set of predicted apps resolved from the component names and the current set of apps
private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
- private final int mIconTextColor;
- private final int mIconFullTextAlpha;
- private int mIconLastSetTextAlpha;
- // Might use mIconFullTextAlpha instead of mIconLastSetTextAlpha if we are translucent.
- private int mIconCurrentTextAlpha;
-
private FloatingHeaderView mParent;
private boolean mScrolledOut;
- private float mScrollTranslation = 0;
- private final AnimatedFloat mContentAlphaFactor =
- new AnimatedFloat(this::updateTranslationAndAlpha);
- private final AnimatedFloat mOverviewScrollFactor =
- new AnimatedFloat(this::updateTranslationAndAlpha);
-
private boolean mPredictionsEnabled = false;
@Nullable
@@ -117,15 +80,9 @@
setOrientation(LinearLayout.HORIZONTAL);
mFocusHelper = new SimpleFocusIndicatorHelper(this);
-
mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numAllAppsColumns;
mLauncher = Launcher.getLauncher(context);
mLauncher.addOnDeviceProfileChangeListener(this);
-
- mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary);
- mIconFullTextAlpha = Color.alpha(mIconTextColor);
- mIconCurrentTextAlpha = mIconFullTextAlpha;
-
updateVisibility();
}
@@ -246,7 +203,6 @@
}
int predictionCount = mPredictedApps.size();
- int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
for (int i = 0; i < getChildCount(); i++) {
BubbleTextView icon = (BubbleTextView) getChildAt(i);
@@ -254,7 +210,6 @@
if (predictionCount > i) {
icon.setVisibility(View.VISIBLE);
icon.applyFromWorkspaceItem(mPredictedApps.get(i));
- icon.setTextColor(iconColor);
} else {
icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
}
@@ -269,27 +224,6 @@
mParent.onHeightUpdated();
}
- public void setTextAlpha(int textAlpha) {
- mIconLastSetTextAlpha = textAlpha;
- if (getAlpha() < 1 && textAlpha > 0) {
- // If the entire header is translucent, make sure the text is at full opacity so it's
- // not double-translucent. However, we support keeping the text invisible (alpha == 0).
- textAlpha = mIconFullTextAlpha;
- }
- mIconCurrentTextAlpha = textAlpha;
- int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
- for (int i = 0; i < getChildCount(); i++) {
- ((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
- }
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- // Reapply text alpha so that we update it to be full alpha if the row is now translucent.
- setTextAlpha(mIconLastSetTextAlpha);
- }
-
@Override
public boolean hasOverlappingRendering() {
return false;
@@ -299,37 +233,16 @@
@Override
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
mScrolledOut = isScrolledOut;
- updateTranslationAndAlpha();
if (!isScrolledOut) {
- mScrollTranslation = scroll;
- updateTranslationAndAlpha();
+ setTranslationY(scroll);
}
- }
-
- private void updateTranslationAndAlpha() {
- if (mPredictionsEnabled) {
- setTranslationY((1 - mOverviewScrollFactor.value) * mScrollTranslation);
-
- float factor = ALPHA_FACTOR_INTERPOLATOR.getInterpolation(mOverviewScrollFactor.value);
- float endAlpha = factor + (1 - factor) * (mScrolledOut ? 0 : 1);
- setAlpha(mContentAlphaFactor.value * endAlpha);
+ setAlpha(mScrolledOut ? 0 : 1);
+ if (getVisibility() != GONE) {
AlphaUpdateListener.updateVisibility(this);
}
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- // Text follows all apps visibility
- int textAlpha = hasHeaderExtra && hasAllAppsContent ? mIconFullTextAlpha : 0;
- setter.setInt(this, TEXT_ALPHA, textAlpha, allAppsFade);
- setter.setFloat(mOverviewScrollFactor, AnimatedFloat.VALUE,
- (hasHeaderExtra && !hasAllAppsContent) ? 1 : 0, LINEAR);
- setter.setFloat(mContentAlphaFactor, AnimatedFloat.VALUE, hasHeaderExtra ? 1 : 0,
- headerFade);
- }
-
- @Override
public void setInsets(Rect insets, DeviceProfile grid) {
int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
+ grid.cellLayoutPaddingLeftRightPx;
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 9944270..eed493d 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -42,6 +42,7 @@
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ShortcutInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
@@ -62,9 +63,11 @@
import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
import java.util.Locale;
+import java.util.Optional;
import java.util.function.ObjIntConsumer;
import java.util.function.Predicate;
@@ -174,6 +177,7 @@
return null;
}
ComponentName cn = null;
+ ShortcutInfo shortcutInfo = null;
String id = null;
switch (info.getItemCase()) {
@@ -188,6 +192,14 @@
LauncherAtom.Shortcut si = info.getShortcut();
if (!TextUtils.isEmpty(si.getShortcutId())
&& (cn = parseNullable(si.getShortcutName())) != null) {
+ Optional<ShortcutInfo> opt = new ShortcutRequest(mContext,
+ userHandle).forPackage(cn.getPackageName(), si.getShortcutId()).query(
+ ShortcutRequest.ALL).stream().findFirst();
+ if (opt.isPresent()) {
+ shortcutInfo = opt.get();
+ } else {
+ return null;
+ }
id = "shortcut:" + si.getShortcutId();
}
break;
@@ -210,6 +222,9 @@
return createTempFolderTarget();
}
if (id != null && cn != null) {
+ if (shortcutInfo != null) {
+ return new AppTarget.Builder(new AppTargetId(id), shortcutInfo).build();
+ }
return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
.setClassName(cn.getClassName())
.build();
@@ -217,6 +232,7 @@
return null;
}
+
private AppTarget createTempFolderTarget() {
return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
mContext.getPackageName(), Process.myUserHandle())
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index b1b4d70..df3657d 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -73,6 +73,7 @@
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
+ private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final boolean IS_DEBUG = false;
private static final String TAG = "QuickstepModelDelegate";
@@ -217,7 +218,7 @@
registerWidgetsPredictor(apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("widgets")
- .setPredictedTargetCount(mIDP.numColumns)
+ .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
.build()));
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
index 13501a4..1f268cc 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AnimatedFloat.VALUE;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
@@ -30,7 +31,7 @@
import com.android.quickstep.SystemUiProxy;
/**
- * State handler for animating back button alpha
+ * State handler for animating back button alpha in two-button nav mode.
*/
public class BackButtonAlphaHandler implements StateHandler<LauncherState> {
@@ -47,18 +48,11 @@
@Override
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation animation) {
- if (config.onlyPlayAtomicComponent()) {
+ if (SysUINavigationMode.getMode(mLauncher) != TWO_BUTTONS) {
return;
}
- if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
- // If the nav mode is not gestural, then force back button alpha to be 1
- UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
- BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */);
- return;
- }
-
- mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
+ mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastNavButtonAlpha();
animation.setFloat(mBackAlpha, VALUE,
mLauncher.shouldBackButtonBeHidden(toState) ? 0 : 1, LINEAR);
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index fe8f0c6..249ef3a 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -193,7 +193,6 @@
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation animation) {
if (mSurface == null
- || config.onlyPlayAtomicComponent()
|| config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER)
|| mIgnoreStateChangesDuringMultiWindowAnimation) {
return;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8312b82..5513c16 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -35,14 +35,13 @@
private final DeviceProfile mDeviceProfile;
private final LayoutInflater mLayoutInflater;
private final TaskbarContainerView mTaskbarContainerView;
- private final float mIconScale;
public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
super(launcher);
mDeviceProfile = launcher.getDeviceProfile().copy(this);
float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
- mIconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
- mDeviceProfile.updateIconSize(mIconScale, getResources());
+ float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+ mDeviceProfile.updateIconSize(iconScale, getResources());
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
@@ -54,10 +53,7 @@
return mTaskbarContainerView;
}
- /**
- * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will
- * be able to access this TaskbarActivityContext via ActivityContext.lookupContext().
- */
+ @Override
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
}
@@ -76,11 +72,4 @@
public Rect getFolderBoundingBox() {
return mTaskbarContainerView.getFolderBoundingBox();
}
-
- /**
- * @return The ratio of taskbar icon size vs normal workspace/hotseat icon size.
- */
- public float getTaskbarIconScale() {
- return mIconScale;
- }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
similarity index 63%
rename from quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
rename to quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
index 6d20d97..7c54e2d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
@@ -20,33 +20,38 @@
import android.animation.Animator;
import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Utilities;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.system.QuickStepContract;
/**
- * Works with TaskbarController to update the TaskbarView's alpha based on LauncherState, whether
- * Launcher is in the foreground, etc.
+ * Works with TaskbarController to update the TaskbarView's visual properties based on factors such
+ * as LauncherState, whether Launcher is in the foreground, etc.
*/
-public class TaskbarVisibilityController {
+public class TaskbarAnimationController {
private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
private final BaseQuickstepLauncher mLauncher;
- private final TaskbarController.TaskbarVisibilityControllerCallbacks mTaskbarCallbacks;
+ private final TaskbarController.TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
// Background alpha.
- private AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
+ private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
this::onTaskbarBackgroundAlphaChanged);
// Overall visibility.
- private AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
+ private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
this::updateVisibilityAlpha);
- private AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
+ private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
this::updateVisibilityAlpha);
- public TaskbarVisibilityController(BaseQuickstepLauncher launcher,
- TaskbarController.TaskbarVisibilityControllerCallbacks taskbarCallbacks) {
+ // Scale.
+ private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
+ this::updateScale);
+
+ public TaskbarAnimationController(BaseQuickstepLauncher launcher,
+ TaskbarController.TaskbarAnimationControllerCallbacks taskbarCallbacks) {
mLauncher = launcher;
mTaskbarCallbacks = taskbarCallbacks;
}
@@ -65,12 +70,17 @@
}
protected void cleanup() {
+ setNavBarButtonAlpha(1f);
}
protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
return mTaskbarVisibilityAlphaForLauncherState;
}
+ protected AnimatedFloat getTaskbarScaleForLauncherState() {
+ return mTaskbarScaleForLauncherState;
+ }
+
protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
.setDuration(duration);
@@ -84,6 +94,7 @@
private void onTaskbarBackgroundAlphaChanged() {
mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
updateVisibilityAlpha();
+ updateScale();
}
private void updateVisibilityAlpha() {
@@ -93,6 +104,23 @@
float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
mTaskbarVisibilityAlphaForLauncherState.value);
float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value;
- mTaskbarCallbacks.updateTaskbarVisibilityAlpha(alphaDueToLauncher * alphaDueToOther);
+ float taskbarAlpha = alphaDueToLauncher * alphaDueToOther;
+ mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
+
+ // Make the nav bar invisible if taskbar is visible.
+ setNavBarButtonAlpha(1f - taskbarAlpha);
+ }
+
+ private void updateScale() {
+ // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
+ // assumption being that Taskbar should always be at scale 1f regardless of the current
+ // LauncherState if Launcher is paused.
+ float scale = mTaskbarScaleForLauncherState.value;
+ scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
+ mTaskbarCallbacks.updateTaskbarScale(scale);
+ }
+
+ private void setNavBarButtonAlpha(float navBarAlpha) {
+ SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 1e5e3e7..ccf6b41 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -27,7 +27,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
-import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.system.ViewTreeObserverWrapper;
@@ -83,13 +82,13 @@
private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
return insetsInfo -> {
- if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
- // We're invisible, let touches pass through us.
+ if (mControllerCallbacks.isTaskbarTouchable()) {
+ // Accept touches anywhere in our bounds.
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ } else {
+ // Let touches pass through us.
insetsInfo.touchableRegion.setEmpty();
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else {
- // We're visible again, accept touches anywhere in our bounds.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
}
// TaskbarContainerView provides insets to other apps based on contentInsets. These
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 544835c..559ede1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,8 +19,6 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
@@ -40,9 +38,11 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
+import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -50,6 +50,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.AnimatedFloat;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -66,13 +67,14 @@
private static final String WINDOW_TITLE = "Taskbar";
private final TaskbarContainerView mTaskbarContainerView;
- private final TaskbarView mTaskbarView;
+ private final TaskbarView mTaskbarViewInApp;
+ private final TaskbarView mTaskbarViewOnHome;
private final BaseQuickstepLauncher mLauncher;
private final WindowManager mWindowManager;
// Layout width and height of the Taskbar in the default state.
private final Point mTaskbarSize;
private final TaskbarStateHandler mTaskbarStateHandler;
- private final TaskbarVisibilityController mTaskbarVisibilityController;
+ private final TaskbarAnimationController mTaskbarAnimationController;
private final TaskbarHotseatController mHotseatController;
private final TaskbarRecentsController mRecentsController;
private final TaskbarDragController mDragController;
@@ -89,17 +91,19 @@
private boolean mIsAnimatingToLauncher;
public TaskbarController(BaseQuickstepLauncher launcher,
- TaskbarContainerView taskbarContainerView) {
+ TaskbarContainerView taskbarContainerView, TaskbarView taskbarViewOnHome) {
mLauncher = launcher;
mTaskbarContainerView = taskbarContainerView;
mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
- mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
- mTaskbarView.construct(createTaskbarViewCallbacks());
+ mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+ mTaskbarViewInApp.construct(createTaskbarViewCallbacks());
+ mTaskbarViewOnHome = taskbarViewOnHome;
+ mTaskbarViewOnHome.construct(createTaskbarViewCallbacks());
mWindowManager = mLauncher.getWindowManager();
mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
- mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
- createTaskbarVisibilityControllerCallbacks());
+ mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
+ createTaskbarAnimationControllerCallbacks());
mHotseatController = new TaskbarHotseatController(mLauncher,
createTaskbarHotseatControllerCallbacks());
mRecentsController = new TaskbarRecentsController(mLauncher,
@@ -107,16 +111,23 @@
mDragController = new TaskbarDragController(mLauncher);
}
- private TaskbarVisibilityControllerCallbacks createTaskbarVisibilityControllerCallbacks() {
- return new TaskbarVisibilityControllerCallbacks() {
+ private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
+ return new TaskbarAnimationControllerCallbacks() {
@Override
public void updateTaskbarBackgroundAlpha(float alpha) {
- mTaskbarView.setBackgroundAlpha(alpha);
+ mTaskbarViewInApp.setBackgroundAlpha(alpha);
}
@Override
public void updateTaskbarVisibilityAlpha(float alpha) {
mTaskbarContainerView.setAlpha(alpha);
+ mTaskbarViewOnHome.setAlpha(alpha);
+ }
+
+ @Override
+ public void updateTaskbarScale(float scale) {
+ mTaskbarViewInApp.setScaleX(scale);
+ mTaskbarViewInApp.setScaleY(scale);
}
};
}
@@ -130,6 +141,13 @@
setTaskbarWindowFullscreen(false);
}
}
+
+ @Override
+ public boolean isTaskbarTouchable() {
+ return mTaskbarContainerView.getAlpha() > AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD
+ && mTaskbarViewInApp.getVisibility() == View.VISIBLE
+ && !mIsAnimatingToLauncher;
+ }
};
}
@@ -171,22 +189,28 @@
@Override
public View.OnLongClickListener getItemOnLongClickListener() {
- return view -> {
- if (mLauncher.hasBeenResumed() && view.getTag() instanceof ItemInfo) {
- alignRealHotseatWithTaskbar();
- return mDragController.startWorkspaceDragOnLongClick(view);
- } else {
- return mDragController.startSystemDragOnLongClick(view);
- }
- };
+ return mDragController::startSystemDragOnLongClick;
}
@Override
- public int getEmptyHotseatViewVisibility() {
+ public int getEmptyHotseatViewVisibility(TaskbarView taskbarView) {
// When on the home screen, we want the empty hotseat views to take up their full
// space so that the others line up with the home screen hotseat.
- return mLauncher.hasBeenResumed() || mIsAnimatingToLauncher
- ? View.INVISIBLE : View.GONE;
+ boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome
+ || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher;
+ return isOnHomeScreen ? View.INVISIBLE : View.GONE;
+ }
+
+ @Override
+ public float getNonIconScale(TaskbarView taskbarView) {
+ return taskbarView == mTaskbarViewOnHome ? getTaskbarScaleOnHome() : 1f;
+ }
+
+ @Override
+ public void onItemPositionsChanged(TaskbarView taskbarView) {
+ if (taskbarView == mTaskbarViewOnHome) {
+ alignRealHotseatWithTaskbar();
+ }
}
};
}
@@ -195,7 +219,7 @@
return new TaskbarHotseatControllerCallbacks() {
@Override
public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
- mTaskbarView.updateHotseatItems(hotseatItemInfos);
+ mTaskbarViewInApp.updateHotseatItems(hotseatItemInfos);
mLatestLoadedHotseatItems = hotseatItemInfos;
dedupeAndUpdateRecentItems();
}
@@ -212,7 +236,8 @@
@Override
public void updateRecentTaskAtIndex(int taskIndex, Task task) {
- mTaskbarView.updateRecentTaskAtIndex(taskIndex, task);
+ mTaskbarViewInApp.updateRecentTaskAtIndex(taskIndex, task);
+ mTaskbarViewOnHome.updateRecentTaskAtIndex(taskIndex, task);
}
};
}
@@ -221,23 +246,32 @@
* Initializes the Taskbar, including adding it to the screen.
*/
public void init() {
- mTaskbarView.init(mHotseatController.getNumHotseatIcons(),
+ mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(),
mRecentsController.getNumRecentIcons());
- mTaskbarContainerView.init(mTaskbarView);
+ mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(),
+ mRecentsController.getNumRecentIcons());
+ mTaskbarContainerView.init(mTaskbarViewInApp);
addToWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
- mTaskbarVisibilityController.init();
+ mTaskbarAnimationController.init();
mHotseatController.init();
mRecentsController.init();
- SCALE_PROPERTY.set(mTaskbarView, mLauncher.hasBeenResumed() ? getTaskbarScaleOnHome() : 1f);
+ setWhichTaskbarViewIsVisible(mLauncher.hasBeenResumed()
+ ? mTaskbarViewOnHome
+ : mTaskbarViewInApp);
}
private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
return new TaskbarStateHandlerCallbacks() {
@Override
public AnimatedFloat getAlphaTarget() {
- return mTaskbarVisibilityController.getTaskbarVisibilityForLauncherState();
+ return mTaskbarAnimationController.getTaskbarVisibilityForLauncherState();
+ }
+
+ @Override
+ public AnimatedFloat getScaleTarget() {
+ return mTaskbarAnimationController.getTaskbarScaleForLauncherState();
}
};
}
@@ -246,17 +280,21 @@
* Removes the Taskbar from the screen, and removes any obsolete listeners etc.
*/
public void cleanup() {
- mTaskbarView.cleanup();
+ if (mAnimator != null) {
+ // End this first, in case it relies on properties that are about to be cleaned up.
+ mAnimator.end();
+ }
+
+ mTaskbarViewInApp.cleanup();
+ mTaskbarViewOnHome.cleanup();
mTaskbarContainerView.cleanup();
removeFromWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(null);
- mTaskbarVisibilityController.cleanup();
+ mTaskbarAnimationController.cleanup();
mHotseatController.cleanup();
mRecentsController.cleanup();
- if (mAnimator != null) {
- mAnimator.end();
- }
+ setWhichTaskbarViewIsVisible(null);
}
private void removeFromWindowManager() {
@@ -289,7 +327,7 @@
TaskbarContainerView.LayoutParams taskbarLayoutParams =
new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y);
taskbarLayoutParams.gravity = gravity;
- mTaskbarView.setLayoutParams(taskbarLayoutParams);
+ mTaskbarViewInApp.setLayoutParams(taskbarLayoutParams);
mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
}
@@ -298,7 +336,7 @@
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
public void onLauncherResumedOrPaused(boolean isResumed) {
- long duration = QuickstepAppTransitionManagerImpl.CONTENT_ALPHA_DURATION;
+ long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
if (mAnimator != null) {
mAnimator.cancel();
}
@@ -322,39 +360,40 @@
*/
public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
PendingAnimation anim = new PendingAnimation(duration);
- anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(0, duration));
+ anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
if (toState != null) {
mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
}
- anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(),
- getTaskbarScaleOnHome(), LINEAR);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mIsAnimatingToLauncher = true;
- mTaskbarView.updateHotseatItemsVisibility();
+ mTaskbarViewInApp.updateHotseatItemsVisibility();
}
@Override
public void onAnimationEnd(Animator animation) {
mIsAnimatingToLauncher = false;
+ setWhichTaskbarViewIsVisible(mTaskbarViewOnHome);
}
});
- anim.addOnFrameCallback(this::alignRealHotseatWithTaskbar);
-
return anim.buildAnim();
}
private Animator createAnimToApp(long duration) {
PendingAnimation anim = new PendingAnimation(duration);
- anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration));
- anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(), 1f, LINEAR);
+ anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- mTaskbarView.updateHotseatItemsVisibility();
+ mTaskbarViewInApp.updateHotseatItemsVisibility();
+ setWhichTaskbarViewIsVisible(mTaskbarViewInApp);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
}
});
return anim.buildAnim();
@@ -364,7 +403,7 @@
* Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
*/
public void setIsImeVisible(boolean isImeVisible) {
- mTaskbarVisibilityController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
+ mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
}
/**
@@ -379,11 +418,11 @@
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
*/
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
- return mTaskbarView.isEventOverAnyItem(ev);
+ return mTaskbarViewInApp.isEventOverAnyItem(ev);
}
public boolean isDraggingItem() {
- return mTaskbarView.isDraggingItem();
+ return mTaskbarViewInApp.isDraggingItem() || mTaskbarViewOnHome.isDraggingItem();
}
private void dedupeAndUpdateRecentItems() {
@@ -428,7 +467,8 @@
tasksArray[tasksArray.length - 1 - i] = task;
}
- mTaskbarView.updateRecentTasks(tasksArray);
+ mTaskbarViewInApp.updateRecentTasks(tasksArray);
+ mTaskbarViewOnHome.updateRecentTasks(tasksArray);
mRecentsController.loadIconsForTasks(tasksArray);
}
@@ -445,14 +485,34 @@
*/
public void alignRealHotseatWithTaskbar() {
Rect hotseatBounds = new Rect();
- mTaskbarView.getHotseatBoundsAtScale(getTaskbarScaleOnHome()).roundOut(hotseatBounds);
- mLauncher.getHotseat().setPadding(hotseatBounds.left, hotseatBounds.top,
- mTaskbarView.getWidth() - hotseatBounds.right,
- mTaskbarView.getHeight() - hotseatBounds.bottom);
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
+ int hotseatTopDiff = hotseatHeight - grid.taskbarSize;
+
+ mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds);
+ mLauncher.getHotseat().setPadding(hotseatBounds.left,
+ hotseatBounds.top + hotseatTopDiff,
+ mTaskbarViewOnHome.getWidth() - hotseatBounds.right,
+ mTaskbarViewOnHome.getHeight() - hotseatBounds.bottom);
}
- private float getTaskbarScaleOnHome() {
- return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale();
+ private void setWhichTaskbarViewIsVisible(@Nullable TaskbarView visibleTaskbar) {
+ mTaskbarViewInApp.setVisibility(visibleTaskbar == mTaskbarViewInApp
+ ? View.VISIBLE : View.INVISIBLE);
+ mTaskbarViewOnHome.setVisibility(visibleTaskbar == mTaskbarViewOnHome
+ ? View.VISIBLE : View.INVISIBLE);
+ mLauncher.getHotseat().setIconsAlpha(visibleTaskbar != mTaskbarViewInApp ? 1f : 0f);
+ }
+
+ /**
+ * Returns the ratio of the taskbar icon size on home vs in an app.
+ */
+ public float getTaskbarScaleOnHome() {
+ DeviceProfile inAppDp = mTaskbarContainerView.getTaskbarActivityContext()
+ .getDeviceProfile();
+ DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext())
+ .getDeviceProfile();
+ return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
}
/**
@@ -474,15 +534,17 @@
*/
protected interface TaskbarStateHandlerCallbacks {
AnimatedFloat getAlphaTarget();
+ AnimatedFloat getScaleTarget();
}
/**
- * Contains methods that TaskbarVisibilityController can call to interface with
+ * Contains methods that TaskbarAnimationController can call to interface with
* TaskbarController.
*/
- protected interface TaskbarVisibilityControllerCallbacks {
+ protected interface TaskbarAnimationControllerCallbacks {
void updateTaskbarBackgroundAlpha(float alpha);
void updateTaskbarVisibilityAlpha(float alpha);
+ void updateTaskbarScale(float scale);
}
/**
@@ -490,6 +552,7 @@
*/
protected interface TaskbarContainerViewCallbacks {
void onViewRemoved();
+ boolean isTaskbarTouchable();
}
/**
@@ -498,7 +561,10 @@
protected interface TaskbarViewCallbacks {
View.OnClickListener getItemOnClickListener();
View.OnLongClickListener getItemOnLongClickListener();
- int getEmptyHotseatViewVisibility();
+ int getEmptyHotseatViewVisibility(TaskbarView taskbarView);
+ /** Returns how much to scale non-icon elements such as spacing and dividers. */
+ float getNonIconScale(TaskbarView taskbarView);
+ void onItemPositionsChanged(TaskbarView taskbarView);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index f51e498..5eb34cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -33,7 +33,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ClipDescriptionCompat;
@@ -126,38 +125,6 @@
}
/**
- * Starts a drag and drop operation that controls a real Workspace (Hotseat) view.
- * @param view The Taskbar item that was long clicked.
- * @return Whether {@link View#startDragAndDrop} started successfully.
- */
- protected boolean startWorkspaceDragOnLongClick(View view) {
- View.DragShadowBuilder transparentShadowBuilder = new View.DragShadowBuilder(view) {
- private static final int ARBITRARY_SHADOW_SIZE = 10;
-
- @Override
- public void onDrawShadow(Canvas canvas) {
- }
-
- @Override
- public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
- outShadowSize.set(ARBITRARY_SHADOW_SIZE, ARBITRARY_SHADOW_SIZE);
- outShadowTouchPoint.set(ARBITRARY_SHADOW_SIZE / 2, ARBITRARY_SHADOW_SIZE / 2);
- }
- };
-
- TaskbarDragListener taskbarDragListener = new TaskbarDragListener(mLauncher,
- (ItemInfo) view.getTag());
- if (view.startDragAndDrop(new ClipData("", new String[] {taskbarDragListener.getMimeType()},
- new ClipData.Item("")),
- transparentShadowBuilder, null /* localState */, View.DRAG_FLAG_GLOBAL)) {
- view.setOnDragListener(getDraggedViewDragListener());
- taskbarDragListener.init(mLauncher.getDragLayer());
- return true;
- }
- return false;
- }
-
- /**
* Hide the original Taskbar item while it is being dragged.
*/
private View.OnDragListener getDraggedViewDragListener() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
deleted file mode 100644
index 2bd5861..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import android.content.ClipDescription;
-import android.graphics.Point;
-import android.view.DragEvent;
-import android.view.View;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.ItemInfo;
-
-import java.util.UUID;
-
-/**
- * Listens to system drag and drop events initated by the Taskbar, and forwards them to Launcher's
- * internal DragController to move Hotseat items.
- */
-public class TaskbarDragListener implements View.OnDragListener {
-
- private static final String MIME_TYPE_PREFIX = "com.android.launcher3.taskbar.drag_and_drop/";
-
- private final BaseQuickstepLauncher mLauncher;
- private final ItemInfo mDraggedItem;
- private final DragOptions mDragOptions;
- // Randomly generated id used to verify the drag event.
- private final String mId;
-
- // Initialized in init().
- DragLayer mDragLayer;
-
- /**
- * @param draggedItem The info of the item that was long clicked, which we will use to find
- * the equivalent match on Hotseat to drag internally.
- */
- public TaskbarDragListener(BaseQuickstepLauncher launcher, ItemInfo draggedItem) {
- mLauncher = launcher;
- mDraggedItem = draggedItem;
- mDragOptions = new DragOptions();
- mDragOptions.simulatedDndStartPoint = new Point();
- mId = UUID.randomUUID().toString();
- }
-
- protected void init(DragLayer dragLayer) {
- mDragLayer = dragLayer;
- mDragLayer.setOnDragListener(this);
- }
-
- private void cleanup() {
- mDragLayer.setOnDragListener(null);
- mLauncher.setWorkspaceDragOptions(new DragOptions());
- }
-
- /**
- * Returns a randomly generated id used to verify the drag event.
- */
- protected String getMimeType() {
- return MIME_TYPE_PREFIX + mId;
- }
-
- @Override
- public boolean onDrag(View dragLayer, DragEvent dragEvent) {
- ClipDescription clipDescription = dragEvent.getClipDescription();
- if (dragEvent.getAction() == DragEvent.ACTION_DRAG_STARTED) {
- if (clipDescription == null || !clipDescription.hasMimeType(getMimeType())) {
- // We didn't initiate this drag, ignore.
- cleanup();
- return false;
- }
- View hotseatView = mLauncher.getHotseat().getFirstItemMatch(
- (info, view) -> info == mDraggedItem);
- if (hotseatView == null) {
- cleanup();
- return false;
- }
- mDragOptions.simulatedDndStartPoint.set((int) dragEvent.getX(), (int) dragEvent.getY());
- mLauncher.setWorkspaceDragOptions(mDragOptions);
- hotseatView.performLongClick();
- } else if (dragEvent.getAction() == DragEvent.ACTION_DRAG_ENDED) {
- cleanup();
- }
- return mLauncher.getDragController().onDragEvent(dragEvent);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
index 082343e..b1bafdb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -78,7 +78,10 @@
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
// Since the hotseat might be laid out vertically or horizontally, use whichever
// index is higher.
- hotseatItemInfos[Math.max(lp.cellX, lp.cellY)] = itemInfo;
+ int index = Math.max(lp.cellX, lp.cellY);
+ if (0 <= index && index < hotseatItemInfos.length) {
+ hotseatItemInfos[index] = itemInfo;
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index b4b5d8b..9fc7d99 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -16,14 +16,12 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherState.TASKBAR;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_TASKBAR_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_TASKBAR;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
@@ -55,8 +53,10 @@
}
AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+ AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
boolean isTaskbarVisible = (state.getVisibleElements(mLauncher) & TASKBAR) != 0;
alphaTarget.updateValue(isTaskbarVisible ? 1f : 0f);
+ scaleTarget.updateValue(state.getTaskbarScale(mLauncher));
}
@Override
@@ -65,13 +65,12 @@
if (mTaskbarCallbacks == null) {
return;
}
- if (config.hasAnimationFlag(SKIP_TASKBAR)) {
- return;
- }
AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+ AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
- animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f,
- config.getInterpolator(ANIM_TASKBAR_FADE, Interpolators.LINEAR));
+ animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
+ animation.setFloat(scaleTarget, AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher),
+ LINEAR);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index a729e77..21a2d51 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,10 +15,14 @@
*/
package com.android.launcher3.taskbar;
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.Matrix;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -35,6 +39,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.folder.FolderIcon;
@@ -47,22 +52,26 @@
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
-public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent {
+public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
private final ColorDrawable mBackgroundDrawable;
- private final int mItemMarginLeftRight;
+ private final int mDividerWidth;
+ private final int mDividerHeight;
private final int mIconTouchSize;
private final boolean mIsRtl;
private final int mTouchSlop;
private final RectF mTempDelegateBounds = new RectF();
private final RectF mDelegateSlopBounds = new RectF();
private final int[] mTempOutLocation = new int[2];
- private final Matrix mTempMatrix = new Matrix();
// Initialized in TaskbarController constructor.
private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+ // Scale on elements that aren't icons.
+ private float mNonIconScale;
+ private int mItemMarginLeftRight;
// Initialized in init().
+ private LayoutTransition mLayoutTransition;
private int mHotseatStartIndex;
private int mHotseatEndIndex;
private View mHotseatRecentsDivider;
@@ -96,7 +105,8 @@
Resources resources = getResources();
mBackgroundDrawable = (ColorDrawable) getBackground();
- mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ mDividerWidth = resources.getDimensionPixelSize(R.dimen.taskbar_divider_thickness);
+ mDividerHeight = resources.getDimensionPixelSize(R.dimen.taskbar_divider_height);
mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
mIsRtl = Utilities.isRtl(resources);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -104,6 +114,9 @@
protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
mControllerCallbacks = taskbarViewCallbacks;
+ mNonIconScale = mControllerCallbacks.getNonIconScale(this);
+ mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale);
}
protected void init(int numHotseatIcons, int numRecentIcons) {
@@ -117,10 +130,50 @@
mRecentsStartIndex = dividerIndex + 1;
mRecentsEndIndex = mRecentsStartIndex + numRecentIcons - 1;
updateRecentTasks(new Task[numRecentIcons]);
+
+ mLayoutTransition = new LayoutTransition();
+ addUpdateListenerForAllLayoutTransitions(() -> {
+ if (getLayoutTransition() == mLayoutTransition) {
+ mControllerCallbacks.onItemPositionsChanged(this);
+ }
+ });
+ setLayoutTransition(mLayoutTransition);
+ }
+
+ private void addUpdateListenerForAllLayoutTransitions(Runnable onUpdate) {
+ addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_APPEARING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_DISAPPEARING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.CHANGING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.APPEARING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.DISAPPEARING, onUpdate);
+ }
+
+ private void addUpdateListenerForLayoutTransition(int transitionType, Runnable onUpdate) {
+ Animator anim = mLayoutTransition.getAnimator(transitionType);
+ if (anim instanceof ValueAnimator) {
+ ((ValueAnimator) anim).addUpdateListener(valueAnimator -> onUpdate.run());
+ } else {
+ AnimatorSet animSet = new AnimatorSet();
+ ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+ updateAnim.addUpdateListener(valueAnimator -> onUpdate.run());
+ animSet.playTogether(anim, updateAnim);
+ mLayoutTransition.setAnimator(transitionType, animSet);
+ }
}
protected void cleanup() {
+ endAllLayoutTransitionAnimators();
+ setLayoutTransition(null);
removeAllViews();
+ mHotseatRecentsDivider = null;
+ }
+
+ private void endAllLayoutTransitionAnimators() {
+ mLayoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING).end();
+ mLayoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING).end();
+ mLayoutTransition.getAnimator(LayoutTransition.CHANGING).end();
+ mLayoutTransition.getAnimator(LayoutTransition.APPEARING).end();
+ mLayoutTransition.getAnimator(LayoutTransition.DISAPPEARING).end();
}
/**
@@ -160,12 +213,11 @@
if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
|| needsReinflate) {
removeView(hotseatView);
- TaskbarActivityContext activityContext =
- ActivityContext.lookupContext(getContext());
+ ActivityContext activityContext = ActivityContext.lookupContext(getContext());
if (isFolder) {
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
- activityContext, this, folderInfo);
+ ActivityContext.lookupContext(getContext()), this, folderInfo);
folderIcon.setTextVisible(false);
hotseatView = folderIcon;
} else {
@@ -211,7 +263,7 @@
hotseatView.setVisibility(VISIBLE);
} else {
int oldVisibility = hotseatView.getVisibility();
- int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility();
+ int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(this);
hotseatView.setVisibility(newVisibility);
if (oldVisibility == GONE && newVisibility != GONE) {
// By default, the layout transition only runs when going to VISIBLE,
@@ -223,7 +275,11 @@
private View addDivider(int dividerIndex) {
View divider = inflate(R.layout.taskbar_divider);
- addView(divider, dividerIndex);
+ LayoutParams lp = new LayoutParams(mDividerWidth, mDividerHeight);
+ lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+ divider.setScaleX(mNonIconScale);
+ divider.setScaleY(mNonIconScale);
+ addView(divider, dividerIndex, lp);
return divider;
}
@@ -417,9 +473,9 @@
}
/**
- * @return The bounding box of where the hotseat elements will be when we reach the given scale.
+ * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
*/
- protected RectF getHotseatBoundsAtScale(float taskbarViewScale) {
+ protected RectF getHotseatBounds() {
View firstHotseatView = null, lastHotseatView = null;
for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
View child = getChildAt(i);
@@ -435,14 +491,11 @@
}
View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView;
View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView;
- RectF hotseatBounds = new RectF(
+ return new RectF(
leftmostHotseatView.getLeft() - mItemMarginLeftRight,
leftmostHotseatView.getTop(),
rightmostHotseatView.getRight() + mItemMarginLeftRight,
rightmostHotseatView.getBottom());
- mTempMatrix.setScale(taskbarViewScale, taskbarViewScale, getPivotX(), getPivotY());
- mTempMatrix.mapRect(hotseatBounds);
- return hotseatBounds;
}
// FolderIconParent implemented methods.
@@ -473,7 +526,12 @@
}
private View inflate(@LayoutRes int layoutResId) {
- TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
- return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+ return ActivityContext.lookupContext(getContext()).getLayoutInflater()
+ .inflate(layoutResId, this, false);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ // Ignore, we just implement Insettable to draw behind system insets.
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 0f13ef9..01616d4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -18,20 +18,16 @@
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.graphics.OverviewScrim.SCRIM_MULTIPLIER;
-import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
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_SCRIM_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.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_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_PRIMARY_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.util.FloatProperty;
@@ -41,9 +37,9 @@
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
@@ -70,21 +66,14 @@
TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
- OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
- SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
- SCRIM_MULTIPLIER.set(scrim, 1f);
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
- RECENTS_GRID_PROGRESS.set(mRecentsView, state.displayOverviewTasksAsGrid(mLauncher)
- ? 1f : 0f);
+ RECENTS_GRID_PROGRESS.set(mRecentsView,
+ state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
}
@Override
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation builder) {
- if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
- // The entire recents animation is played atomically.
- return;
- }
if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
return;
}
@@ -105,23 +94,23 @@
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
- setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+ PagedOrientationHandler orientationHandler =
+ ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
+ FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
+ TASK_PRIMARY_TRANSLATION, TASK_SECONDARY_TRANSLATION, mLauncher.getDeviceProfile());
+ setter.setFloat(mRecentsView, taskViewsFloat,
+ toState.getOverviewSecondaryTranslation(mLauncher),
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
- OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
- setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
- config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
- setter.setFloat(scrim, SCRIM_MULTIPLIER, 1f,
- config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
setter.setFloat(
mRecentsView, getTaskModalnessProperty(),
toState.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
- toState.displayOverviewTasksAsGrid(mLauncher) ? 1f : 0f, LINEAR);
+ toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index d330a68..b4aa596 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -34,11 +34,11 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
@@ -85,8 +85,13 @@
public void onDraw(Canvas canvas) {
int count = canvas.save();
if (!mIsPinned) {
- boolean isBadged = getTag() instanceof WorkspaceItemInfo
- && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
+ boolean isBadged = false;
+ if (getTag() instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
+ isBadged = !Process.myUserHandle().equals(info.user)
+ || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+ || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+ }
drawEffect(canvas, isBadged);
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b009629..0ceb8c7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -28,14 +28,15 @@
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
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.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.view.HapticFeedbackConstants;
import android.view.View;
import com.android.launcher3.BaseQuickstepLauncher;
@@ -235,7 +236,6 @@
@Override
public void onDestroy() {
super.onDestroy();
- getAppsView().getSearchUiManager().destroy();
mHotseatPredictionController.destroy();
}
@@ -252,6 +252,11 @@
}
break;
}
+ case HINT_STATE_TWO_BUTTON_ORDINAL: {
+ getStateManager().goToState(OVERVIEW);
+ getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ break;
+ }
case OVERVIEW_STATE_ORDINAL: {
RecentsView rv = getOverviewPanel();
sendCustomAccessibilityEvent(
@@ -262,14 +267,13 @@
RecentsView rv = getOverviewPanel();
TaskView tasktolaunch = rv.getTaskViewAt(0);
if (tasktolaunch != null) {
- tasktolaunch.launchTask(false, success -> {
+ tasktolaunch.launchTask(success -> {
if (!success) {
getStateManager().goToState(OVERVIEW);
- tasktolaunch.notifyTaskLaunchFailed(TAG);
} else {
getStateManager().moveToRestState();
}
- }, MAIN_EXECUTOR.getHandler());
+ });
} else {
getStateManager().goToState(NORMAL);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index c9de662..9097c8b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -17,11 +17,15 @@
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
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.TASK_MODALNESS;
+import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
+import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
import android.annotation.TargetApi;
import android.os.Build;
@@ -70,29 +74,48 @@
if (toState.overviewUi) {
// While animating into recents, update the visible task data as needed
- builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+ builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
} else {
builder.addListener(
AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
+ // Create or dismiss split screen select animations
+ LauncherState currentState = mLauncher.getStateManager().getState();
+ if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
+ builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+ } else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
+ builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
+ }
+
setAlphas(builder, config, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
toState.getOverviewFullscreenProgress(), LINEAR);
}
+ /**
+ * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
+ */
+ private boolean isSplitSelectionState(@NonNull LauncherState toState) {
+ return toState == OVERVIEW_SPLIT_SELECT;
+ }
+
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
LauncherState state) {
- float clearAllButtonAlpha = (state.getVisibleElements(mLauncher) & CLEAR_ALL_BUTTON) != 0
- ? 1 : 0;
+ float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
- float overviewButtonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0
- ? 1 : 0;
+ float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS)
+ && mRecentsView.shouldShowOverviewActionsForState(state) ? 1 : 0;
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
+
+ float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
+ 0.7f : 0;
+ propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
+ splitPlaceholderAlpha, LINEAR);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
index 36c0e34..1417995 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
@@ -102,9 +102,11 @@
private void notifyChange() {
// Create a new array to avoid concurrent modification when the activity destroys itself.
mTempListeners = mListeners.toArray(mTempListeners);
- for (OnChangeListener listener : mTempListeners) {
+ for (int i = mTempListeners.length - 1; i >= 0; --i) {
+ final OnChangeListener listener = mTempListeners[i];
if (listener != null) {
listener.onExtractedColorsChanged(this);
+ mTempListeners[i] = null;
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 37c774b..4e03971 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -78,7 +78,7 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+ return ALL_APPS_CONTENT;
}
@Override
@@ -90,4 +90,9 @@
public LauncherState getHistoryForState(LauncherState previousState) {
return previousState == OVERVIEW ? OVERVIEW : NORMAL;
}
+
+ @Override
+ public float getWorkspaceScrimAlpha(Launcher launcher) {
+ return 1;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 2ad718b..aa770d2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -20,6 +20,7 @@
import android.content.Context;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.util.LayoutUtils;
@@ -76,25 +77,11 @@
}
@Override
- public boolean displayOverviewTasksAsGrid(Launcher launcher) {
+ public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
return false;
}
@Override
- public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
- if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
- // Translate hotseat offscreen if we show it in overview.
- RecentsView recentsView = launcher.getOverviewPanel();
- ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
- scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
- launcher.getDeviceProfile(),
- recentsView.getPagedOrientationHandler());
- return scaleAndTranslation;
- }
- return super.getHotseatScaleAndTranslation(launcher);
- }
-
- @Override
protected float getDepthUnchecked(Context context) {
return 1f;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index bdba482..6f084a1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import com.android.launcher3.BaseDraggingActivity;
@@ -70,13 +71,12 @@
}
public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
- Rect out = new Rect();
- activity.<RecentsView>getOverviewPanel().getTaskSize(out);
- int taskHeight = out.height();
- activity.<RecentsView>getOverviewPanel().getModalTaskSize(out);
- int newHeight = out.height();
+ Point taskSize = activity.<RecentsView>getOverviewPanel().getSelectedTaskSize();
+ Rect modalTaskSize = new Rect();
+ activity.<RecentsView>getOverviewPanel().getModalTaskSize(modalTaskSize);
- float scale = (float) newHeight / taskHeight;
+ float scale = Math.min((float) modalTaskSize.height() / taskSize.y,
+ (float) modalTaskSize.width() / taskSize.x);
return new float[] {scale, NO_OFFSET};
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d480b6d..6cdeb0f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -78,37 +78,13 @@
}
@Override
- public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
- if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
- DeviceProfile dp = launcher.getDeviceProfile();
- if (dp.allAppsIconSizePx >= dp.iconSizePx) {
- return new ScaleAndTranslation(1, 0, 0);
- } else {
- float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
- // Distance between the screen center (which is the pivotY for hotseat) and the
- // bottom of the hotseat (which we want to preserve)
- float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
- // On scaling, the bottom edge is moved closer to the pivotY. We move the
- // hotseat back down so that the bottom edge's position is preserved.
- float translationY = distanceFromBottom * (1 - scale);
- return new ScaleAndTranslation(scale, 0, translationY);
- }
- }
- return getWorkspaceScaleAndTranslation(launcher);
- }
-
- @Override
public float[] getOverviewScaleAndOffset(Launcher launcher) {
return new float[] {NO_SCALE, NO_OFFSET};
}
@Override
- public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- if (this == OVERVIEW) {
- // Treat the QSB as part of the hotseat so they move together.
- return getHotseatScaleAndTranslation(launcher);
- }
- return super.getQsbScaleAndTranslation(launcher);
+ public float getTaskbarScale(Launcher launcher) {
+ return 1f;
}
@Override
@@ -123,18 +99,17 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return displayOverviewTasksAsGrid(launcher) ? CLEAR_ALL_BUTTON
- : CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+ return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
}
@Override
- public float getOverviewScrimAlpha(Launcher launcher) {
- return 0.5f;
+ public float getWorkspaceScrimAlpha(Launcher launcher) {
+ return 1f;
}
@Override
- public boolean displayOverviewTasksAsGrid(Launcher launcher) {
- return launcher.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+ public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+ return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
}
@Override
@@ -155,7 +130,7 @@
public void onBackPressed(Launcher launcher) {
TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
if (taskView != null) {
- taskView.launchTask(true);
+ taskView.launchTaskAnimated();
} else {
super.onBackPressed(launcher);
}
@@ -175,4 +150,12 @@
public static OverviewState newModalTaskState(int id) {
return new OverviewModalTaskState(id);
}
+
+ /**
+ * New Overview substate representing state where 1 app for split screen has been selected and
+ * pinned and user is selecting the second one
+ */
+ public static OverviewState newSplitSelectState(int id) {
+ return new SplitScreenSelectState(id);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 473fe2d..ba61923 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -18,7 +18,7 @@
import static android.view.View.VISIBLE;
import static com.android.launcher3.LauncherState.HINT_STATE;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+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.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
@@ -33,12 +33,10 @@
import static com.android.launcher3.anim.Interpolators.clampToProgress;
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_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
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_TASKBAR_FADE;
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;
@@ -82,7 +80,6 @@
if (toState == NORMAL && fromState == OVERVIEW) {
config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
- config.setInterpolator(ANIM_TASKBAR_FADE, ACCEL);
config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
@@ -119,7 +116,8 @@
qsbContainer.setScaleY(0.92f);
}
}
- } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
+ } else if ((fromState == NORMAL || fromState == HINT_STATE
+ || fromState == HINT_STATE_TWO_BUTTON) && toState == OVERVIEW) {
if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
config.setInterpolator(ANIM_WORKSPACE_SCALE,
fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
@@ -141,7 +139,6 @@
config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_2);
- config.setInterpolator(ANIM_TASKBAR_FADE, OVERSHOOT_1_2);
} else if (fromState == HINT_STATE && toState == NORMAL) {
config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
if (mHintToNormalDuration == -1) {
@@ -150,17 +147,6 @@
mHintToNormalDuration = (int) va.getDuration();
}
config.duration = Math.max(config.duration, mHintToNormalDuration);
- } else if (mActivity.getTaskbarController() != null) {
- boolean wasHotseatVisible = fromState.areElementsVisible(mActivity, HOTSEAT_ICONS);
- boolean isHotseatVisible = toState.areElementsVisible(mActivity, HOTSEAT_ICONS);
- if (wasHotseatVisible || isHotseatVisible) {
- config.setInterpolator(ANIM_TASKBAR_FADE, INSTANT);
- config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT);
-
- if (isHotseatVisible) {
- mActivity.getTaskbarController().alignRealHotseatWithTaskbar();
- }
- }
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
new file mode 100644
index 0000000..722d74a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.states;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * New Overview substate representing state where 1 app for split screen has been selected and
+ * pinned and user is selecting the second one
+ */
+public class SplitScreenSelectState extends OverviewState {
+ public SplitScreenSelectState(int id) {
+ super(id);
+ }
+
+ @Override
+ public void onBackPressed(Launcher launcher) {
+ launcher.getStateManager().goToState(OVERVIEW);
+ }
+
+ @Override
+ public int getVisibleElements(Launcher launcher) {
+ return SPLIT_PLACHOLDER_VIEW;
+ }
+
+ @Override
+ public float getOverviewSecondaryTranslation(Launcher launcher) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
+ .getActiveSplitPositionOption().mStagePosition;
+ int direction = orientationHandler.getSplitTranslationDirectionFactor(splitPosition);
+ return launcher.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index a990f3e..b8caf81 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -44,7 +44,6 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.TouchController;
@@ -141,10 +140,6 @@
AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
builder);
- builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(),
- OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER,
- PULLBACK_INTERPOLATOR);
-
if (LIVE_TILE.get()) {
builder.addOnFrameCallback(recentsView::redrawLiveTile);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 45cb46f..74a253e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HINT_STATE;
@@ -38,7 +39,6 @@
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.view.MotionEvent;
-import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
@@ -46,7 +46,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
@@ -108,11 +107,11 @@
}
@Override
- protected float initCurrentAnimation(int animComponents) {
- float progressMultiplier = super.initCurrentAnimation(animComponents);
+ protected float initCurrentAnimation() {
+ float progressMultiplier = super.initCurrentAnimation();
if (mToState == HINT_STATE) {
// Track the drag across the entire height of the screen.
- progressMultiplier = -1 / getShiftRange();
+ progressMultiplier = -1f / mLauncher.getDeviceProfile().heightPx;
}
return progressMultiplier;
}
@@ -129,10 +128,10 @@
if (mFromState == NORMAL && mToState == HINT_STATE) {
mNormalToHintOverviewScrimAnimator = ObjectAnimator.ofFloat(
- mLauncher.getDragLayer().getOverviewScrim(),
- OverviewScrim.SCRIM_PROGRESS,
- mFromState.getOverviewScrimAlpha(mLauncher),
- mToState.getOverviewScrimAlpha(mLauncher));
+ mLauncher.getScrimView(),
+ VIEW_ALPHA,
+ mFromState.getWorkspaceScrimAlpha(mLauncher),
+ mToState.getWorkspaceScrimAlpha(mLauncher));
}
mStartedOverview = false;
mReachedOverview = false;
@@ -155,11 +154,6 @@
super.onDragEnd(velocity);
}
- View searchView = mLauncher.getAppsView().getSearchView();
- if (searchView instanceof FeedbackHandler) {
- ((FeedbackHandler) searchView).resetFeedback();
- }
-
mMotionPauseDetector.clear();
mNormalToHintOverviewScrimAnimator = null;
if (mLauncher.isInState(OVERVIEW)) {
@@ -188,13 +182,13 @@
}
mNormalToHintOverviewScrimAnimator = null;
mCurrentAnimation.getTarget().addListener(newCancelListener(() ->
- mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
- mOverviewResistYAnim = AnimatorControllerWithResistance
- .createRecentsResistanceFromOverviewAnim(mLauncher, null)
- .createPlaybackController();
- mReachedOverview = true;
- maybeSwipeInteractionToOverviewComplete();
- })));
+ mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+ mOverviewResistYAnim = AnimatorControllerWithResistance
+ .createRecentsResistanceFromOverviewAnim(mLauncher, null)
+ .createPlaybackController();
+ mReachedOverview = true;
+ maybeSwipeInteractionToOverviewComplete();
+ })));
mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
mCurrentAnimation.dispatchOnCancel();
@@ -203,7 +197,7 @@
}
private void maybeSwipeInteractionToOverviewComplete() {
- if (mReachedOverview && mDetector.isSettlingState()) {
+ if (mReachedOverview && !mDetector.isDraggingState()) {
onSwipeInteractionCompleted(OVERVIEW);
}
}
@@ -245,7 +239,7 @@
private void goToOverviewOrHomeOnDragEnd(float velocity) {
boolean goToHomeInsteadOfOverview = !mMotionPauseDetector.isPaused();
if (goToHomeInsteadOfOverview) {
- new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL))
+ new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL))
.animateWithVelocity(velocity);
}
if (mReachedOverview) {
@@ -281,17 +275,14 @@
LauncherState fromState, LauncherState toState) {
if (fromState == NORMAL && toState == ALL_APPS) {
StateAnimationConfig builder = new StateAnimationConfig();
- // Fade in prediction icons quickly, then rest of all apps after reaching overview.
- float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
- - OVERVIEW.getVerticalProgress(mLauncher);
builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
ACCEL,
0,
- ALL_APPS_CONTENT_FADE_THRESHOLD));
+ ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD));
builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
ACCEL,
- progressToReachOverview,
- progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
+ ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD,
+ ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD));
// Get workspace out of the way quickly, to prepare for potential pause.
builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
@@ -300,29 +291,17 @@
return builder;
} else if (fromState == ALL_APPS && toState == NORMAL) {
StateAnimationConfig builder = new StateAnimationConfig();
- // Keep all apps/predictions opaque until the very end of the transition.
- float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
+
builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
DEACCEL,
- progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
- progressToReachOverview));
+ 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
+ 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
DEACCEL,
- 1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
+ 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
1));
return builder;
}
return super.getConfigForStates(fromState, toState);
}
-
- /**
- * Interface for views with feedback animation requiring reset
- */
- public interface FeedbackHandler {
-
- /**
- * reset searchWidget feedback
- */
- void resetFeedback();
- }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 4766870..697516d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -33,6 +34,7 @@
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.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
@@ -58,7 +60,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
@@ -230,8 +231,8 @@
// - OverviewScrim
PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
xAnim.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1], LINEAR);
- xAnim.setFloat(mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
- toState.getOverviewScrimAlpha(mLauncher), LINEAR);
+ xAnim.setFloat(mLauncher.getScrimView(), VIEW_ALPHA,
+ toState.getWorkspaceScrimAlpha(mLauncher), LINEAR);
mXOverviewAnim = xAnim.createPlaybackController();
mXOverviewAnim.dispatchOnStart();
@@ -372,7 +373,7 @@
// animation as from an app.
StateAnimationConfig config = new StateAnimationConfig();
// Update mNonOverviewAnim to do nothing so it doesn't interfere.
- config.animFlags = 0;
+ config.animFlags = SKIP_ALL_ANIMATIONS;
updateNonOverviewAnim(targetState, config);
nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index facfb9d..b8f9452 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -23,17 +23,9 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.util.Log;
import android.view.MotionEvent;
-import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -41,8 +33,6 @@
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.uioverrides.states.OverviewState;
@@ -59,9 +49,15 @@
private static final String TAG = "PortraitStatesTouchCtrl";
/**
- * The progress at which all apps content will be fully visible when swiping up from overview.
+ * The progress at which all apps content will be fully visible.
*/
- protected static final float ALL_APPS_CONTENT_FADE_THRESHOLD = 0.08f;
+ protected static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
+
+ /**
+ * Minimum clamping progress for fading in all apps content
+ */
+ protected static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.4f;
+
/**
* The progress at which recents will begin fading out when swiping up from overview.
@@ -70,11 +66,6 @@
private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
- private final InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
-
- // If true, we will finish the current animation instantly on second touch.
- private boolean mFinishFastOnSecondTouch;
-
public PortraitStatesTouchController(Launcher l) {
super(l, SingleAxisSwipeDetector.VERTICAL);
mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
@@ -85,10 +76,6 @@
// If we are swiping to all apps instead of overview, allow it from anywhere.
boolean interceptAnywhere = mLauncher.isInState(NORMAL);
if (mCurrentAnimation != null) {
- if (mFinishFastOnSecondTouch) {
- mCurrentAnimation.getAnimationPlayer().end();
- }
-
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress()
|| interceptAnywhere) {
@@ -96,11 +83,7 @@
// the touch is below the current all apps progress (to allow for double swipe).
return true;
}
- // Otherwise, make sure everything is settled and don't intercept so they can scroll
- // recents, dismiss a task, etc.
- if (mAtomicAnim != null) {
- mAtomicAnim.end();
- }
+ // Otherwise, don't intercept so they can scroll recents, dismiss a task, etc.
return false;
}
if (mLauncher.isInState(ALL_APPS)) {
@@ -136,43 +119,17 @@
return fromState;
}
- private StateAnimationConfig getNormalToOverviewAnimation() {
- mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
-
- StateAnimationConfig builder = new StateAnimationConfig();
- builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
- return builder;
- }
-
- private static StateAnimationConfig getOverviewToAllAppsAnimation() {
- StateAnimationConfig builder = new StateAnimationConfig();
- builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
- 0, ALL_APPS_CONTENT_FADE_THRESHOLD));
- builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(DEACCEL,
- RECENTS_FADE_THRESHOLD, 1));
- return builder;
- }
-
- private StateAnimationConfig getAllAppsToOverviewAnimation() {
- StateAnimationConfig builder = new StateAnimationConfig();
- builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
- 1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
- builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(ACCEL,
- 0f, 1 - RECENTS_FADE_THRESHOLD));
- return builder;
- }
-
private StateAnimationConfig getNormalToAllAppsAnimation() {
StateAnimationConfig builder = new StateAnimationConfig();
builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
- 0, ALL_APPS_CONTENT_FADE_THRESHOLD));
+ 0, ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD));
return builder;
}
private StateAnimationConfig getAllAppsToNormalAnimation() {
StateAnimationConfig builder = new StateAnimationConfig();
builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
- 1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
+ 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD, 1));
return builder;
}
@@ -180,24 +137,18 @@
protected StateAnimationConfig getConfigForStates(
LauncherState fromState, LauncherState toState) {
final StateAnimationConfig config;
- if (fromState == NORMAL && toState == OVERVIEW) {
- config = getNormalToOverviewAnimation();
- } else if (fromState == OVERVIEW && toState == ALL_APPS) {
- config = getOverviewToAllAppsAnimation();
- } else if (fromState == ALL_APPS && toState == OVERVIEW) {
- config = getAllAppsToOverviewAnimation();
- } else if (fromState == NORMAL && toState == ALL_APPS) {
+ if (fromState == NORMAL && toState == ALL_APPS) {
config = getNormalToAllAppsAnimation();
} else if (fromState == ALL_APPS && toState == NORMAL) {
config = getAllAppsToNormalAnimation();
- } else {
+ } else {
config = new StateAnimationConfig();
}
return config;
}
@Override
- protected float initCurrentAnimation(@AnimationFlags int animFlags) {
+ protected float initCurrentAnimation() {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
@@ -208,7 +159,6 @@
final StateAnimationConfig config = totalShift == 0 ? new StateAnimationConfig()
: getConfigForStates(mFromState, mToState);
- config.animFlags = animFlags;
config.duration = maxAccuracy;
if (mCurrentAnimation != null) {
@@ -243,35 +193,6 @@
}
@Override
- protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
- LauncherState targetState, float velocity, boolean isFling) {
- super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
- velocity, isFling);
- handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
- }
-
- private void handleFirstSwipeToOverview(final ValueAnimator animator,
- final long expectedDuration, final LauncherState targetState, final float velocity,
- final boolean isFling) {
- if (UNSTABLE_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
- && targetState == OVERVIEW) {
- mFinishFastOnSecondTouch = true;
- } else if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
- mFinishFastOnSecondTouch = true;
- if (isFling && expectedDuration != 0) {
- // Update all apps interpolator to add a bit of overshoot starting from currFraction
- final float currFraction = mCurrentAnimation.getProgressFraction();
- mAllAppsInterpolatorWrapper.baseInterpolator = Interpolators.clampToProgress(
- Interpolators.overshootInterpolatorForVelocity(velocity), currFraction, 1);
- animator.setDuration(Math.min(expectedDuration, ATOMIC_DURATION))
- .setInterpolator(LINEAR);
- }
- } else {
- mFinishFastOnSecondTouch = false;
- }
- }
-
- @Override
protected void onSwipeInteractionCompleted(LauncherState targetState) {
super.onSwipeInteractionCompleted(targetState);
if (mStartState == NORMAL && targetState == OVERVIEW) {
@@ -283,7 +204,7 @@
* Whether the motion event is over the hotseat.
*
* @param launcher the launcher activity
- * @param ev the event to check
+ * @param ev the event to check
* @return true if the event is over the hotseat
*/
static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
@@ -296,16 +217,6 @@
return launcher.getDragLayer().getHeight() - hotseatHeight;
}
- private static class InterpolatorWrapper implements Interpolator {
-
- public TimeInterpolator baseInterpolator = LINEAR;
-
- @Override
- public float getInterpolation(float v) {
- return baseInterpolator.getInterpolation(v);
- }
- }
-
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index fc9e1bb..94a7442 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -101,7 +101,7 @@
}
@Override
- protected float initCurrentAnimation(int animComponents) {
+ protected float initCurrentAnimation() {
StateAnimationConfig config = new StateAnimationConfig();
setupInterpolators(config);
config.duration = (long) (getShiftRange() * 2);
@@ -137,14 +137,17 @@
private void updateFullscreenProgress(float progress) {
mOverviewPanel.setFullscreenProgress(progress);
- int sysuiFlags = 0;
if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
+ int sysuiFlags = 0;
TaskView tv = mOverviewPanel.getTaskViewAt(0);
if (tv != null) {
sysuiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
}
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
+ } else {
+ mLauncher.getSystemUiController().updateUiState(
+ UI_STATE_OVERVIEW, mOverviewPanel.hasLightBackground());
}
- mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
index 0ed5291..8f9c014 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
@@ -32,8 +32,8 @@
}
@Override
- protected float initCurrentAnimation(int animComponents) {
- float multiplier = super.initCurrentAnimation(animComponents);
+ protected float initCurrentAnimation() {
+ float multiplier = super.initCurrentAnimation();
return mLauncher.getDeviceProfile().isSeascape() ? multiplier : -multiplier;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
index faf5054..3f7190f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
@@ -17,17 +17,18 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.AbstractFloatingView.getOpenView;
+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.Utilities.EDGE_NAV_BAR;
import android.animation.ValueAnimator;
+import android.os.SystemClock;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.quickstep.SystemUiProxy;
@@ -91,10 +92,10 @@
if (mIsTransposed) {
boolean draggingFromNav =
mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
- return draggingFromNav ? OVERVIEW : NORMAL;
+ return draggingFromNav ? HINT_STATE_TWO_BUTTON : NORMAL;
} else {
LauncherState startState = mStartState != null ? mStartState : fromState;
- return isDragTowardPositive ^ (startState == OVERVIEW) ? OVERVIEW : NORMAL;
+ return isDragTowardPositive ^ (startState == OVERVIEW) ? HINT_STATE_TWO_BUTTON : NORMAL;
}
}
@@ -104,6 +105,12 @@
super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
velocity, isFling);
mFinishFastOnSecondTouch = !mIsTransposed && mFromState == NORMAL;
+
+ if (targetState == HINT_STATE_TWO_BUTTON) {
+ // We were going to HINT_STATE_TWO_BUTTON, but end that animation immediately so we go
+ // to OVERVIEW instead.
+ animator.setDuration(0);
+ }
}
@Override
@@ -113,21 +120,35 @@
}
@Override
- protected float initCurrentAnimation(@AnimationFlags int animComponent) {
+ protected float initCurrentAnimation() {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
- maxAccuracy, animComponent);
+ maxAccuracy);
return (mLauncher.getDeviceProfile().isSeascape() ? 1 : -1) / range;
}
@Override
+ protected void updateProgress(float fraction) {
+ super.updateProgress(fraction);
+
+ // We have reached HINT_STATE, end the gesture now to go to OVERVIEW.
+ if (fraction >= 1 && mToState == HINT_STATE_TWO_BUTTON) {
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
+ mDetector.onTouchEvent(event);
+ event.recycle();
+ }
+ }
+
+ @Override
protected void onSwipeInteractionCompleted(LauncherState targetState) {
super.onSwipeInteractionCompleted(targetState);
if (!mIsTransposed) {
mContinuousTouchCount++;
}
- if (mStartState == NORMAL && targetState == OVERVIEW) {
+ if (mStartState == NORMAL && targetState == HINT_STATE_TWO_BUTTON) {
SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
} else if (targetState == NORMAL
&& mContinuousTouchCount >= MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index e0c041e..2a903eb 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -46,8 +45,11 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import static com.android.quickstep.util.SwipePipToHomeAnimator.FRACTION_END;
+import static com.android.quickstep.util.SwipePipToHomeAnimator.FRACTION_START;
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.Animator;
@@ -80,9 +82,9 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -91,16 +93,16 @@
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
-import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
+import com.android.quickstep.util.InputProxyHandlerFactory;
import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TransformParams;
@@ -112,26 +114,25 @@
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.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import com.android.systemui.shared.system.TaskInfoCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.function.Consumer;
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@TargetApi(Build.VERSION_CODES.R)
-public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
+ Q extends RecentsView, S extends BaseState<S>>
extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
RecentsAnimationCallbacks.RecentsAnimationListener {
private static final String TAG = "AbsSwipeUpHandler";
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
- protected final BaseActivityInterface<?, T> mActivityInterface;
+ protected final BaseActivityInterface<S, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
// Callbacks to be made once the recents animation starts
@@ -198,7 +199,7 @@
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
public static final long MAX_SWIPE_DURATION = 350;
- public static final long MIN_OVERSHOOT_DURATION = 120;
+ 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 =
@@ -216,6 +217,8 @@
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
+ // Possible second animation running at the same time as mRunningWindowAnim
+ private Animator mParallelRunningAnim;
private boolean mIsMotionPaused;
private boolean mHasMotionEverBeenPaused;
@@ -253,7 +256,10 @@
mActivityInterface = gestureState.getActivityInterface();
mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
mInputConsumerProxy =
- new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler);
+ new InputConsumerProxy(inputConsumer, () -> {
+ endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
+ endLauncherTransitionController();
+ }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
@@ -313,9 +319,9 @@
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
- this::notifyTransitionCancelled);
+ this::resetStateForAnimationCancel);
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
- this::notifyTransitionCancelled);
+ this::resetStateForAnimationCancel);
if (!LIVE_TILE.get()) {
mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
@@ -615,7 +621,7 @@
final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
if (passed != mPassedOverviewThreshold) {
mPassedOverviewThreshold = passed;
- if (!mDeviceState.isFullyGesturalNavMode()) {
+ if (mDeviceState.isTwoButtonNavMode()) {
performHapticFeedback();
}
}
@@ -651,8 +657,13 @@
|| (quickswitchThresholdPassed && centermostTaskFlags != 0));
mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
- int sysuiFlags = swipeUpThresholdPassed ? 0 : centermostTaskFlags;
- mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
+ if (swipeUpThresholdPassed) {
+ mActivity.getSystemUiController().updateUiState(
+ UI_STATE_OVERVIEW, mRecentsView.hasLightBackground());
+ } else {
+ mActivity.getSystemUiController().updateUiState(
+ UI_STATE_OVERVIEW, centermostTaskFlags);
+ }
}
}
@@ -721,6 +732,9 @@
@UiThread
public void onGestureStarted(boolean isLikelyToStartNewTask) {
+ mActivityInterface.closeOverlay();
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+
if (mRecentsView != null) {
InteractionJankMonitorWrapper.begin(mRecentsView,
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
@@ -778,19 +792,6 @@
handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
}
- /**
- * Called to create a input proxy for the running task
- */
- @UiThread
- protected InputConsumer createNewInputProxyHandler() {
- endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
- endLauncherTransitionController();
-
- StatefulActivity activity = mActivityInterface.getCreatedActivity();
- return activity == null ? InputConsumer.NO_OP
- : new OverviewInputConsumer(mGestureState, activity, null, true);
- }
-
private void endRunningWindowAnim(boolean cancel) {
if (mRunningWindowAnim != null) {
if (cancel) {
@@ -799,6 +800,13 @@
mRunningWindowAnim.end();
}
}
+ if (mParallelRunningAnim != null) {
+ if (cancel) {
+ mParallelRunningAnim.cancel();
+ } else {
+ mParallelRunningAnim.end();
+ }
+ }
}
private void onSettledOnEndTarget() {
@@ -848,6 +856,10 @@
private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
boolean isCancel) {
+ if (mDeviceState.isButtonNavMode()) {
+ // Button mode, this is only used to go to recents
+ return RECENTS;
+ }
final GestureEndTarget endTarget;
final boolean goingToNewTask;
if (mRecentsView != null) {
@@ -914,6 +926,9 @@
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
isFling, isCancel);
+ // Set the state, but don't notify until the animation completes
+ mGestureState.setEndTarget(endTarget, false /* isAtomic */);
+
float endShift = endTarget.isLauncher ? 1 : 0;
final float startShift;
if (!isFling) {
@@ -934,27 +949,38 @@
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
- Interpolator interpolator =
- endTarget == RECENTS ? (mDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
- ? ACCEL_DEACCEL : OVERSHOOT_1_2) : DEACCEL;
+ Interpolator interpolator;
+ S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
+ if (state.displayOverviewTasksAsGrid(mDp)) {
+ interpolator = ACCEL_DEACCEL;
+ } else if (endTarget == RECENTS) {
+ interpolator = OVERSHOOT_1_2;
+ } else {
+ interpolator = DEACCEL;
+ }
if (endTarget.isLauncher) {
mInputConsumerProxy.enable();
}
if (endTarget == HOME) {
- duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
+ duration = HOME_DURATION;
} else if (endTarget == RECENTS) {
if (mRecentsView != null) {
int nearestPage = mRecentsView.getDestinationPage();
+ boolean isScrolling = false;
if (mRecentsView.getNextPage() != nearestPage) {
// We shouldn't really scroll to the next page when swiping up to recents.
// Only allow settling on the next page if it's nearest to the center.
mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
+ isScrolling = true;
}
if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
+ isScrolling = true;
}
- duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+ if (!mDeviceState.isButtonNavMode() || isScrolling) {
+ duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+ }
}
}
@@ -1035,8 +1061,6 @@
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
- // Set the state, but don't notify until the animation completes
- mGestureState.setEndTarget(target, false /* isAtomic */);
maybeUpdateRecentsAttachedState();
// If we are transitioning to launcher, then listen for the activity to be restarted while
@@ -1045,7 +1069,11 @@
ActivityManagerWrapper.getInstance().registerTaskStackListener(
mActivityRestartListener);
- mActivityInterface.onAnimateToLauncher(mGestureState.getEndTarget(), duration);
+ mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
+ mGestureState.getEndTarget(), duration);
+ if (mParallelRunningAnim != null) {
+ mParallelRunningAnim.start();
+ }
}
if (mGestureState.getEndTarget() == HOME) {
@@ -1056,15 +1084,15 @@
HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome()
&& runningTaskTarget != null
- && runningTaskTarget.pictureInPictureParams != null
+ && runningTaskTarget.taskInfo.pictureInPictureParams != null
&& TaskInfoCompat.isAutoEnterPipEnabled(
- runningTaskTarget.pictureInPictureParams);
+ runningTaskTarget.taskInfo.pictureInPictureParams);
if (mIsSwipingPipToHome) {
mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
homeAnimFactory, runningTaskTarget, start);
mSwipePipToHomeAnimator.setDuration(SWIPE_PIP_TO_HOME_DURATION);
mSwipePipToHomeAnimator.setInterpolator(interpolator);
- mSwipePipToHomeAnimator.setFloatValues(0f, 1f);
+ mSwipePipToHomeAnimator.setFloatValues(FRACTION_START, FRACTION_END);
mSwipePipToHomeAnimator.start();
mRunningWindowAnim = RunningWindowAnim.wrap(mSwipePipToHomeAnimator);
} else {
@@ -1126,10 +1154,9 @@
}
});
animatorSet.play(windowAnim);
- if (mRecentsView != null && mDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
- && mGestureState.getEndTarget() == RECENTS) {
+ S state = mActivityInterface.stateFromGestureEndTarget(mGestureState.getEndTarget());
+ if (mRecentsView != null && state.displayOverviewTasksAsGrid(mDp)) {
animatorSet.play(ObjectAnimator.ofFloat(mRecentsView, RECENTS_GRID_PROGRESS, 1));
- animatorSet.play(mTaskViewSimulator.gridProgress.animateToValue(0, 1));
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
animatorSet.start();
@@ -1147,7 +1174,7 @@
final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
.startSwipePipToHome(taskInfo.topActivity,
TaskInfoCompat.getTopActivityInfo(taskInfo),
- runningTaskTarget.pictureInPictureParams,
+ runningTaskTarget.taskInfo.pictureInPictureParams,
homeRotation,
mDp.hotseatBarSizePx);
final Rect startBounds = new Rect();
@@ -1156,7 +1183,8 @@
runningTaskTarget.taskId,
taskInfo.topActivity,
runningTaskTarget.leash.getSurfaceControl(),
- TaskInfoCompat.getPipSourceRectHint(runningTaskTarget.pictureInPictureParams),
+ TaskInfoCompat.getPipSourceRectHint(
+ runningTaskTarget.taskInfo.pictureInPictureParams),
TaskInfoCompat.getWindowConfigurationBounds(taskInfo),
startBounds,
destinationBounds,
@@ -1313,7 +1341,12 @@
}
private void invalidateHandler() {
- mInputConsumerProxy.destroy();
+ if (!LIVE_TILE.get() || !mActivityInterface.isInLiveTileMode()
+ || mGestureState.getEndTarget() != RECENTS) {
+ mInputConsumerProxy.destroy();
+ mTaskAnimationManager.setLiveTileCleanUpHandler(null);
+ }
+ mInputConsumerProxy.unregisterCallback();
endRunningWindowAnim(false /* cancel */);
if (mGestureEndCallback != null) {
@@ -1350,10 +1383,6 @@
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
}
- private void notifyTransitionCancelled() {
- mAnimationFactory.onTransitionCancelled();
- }
-
private void resetStateForAnimationCancel() {
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
mActivityInterface.onTransitionCancelled(wasVisible);
@@ -1469,42 +1498,21 @@
mSwipePipToHomeAnimator.getDestinationBounds());
mRecentsAnimationController.setFinishTaskBounds(
mSwipePipToHomeAnimator.getTaskId(),
- mSwipePipToHomeAnimator.getDestinationBounds());
+ mSwipePipToHomeAnimator.getDestinationBounds(),
+ mSwipePipToHomeAnimator.getFinishTransaction());
mIsSwipingPipToHome = false;
}
}
protected abstract void finishRecentsControllerToHome(Runnable callback);
- private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (mRecentsView.getRunningTaskIndex() != -1
- && mRecentsView.getRunningTaskId() == task.taskId
- && mRecentsAnimationTargets.hasTask(task.taskId)) {
- launchOtherTaskInLiveTileMode(task.taskId, mRecentsAnimationTargets.apps);
- }
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
- mLiveTileRestartListener);
- }
- };
-
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
mActivityInterface.onSwipeUpToRecentsComplete();
mRecentsView.onSwipeUpAnimationSuccess();
if (LIVE_TILE.get()) {
- mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
- appearedTaskTarget -> {
- RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
- mRecentsAnimationTargets.apps,
- mRecentsAnimationTargets.apps.length + 1);
- apps[apps.length - 1] = appearedTaskTarget;
- launchOtherTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
- });
- ActivityManagerWrapper.getInstance().registerTaskStackListener(
- mLiveTileRestartListener);
+ mTaskAnimationManager.setLiveTileCleanUpHandler(mInputConsumerProxy::destroy);
+ mTaskAnimationManager.enableLiveTileRestartListener();
}
SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -1512,65 +1520,6 @@
reset();
}
- private void launchOtherTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) {
- AnimatorSet anim = new AnimatorSet();
- TaskView taskView = mRecentsView.getTaskView(taskId);
- if (taskView == null || !mRecentsView.isTaskViewVisible(taskView)) {
- // TODO: Refine this animation.
- SurfaceTransactionApplier surfaceApplier =
- new SurfaceTransactionApplier(mActivity.getDragLayer());
- ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
- appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
- appAnimator.setInterpolator(ACCEL_DEACCEL);
- appAnimator.addUpdateListener(new MultiValueUpdateListener() {
- @Override
- public void onUpdate(float percent) {
- SurfaceParams.Builder builder = new SurfaceParams.Builder(
- apps[apps.length - 1].leash);
- Matrix matrix = new Matrix();
- matrix.postScale(percent, percent);
- matrix.postTranslate(mDp.widthPx * (1 - percent) / 2,
- mDp.heightPx * (1 - percent) / 2);
- builder.withAlpha(percent).withMatrix(matrix);
- surfaceApplier.scheduleApply(builder.build());
- }
- });
- anim.play(appAnimator);
- } else {
- TaskViewUtils.composeRecentsLaunchAnimator(
- anim, taskView, apps,
- mRecentsAnimationTargets.wallpapers, true /* launcherClosing */,
- mActivity.getStateManager(), mRecentsView,
- mActivityInterface.getDepthController());
- }
- anim.addListener(new AnimatorListenerAdapter(){
-
- @Override
- public void onAnimationEnd(Animator animator) {
- cleanUp(false);
- }
-
- @Override
- public void onAnimationCancel(Animator animator) {
- cleanUp(true);
- }
-
- private void cleanUp(boolean canceled) {
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.finish(false /* toRecents */,
- null /* onFinishComplete */);
- if (canceled) {
- mRecentsAnimationController = null;
- } else {
- mActivityInterface.onLaunchTaskSuccess();
- }
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
- }
- }
- });
- anim.start();
- }
-
private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
return app.isNotInRecents
|| app.activityType == ACTIVITY_TYPE_HOME;
@@ -1624,19 +1573,17 @@
mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
- nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
- success -> {
- resultCallback.accept(success);
- if (success) {
- if (hasTaskPreviouslyAppeared) {
- onRestartPreviouslyAppearedTask();
- }
- } else {
- mActivityInterface.onLaunchTaskFailed();
- nextTask.notifyTaskLaunchFailed(TAG);
- mRecentsAnimationController.finish(true /* toRecents */, null);
- }
- }, MAIN_EXECUTOR.getHandler());
+ nextTask.launchTask(success -> {
+ resultCallback.accept(success);
+ if (success) {
+ if (hasTaskPreviouslyAppeared) {
+ onRestartPreviouslyAppearedTask();
+ }
+ } else {
+ mActivityInterface.onLaunchTaskFailed();
+ mRecentsAnimationController.finish(true /* toRecents */, null);
+ }
+ }, true /* freezeTaskList */);
} else {
mActivityInterface.onLaunchTaskFailed();
Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
@@ -1756,7 +1703,6 @@
public interface Factory {
- AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
- GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
+ AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
}
}
diff --git a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
deleted file mode 100644
index d159fa0..0000000
--- a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.taskbar.TaskbarController;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.SurfaceTransactionApplier;
-import com.android.quickstep.util.TaskViewSimulator;
-import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
- *
- * @param <T> activity that contains the overview
- */
-final class AppToOverviewAnimationProvider<T extends StatefulActivity<?>> extends
- RemoteAnimationProvider {
-
- private static final long RECENTS_LAUNCH_DURATION = 250;
- private static final String TAG = "AppToOverviewAnimationProvider";
-
- private final BaseActivityInterface<?, T> mActivityInterface;
- // The id of the currently running task that is transitioning to overview.
- private final RunningTaskInfo mTargetTask;
- private final RecentsAnimationDeviceState mDeviceState;
-
- private T mActivity;
- private RecentsView mRecentsView;
-
- AppToOverviewAnimationProvider(
- BaseActivityInterface<?, T> activityInterface, RunningTaskInfo targetTask,
- RecentsAnimationDeviceState deviceState) {
- mActivityInterface = activityInterface;
- mTargetTask = targetTask;
- mDeviceState = deviceState;
- }
-
- /**
- * Callback for when the activity is ready/initialized.
- *
- * @param activity the activity that is ready
- * @param wasVisible true if it was visible before
- */
- boolean onActivityReady(T activity, Boolean wasVisible) {
- activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTask);
- AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
- BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
- mDeviceState,
- wasVisible, (controller) -> {
- controller.getNormalController().dispatchOnStart();
- controller.getNormalController().getAnimationPlayer().end();
- });
- factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
- factory.setRecentsAttachedToAppWindow(true, false);
- mActivity = activity;
- mRecentsView = mActivity.getOverviewPanel();
- return false;
- }
-
- /**
- * Create remote window animation from the currently running app to the overview panel.
- *
- * @param appTargets the target apps
- * @return animation from app to overview
- */
- @Override
- public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
- PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- if (mActivity == null) {
- Log.e(TAG, "Animation created, before activity");
- return pa.buildAnim();
- }
-
- mRecentsView.setRunningTaskIconScaledDown(true);
- pa.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- mActivityInterface.onSwipeUpToRecentsComplete();
- mRecentsView.animateUpRunningTaskIconScale();
- }
- });
-
- DepthController depthController = mActivityInterface.getDepthController();
- if (depthController != null) {
- pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity),
- OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR);
- }
-
- TaskbarController taskbarController = mActivityInterface.getTaskbarController();
- if (taskbarController != null) {
- pa.add(taskbarController.createAnimToLauncher(OVERVIEW, getRecentsLaunchDuration()));
- }
-
- RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
- wallpaperTargets, MODE_CLOSING);
-
- // Use the top closing app to determine the insets for the animation
- RemoteAnimationTargetCompat runningTaskTarget = mTargetTask == null ? null
- : targets.findTask(mTargetTask.taskId);
- if (runningTaskTarget == null) {
- Log.e(TAG, "No closing app");
- return pa.buildAnim();
- }
-
- TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy());
- tsv.setDp(mActivity.getDeviceProfile());
- tsv.setOrientationState(mRecentsView.getPagedViewOrientedState());
- tsv.setPreview(runningTaskTarget);
-
- TransformParams params = new TransformParams()
- .setTargetSet(targets)
- .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView()));
-
- AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
- params.setBaseBuilderProxy((builder, app, p)
- -> builder.withAlpha(recentsAlpha.value));
-
- Interpolator taskInterpolator;
- if (targets.isAnimatingHome()) {
- params.setHomeBuilderProxy((builder, app, p) -> builder.withAlpha(1 - p.getProgress()));
-
- taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR;
- pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR);
- } else {
- // When animation from app to recents, the recents layer is drawn on top of the app. To
- // prevent the overlap, we animate the task first and then quickly fade in the recents.
- taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f);
- pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1,
- clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1));
- }
-
- pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator);
- tsv.addAppToOverviewAnim(pa, taskInterpolator);
- pa.addOnFrameCallback(() -> tsv.apply(params));
- return pa.buildAnim();
- }
-
- /**
- * Get duration of animation from app to overview.
- *
- * @return duration of animation
- */
- long getRecentsLaunchDuration() {
- return RECENTS_LAUNCH_DURATION;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index ce14197..147297a 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -31,8 +31,10 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
+import android.view.Gravity;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -42,6 +44,7 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -53,6 +56,7 @@
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;
@@ -193,90 +197,141 @@
}
/**
- * Calculates the taskView size for the provided device configuration
+ * Calculates the taskView size for the provided device configuration.
*/
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
PagedOrientationHandler orientedState) {
- calculateTaskSize(context, dp, getExtraSpace(context, dp, orientedState), outRect);
- }
-
- protected abstract float getExtraSpace(Context context, DeviceProfile dp,
- PagedOrientationHandler orientedState);
-
- private void calculateTaskSize(Context context, DeviceProfile dp, float extraVerticalSpace,
- Rect outRect) {
Resources res = context.getResources();
+ if (dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+ Rect gridRect = new Rect();
+ calculateGridSize(context, dp, gridRect);
- final int paddingResId;
- if (dp.isMultiWindowMode) {
- paddingResId = R.dimen.multi_window_task_card_horz_space;
- } else if (dp.isVerticalBarLayout()) {
- paddingResId = R.dimen.landscape_task_card_horz_space;
+ int verticalMargin = res.getDimensionPixelSize(
+ R.dimen.overview_grid_focus_vertical_margin);
+ float taskHeight = gridRect.height() - verticalMargin * 2;
+
+ PointF taskDimension = getTaskDimension(context, dp);
+ float scale = taskHeight / Math.max(taskDimension.x, taskDimension.y);
+ int outWidth = Math.round(scale * taskDimension.x);
+ int outHeight = Math.round(scale * taskDimension.y);
+
+ int gravity = Gravity.CENTER_VERTICAL;
+ gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
+ Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
} else {
- paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
+ int taskMargin = dp.overviewTaskMarginPx;
+ int proactiveRowAndMargin;
+ if (dp.isVerticalBarLayout()) {
+ // In Vertical Bar Layout the proactive row doesn't have its own space, it's inside
+ // the actions row.
+ proactiveRowAndMargin = 0;
+ } else {
+ proactiveRowAndMargin = res.getDimensionPixelSize(
+ R.dimen.overview_proactive_row_height)
+ + res.getDimensionPixelSize(R.dimen.overview_proactive_row_bottom_margin);
+ }
+ calculateTaskSizeInternal(context, dp,
+ dp.overviewTaskThumbnailTopMarginPx,
+ proactiveRowAndMargin + getOverviewActionsHeight(context) + taskMargin,
+ res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+ outRect);
}
- float paddingHorz = res.getDimension(paddingResId);
- float paddingVert = 0;
-
- calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
- res.getDimension(R.dimen.task_thumbnail_top_margin), outRect);
}
private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
- float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin,
+ int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding,
Rect outRect) {
- float taskWidth, taskHeight;
+ PointF taskDimension = getTaskDimension(context, dp);
Rect insets = dp.getInsets();
+
+ Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
+ potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);
+ potentialTaskRect.inset(
+ minimumHorizontalPadding,
+ claimedSpaceAbove,
+ minimumHorizontalPadding,
+ claimedSpaceBelow);
+
+ float scale = Math.min(
+ potentialTaskRect.width() / taskDimension.x,
+ potentialTaskRect.height() / taskDimension.y);
+ int outWidth = Math.round(scale * taskDimension.x);
+ int outHeight = Math.round(scale * taskDimension.y);
+
+ Gravity.apply(Gravity.CENTER, outWidth, outHeight, potentialTaskRect, outRect);
+ }
+
+ private PointF getTaskDimension(Context context, DeviceProfile dp) {
+ PointF dimension = new PointF();
if (dp.isMultiWindowMode) {
WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
- taskWidth = bounds.availableSize.x;
- taskHeight = bounds.availableSize.y;
+ dimension.x = bounds.availableSize.x;
+ dimension.y = bounds.availableSize.y;
+ } else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ dimension.x = dp.availableWidthPx;
+ dimension.y = dp.availableHeightPx;
} else {
- taskWidth = dp.availableWidthPx;
- taskHeight = dp.availableHeightPx;
+ dimension.x = dp.widthPx;
+ dimension.y = dp.heightPx;
}
+ return dimension;
+ }
- // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
- // we override the insets ourselves.
- int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
- int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
+ /**
+ * Calculates the overview grid size for the provided device configuration.
+ */
+ public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
+ Resources res = context.getResources();
+ int topMargin = res.getDimensionPixelSize(R.dimen.overview_grid_top_margin);
+ int bottomMargin = res.getDimensionPixelSize(R.dimen.overview_grid_bottom_margin);
+ int sideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
- float availableHeight = launcherVisibleHeight
- - topIconMargin - extraVerticalSpace - paddingVert;
- float availableWidth = launcherVisibleWidth - paddingHorz;
+ Rect insets = dp.getInsets();
+ outRect.set(0, 0, dp.widthPx, dp.heightPx);
+ outRect.inset(Math.max(insets.left, sideMargin), Math.max(insets.top, topMargin),
+ Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
+ }
- float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
- float outWidth = scale * taskWidth;
- float outHeight = scale * taskHeight;
+ /**
+ * Calculates the overview grid non-focused task size for the provided device configuration.
+ */
+ public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ PagedOrientationHandler orientedState) {
+ Resources res = context.getResources();
+ Rect gridRect = new Rect();
+ calculateGridSize(context, dp, gridRect);
- // Center in the visible space
- float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
- float y = insets.top + Math.max(topIconMargin,
- (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
- outRect.set(Math.round(x), Math.round(y),
- Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
+ int rowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
+ float rowHeight = (gridRect.height() - rowSpacing) / 2f;
+
+ PointF taskDimension = getTaskDimension(context, dp);
+ float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / Math.max(
+ taskDimension.x, taskDimension.y);
+ int outWidth = Math.round(scale * taskDimension.x);
+ int outHeight = Math.round(scale * taskDimension.y);
+
+ int gravity = Gravity.TOP;
+ gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
+ gridRect.inset(0, dp.overviewTaskThumbnailTopMarginPx, 0, 0);
+ Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
}
/**
* Calculates the modal taskView size for the provided device configuration
*/
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode
- ? R.dimen.multi_window_task_card_horz_space
- : dp.isVerticalBarLayout()
- ? R.dimen.landscape_task_card_horz_space
- : R.dimen.portrait_modal_task_card_horz_space);
- float extraVerticalSpace = getOverviewActionsHeight(context);
- float paddingVert = 0;
- float topIconMargin = 0;
- calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
- topIconMargin, outRect);
+ calculateTaskSizeInternal(
+ context, dp,
+ dp.overviewTaskMarginPx,
+ getOverviewActionsHeight(context) + dp.overviewTaskMarginPx,
+ dp.overviewTaskMarginPx,
+ outRect);
}
- /** Gets the space that the overview actions will take, including margins. */
- public final float getOverviewActionsHeight(Context context) {
+ /** Gets the space that the overview actions will take, including bottom margin. */
+ public final int getOverviewActionsHeight(Context context) {
Resources res = context.getResources();
- float actionsBottomMargin = 0;
+ int actionsBottomMargin = 0;
if (getMode(context) == Mode.THREE_BUTTONS) {
actionsBottomMargin = res.getDimensionPixelSize(
R.dimen.overview_actions_bottom_margin_three_button);
@@ -284,16 +339,17 @@
actionsBottomMargin = res.getDimensionPixelSize(
R.dimen.overview_actions_bottom_margin_gesture);
}
- float overviewActionsHeight = actionsBottomMargin
+ return actionsBottomMargin
+ res.getDimensionPixelSize(R.dimen.overview_actions_height);
- return overviewActionsHeight;
}
/**
* Called when the gesture ends and the animation starts towards the given target. No-op by
* default, but subclasses can override to add an additional animation with the same duration.
*/
- public void onAnimateToLauncher(GestureState.GestureEndTarget endTarget, long duration) {
+ public @Nullable Animator getParallelAnimationToLauncher(
+ GestureState.GestureEndTarget endTarget, long duration) {
+ return null;
}
/**
@@ -303,12 +359,15 @@
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
}
+ /**
+ * Returns the expected STATE_TYPE from the provided GestureEndTarget.
+ */
+ public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
+
public interface AnimationFactory {
void createActivityInterface(long transitionLength);
- default void onTransitionCancelled() { }
-
/**
* @param attached Whether to show RecentsView alongside the app window. If false, recents
* will be hidden by some property we can animate, e.g. alpha.
@@ -375,11 +434,6 @@
}
@Override
- public void onTransitionCancelled() {
- mActivity.getStateManager().goToState(mStartState, false /* animate */);
- }
-
- @Override
public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
if (mIsAttachedToWindow == attached && animate) {
return;
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 96e4f38..e13d1a4 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -18,6 +18,7 @@
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
import static com.android.quickstep.fallback.RecentsState.DEFAULT;
+import static com.android.quickstep.fallback.RecentsState.HOME;
import android.content.Context;
import android.graphics.Rect;
@@ -26,7 +27,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.ActivityInitListener;
@@ -82,6 +82,7 @@
@Override
public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
+ notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
factory.initUI();
return factory;
@@ -104,7 +105,7 @@
@Override
public RecentsView getVisibleRecentsView() {
RecentsActivity activity = getCreatedActivity();
- if (activity != null && activity.hasWindowFocus()) {
+ if (activity != null && activity.hasBeenResumed()) {
return activity.getOverviewPanel();
}
return null;
@@ -157,8 +158,23 @@
}
@Override
- protected float getExtraSpace(Context context, DeviceProfile dp,
- PagedOrientationHandler orientationHandler) {
- return context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height);
+ public RecentsState stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget) {
+ switch (endTarget) {
+ case RECENTS:
+ return DEFAULT;
+ case NEW_TASK:
+ case LAST_TASK:
+ return BACKGROUND_APP;
+ case HOME:
+ default:
+ return HOME;
+ }
+ }
+
+ private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+ // reset layout on swipe to home
+ RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+ recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+ rotationTouchHelper.getDisplayRotation());
}
}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index a80c111..ec1cc4a 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -55,6 +55,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
@@ -73,7 +74,7 @@
*/
@TargetApi(Build.VERSION_CODES.R)
public class FallbackSwipeHandler extends
- AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+ AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView, RecentsState> {
/**
* Message used for receiving gesture nav contract information. We use a static messenger to
@@ -246,7 +247,8 @@
if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
RemoteAnimationTargets targets = new RemoteAnimationTargets(
new RemoteAnimationTargetCompat[] {appearedTaskTarget},
- new RemoteAnimationTargetCompat[0], appearedTaskTarget.mode);
+ new RemoteAnimationTargetCompat[0], new RemoteAnimationTargetCompat[0],
+ appearedTaskTarget.mode);
mHomeAlphaParams.setTargetSet(targets);
updateHomeAlpha();
return true;
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 8d67ee6..ebdc1e6 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.os.Build;
+import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.GestureStateProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -213,7 +214,8 @@
/**
* @return the interface to the activity handing the UI updates for this gesture.
*/
- public <T extends StatefulActivity<?>> BaseActivityInterface<?, T> getActivityInterface() {
+ public <S extends BaseState<S>,
+ T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() {
return mActivityInterface;
}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 3f3e5ad..878f5c9 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -18,14 +18,13 @@
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.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.SysUINavigationMode.getMode;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import android.animation.Animator;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Rect;
-import android.util.Log;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -36,13 +35,11 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.taskbar.TaskbarController;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -181,7 +178,7 @@
@UiThread
private Launcher getVisibleLauncher() {
Launcher launcher = getCreatedActivity();
- return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus()
+ return (launcher != null) && launcher.isStarted() && launcher.hasBeenResumed()
? launcher : null;
}
@@ -192,6 +189,7 @@
return false;
}
+ closeOverlay();
launcher.getStateManager().goToState(OVERVIEW,
launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
return true;
@@ -263,27 +261,6 @@
}
@Override
- protected float getExtraSpace(Context context, DeviceProfile dp,
- PagedOrientationHandler orientationHandler) {
- Resources res = context.getResources();
- //TODO: this needs to account for the swipe gesture height and accessibility
- // UI when shown.
- float actionsBottomMargin = 0;
- if (!dp.isVerticalBarLayout()) {
- if (getMode(context) == Mode.THREE_BUTTONS) {
- actionsBottomMargin = res.getDimensionPixelSize(
- R.dimen.overview_actions_bottom_margin_three_button);
- } else {
- actionsBottomMargin = res.getDimensionPixelSize(
- R.dimen.overview_actions_bottom_margin_gesture);
- }
- }
- float actionsHeight = actionsBottomMargin
- + res.getDimensionPixelSize(R.dimen.overview_actions_height);
- return actionsHeight;
- }
-
- @Override
void onOverviewServiceBound() {
final BaseQuickstepLauncher activity = getCreatedActivity();
if (activity == null) return;
@@ -291,13 +268,14 @@
}
@Override
- public void onAnimateToLauncher(GestureEndTarget endTarget, long duration) {
+ public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
+ long duration) {
TaskbarController taskbarController = getTaskbarController();
if (taskbarController == null) {
- return;
+ return null;
}
- LauncherState toState = endTarget == GestureEndTarget.RECENTS ? OVERVIEW : NORMAL;
- taskbarController.createAnimToLauncher(toState, duration).start();
+ LauncherState toState = stateFromGestureEndTarget(endTarget);
+ return taskbarController.createAnimToLauncher(toState, duration);
}
@Override
@@ -327,4 +305,18 @@
}
return taskbarController.isDraggingItem();
}
+
+ @Override
+ public LauncherState stateFromGestureEndTarget(GestureEndTarget endTarget) {
+ switch (endTarget) {
+ case RECENTS:
+ return OVERVIEW;
+ case NEW_TASK:
+ case LAST_TASK:
+ return QUICK_SWITCH;
+ case HOME:
+ default:
+ return NORMAL;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 842fb84..311ac83 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -27,7 +27,9 @@
import androidx.annotation.NonNull;
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.views.FloatingIconView;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -39,7 +41,7 @@
* Temporary class to allow easier refactoring
*/
public class LauncherSwipeHandlerV2 extends
- AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
+ AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView, LauncherState> {
public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
@@ -133,7 +135,7 @@
// to home.
long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
return mActivity.getStateManager().createAnimationToNewWorkspace(
- NORMAL, accuracy, 0 /* animComponents */);
+ NORMAL, accuracy, StateAnimationConfig.SKIP_ALL_ANIMATIONS);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index a749f9a..2a6e478 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -53,7 +53,7 @@
*/
class OrientationTouchTransformer {
- class CurrentDisplay {
+ private static class CurrentDisplay {
public Point size;
public int rotation;
@@ -68,6 +68,13 @@
}
@Override
+ public String toString() {
+ return "CurrentDisplay:"
+ + " rotation: " + rotation
+ + " size: " + size;
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -86,21 +93,20 @@
private static final String TAG = "OrientationTouchTransformer";
private static final boolean DEBUG = false;
- private static final int MAX_ORIENTATIONS = 4;
private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
private final Matrix mTmpMatrix = new Matrix();
private final float[] mTmpPoint = new float[2];
- private Map<CurrentDisplay, OrientationRectF> mSwipeTouchRegions =
+ private final Map<CurrentDisplay, OrientationRectF> mSwipeTouchRegions =
new HashMap<CurrentDisplay, OrientationRectF>();
private final RectF mAssistantLeftRegion = new RectF();
private final RectF mAssistantRightRegion = new RectF();
private final RectF mOneHandedModeRegion = new RectF();
private CurrentDisplay mCurrentDisplay = new CurrentDisplay();
private int mNavBarGesturalHeight;
- private int mNavBarLargerGesturalHeight;
+ private final int mNavBarLargerGesturalHeight;
private boolean mEnableMultipleRegions;
private Resources mResources;
private OrientationRectF mLastRectTouched;
@@ -374,10 +380,7 @@
return;
}
- for (int i = 0; i < MAX_ORIENTATIONS; i++) {
- CurrentDisplay display = new CurrentDisplay(mCurrentDisplay.size, i);
- OrientationRectF rect = mSwipeTouchRegions.get(display);
-
+ for (OrientationRectF rect : mSwipeTouchRegions.values()) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "transform:DOWN, rect=" + rect);
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 985389e..923d4f1 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -16,29 +16,28 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.annotation.TargetApi;
-import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
import android.os.Build;
import android.os.SystemClock;
import android.os.Trace;
-import android.view.ViewConfiguration;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.ArrayList;
/**
* Helper class to handle various atomic commands for switching between Overview.
@@ -46,66 +45,191 @@
@TargetApi(Build.VERSION_CODES.P)
public class OverviewCommandHelper {
- private final Context mContext;
- private final RecentsAnimationDeviceState mDeviceState;
+ public static final int TYPE_SHOW = 1;
+ public static final int TYPE_SHOW_NEXT_FOCUS = 2;
+ public static final int TYPE_HIDE = 3;
+ public static final int TYPE_TOGGLE = 4;
+
+ private static final String TRANSITION_NAME = "Transition:toOverview";
+
+ private final TouchInteractionService mService;
private final OverviewComponentObserver mOverviewComponentObserver;
+ private final TaskAnimationManager mTaskAnimationManager;
+ private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
- private long mLastToggleTime;
-
- public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
- OverviewComponentObserver observer) {
- mContext = context;
- mDeviceState = deviceState;
+ public OverviewCommandHelper(TouchInteractionService service,
+ OverviewComponentObserver observer,
+ TaskAnimationManager taskAnimationManager) {
+ mService = service;
mOverviewComponentObserver = observer;
+ mTaskAnimationManager = taskAnimationManager;
}
- @BinderThread
- public void onOverviewToggle() {
- // If currently screen pinning, do not enter overview
- if (mDeviceState.isScreenPinningActive()) {
+ /**
+ * Called when the command finishes execution.
+ */
+ private void scheduleNextTask(CommandInfo command) {
+ if (!mPendingCommands.isEmpty() && mPendingCommands.get(0) == command) {
+ mPendingCommands.remove(0);
+ executeNext();
+ }
+ }
+
+ /**
+ * Executes the next command from the queue. If the command finishes immediately (returns true),
+ * it continues to execute the next command, until the queue is empty of a command defer's its
+ * completion (returns false).
+ */
+ @UiThread
+ private void executeNext() {
+ if (mPendingCommands.isEmpty()) {
return;
}
-
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
+ CommandInfo cmd = mPendingCommands.get(0);
+ if (executeCommand(cmd)) {
+ scheduleNextTask(cmd);
+ }
}
+ @UiThread
+ private void addCommand(CommandInfo cmd) {
+ boolean wasEmpty = mPendingCommands.isEmpty();
+ mPendingCommands.add(cmd);
+ if (wasEmpty) {
+ executeNext();
+ }
+ }
+
+ /**
+ * Adds a command to be executed next, after all pending tasks are completed
+ */
@BinderThread
- public void onOverviewShown(boolean triggeredFromAltTab) {
- if (triggeredFromAltTab) {
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- }
- MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
+ public void addCommand(int type) {
+ CommandInfo cmd = new CommandInfo(type);
+ MAIN_EXECUTOR.execute(() -> addCommand(cmd));
}
- @BinderThread
- public void onOverviewHidden() {
- MAIN_EXECUTOR.execute(new HideRecentsCommand());
+ private TaskView getNextTask(RecentsView view) {
+ final TaskView runningTaskView = view.getRunningTaskView();
+
+ if (runningTaskView == null) {
+ return view.getTaskViewCount() > 0 ? view.getTaskViewAt(0) : null;
+ } else {
+ final TaskView nextTask = view.getNextTaskView();
+ return nextTask != null ? nextTask : runningTaskView;
+ }
}
- private class ShowRecentsCommand extends RecentsActivityCommand {
-
- private final boolean mTriggeredFromAltTab;
-
- ShowRecentsCommand(boolean triggeredFromAltTab) {
- mTriggeredFromAltTab = triggeredFromAltTab;
+ private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
+ RunnableList callbackList = null;
+ if (taskView != null) {
+ taskView.setEndQuickswitchCuj(true);
+ callbackList = taskView.launchTaskAnimated();
}
- @Override
- protected boolean handleCommand(long elapsedTime) {
- // TODO: Go to the next page if started from alt-tab.
- return mActivityInterface.getVisibleRecentsView() != null;
+ if (callbackList != null) {
+ callbackList.add(() -> scheduleNextTask(cmd));
+ return false;
+ } else {
+ recents.startHome();
+ return true;
}
+ }
- @Override
- protected void onTransitionComplete() {
- // TODO(b/138729100) This doesn't execute first time launcher is run
- if (mTriggeredFromAltTab) {
- RecentsView rv = mActivityInterface.getVisibleRecentsView();
- if (rv == null) {
- return;
+ /**
+ * Executes the task and returns true if next task can be executed. If false, then the next
+ * task is deferred until {@link #scheduleNextTask} is called
+ */
+ private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
+ BaseActivityInterface<?, T> activityInterface =
+ mOverviewComponentObserver.getActivityInterface();
+ RecentsView recents = activityInterface.getVisibleRecentsView();
+ if (recents == null) {
+ if (cmd.type == TYPE_HIDE) {
+ // already hidden
+ return true;
+ }
+ } else {
+ switch (cmd.type) {
+ case TYPE_SHOW:
+ // already visible
+ return true;
+ case TYPE_HIDE: {
+ int currentPage = recents.getNextPage();
+ TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
+ ? (TaskView) recents.getPageAt(currentPage)
+ : null;
+ return launchTask(recents, tv, cmd);
}
+ case TYPE_TOGGLE:
+ return launchTask(recents, getNextTask(recents), cmd);
+ }
+ }
+ if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
+ // If successfully switched, wait until animation finishes
+ return false;
+ }
+
+ final T activity = activityInterface.getCreatedActivity();
+ if (activity != null) {
+ InteractionJankMonitorWrapper.begin(
+ activity.getRootView(),
+ InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ }
+
+ GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE);
+ AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
+ .newHandler(gestureState, cmd.createTime);
+ interactionHandler.setGestureEndCallback(
+ () -> onTransitionComplete(cmd, interactionHandler));
+
+ Intent intent = new Intent(interactionHandler.getLaunchIntent());
+ interactionHandler.initWhenReady(intent);
+
+ RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ interactionHandler.onGestureEnded(0, new PointF(), new PointF());
+ cmd.removeListener(this);
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ interactionHandler.onGestureCancelled();
+ cmd.removeListener(this);
+ }
+ };
+
+ if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+ cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
+ cmd.mActiveCallbacks.addListener(interactionHandler);
+ mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
+ interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
+
+ cmd.mActiveCallbacks.addListener(recentAnimListener);
+ mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
+ } else {
+ intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
+ cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
+ gestureState, intent, interactionHandler);
+ interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
+ cmd.mActiveCallbacks.addListener(recentAnimListener);
+ }
+
+ Trace.beginAsyncSection(TRANSITION_NAME, 0);
+ return false;
+ }
+
+ private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
+ cmd.removeListener(handler);
+ Trace.endAsyncSection(TRANSITION_NAME, 0);
+
+ if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
+ RecentsView rv =
+ mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+ if (rv != null) {
// Ensure that recents view has focus so that it receives the followup key inputs
TaskView taskView = rv.getNextTaskView();
if (taskView == null) {
@@ -120,130 +244,22 @@
}
}
}
+ scheduleNextTask(cmd);
}
- private class HideRecentsCommand extends RecentsActivityCommand {
+ private static class CommandInfo {
+ public final long createTime = SystemClock.elapsedRealtime();
+ public final int type;
+ RecentsAnimationCallbacks mActiveCallbacks;
- @Override
- protected boolean handleCommand(long elapsedTime) {
- RecentsView recents = mActivityInterface.getVisibleRecentsView();
- if (recents == null) {
- return false;
- }
- int currentPage = recents.getNextPage();
- if (currentPage >= 0 && currentPage < recents.getTaskViewCount()) {
- ((TaskView) recents.getPageAt(currentPage)).launchTask(true);
- } else {
- recents.startHome();
- }
- return true;
- }
- }
-
- private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
-
- private static final String TRANSITION_NAME = "Transition:toOverview";
- protected final BaseActivityInterface<?, T> mActivityInterface;
- private final long mCreateTime;
- private final AppToOverviewAnimationProvider<T> mAnimationProvider;
-
- private final long mToggleClickedTime = SystemClock.uptimeMillis();
- private ActivityInitListener mListener;
-
- public RecentsActivityCommand() {
- mActivityInterface = mOverviewComponentObserver.getActivityInterface();
- mCreateTime = SystemClock.elapsedRealtime();
- mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
- ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
-
- // Preload the plan
- RecentsModel.INSTANCE.get(mContext).getTasks(null);
+ CommandInfo(int type) {
+ this.type = type;
}
- @Override
- public void run() {
- long elapsedTime = mCreateTime - mLastToggleTime;
- mLastToggleTime = mCreateTime;
-
- if (handleCommand(elapsedTime)) {
- // Command already handled.
- return;
+ void removeListener(RecentsAnimationListener listener) {
+ if (mActiveCallbacks != null) {
+ mActiveCallbacks.removeListener(listener);
}
-
- if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
- // If successfully switched, then return
- return;
- }
-
- final T activity = mActivityInterface.getCreatedActivity();
- if (activity != null) {
- InteractionJankMonitorWrapper.begin(
- activity.getRootView(),
- InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
- }
-
- // Otherwise, start overview.
- mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
- mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
- new RemoteAnimationProvider() {
- @Override
- public AnimatorSet createWindowAnimation(
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
- return RecentsActivityCommand.this.createWindowAnimation(appTargets,
- wallpaperTargets);
- }
- }, mContext, MAIN_EXECUTOR.getHandler(),
- mAnimationProvider.getRecentsLaunchDuration());
}
-
- protected boolean handleCommand(long elapsedTime) {
- // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
- // the menu activity which takes window focus, preventing the right condition from
- // being run below
- RecentsView recents = mActivityInterface.getVisibleRecentsView();
- if (recents != null) {
- // Launch the next task
- recents.showNextTask();
- return true;
- } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
- // The user tried to launch back into overview too quickly, either after
- // launching an app, or before overview has actually shown, just ignore for now
- return true;
- }
- return false;
- }
-
- private boolean onActivityReady(Boolean wasVisible) {
- final T activity = mActivityInterface.getCreatedActivity();
- return mAnimationProvider.onActivityReady(activity, wasVisible);
- }
-
- private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
- LatencyTrackerCompat.logToggleRecents(
- mContext, (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
-
- mListener.unregister();
-
- AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(appTargets,
- wallpaperTargets);
- animatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- Trace.beginAsyncSection(TRANSITION_NAME, 0);
- super.onAnimationStart(animation);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onTransitionComplete();
- Trace.endAsyncSection(TRANSITION_NAME, 0);
- }
- });
- return animatorSet;
- }
-
- protected void onTransitionComplete() { }
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 1b2fd41..06137f2 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,9 +18,9 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.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.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
@@ -30,7 +30,6 @@
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;
@@ -46,7 +45,6 @@
import com.android.launcher3.R;
import com.android.launcher3.WrappedAnimationRunnerImpl;
import com.android.launcher3.WrappedLauncherAnimationRunner;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -54,16 +52,19 @@
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.fallback.FallbackRecentsStateController;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsDragLayer;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -94,7 +95,6 @@
// Strong refs to runners which are cleared when the activity is destroyed
private WrappedAnimationRunnerImpl mActivityLaunchAnimationRunner;
- private SearchAdapterProvider mSearchAdapterProvider;
/**
* Init drag layer and overview panel views.
@@ -106,8 +106,14 @@
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
+ SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
+ splitPlaceholderView.init(
+ new SplitSelectStateController(
+ SystemUiProxy.INSTANCE.get(this))
+ );
+
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView);
+ mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
}
@Override
@@ -172,49 +178,48 @@
}
@Override
- public ActivityOptions getActivityLaunchOptions(final View v) {
+ public ActivityOptionsWrapper getActivityLaunchOptions(final View v) {
if (!(v instanceof TaskView)) {
- return null;
+ return super.getActivityLaunchOptions(v);
}
final TaskView taskView = (TaskView) v;
- mActivityLaunchAnimationRunner = new WrappedAnimationRunnerImpl() {
- @Override
- public Handler getHandler() {
- return mUiHandler;
- }
+ RunnableList onEndCallback = new RunnableList();
- @Override
- public void onCreateAnimation(int transit,
+ mActivityLaunchAnimationRunner = (int transit,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
RemoteAnimationTargetCompat[] nonAppTargets,
- AnimationResult result) {
- AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
- wallpaperTargets);
- anim.addListener(resetStateListener());
- result.setAnimation(anim, RecentsActivity.this);
- }
+ AnimationResult result) -> {
+ AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
+ wallpaperTargets, nonAppTargets);
+ anim.addListener(resetStateListener());
+ result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy);
};
+
final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner<>(
- mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */);
- return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+ mUiHandler, mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */);
+ RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(
wrapper, RECENTS_LAUNCH_DURATION,
RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
- - STATUS_BAR_TRANSITION_PRE_DELAY));
+ - STATUS_BAR_TRANSITION_PRE_DELAY);
+ return new ActivityOptionsWrapper(
+ ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
+ onEndCallback);
}
/**
* Composes the animations for a launch from the recents list if possible.
*/
- private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
+ private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets) {
AnimatorSet target = new AnimatorSet();
boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, !activityClosing, appTargets,
- wallpaperTargets, null /* depthController */, pa);
+ wallpaperTargets, nonAppTargets, null /* depthController */, pa);
target.play(pa.buildAnim());
// Found a visible recents task that matches the opening app, lets launch the app from there
@@ -262,7 +267,7 @@
setupViews();
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
- Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
+ mFallbackRecentsView.hasLightBackground());
ACTIVITY_TRACKER.handleCreate(this);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 646c5a0..82e8a93 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.graphics.Rect;
+import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
@@ -149,10 +150,13 @@
* accordingly. This should be called before `finish`
* @param taskId for which the leash should be updated
* @param destinationBounds bounds of the final PiP window
+ * @param finishTransaction leash operations for the final transform.
*/
- public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+ public void setFinishTaskBounds(int taskId, Rect destinationBounds,
+ PictureInPictureSurfaceTransaction finishTransaction) {
UI_HELPER_EXECUTOR.execute(
- () -> mController.setFinishTaskBounds(taskId, destinationBounds));
+ () -> mController.setFinishTaskBounds(taskId, destinationBounds,
+ finishTransaction));
}
/**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index f99b7e6..23e35f6 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,12 +17,13 @@
import static android.content.Intent.ACTION_USER_UNLOCKED;
-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.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
+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.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -60,11 +61,11 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayHolder;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
@@ -186,7 +187,7 @@
SettingsCache.OnChangeListener onChangeListener =
enabled -> mIsOneHandedModeEnabled = enabled;
settingsCache.register(oneHandedUri, onChangeListener);
- settingsCache.dispatchOnChange(oneHandedUri);
+ mIsOneHandedModeEnabled = settingsCache.getValue(oneHandedUri);
runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
} else {
mIsOneHandedModeEnabled = false;
@@ -198,7 +199,7 @@
SettingsCache.OnChangeListener onChangeListener =
enabled -> mIsSwipeToNotificationEnabled = enabled;
settingsCache.register(swipeBottomNotificationUri, onChangeListener);
- settingsCache.dispatchOnChange(swipeBottomNotificationUri);
+ mIsSwipeToNotificationEnabled = settingsCache.getValue(swipeBottomNotificationUri);
runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
@@ -304,6 +305,13 @@
}
/**
+ * @return whether the current nav mode is 2-button-based.
+ */
+ public boolean isTwoButtonNavMode() {
+ return mMode == TWO_BUTTONS;
+ }
+
+ /**
* @return whether the current nav mode is button-based.
*/
public boolean isButtonNavMode() {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index da0a664..3861bab 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -33,7 +33,7 @@
public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
Rect minimizedHomeBounds) {
- super(apps, wallpapers, MODE_CLOSING);
+ super(apps, wallpapers, new RemoteAnimationTargetCompat[0], MODE_CLOSING);
this.homeContentInsets = homeContentInsets;
this.minimizedHomeBounds = minimizedHomeBounds;
}
@@ -41,13 +41,4 @@
public boolean hasTargets() {
return unfilteredApps.length != 0;
}
-
- public boolean hasTask(int taskId) {
- for (RemoteAnimationTargetCompat target : unfilteredApps) {
- if (target.taskId == taskId) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index ab5e3ba..a1af77d 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.ArrayList;
@@ -30,13 +32,15 @@
public final RemoteAnimationTargetCompat[] unfilteredApps;
public final RemoteAnimationTargetCompat[] apps;
public final RemoteAnimationTargetCompat[] wallpapers;
+ public final RemoteAnimationTargetCompat[] nonApps;
public final int targetMode;
public final boolean hasRecents;
private boolean mReleased = false;
public RemoteAnimationTargets(RemoteAnimationTargetCompat[] apps,
- RemoteAnimationTargetCompat[] wallpapers, int targetMode) {
+ RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
+ int targetMode) {
ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
boolean hasRecents = false;
if (apps != null) {
@@ -55,6 +59,7 @@
this.wallpapers = wallpapers;
this.targetMode = targetMode;
this.hasRecents = hasRecents;
+ this.nonApps = nonApps;
}
public RemoteAnimationTargetCompat findTask(int taskId) {
@@ -66,6 +71,18 @@
return null;
}
+ /**
+ * Gets the navigation bar remote animation target if exists.
+ */
+ public RemoteAnimationTargetCompat getNavBarRemoteAnimationTarget() {
+ for (RemoteAnimationTargetCompat target : nonApps) {
+ if (target.windowType == TYPE_NAVIGATION_BAR) {
+ return target;
+ }
+ }
+ return null;
+ }
+
public boolean isAnimatingHome() {
for (RemoteAnimationTargetCompat target : unfilteredApps) {
if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
@@ -98,6 +115,9 @@
for (RemoteAnimationTargetCompat target : wallpapers) {
target.release();
}
+ for (RemoteAnimationTargetCompat target : nonApps) {
+ target.release();
+ }
}
/**
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index f4b8b62..a8c09dc 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -29,7 +29,6 @@
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -67,8 +66,6 @@
// How much further we can drag past recents, as a factor of mTransitionDragLength.
protected float mDragLengthFactor = 1;
- protected final float mMaxShadowRadius;
-
protected AnimatorControllerWithResistance mWindowTransitionController;
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
@@ -82,9 +79,6 @@
mTaskViewSimulator.getOrientationState().update(
mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
mDeviceState.getRotationTouchHelper().getDisplayRotation());
-
- mMaxShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.max_shadow_radius);
- mTransformParams.setShadowRadius(mMaxShadowRadius);
}
protected void initTransitionEndpoints(DeviceProfile dp) {
@@ -271,11 +265,9 @@
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
- float shadowRadius = Utilities.mapRange(progress, mMaxShadowRadius, 0);
mTransformParams
.setTargetAlpha(getWindowAlpha(progress))
- .setCornerRadius(cornerRadius)
- .setShadowRadius(shadowRadius);
+ .setCornerRadius(cornerRadius);
mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
@@ -286,8 +278,7 @@
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
builder.withMatrix(mMatrix)
.withWindowCrop(mCropRect)
- .withCornerRadius(params.getCornerRadius())
- .withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
+ .withCornerRadius(params.getCornerRadius());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 5668817..1f332c4 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -35,38 +35,67 @@
import android.view.MotionEvent;
import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-import com.android.systemui.shared.recents.ISplitScreenListener;
-import com.android.systemui.shared.recents.IStartingWindowListener;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteTransitionCompat;
+import com.android.wm.shell.onehanded.IOneHanded;
+import com.android.wm.shell.pip.IPip;
+import com.android.wm.shell.pip.IPipAnimationListener;
+import com.android.wm.shell.splitscreen.ISplitScreen;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.startingsurface.IStartingWindow;
+import com.android.wm.shell.startingsurface.IStartingWindowListener;
+import com.android.wm.shell.transition.IShellTransitions;
/**
* Holds the reference to SystemUI.
*/
-public class SystemUiProxy implements ISystemUiProxy {
+public class SystemUiProxy implements ISystemUiProxy,
+ SysUINavigationMode.NavigationModeChangeListener {
private static final String TAG = SystemUiProxy.class.getSimpleName();
public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
new MainThreadInitializedObject<>(SystemUiProxy::new);
private ISystemUiProxy mSystemUiProxy;
+ private IPip mPip;
+ private ISplitScreen mSplitScreen;
+ private IOneHanded mOneHanded;
+ private IShellTransitions mShellTransitions;
+ private IStartingWindow mStartingWindow;
private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
- MAIN_EXECUTOR.execute(() -> setProxy(null));
+ MAIN_EXECUTOR.execute(() -> clearProxy());
};
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
private boolean mLastShelfVisible;
- private float mLastBackButtonAlpha;
- private boolean mLastBackButtonAnimate;
+ private float mLastNavButtonAlpha;
+ private boolean mLastNavButtonAnimate;
+ private boolean mHasNavButtonAlphaBeenSet = false;
// TODO(141886704): Find a way to remove this
private int mLastSystemUiStateFlags;
public SystemUiProxy(Context context) {
- // Do nothing
+ SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+ }
+
+ @Override
+ public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+ // Whenever the nav mode changes, force reset the nav button alpha
+ setNavBarButtonAlpha(1f, false);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onBackPressed();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onBackPressed", e);
+ }
+ }
}
@Override
@@ -75,12 +104,23 @@
return null;
}
- public void setProxy(ISystemUiProxy proxy) {
+ public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
+ IOneHanded oneHanded, IShellTransitions shellTransitions,
+ IStartingWindow startingWindow) {
unlinkToDeath();
mSystemUiProxy = proxy;
+ mPip = pip;
+ mSplitScreen = splitScreen;
+ mOneHanded = oneHanded;
+ mShellTransitions = shellTransitions;
+ mStartingWindow = startingWindow;
linkToDeath();
}
+ public void clearProxy() {
+ setProxy(null, null, null, null, null, null);
+ }
+
// TODO(141886704): Find a way to remove this
public void setLastSystemUiStateFlags(int stateFlags) {
mLastSystemUiStateFlags = stateFlags;
@@ -149,28 +189,19 @@
return null;
}
- @Override
- public void setBackButtonAlpha(float alpha, boolean animate) {
- boolean changed = Float.compare(alpha, mLastBackButtonAlpha) != 0
- || animate != mLastBackButtonAnimate;
- if (mSystemUiProxy != null && changed) {
- mLastBackButtonAlpha = alpha;
- mLastBackButtonAnimate = animate;
- try {
- mSystemUiProxy.setBackButtonAlpha(alpha, animate);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setBackButtonAlpha", e);
- }
- }
- }
-
- public float getLastBackButtonAlpha() {
- return mLastBackButtonAlpha;
+ public float getLastNavButtonAlpha() {
+ return mLastNavButtonAlpha;
}
@Override
public void setNavBarButtonAlpha(float alpha, boolean animate) {
- if (mSystemUiProxy != null) {
+ boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
+ || animate != mLastNavButtonAnimate
+ || !mHasNavButtonAlphaBeenSet;
+ if (mSystemUiProxy != null && changed) {
+ mLastNavButtonAlpha = alpha;
+ mLastNavButtonAnimate = animate;
+ mHasNavButtonAlphaBeenSet = true;
try {
mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
} catch (RemoteException e) {
@@ -269,21 +300,6 @@
}
@Override
- public void setShelfHeight(boolean visible, int shelfHeight) {
- boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
- if (mSystemUiProxy != null && changed) {
- mLastShelfVisible = visible;
- mLastShelfHeight = shelfHeight;
- try {
- mSystemUiProxy.setShelfHeight(visible, shelfHeight);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setShelfHeight visible: " + visible
- + " height: " + shelfHeight, e);
- }
- }
- }
-
- @Override
public void handleImageAsScreenshot(Bitmap bitmap, Rect rect, Insets insets, int i) {
if (mSystemUiProxy != null) {
try {
@@ -319,20 +335,6 @@
}
}
- /**
- * Sets listener to get pinned stack animation callbacks.
- */
- @Override
- public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.setPinnedStackAnimationListener(listener);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
- }
- }
- }
-
@Override
public void onQuickSwitchToNewTask(int rotation) {
if (mSystemUiProxy != null) {
@@ -358,28 +360,6 @@
}
@Override
- public void startOneHandedMode() {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.startOneHandedMode();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call startOneHandedMode", e);
- }
- }
- }
-
- @Override
- public void stopOneHandedMode() {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.stopOneHandedMode();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call stopOneHandedMode", e);
- }
- }
- }
-
- @Override
public void expandNotificationPanel() {
if (mSystemUiProxy != null) {
try {
@@ -390,12 +370,45 @@
}
}
- @Override
+ //
+ // Pip
+ //
+
+ /**
+ * Sets the shelf height.
+ */
+ public void setShelfHeight(boolean visible, int shelfHeight) {
+ boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
+ if (mPip != null && changed) {
+ mLastShelfVisible = visible;
+ mLastShelfHeight = shelfHeight;
+ try {
+ mPip.setShelfHeight(visible, shelfHeight);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setShelfHeight visible: " + visible
+ + " height: " + shelfHeight, e);
+ }
+ }
+ }
+
+ /**
+ * Sets listener to get pinned stack animation callbacks.
+ */
+ public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
+ if (mPip != null) {
+ try {
+ mPip.setPinnedStackAnimationListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
+ }
+ }
+ }
+
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams, int launcherRotation, int shelfHeight) {
- if (mSystemUiProxy != null) {
+ if (mPip != null) {
try {
- return mSystemUiProxy.startSwipePipToHome(componentName, activityInfo,
+ return mPip.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams, launcherRotation, shelfHeight);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startSwipePipToHome", e);
@@ -404,111 +417,85 @@
return null;
}
- @Override
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- if (mSystemUiProxy != null) {
+ if (mPip != null) {
try {
- mSystemUiProxy.stopSwipePipToHome(componentName, destinationBounds);
+ mPip.stopSwipePipToHome(componentName, destinationBounds);
} catch (RemoteException e) {
Log.w(TAG, "Failed call stopSwipePipToHome");
}
}
}
- @Override
- public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.registerRemoteTransition(remoteTransition);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call registerRemoteTransition");
- }
- }
- }
+ //
+ // Splitscreen
+ //
- @Override
- public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.unregisterRemoteTransition(remoteTransition);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call registerRemoteTransition");
- }
- }
- }
-
- @Override
public void registerSplitScreenListener(ISplitScreenListener listener) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.registerSplitScreenListener(listener);
+ mSplitScreen.registerSplitScreenListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
}
}
- @Override
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.unregisterSplitScreenListener(listener);
+ mSplitScreen.unregisterSplitScreenListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call unregisterSplitScreenListener");
}
}
}
- @Override
public void setSideStageVisibility(boolean visible) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.setSideStageVisibility(visible);
+ mSplitScreen.setSideStageVisibility(visible);
} catch (RemoteException e) {
Log.w(TAG, "Failed call setSideStageVisibility");
}
}
}
- @Override
public void exitSplitScreen() {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.exitSplitScreen();
+ mSplitScreen.exitSplitScreen();
} catch (RemoteException e) {
Log.w(TAG, "Failed call exitSplitScreen");
}
}
}
- @Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ mSplitScreen.exitSplitScreenOnHide(exitSplitScreenOnHide);
} catch (RemoteException e) {
Log.w(TAG, "Failed call exitSplitScreen");
}
}
}
- @Override
public void startTask(int taskId, int stage, int position, Bundle options) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.startTask(taskId, stage, position, options);
+ mSplitScreen.startTask(taskId, stage, position, options);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startTask");
}
}
}
- @Override
public void startShortcut(String packageName, String shortcutId, int stage, int position,
Bundle options, UserHandle user) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.startShortcut(packageName, shortcutId, stage, position, options,
+ mSplitScreen.startShortcut(packageName, shortcutId, stage, position, options,
user);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startShortcut");
@@ -516,38 +503,87 @@
}
}
- @Override
- public void startIntent(PendingIntent intent, Intent fillInIntent, int stage,
- int position, Bundle options) {
- if (mSystemUiProxy != null) {
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ Bundle options) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.startIntent(intent, fillInIntent, stage, position,
- options);
+ mSplitScreen.startIntent(intent, fillInIntent, stage, position, options);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntent");
}
}
}
- @Override
public void removeFromSideStage(int taskId) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.removeFromSideStage(taskId);
+ mSplitScreen.removeFromSideStage(taskId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call removeFromSideStage");
}
}
}
+ //
+ // One handed
+ //
+
+ public void startOneHandedMode() {
+ if (mOneHanded != null) {
+ try {
+ mOneHanded.startOneHanded();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startOneHandedMode", e);
+ }
+ }
+ }
+
+ public void stopOneHandedMode() {
+ if (mOneHanded != null) {
+ try {
+ mOneHanded.stopOneHanded();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopOneHandedMode", e);
+ }
+ }
+ }
+
+ //
+ // Remote transitions
+ //
+
+ public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) {
+ if (mShellTransitions != null) {
+ try {
+ mShellTransitions.registerRemote(remoteTransition.getFilter(),
+ remoteTransition.getTransition());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerRemoteTransition");
+ }
+ }
+ }
+
+ public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
+ if (mShellTransitions != null) {
+ try {
+ mShellTransitions.unregisterRemote(remoteTransition.getTransition());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerRemoteTransition");
+ }
+ }
+ }
+
+ //
+ // Starting window
+ //
+
/**
* Sets listener to get callbacks when launching a task.
*/
- @Override
public void setStartingWindowListener(IStartingWindowListener listener) {
- if (mSystemUiProxy != null) {
+ if (mStartingWindow != null) {
try {
- mSystemUiProxy.setStartingWindowListener(listener);
+ mStartingWindow.setStartingWindowListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 02c2763..b6dad2d 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -17,10 +17,11 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -31,13 +32,13 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+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 java.util.function.Consumer;
+import com.android.systemui.shared.system.TaskStackChangeListener;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
public static final boolean ENABLE_SHELL_TRANSITIONS =
@@ -49,9 +50,24 @@
// Temporary until we can hook into gesture state events
private GestureState mLastGestureState;
private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
- private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
+ private Runnable mLiveTileCleanUpHandler;
private Context mCtx;
+ private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+ if (LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+ && activityInterface.getCreatedActivity() != null) {
+ RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
+ recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId);
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+ mLiveTileRestartListener);
+ }
+ }
+ };
+
TaskAnimationManager(Context ctx) {
mCtx = ctx;
}
@@ -113,9 +129,14 @@
@Override
public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
- if (mLaunchOtherTaskHandler != null
- && mLastGestureState.getEndTarget() == RECENTS) {
- mLaunchOtherTaskHandler.accept(appearedTaskTarget);
+ BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+ if (LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+ && activityInterface.getCreatedActivity() != null) {
+ RecentsView recentsView =
+ activityInterface.getCreatedActivity().getOverviewPanel();
+ RemoteAnimationTargetCompat[] apps = new RemoteAnimationTargetCompat[1];
+ apps[0] = appearedTaskTarget;
+ recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
return;
}
if (mController != null) {
@@ -160,13 +181,12 @@
return mCallbacks;
}
- /**
- * The passed-in handler is used to render side task launch animation in recents in live tile
- * mode.
- */
- public void setLaunchOtherTaskInLiveTileModeHandler(
- Consumer<RemoteAnimationTargetCompat> handler) {
- mLaunchOtherTaskHandler = handler;
+ public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
+ mLiveTileCleanUpHandler = cleanUpHandler;
+ }
+
+ public void enableLiveTileRestartListener() {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mLiveTileRestartListener);
}
/**
@@ -206,6 +226,12 @@
* Cleans up the recents animation entirely.
*/
private void cleanUpRecentsAnimation() {
+ if (mLiveTileCleanUpHandler != null) {
+ mLiveTileCleanUpHandler.run();
+ mLiveTileCleanUpHandler = null;
+ }
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mLiveTileRestartListener);
+
// Release all the target leashes
if (mTargets != null) {
mTargets.release();
@@ -221,7 +247,6 @@
mTargets = null;
mLastGestureState = null;
mLastAppearedTaskTarget = null;
- mLaunchOtherTaskHandler = null;
}
public void dump() {
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index dd0ed8f..ba1c413 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
import android.app.ActivityManager.TaskDescription;
@@ -34,7 +33,6 @@
import androidx.annotation.WorkerThread;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.BitmapInfo;
@@ -137,21 +135,22 @@
// TODO: Load icon resource (b/143363444)
Bitmap icon = TaskDescriptionCompat.getIcon(desc, key.userId);
if (icon != null) {
- entry.icon = new FastBitmapDrawable(getBitmapInfo(
+ /* isInstantApp */
+ entry.icon = getBitmapInfo(
new BitmapDrawable(mContext.getResources(), icon),
key.userId,
desc.getPrimaryColor(),
- false /* isInstantApp */));
+ false /* isInstantApp */).newIcon(mContext);
} else {
activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
key.getComponent(), key.userId);
if (activityInfo != null) {
BitmapInfo bitmapInfo = getBitmapInfo(
- mIconProvider.getIcon(activityInfo, UserHandle.of(key.userId)),
+ mIconProvider.getIcon(activityInfo),
key.userId,
desc.getPrimaryColor(),
activityInfo.applicationInfo.isInstantApp());
- entry.icon = newIcon(mContext, bitmapInfo);
+ entry.icon = bitmapInfo.newIcon(mContext);
} else {
entry.icon = getDefaultIcon(key.userId);
}
@@ -199,7 +198,7 @@
}
mDefaultIcons.put(userId, info);
}
- return new FastBitmapDrawable(info);
+ return info.newIcon(mContext);
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 8636130..1ad5f2c 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -36,11 +36,16 @@
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.quickstep.TaskShortcutFactory.SplitSelectSystemShortcut;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -57,11 +62,18 @@
*/
public class TaskOverlayFactory implements ResourceBasedOverride {
- public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
+ public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
+ DeviceProfile deviceProfile) {
final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
+ if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
+ FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+ continue;
+ }
+
if (shortcut != null) {
shortcuts.add(shortcut);
}
@@ -91,6 +103,18 @@
return shortcuts;
}
+
+ public static void addSplitOptions(List<SystemShortcut> outShortcuts,
+ BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
+ PagedOrientationHandler orientationHandler =
+ taskView.getRecentsView().getPagedOrientationHandler();
+ List<SplitPositionOption> positions =
+ orientationHandler.getSplitPositionOptions(deviceProfile);
+ for (SplitPositionOption option : positions) {
+ outShortcuts.add(new SplitSelectSystemShortcut(activity, taskView, option));
+ }
+ }
+
public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
return new TaskOverlay(thumbnailView);
}
@@ -152,7 +176,7 @@
if (thumbnail != null) {
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
- boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+ boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 65bb0f3..c06e9a9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -34,12 +34,13 @@
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.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.util.Executors;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -54,13 +55,11 @@
import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
/**
* Represents a system shortcut that can be shown for a recent task.
*/
public interface TaskShortcutFactory {
-
SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
@@ -95,6 +94,23 @@
}
}
+ class SplitSelectSystemShortcut extends SystemShortcut {
+ private final TaskView mTaskView;
+ private SplitPositionOption mSplitPositionOption;
+ public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
+ SplitPositionOption option) {
+ super(option.mIconResId, option.mTextResId, target, taskView.getItemInfo());
+ mTaskView = taskView;
+ mSplitPositionOption = option;
+ setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+ }
+
+ @Override
+ public void onClick(View view) {
+ mTaskView.initiateSplitSelect(mSplitPositionOption);
+ }
+ }
+
class MultiWindowSystemShortcut extends SystemShortcut {
private Handler mHandler;
@@ -213,6 +229,16 @@
}
@Override
+ public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
+ SystemShortcut shortcut = super.getShortcut(activity, taskView);
+ if (FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ // Disable if there's only one recent app for split screen
+ shortcut.setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+ }
+ return shortcut;
+ }
+
+ @Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
final ActivityCompat act = new ActivityCompat(activity);
final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
@@ -281,15 +307,9 @@
@Override
public void onClick(View view) {
- Consumer<Boolean> resultCallback = success -> {
- if (success) {
- SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
- mTaskView.getTask().key.id);
- } else {
- mTaskView.notifyTaskLaunchFailed(TAG);
- }
- };
- mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
+ if (mTaskView.launchTaskAnimated() != null) {
+ SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(mTaskView.getTask().key.id);
+ }
dismissTaskMenuView(mTarget);
mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
.log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 17822e6..bea1250 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -18,11 +18,18 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_DELAY_NAV_FADE_IN;
+import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_IN_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_OUT_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_IN_INTERPOLATOR;
+import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_OUT_INTERPOLATOR;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
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.TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL;
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.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -37,6 +44,7 @@
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.view.View;
@@ -55,6 +63,7 @@
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
@@ -64,6 +73,7 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
/**
* Utility class for helpful methods related to {@link TaskView} objects and their tasks.
@@ -134,7 +144,8 @@
public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
PendingAnimation out) {
boolean isRunningTask = v.isRunningTask();
TransformParams params = null;
@@ -143,7 +154,7 @@
params = v.getRecentsView().getLiveTileParams();
tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
}
- createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets,
+ createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets,
depthController, out, params, tsv);
}
@@ -153,7 +164,8 @@
*/
public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
PendingAnimation out, @Nullable TransformParams params,
@Nullable TaskViewSimulator tsv) {
boolean isQuickSwitch = v.isEndQuickswitchCuj();
@@ -161,8 +173,9 @@
boolean inLiveTileMode = LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
final RemoteAnimationTargets targets =
- new RemoteAnimationTargets(appTargets, wallpaperTargets,
+ new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+ final RemoteAnimationTargetCompat navBarTarget = targets.getNavBarRemoteAnimationTarget();
if (params == null) {
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
@@ -177,9 +190,9 @@
int taskIndex = recentsView.indexOfChild(v);
Context context = v.getContext();
DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+ boolean showAsGrid = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
boolean parallaxCenterAndAdjacentTask =
- taskIndex != recentsView.getCurrentPage() && !(dp.isTablet
- && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
+ taskIndex != recentsView.getCurrentPage() && !showAsGrid;
float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
int startScroll = recentsView.getScrollOffset(taskIndex);
@@ -197,8 +210,9 @@
tsv.setPreview(targets.apps[targets.apps.length - 1]);
tsv.fullScreenProgress.value = 0;
tsv.recentsViewScale.value = 1;
- tsv.gridProgress.value = 1;
- tsv.gridTranslationSecondary.value = gridTranslationSecondary;
+ if (showAsGrid) {
+ tsv.taskSecondaryTranslation.value = gridTranslationSecondary;
+ }
tsv.setScroll(startScroll);
// Fade in the task during the initial 20% of the animation
@@ -211,12 +225,40 @@
AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
out.setFloat(tsv.recentsViewScale,
AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
- out.setFloat(tsv.gridProgress, AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
- out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
+ if (showAsGrid) {
+ out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
+ TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
+ }
+ out.setFloat(tsv.recentsViewScroll, AnimatedFloat.VALUE, 0,
+ TOUCH_RESPONSE_INTERPOLATOR);
TaskViewSimulator finalTsv = tsv;
TransformParams finalParams = params;
out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+ if (navBarTarget != null) {
+ final Rect cropRect = new Rect();
+ out.addOnFrameListener(new MultiValueUpdateListener() {
+ FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0,
+ ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR);
+ FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+
+ @Override
+ public void onUpdate(float percent) {
+ final SurfaceParams.Builder navBuilder =
+ new SurfaceParams.Builder(navBarTarget.leash);
+ if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
+ finalTsv.getCurrentCropRect().round(cropRect);
+ navBuilder.withMatrix(finalTsv.getCurrentMatrix())
+ .withWindowCrop(cropRect)
+ .withAlpha(mNavFadeIn.value);
+ } else {
+ navBuilder.withAlpha(mNavFadeOut.value);
+ }
+ finalParams.applySurfaceParams(navBuilder.build());
+ }
+ });
+ }
topMostSimulator = tsv;
}
@@ -294,9 +336,99 @@
}
}
+ /**
+ * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
+ * if needed
+ *
+ * We could manually try to animate the just the bounds for the leashes we get back, but we try
+ * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
+ * us.
+ *
+ * First you have to call TVS#setPreview() to indicate which leash it will operate one
+ * Then operations happen in TVS#apply() on each frame callback.
+ *
+ * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
+ * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
+ * device is considered in multiWindowMode and things like insets and stuff change
+ * and calculations have to be adjusted in the animations for that
+ */
+ public static void composeRecentsSplitLaunchAnimator(@NonNull AnimatorSet anim,
+ @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
+ @NonNull StateManager stateManager, @NonNull DepthController depthController,
+ int targetStage) {
+ PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+ boolean isRunningTask = v.isRunningTask();
+ TransformParams params = null;
+ TaskViewSimulator tvs = null;
+ RecentsView recentsView = v.getRecentsView();
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+ params = recentsView.getLiveTileParams();
+ tvs = recentsView.getLiveTileTaskViewSimulator();
+ }
+
+ boolean inLiveTileMode =
+ ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
+ final RemoteAnimationTargets targets =
+ new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
+ inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+
+ if (params == null) {
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+ targets.addReleaseCheck(applier);
+
+ params = new TransformParams()
+ .setSyncTransactionApplier(applier)
+ .setTargetSet(targets);
+ }
+
+ Rect crop = new Rect();
+ Context context = v.getContext();
+ DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+ if (tvs == null && targets.apps.length > 0) {
+ tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
+ tvs.setDp(dp);
+
+ // RecentsView never updates the display rotation until swipe-up so the value may
+ // be stale. Use the display value instead.
+ int displayRotation = DisplayController.getDefaultDisplay(recentsView.getContext())
+ .getInfo().rotation;
+ tvs.getOrientationState().update(displayRotation, displayRotation);
+
+ tvs.setPreview(targets.apps[targets.apps.length - 1]);
+ tvs.fullScreenProgress.value = 0;
+ tvs.recentsViewScale.value = 1;
+// tvs.setScroll(startScroll);
+
+ // Fade in the task during the initial 20% of the animation
+ out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
+ clampToProgress(LINEAR, 0, 0.2f));
+ }
+
+ TaskViewSimulator topMostSimulator = null;
+
+ if (tvs != null) {
+ out.setFloat(tvs.fullScreenProgress,
+ AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tvs.recentsViewScale,
+ AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tvs.recentsViewScroll,
+ AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
+
+ TaskViewSimulator finalTsv = tvs;
+ TransformParams finalParams = params;
+ out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+ topMostSimulator = tvs;
+ }
+
+ anim.play(out.buildAnim());
+ }
+
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
- @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
@NonNull StateManager stateManager, @NonNull RecentsView recentsView,
@NonNull DepthController depthController) {
boolean skipLauncherChanges = !launcherClosing;
@@ -304,7 +436,7 @@
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
- depthController, pa);
+ nonAppTargets, depthController, pa);
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index fc805d0..e9d2eb4 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,12 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+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;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -101,6 +107,11 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.tracing.ProtoTraceable;
+import com.android.wm.shell.onehanded.IOneHanded;
+import com.android.wm.shell.pip.IPip;
+import com.android.wm.shell.splitscreen.ISplitScreen;
+import com.android.wm.shell.startingsurface.IStartingWindow;
+import com.android.wm.shell.transition.IShellTransitions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -140,8 +151,18 @@
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+ IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
+ ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
+ KEY_EXTRA_SHELL_SPLIT_SCREEN));
+ IOneHanded onehanded = IOneHanded.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
+ IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));
+ IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));
MAIN_EXECUTOR.execute(() -> {
- SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
+ SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
+ splitscreen, onehanded, shellTransitions, startingWindow);
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
mDeviceState.runOnUserUnlocked(() -> {
@@ -157,13 +178,23 @@
@BinderThread
public void onOverviewToggle() {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
- mOverviewCommandHelper.onOverviewToggle();
+ // If currently screen pinning, do not enter overview
+ if (mDeviceState.isScreenPinningActive()) {
+ return;
+ }
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
}
@BinderThread
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
- mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+ if (triggeredFromAltTab) {
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW_NEXT_FOCUS);
+ } else {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+ }
}
@BinderThread
@@ -171,7 +202,7 @@
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab hides the overview and ends at the target app
- mOverviewCommandHelper.onOverviewHidden();
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
}
}
@@ -208,8 +239,9 @@
@BinderThread
public void onSystemUiStateChanged(int stateFlags) {
MAIN_EXECUTOR.execute(() -> {
+ int lastFlags = mDeviceState.getSystemUiStateFlags();
mDeviceState.setSystemUiFlags(stateFlags);
- TouchInteractionService.this.onSystemUiFlagsChanged();
+ TouchInteractionService.this.onSystemUiFlagsChanged(lastFlags);
});
}
@@ -326,12 +358,12 @@
public void onUserUnlocked() {
mTaskAnimationManager = new TaskAnimationManager(this);
mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
- mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
- mOverviewComponentObserver);
+ mOverviewCommandHelper = new OverviewCommandHelper(this,
+ mOverviewComponentObserver, mTaskAnimationManager);
mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
mInputConsumer.registerInputConsumer();
- onSystemUiFlagsChanged();
+ onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
onAssistantVisibilityChanged();
// Temporarily disable model preload
@@ -383,7 +415,7 @@
}
@UiThread
- private void onSystemUiFlagsChanged() {
+ private void onSystemUiFlagsChanged(int lastSysUIFlags) {
if (mDeviceState.isUserUnlocked()) {
int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
@@ -391,14 +423,17 @@
mOverviewComponentObserver.getActivityInterface().onSystemUiFlagsChanged(
systemUiStateFlags);
- // Update the tracing state
- if ((systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED) != 0) {
- Log.d(TAG, "Starting tracing.");
- ProtoTracer.INSTANCE.get(this).start();
- } else {
- Log.d(TAG, "Stopping tracing. Dumping to file="
- + ProtoTracer.INSTANCE.get(this).getTraceFile());
- ProtoTracer.INSTANCE.get(this).stop();
+ if ((lastSysUIFlags & SYSUI_STATE_TRACING_ENABLED) !=
+ (systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED)) {
+ // Update the tracing state
+ if ((systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED) != 0) {
+ Log.d(TAG, "Starting tracing.");
+ ProtoTracer.INSTANCE.get(this).start();
+ } else {
+ Log.d(TAG, "Stopping tracing. Dumping to file="
+ + ProtoTracer.INSTANCE.get(this).getTraceFile());
+ ProtoTracer.INSTANCE.get(this).stop();
+ }
}
}
}
@@ -421,7 +456,7 @@
}
disposeEventHandlers();
mDeviceState.destroy();
- SystemUiProxy.INSTANCE.get(this).setProxy(null);
+ SystemUiProxy.INSTANCE.get(this).clearProxy();
ProtoTracer.INSTANCE.get(this).stop();
ProtoTracer.INSTANCE.get(this).remove(this);
@@ -555,7 +590,7 @@
ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
}
- private GestureState createGestureState(GestureState previousGestureState) {
+ public GestureState createGestureState(GestureState previousGestureState) {
GestureState gestureState = new GestureState(mOverviewComponentObserver,
ActiveGestureLog.INSTANCE.generateAndSetLogId());
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
@@ -704,16 +739,15 @@
}
}
+ public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
+ return !mOverviewComponentObserver.isHomeAndOverviewSame()
+ ? mFallbackSwipeHandlerFactory : mLauncherSwipeHandlerFactory;
+ }
+
private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
MotionEvent event) {
- final AbsSwipeUpHandler.Factory factory;
- if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
- factory = mFallbackSwipeHandlerFactory;
- } else {
- factory = mLauncherSwipeHandlerFactory;
- }
-
+ final AbsSwipeUpHandler.Factory factory = getSwipeUpHandlerFactory();
final boolean shouldDefer = !mOverviewComponentObserver.isHomeAndOverviewSame()
|| gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event);
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
@@ -857,6 +891,9 @@
if (mGestureState != null) {
mGestureState.dump(pw);
}
+ pw.println("Input state:");
+ pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
+ pw.println(" mInputEventReceiver=" + mInputEventReceiver);
SysUINavigationMode.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
@@ -886,15 +923,17 @@
}
private AbsSwipeUpHandler createLauncherSwipeHandler(
- GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+ GestureState gestureState, long touchTimeMs) {
return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
- gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+ gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+ mInputConsumer);
}
private AbsSwipeUpHandler createFallbackSwipeHandler(
- GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+ GestureState gestureState, long touchTimeMs) {
return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
- gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+ gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+ mInputConsumer);
}
protected boolean shouldNotifyBackGesture() {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 24a7610..8962ec9 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -20,14 +20,14 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
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.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+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_MODALNESS;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
@@ -61,15 +61,11 @@
@Override
public void setStateWithAnimation(RecentsState toState, StateAnimationConfig config,
PendingAnimation setter) {
- if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
- // The entire recents animation is played atomically.
- return;
- }
if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
return;
}
// While animating into recents, update the visible task data as needed
- setter.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+ setter.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
setProperties(toState, config, setter);
@@ -77,11 +73,14 @@
private void setProperties(RecentsState state, StateAnimationConfig config,
PropertySetter setter) {
- float buttonAlpha = state.hasButtons() ? 1 : 0;
+ float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
- buttonAlpha, LINEAR);
+ clearAllButtonAlpha, LINEAR);
+ float overviewButtonAlpha =
+ state.hasOverviewActions() && mRecentsView.shouldShowOverviewActionsForState(state)
+ ? 1 : 0;
setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
- MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+ MultiValueAlpha.VALUE, overviewButtonAlpha, LINEAR);
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
@@ -94,5 +93,7 @@
setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
+ setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
+ state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()) ? 1f : 0f, LINEAR);
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 13f6137..854a140 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -34,6 +34,7 @@
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -41,7 +42,7 @@
import java.util.ArrayList;
@TargetApi(Build.VERSION_CODES.R)
-public class FallbackRecentsView extends RecentsView<RecentsActivity>
+public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
implements StateListener<RecentsState> {
private RunningTaskInfo mHomeTaskInfo;
@@ -56,8 +57,8 @@
}
@Override
- public void init(OverviewActionsView actionsView) {
- super.init(actionsView);
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ super.init(actionsView, splitPlaceholderView);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
@@ -65,6 +66,7 @@
@Override
public void startHome() {
mActivity.startHome();
+ mActivity.getStateManager().goToState(RecentsState.HOME);
}
/**
@@ -154,6 +156,11 @@
}
@Override
+ protected boolean isHomeTask(TaskView taskView) {
+ return mHomeTaskInfo != null && taskView.hasTaskId(mHomeTaskInfo.taskId);
+ }
+
+ @Override
public void setModalStateEnabled(boolean isModalState) {
super.setModalStateEnabled(isModalState);
if (isModalState) {
@@ -168,6 +175,8 @@
@Override
public void onStateTransitionStart(RecentsState toState) {
setOverviewStateEnabled(true);
+ setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ setOverviewFullscreenEnabled(toState.isFullScreen());
setFreezeViewVisibility(true);
}
@@ -182,7 +191,7 @@
super.setOverviewStateEnabled(enabled);
if (enabled) {
RecentsState state = mActivity.getStateManager().getState();
- setDisallowScrollToClearAll(!state.hasButtons());
+ setDisallowScrollToClearAll(!state.hasClearAllButton());
}
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index f15a9de..bbe279e 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -20,6 +20,8 @@
import android.content.Context;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.BaseState;
import com.android.quickstep.RecentsActivity;
@@ -29,14 +31,19 @@
public class RecentsState implements BaseState<RecentsState> {
private static final int FLAG_MODAL = BaseState.getFlag(0);
- private static final int FLAG_HAS_BUTTONS = BaseState.getFlag(1);
+ private static final int FLAG_CLEAR_ALL_BUTTON = BaseState.getFlag(1);
private static final int FLAG_FULL_SCREEN = BaseState.getFlag(2);
+ private static final int FLAG_OVERVIEW_ACTIONS = BaseState.getFlag(3);
+ private static final int FLAG_SHOW_AS_GRID = BaseState.getFlag(4);
- public static final RecentsState DEFAULT = new RecentsState(0, FLAG_HAS_BUTTONS);
+ public static final RecentsState DEFAULT = new RecentsState(0,
+ FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID);
public static final RecentsState MODAL_TASK = new ModalState(1,
- FLAG_DISABLE_RESTORE | FLAG_HAS_BUTTONS | FLAG_MODAL);
+ FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+ | FLAG_SHOW_AS_GRID);
public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+ public static final RecentsState HOME = new RecentsState(3, 0);
public final int ordinal;
private final int mFlags;
@@ -82,14 +89,35 @@
return hasFlag(FLAG_FULL_SCREEN);
}
- public boolean hasButtons() {
- return hasFlag(FLAG_HAS_BUTTONS);
+ /**
+ * For this state, whether clear all button should be shown.
+ */
+ public boolean hasClearAllButton() {
+ return hasFlag(FLAG_CLEAR_ALL_BUTTON);
+ }
+
+ /**
+ * For this state, whether overview actions should be shown.
+ */
+ public boolean hasOverviewActions() {
+ return hasFlag(FLAG_OVERVIEW_ACTIONS);
}
public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
return new float[] { NO_SCALE, NO_OFFSET };
}
+ /**
+ * For this state, whether tasks should layout as a grid rather than a list.
+ */
+ public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+ return hasFlag(FLAG_SHOW_AS_GRID) && showAsGrid(deviceProfile);
+ }
+
+ private boolean showAsGrid(DeviceProfile deviceProfile) {
+ return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+ }
+
private static class ModalState extends RecentsState {
public ModalState(int id, int flags) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 5baf518..9878d45 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -31,7 +31,6 @@
import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.annotation.TargetApi;
import android.content.Context;
@@ -65,7 +64,6 @@
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.CachedEventDispatcher;
@@ -381,9 +379,6 @@
// Once we detect the gesture, we can enable batching to reduce further updates
mInputEventReceiver.setBatchingEnabled(true);
- mActivityInterface.closeOverlay();
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-
// Notify the handler that the gesture has actually started
mInteractionHandler.onGestureStarted(isLikelyToStartNewTask);
}
@@ -391,8 +386,7 @@
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
- mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
- mTaskAnimationManager.isRecentsAnimationRunning());
+ mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs);
mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener());
Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index cee3363..fa9e0ec 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -24,6 +24,7 @@
import androidx.annotation.Nullable;
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;
@@ -38,7 +39,7 @@
/**
* Input consumer for handling touch on the recents/Launcher activity.
*/
-public class OverviewInputConsumer<T extends StatefulActivity<?>>
+public class OverviewInputConsumer<S extends BaseState<S>, T extends StatefulActivity<S>>
implements InputConsumer {
private final T mActivity;
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 161e015..a1b7e01 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -36,9 +36,8 @@
Integer getTitleStringId() {
switch (mTutorialType) {
case RIGHT_EDGE_BACK_NAVIGATION:
- return R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge;
case LEFT_EDGE_BACK_NAVIGATION:
- return R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge;
+ return R.string.back_gesture_intro_title;
case BACK_NAVIGATION_COMPLETE:
return R.string.gesture_tutorial_confirm_title;
}
@@ -49,9 +48,8 @@
Integer getSubtitleStringId() {
switch (mTutorialType) {
case RIGHT_EDGE_BACK_NAVIGATION:
- return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge;
case LEFT_EDGE_BACK_NAVIGATION:
- return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge;
+ return R.string.back_gesture_intro_subtitle;
case BACK_NAVIGATION_COMPLETE:
return R.string.back_gesture_tutorial_confirm_subtitle;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 95352d1..fbf3a0a 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -38,7 +38,7 @@
Integer getTitleStringId() {
switch (mTutorialType) {
case HOME_NAVIGATION:
- return R.string.home_gesture_tutorial_playground_title;
+ return R.string.home_gesture_intro_title;
case HOME_NAVIGATION_COMPLETE:
return R.string.gesture_tutorial_confirm_title;
}
@@ -48,7 +48,7 @@
@Override
Integer getSubtitleStringId() {
if (mTutorialType == TutorialType.HOME_NAVIGATION) {
- return R.string.home_gesture_tutorial_playground_subtitle;
+ return R.string.home_gesture_intro_subtitle;
}
return null;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 45cbd0b..31f26d1 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -39,7 +39,7 @@
Integer getTitleStringId() {
switch (mTutorialType) {
case OVERVIEW_NAVIGATION:
- return R.string.overview_gesture_tutorial_playground_title;
+ return R.string.overview_gesture_intro_title;
case OVERVIEW_NAVIGATION_COMPLETE:
return R.string.gesture_tutorial_confirm_title;
}
@@ -49,7 +49,7 @@
@Override
Integer getSubtitleStringId() {
if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) {
- return R.string.overview_gesture_tutorial_playground_subtitle;
+ return R.string.overview_gesture_intro_subtitle;
}
return null;
}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index f336bf5..b336d2f 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -80,7 +80,7 @@
SettingsCache mSettingsCache = SettingsCache.INSTANCE.get(context);
mSettingsCache.register(NOTIFICATION_BADGING_URI,
this::onNotificationDotsChanged);
- mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
+ onNotificationDotsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));
}
private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
diff --git a/quickstep/src/com/android/quickstep/util/AssistContentRequester.java b/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
new file mode 100644
index 0000000..3730284
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.IAssistDataReceiver;
+import android.app.assist.AssistContent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.launcher3.util.Executors;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Can be used to request the AssistContent from a provided task id, useful for getting the web uri
+ * if provided from the task.
+ */
+public class AssistContentRequester {
+ private static final String TAG = "AssistContentRequester";
+ private static final String ASSIST_KEY_CONTENT = "content";
+
+ /** For receiving content, called on the main thread. */
+ public interface Callback {
+ /**
+ * Called when the {@link android.app.assist.AssistContent} of the requested task is
+ * available.
+ **/
+ void onAssistContentAvailable(AssistContent assistContent);
+ }
+
+ private final IActivityTaskManager mActivityTaskManager;
+ private final String mPackageName;
+ private final Executor mCallbackExecutor;
+
+ public AssistContentRequester(Context context) {
+ mActivityTaskManager = ActivityTaskManager.getService();
+ mPackageName = context.getApplicationContext().getPackageName();
+ mCallbackExecutor = Executors.MAIN_EXECUTOR;
+ }
+
+ /**
+ * Request the {@link AssistContent} from the task with the provided id.
+ *
+ * @param taskId to query for the content.
+ * @param callback to call when the content is available, called on the main thread.
+ */
+ public void requestAssistContent(int taskId, Callback callback) {
+ try {
+ mActivityTaskManager.requestAssistDataForTask(
+ new AssistDataReceiver(callback, mCallbackExecutor), taskId, mPackageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
+ }
+ }
+
+ private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
+
+ private final Executor mExecutor;
+ private final Callback mCallback;
+
+ AssistDataReceiver(Callback callback, Executor callbackExecutor) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onHandleAssistData(Bundle data) {
+ if (data == null) {
+ return;
+ }
+
+ final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+ if (content == null) {
+ Log.e(TAG, "Received AssistData, but no AssistContent found");
+ return;
+ }
+
+ mExecutor.execute(() -> mCallback.onAssistContentAvailable(content));
+ }
+
+ @Override
+ public void onHandleAssistScreenshot(Bitmap screenshot) {}
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index d022085..2285d74 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -19,9 +19,12 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.prediction.AppTarget;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -37,11 +40,13 @@
import android.graphics.RectF;
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;
+import com.android.internal.app.ChooserActivity;
import com.android.launcher3.BuildConfig;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
@@ -99,7 +104,14 @@
.putExtra(Intent.EXTRA_STREAM, uri)
.putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId())
.setClipData(clipdata);
- 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);
+ }
}
/**
@@ -119,6 +131,22 @@
}
/**
+ * Launch the activity to share image with shared element transition.
+ */
+ @UiThread
+ public static void startShareActivity(Context context, Supplier<Bitmap> 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));
+ }
+
+ /**
* Starts activity based on given intent created from image uri.
*/
@WorkerThread
@@ -135,6 +163,30 @@
}
/**
+ * Starts activity based on given intent created from image uri with shared element transition.
+ */
+ @WorkerThread
+ public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
+ Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag,
+ View scaledImage) {
+ Intent[] intents = uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent);
+
+ // Work around b/159412574
+ if (intents.length == 1) {
+ MAIN_EXECUTOR.execute(() -> context.startActivity(intents[0],
+ ActivityOptions.makeSceneTransitionAnimation((Activity) context, scaledImage,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()));
+
+ } else {
+ MAIN_EXECUTOR.execute(() -> context.startActivities(intents,
+ ActivityOptions.makeSceneTransitionAnimation((Activity) context, scaledImage,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()));
+ }
+ }
+
+
+
+ /**
* Converts image bitmap to Uri by temporarily saving bitmap to cache, and creating Uri pointing
* to that location. Used to be able to share an image with another app.
*
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index 3e87f48..2e5b33a 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -38,7 +38,8 @@
private static final String TAG = "InputConsumerProxy";
private final InputConsumerController mInputConsumerController;
- private final Supplier<InputConsumer> mConsumerSupplier;
+ private Runnable mCallback;
+ private Supplier<InputConsumer> mConsumerSupplier;
// The consumer is created lazily on demand.
private InputConsumer mInputConsumer;
@@ -48,8 +49,9 @@
private boolean mDestroyPending = false;
public InputConsumerProxy(InputConsumerController inputConsumerController,
- Supplier<InputConsumer> consumerSupplier) {
+ Runnable callback, Supplier<InputConsumer> consumerSupplier) {
mInputConsumerController = inputConsumerController;
+ mCallback = callback;
mConsumerSupplier = consumerSupplier;
}
@@ -64,9 +66,7 @@
if (ev instanceof MotionEvent) {
onInputConsumerMotionEvent((MotionEvent) ev);
} else if (ev instanceof KeyEvent) {
- if (mInputConsumer == null) {
- mInputConsumer = mConsumerSupplier.get();
- }
+ initInputConsumerIfNeeded();
mInputConsumer.onKeyEvent((KeyEvent) ev);
return true;
}
@@ -89,9 +89,7 @@
if (action == ACTION_DOWN) {
mTouchInProgress = true;
- if (mInputConsumer == null) {
- mInputConsumer = mConsumerSupplier.get();
- }
+ initInputConsumerIfNeeded();
} else if (action == ACTION_CANCEL || action == ACTION_UP) {
// Finish any pending actions
mTouchInProgress = false;
@@ -115,4 +113,18 @@
mDestroyed = true;
mInputConsumerController.setInputListener(null);
}
+
+ public void unregisterCallback() {
+ mCallback = null;
+ }
+
+ private void initInputConsumerIfNeeded() {
+ if (mInputConsumer == null) {
+ if (mCallback != null) {
+ mCallback.run();
+ }
+ mInputConsumer = mConsumerSupplier.get();
+ mConsumerSupplier = null;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
new file mode 100644
index 0000000..8209c09
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
+
+import java.util.function.Supplier;
+
+/**
+ * A factory that creates a input consumer for
+ * {@link com.android.quickstep.util.InputConsumerProxy}.
+ */
+public class InputProxyHandlerFactory implements Supplier<InputConsumer> {
+
+ private final BaseActivityInterface mActivityInterface;
+ private final GestureState mGestureState;
+
+ @UiThread
+ public InputProxyHandlerFactory(BaseActivityInterface activityInterface,
+ GestureState gestureState) {
+ mActivityInterface = activityInterface;
+ mGestureState = gestureState;
+ }
+
+ /**
+ * Called to create a input proxy for the running task
+ */
+ @Override
+ public InputConsumer get() {
+ StatefulActivity activity = mActivityInterface.getCreatedActivity();
+ return activity == null ? InputConsumer.NO_OP
+ : new OverviewInputConsumer(mGestureState, activity, null, true);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
index e798d5c..b4ae1ca 100644
--- a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
+++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
@@ -64,5 +64,12 @@
mAllProperties.add(this);
}
+
+ /**
+ * Gets the start value.
+ */
+ public float getStartValue() {
+ return mStart;
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
index 60c7add..351adf4 100644
--- a/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
+++ b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
@@ -20,6 +20,8 @@
import android.content.Context;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.SysUINavigationMode;
import java.util.function.Predicate;
@@ -35,6 +37,7 @@
private final Supplier<Boolean> mBasePredicate;
private final Predicate<SysUINavigationMode.Mode> mModePredicate;
private boolean mSupported;
+ private OverviewComponentObserver mObserver;
private NavigationModeFeatureFlag(Supplier<Boolean> basePredicate,
Predicate<SysUINavigationMode.Mode> modePredicate) {
@@ -43,12 +46,16 @@
}
public boolean get() {
- return mBasePredicate.get() && mSupported;
+ return mBasePredicate.get() && mSupported && mObserver.isHomeAndOverviewSame();
}
public void initialize(Context context) {
onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context).getMode());
SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+
+ // Temporary solution to disable live tile for the fallback launcher
+ RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(context);
+ mObserver = new OverviewComponentObserver(context, rads);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 1031c5b..1128dac 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -22,13 +22,11 @@
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
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_TRANSLATE_X;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -105,10 +103,6 @@
StateAnimationConfig config = new UseFirstInterpolatorStateAnimConfig();
config.duration = duration;
- config.animFlags = playStaggeredWorkspaceAnim
- // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
- ? PLAY_ATOMIC_OVERVIEW_PEEK
- : ANIM_ALL_COMPONENTS;
boolean isLayoutNaturalToLauncher = recentsView.getPagedOrientationHandler()
.isLayoutNaturalToLauncher();
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, isLayoutNaturalToLauncher
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 1544f00..6d6e802 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -26,6 +26,7 @@
import android.content.SharedPreferences;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
@@ -63,7 +64,8 @@
});
}
- if (!hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
+ if (!Utilities.IS_RUNNING_IN_TEST_HARNESS
+ && !hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
stateManager.addStateListener(new StateListener<LauncherState>() {
boolean mFromAllApps = false;
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 215f05a..188efad 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -23,9 +23,9 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -47,11 +47,12 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.views.TaskView;
import java.lang.annotation.Retention;
import java.util.function.IntConsumer;
@@ -367,8 +368,12 @@
*/
public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
Rect insets = dp.getInsets();
- float fullWidth = dp.widthPx - insets.left - insets.right;
- float fullHeight = dp.heightPx - insets.top - insets.bottom;
+ float fullWidth = dp.widthPx;
+ float fullHeight = dp.heightPx;
+ if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ fullWidth -= insets.left + insets.right;
+ fullHeight -= insets.top + insets.bottom;
+ }
if (dp.isMultiWindowMode) {
WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(mContext);
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 3adb459..5c6da16 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -21,7 +21,6 @@
import android.os.Handler;
import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.AnimationResult;
import com.android.launcher3.WrappedAnimationRunnerImpl;
import com.android.launcher3.WrappedLauncherAnimationRunner;
import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -36,23 +35,10 @@
RemoteAnimationTargetCompat[] wallpaperTargets);
ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
- mAnimationRunner = new WrappedAnimationRunnerImpl() {
- @Override
- public Handler getHandler() {
- return handler;
- }
-
- @Override
- public void onCreateAnimation(int transit,
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- RemoteAnimationTargetCompat[] nonApps,
- AnimationResult result) {
+ mAnimationRunner = (transit, appTargets, wallpaperTargets, nonApps, result) ->
result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
- }
- };
final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner(
- mAnimationRunner, false /* startAtFrontOfQueue */);
+ handler, mAnimationRunner, false /* startAtFrontOfQueue */);
return ActivityOptionsCompat.makeRemoteAnimation(
new RemoteAnimationAdapterCompat(wrapper, duration, 0));
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
index 958ee24..81c124f 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
@@ -34,7 +34,8 @@
public RemoteFadeOutAnimationListener(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
- mTarget = new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_CLOSING);
+ mTarget = new RemoteAnimationTargets(appTargets, wallpaperTargets,
+ new RemoteAnimationTargetCompat[0], MODE_CLOSING);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
new file mode 100644
index 0000000..e3f2925
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherAnimationRunner;
+import com.android.launcher3.WrappedAnimationRunnerImpl;
+import com.android.launcher3.WrappedLauncherAnimationRunner;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskViewUtils;
+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.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Represent data needed for the transient state when user has selected one app for split screen
+ * and is in the process of either a) selecting a second app or b) exiting intention to invoke split
+ */
+public class SplitSelectStateController {
+
+ private final SystemUiProxy mSystemUiProxy;
+ private TaskView mInitialTaskView;
+ private SplitPositionOption mInitialPosition;
+
+ public SplitSelectStateController(SystemUiProxy systemUiProxy) {
+ mSystemUiProxy = systemUiProxy;
+ }
+
+ /**
+ * To be called after first task selected
+ */
+ public void setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption) {
+ mInitialTaskView = taskView;
+ mInitialPosition = positionOption;
+ }
+
+ /**
+ * To be called after second task selected
+ */
+ public void setSecondTaskId(TaskView taskView) {
+ // Assume initial mInitialTaskId is for top/left part of screen
+ WrappedAnimationRunnerImpl initialSplitRunnerWrapped = new SplitLaunchAnimationRunner(
+ mInitialTaskView, 0);
+ WrappedAnimationRunnerImpl secondarySplitRunnerWrapped = new SplitLaunchAnimationRunner(
+ taskView, 1);
+ RemoteAnimationRunnerCompat initialSplitRunner = new WrappedLauncherAnimationRunner(
+ new Handler(Looper.getMainLooper()), initialSplitRunnerWrapped,
+ true /* startAtFrontOfQueue */);
+ RemoteAnimationRunnerCompat secondarySplitRunner = new WrappedLauncherAnimationRunner(
+ new Handler(Looper.getMainLooper()), secondarySplitRunnerWrapped,
+ true /* startAtFrontOfQueue */);
+ ActivityOptions initialOptions = ActivityOptionsCompat.makeRemoteAnimation(
+ new RemoteAnimationAdapterCompat(initialSplitRunner, 300, 150));
+ ActivityOptions secondaryOptions = ActivityOptionsCompat.makeRemoteAnimation(
+ new RemoteAnimationAdapterCompat(secondarySplitRunner, 300, 150));
+ mSystemUiProxy.startTask(mInitialTaskView.getTask().key.id, mInitialPosition.mStageType,
+ mInitialPosition.mStagePosition,
+ /*null*/ initialOptions.toBundle());
+ Pair<Integer, Integer> compliment = getComplimentaryStageAndPosition(mInitialPosition);
+ mSystemUiProxy.startTask(taskView.getTask().key.id, compliment.first,
+ compliment.second,
+ /*null*/ secondaryOptions.toBundle());
+ // After successful launch, call resetState
+ resetState();
+ }
+
+ @Nullable
+ public SplitPositionOption getActiveSplitPositionOption() {
+ return mInitialPosition;
+ }
+
+ /**
+ * @return the opposite stage and position from the {@param position} provided as first and
+ * second object, respectively
+ * Ex. If position is has stage = Main and position = Top/Left, this will return
+ * Pair(stage=Side, position=Bottom/Left)
+ */
+ private Pair<Integer, Integer> getComplimentaryStageAndPosition(SplitPositionOption position) {
+ // Right now this is as simple as flipping between 0 and 1
+ int complimentStageType = position.mStageType ^ 1;
+ int complimentStagePosition = position.mStagePosition ^ 1;
+ return new Pair<>(complimentStageType, complimentStagePosition);
+ }
+
+ /**
+ * Remote animation runner for animation to launch an app.
+ */
+ private class SplitLaunchAnimationRunner implements WrappedAnimationRunnerImpl {
+
+ private final TaskView mV;
+ private final int mTargetState;
+
+ SplitLaunchAnimationRunner(TaskView v, int targetState) {
+ mV = v;
+ mTargetState = targetState;
+ }
+
+ @Override
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ LauncherAnimationRunner.AnimationResult result) {
+ AnimatorSet anim = new AnimatorSet();
+ BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext());
+ TaskViewUtils.composeRecentsSplitLaunchAnimator(anim, mV,
+ appTargets, wallpaperTargets, nonAppTargets, true, activity.getStateManager(),
+ activity.getDepthController(), mTargetState);
+ result.setAnimation(anim, activity);
+ }
+ }
+
+
+ /**
+ * To be called if split select was cancelled
+ */
+ public void resetState() {
+ mInitialTaskView = null;
+ mInitialPosition = null;
+ }
+
+ public boolean isSplitSelectActive() {
+ return mInitialTaskView != null;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 932ff27..c12bd9b 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -18,13 +18,10 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_TASKBAR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -37,15 +34,14 @@
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.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DynamicResource;
@@ -60,7 +56,10 @@
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;
private static final float MAX_VELOCITY_PX_PER_S = 22f;
@@ -82,85 +81,107 @@
DeviceProfile grid = launcher.getDeviceProfile();
Workspace workspace = launcher.getWorkspace();
- CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
- ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
- ViewGroup hotseat = launcher.getHotseat();
+ Hotseat hotseat = launcher.getHotseat();
+
+ // Hotseat and QSB takes up two additional rows.
+ int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
+
+ // Add animation for all the visible workspace pages
+ workspace.getVisiblePages()
+ .forEach(page -> addAnimationForPage((CellLayout) page, totalRows));
boolean workspaceClipChildren = workspace.getClipChildren();
boolean workspaceClipToPadding = workspace.getClipToPadding();
- boolean cellLayoutClipChildren = cellLayout.getClipChildren();
- boolean cellLayoutClipToPadding = cellLayout.getClipToPadding();
boolean hotseatClipChildren = hotseat.getClipChildren();
boolean hotseatClipToPadding = hotseat.getClipToPadding();
workspace.setClipChildren(false);
workspace.setClipToPadding(false);
- cellLayout.setClipChildren(false);
- cellLayout.setClipToPadding(false);
hotseat.setClipChildren(false);
hotseat.setClipToPadding(false);
- // Hotseat and QSB takes up two additional rows.
- int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
-
- // Set up springs on workspace items.
- for (int i = currentPage.getChildCount() - 1; i >= 0; i--) {
- View child = currentPage.getChildAt(i);
- CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
- }
-
// Set up springs for the hotseat and qsb.
- ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0);
+ ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
if (grid.isVerticalBarLayout()) {
- for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
- View child = hotseatChild.getChildAt(i);
+ for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+ View child = hotseatIcons.getChildAt(i);
CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
}
} else {
- for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
- View child = hotseatChild.getChildAt(i);
- addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
+ final int hotseatRow, qsbRow, taskbarRow;
+ if (grid.isTaskbarPresent) {
+ qsbRow = grid.inv.numRows + 1;
+ hotseatRow = grid.inv.numRows + 2;
+ } else {
+ hotseatRow = grid.inv.numRows + 1;
+ qsbRow = grid.inv.numRows + 2;
+ }
+ // Taskbar and hotseat overlap.
+ taskbarRow = hotseatRow;
+
+ for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+ View child = hotseatIcons.getChildAt(i);
+ addStaggeredAnimationForView(child, hotseatRow, totalRows);
}
- if (launcher.getAppsView().getSearchUiManager()
- .isQsbVisible(NORMAL.getVisibleElements(launcher))) {
- addStaggeredAnimationForView(launcher.getAppsView().getSearchView(),
- grid.inv.numRows + 2, totalRows);
- }
+ addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows);
+ addStaggeredAnimationForView(hotseat.getTaskbarView(), taskbarRow, totalRows);
}
if (animateOverviewScrim) {
- PendingAnimation pendingAnimation = new PendingAnimation(ALPHA_DURATION_MS);
- addScrimAnimationForState(launcher, NORMAL, pendingAnimation);
+ PendingAnimation pendingAnimation = new PendingAnimation(DURATION_MS);
+ launcher.getWorkspace().getStateTransitionAnimation()
+ .setScrim(pendingAnimation, NORMAL, new StateAnimationConfig());
mAnimators.play(pendingAnimation.buildAnim());
}
- addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+ addDepthAnimationForState(launcher, NORMAL, DURATION_MS);
mAnimators.play(launcher.getDragLayer().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
- .setDuration(ALPHA_DURATION_MS));
+ .setDuration(DURATION_MS));
mAnimators.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
workspace.setClipChildren(workspaceClipChildren);
workspace.setClipToPadding(workspaceClipToPadding);
- cellLayout.setClipChildren(cellLayoutClipChildren);
- cellLayout.setClipToPadding(cellLayoutClipToPadding);
hotseat.setClipChildren(hotseatClipChildren);
hotseat.setClipToPadding(hotseatClipToPadding);
}
});
}
+ private void addAnimationForPage(CellLayout page, int totalRows) {
+ ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets();
+
+ boolean pageClipChildren = page.getClipChildren();
+ boolean pageClipToPadding = page.getClipToPadding();
+
+ page.setClipChildren(false);
+ page.setClipToPadding(false);
+
+ // Set up springs on workspace items.
+ 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);
+ }
+
+ mAnimators.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ page.setClipChildren(pageClipChildren);
+ page.setClipToPadding(pageClipToPadding);
+ }
+ });
+ }
+
/**
* Setup workspace with 0 duration to prepare for our staggered animation.
*/
private void prepareToAnimate(Launcher launcher, boolean animateOverviewScrim) {
StateAnimationConfig config = new StateAnimationConfig();
- config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER
- | SKIP_TASKBAR;
+ config.animFlags = SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER;
config.duration = 0;
// setRecentsAttachedToAppWindow() will animate recents out.
launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
@@ -169,7 +190,8 @@
launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
if (animateOverviewScrim) {
- addScrimAnimationForState(launcher, BACKGROUND_APP, NO_ANIM_PROPERTY_SETTER);
+ launcher.getWorkspace().getStateTransitionAnimation()
+ .setScrim(NO_ANIM_PROPERTY_SETTER, BACKGROUND_APP, config);
}
}
@@ -240,16 +262,6 @@
mAnimators.play(alpha);
}
- private void addScrimAnimationForState(Launcher launcher, LauncherState state,
- PropertySetter setter) {
- launcher.getWorkspace().getStateTransitionAnimation().setScrim(setter, state);
- setter.setFloat(
- launcher.getDragLayer().getOverviewScrim(),
- OverviewScrim.SCRIM_PROGRESS,
- state.getOverviewScrimAlpha(launcher),
- ACCEL_DEACCEL);
- }
-
private void addDepthAnimationForState(Launcher launcher, LauncherState state, long duration) {
if (!(launcher instanceof BaseQuickstepLauncher)) {
return;
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 0ce5072..a1240e0 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -29,6 +29,7 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
+import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -46,10 +47,12 @@
* Launcher and SysUI. Also, there should be one source of truth for the corner radius of the
* PiP window, which would ideally be on SysUI side as well.
*/
-public class SwipePipToHomeAnimator extends ValueAnimator implements
- ValueAnimator.AnimatorUpdateListener {
+public class SwipePipToHomeAnimator extends ValueAnimator {
private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName();
+ public static final float FRACTION_START = 0f;
+ public static final float FRACTION_END = 1f;
+
private final int mTaskId;
private final ComponentName mComponentName;
private final SurfaceControl mLeash;
@@ -134,11 +137,12 @@
@Override
public void onAnimationEnd(Animator animation) {
- if (!mHasAnimationEnded) super.onAnimationEnd(animation);
- SwipePipToHomeAnimator.this.onAnimationEnd();
+ if (mHasAnimationEnded) return;
+ super.onAnimationEnd(animation);
+ mHasAnimationEnded = true;
}
});
- addUpdateListener(this);
+ addUpdateListener(this::onAnimationUpdate);
}
/** sets the from rotation if it's different from the target rotation. */
@@ -166,48 +170,53 @@
mAppBounds.top + mDestinationBounds.height());
}
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
+ private void onAnimationUpdate(ValueAnimator animator) {
if (mHasAnimationEnded) return;
-
- final float fraction = animator.getAnimatedFraction();
- final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds,
- mDestinationBoundsAnimation);
final SurfaceControl.Transaction tx =
PipSurfaceTransactionHelper.newSurfaceControlTransaction();
- if (mSourceHintRectInsets == null) {
- // no source rect hint been set, directly scale the window down
- onAnimationScale(fraction, tx, bounds);
- } else {
- // scale and crop according to the source rect hint
- onAnimationScaleAndCrop(fraction, tx, bounds);
- }
- mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
+ onAnimationUpdate(tx, animator.getAnimatedFraction());
tx.apply();
}
+ private PictureInPictureSurfaceTransaction onAnimationUpdate(SurfaceControl.Transaction tx,
+ float fraction) {
+ final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds,
+ mDestinationBoundsAnimation);
+ final PictureInPictureSurfaceTransaction op;
+ if (mSourceHintRectInsets == null) {
+ // no source rect hint been set, directly scale the window down
+ op = onAnimationScale(fraction, tx, bounds);
+ } else {
+ // scale and crop according to the source rect hint
+ op = onAnimationScaleAndCrop(fraction, tx, bounds);
+ }
+ return op;
+ }
+
/** scale the window directly with no source rect hint being set */
- private void onAnimationScale(float fraction, SurfaceControl.Transaction tx, Rect bounds) {
+ private PictureInPictureSurfaceTransaction onAnimationScale(
+ float fraction, SurfaceControl.Transaction tx, Rect bounds) {
if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
final RotatedPosition rotatedPosition = getRotatedPosition(fraction);
- mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds,
+ return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds,
rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
} else {
- mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds);
+ return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds);
}
}
/** scale and crop the window with source rect hint */
- private void onAnimationScaleAndCrop(float fraction, SurfaceControl.Transaction tx,
+ private PictureInPictureSurfaceTransaction onAnimationScaleAndCrop(
+ float fraction, SurfaceControl.Transaction tx,
Rect bounds) {
final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
mSourceHintRectInsets);
if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
final RotatedPosition rotatedPosition = getRotatedPosition(fraction);
- mSurfaceTransactionHelper.scaleAndRotate(tx, mLeash, mAppBounds, bounds, insets,
+ return mSurfaceTransactionHelper.scaleAndRotate(tx, mLeash, mAppBounds, bounds, insets,
rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
} else {
- mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mAppBounds, bounds, insets);
+ return mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mAppBounds, bounds, insets);
}
}
@@ -223,14 +232,12 @@
return mDestinationBounds;
}
- private void onAnimationEnd() {
- if (mHasAnimationEnded) return;
-
+ /** @return {@link PictureInPictureSurfaceTransaction} for the final leash transaction. */
+ public PictureInPictureSurfaceTransaction getFinishTransaction() {
+ // get the final leash operations but do not apply to the leash.
final SurfaceControl.Transaction tx =
PipSurfaceTransactionHelper.newSurfaceControlTransaction();
- mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBoundsTransformed, mFromRotation);
- tx.apply();
- mHasAnimationEnded = true;
+ return onAnimationUpdate(tx, FRACTION_END);
}
private RotatedPosition getRotatedPosition(float fraction) {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 9537247..e63f8bb 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.states.RotationHelper.deltaRotation;
import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
@@ -31,18 +30,14 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.util.IntProperty;
import androidx.annotation.NonNull;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.BaseActivityInterface;
-import com.android.quickstep.views.RecentsView.ScrollState;
import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -54,19 +49,6 @@
*/
public class TaskViewSimulator implements TransformParams.BuilderProxy {
- public static final IntProperty<TaskViewSimulator> SCROLL =
- new IntProperty<TaskViewSimulator>("scroll") {
- @Override
- public void setValue(TaskViewSimulator simulator, int scroll) {
- simulator.setScroll(scroll);
- }
-
- @Override
- public Integer get(TaskViewSimulator simulator) {
- return simulator.mScrollState.scroll;
- }
- };
-
private final Rect mTmpCropRect = new Rect();
private final RectF mTempRectF = new RectF();
private final float[] mTempPoint = new float[2];
@@ -97,21 +79,17 @@
private final FullscreenDrawParams mCurrentFullscreenParams;
public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
- public final AnimatedFloat gridTranslationSecondary = new AnimatedFloat();
// RecentsView properties
public final AnimatedFloat recentsViewScale = new AnimatedFloat();
public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
- public final AnimatedFloat gridProgress = new AnimatedFloat();
- private final ScrollState mScrollState = new ScrollState();
+ public final AnimatedFloat recentsViewPrimaryTranslation = new AnimatedFloat();
+ public final AnimatedFloat recentsViewScroll = new AnimatedFloat();
// Cached calculations
private boolean mLayoutValid = false;
- private boolean mScrollValid = false;
private int mOrientationStateId;
- private final int mTaskThumbnailPadding;
- private final int mRowSpacing;
public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
mContext = context;
@@ -123,8 +101,6 @@
mOrientationStateId = mOrientationState.getStateId();
Resources resources = context.getResources();
mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
- mTaskThumbnailPadding = (int) resources.getDimension(R.dimen.task_thumbnail_top_margin);
- mRowSpacing = (int) resources.getDimension(R.dimen.recents_row_spacing);
}
/**
@@ -179,11 +155,8 @@
/**
* Updates the scroll for RecentsView
*/
- public void setScroll(int scroll) {
- if (mScrollState.scroll != scroll) {
- mScrollState.scroll = scroll;
- mScrollValid = false;
- }
+ public void setScroll(float scroll) {
+ recentsViewScroll.value = scroll;
}
public void setDrawsBelowRecents(boolean drawsBelowRecents) {
@@ -268,25 +241,13 @@
getFullScreenScale();
mThumbnailData.rotation = mOrientationState.getDisplayRotation();
+ // mIsRecentsRtl is the inverse of TaskView RTL.
+ boolean isRtlEnabled = !mIsRecentsRtl;
mPositionHelper.updateThumbnailMatrix(
mThumbnailPosition, mThumbnailData,
mTaskRect.width(), mTaskRect.height(),
- mDp, mOrientationState.getRecentsActivityRotation());
+ mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
mPositionHelper.getMatrix().invert(mInversePositionMatrix);
-
- PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
- mScrollState.halfPageSize =
- poh.getPrimaryValue(mTaskRect.width(), mTaskRect.height()) / 2;
- mScrollState.halfScreenSize = poh.getPrimaryValue(mDp.widthPx, mDp.heightPx) / 2;
- mScrollValid = false;
- }
-
- if (!mScrollValid) {
- mScrollValid = true;
- int start = mOrientationState.getOrientationHandler()
- .getPrimaryValue(mTaskRect.left, mTaskRect.top);
- mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
- mScrollState.updateInterpolation(mDp, start);
}
float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
@@ -304,24 +265,6 @@
mMatrix.postTranslate(insets.left, insets.top);
mMatrix.postScale(scale, scale);
- float interpolatedGridProgress = ACCEL_DEACCEL.getInterpolation(gridProgress.value);
-
- // Apply TaskView matrix: gridProgress
- final int boxLength = (int) Math.max(taskWidth, taskHeight);
- float availableHeight =
- mTaskThumbnailPadding + taskHeight + mSizeStrategy.getOverviewActionsHeight(
- mContext);
- float rowHeight = (availableHeight - mRowSpacing) / 2;
- float gridScale = rowHeight / (boxLength + mTaskThumbnailPadding);
- scale = Utilities.mapRange(interpolatedGridProgress, 1f, gridScale);
- mMatrix.postScale(scale, scale, mIsRecentsRtl ? 0 : taskWidth, 0);
- float taskWidthDiff = taskWidth * (1 - gridScale);
- float taskWidthOffset = mIsRecentsRtl ? taskWidthDiff : -taskWidthDiff;
- mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
- Utilities.mapRange(interpolatedGridProgress, 0, taskWidthOffset));
- mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
- Utilities.mapRange(interpolatedGridProgress, 0, gridTranslationSecondary.value));
-
// Apply TaskView matrix: translate, scroll
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
@@ -329,12 +272,14 @@
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
taskSecondaryTranslation.value);
mOrientationState.getOrientationHandler().set(
- mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
+ mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
// Apply RecentsView matrix
mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
recentsViewSecondaryTranslation.value);
+ mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+ recentsViewPrimaryTranslation.value);
applyWindowToHomeRotation(mMatrix);
// Crop rect is the inverse of thumbnail matrix
@@ -351,8 +296,7 @@
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
builder.withMatrix(mMatrix)
.withWindowCrop(mTmpCropRect)
- .withCornerRadius(getCurrentCornerRadius())
- .withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
+ .withCornerRadius(getCurrentCornerRadius());
if (LIVE_TILE.get() && params.getRecentsSurface() != null) {
// When relativeLayer = 0, it reverts the surfaces back to the original order.
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index cdf5163..03d7a37 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -57,7 +57,6 @@
private float mProgress;
private float mTargetAlpha;
private float mCornerRadius;
- private float mShadowRadius;
private RemoteAnimationTargets mTargetSet;
private SurfaceTransactionApplier mSyncTransactionApplier;
private SurfaceControl mRecentsSurface;
@@ -69,7 +68,6 @@
mProgress = 0;
mTargetAlpha = 1;
mCornerRadius = -1;
- mShadowRadius = 0;
}
/**
@@ -93,14 +91,6 @@
}
/**
- * Sets the shadow radius of the transformed window, in pixels.
- */
- public TransformParams setShadowRadius(float shadowRadius) {
- mShadowRadius = shadowRadius;
- return this;
- }
-
- /**
* Specifies the alpha of the transformed window. Default is 1.
*/
public TransformParams setTargetAlpha(float targetAlpha) {
@@ -207,10 +197,6 @@
return mCornerRadius;
}
- public float getShadowRadius() {
- return mShadowRadius;
- }
-
public SurfaceControl getRecentsSurface() {
return mRecentsSurface;
}
@@ -219,7 +205,7 @@
return mTargetSet;
}
- public void applySurfaceParams(SurfaceParams[] params) {
+ public void applySurfaceParams(SurfaceParams... params) {
if (mSyncTransactionApplier != null) {
mSyncTransactionApplier.scheduleApply(params);
} else {
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index e7101cc..4300329 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -21,11 +21,10 @@
import android.util.FloatProperty;
import android.widget.Button;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.quickstep.views.RecentsView.PageCallbacks;
-import com.android.quickstep.views.RecentsView.ScrollState;
-public class ClearAllButton extends Button implements PageCallbacks {
+public class ClearAllButton extends Button {
public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
new FloatProperty<ClearAllButton>("visibilityAlpha") {
@@ -40,30 +39,33 @@
}
};
+ private final StatefulActivity mActivity;
private float mScrollAlpha = 1;
private float mContentAlpha = 1;
private float mVisibilityAlpha = 1;
+ private float mFullscreenProgress = 1;
private float mGridProgress = 1;
private boolean mIsRtl;
- private final float mOriginalTranslationX, mOriginalTranslationY;
private float mNormalTranslationPrimary;
+ private float mFullscreenTranslationPrimary;
private float mGridTranslationPrimary;
+ private float mGridScrollOffset;
+ private float mScrollOffsetPrimary;
- private int mScrollOffset;
+ private int mSidePadding;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- mOriginalTranslationX = getTranslationX();
- mOriginalTranslationY = getTranslationY();
+ mActivity = StatefulActivity.fromContext(context);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
- mScrollOffset = orientationHandler.getClearAllScrollOffset(getRecentsView(), mIsRtl);
+ mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
}
private RecentsView getRecentsView() {
@@ -95,26 +97,27 @@
}
}
- @Override
- public void onPageScroll(ScrollState scrollState) {
- PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+ public void onRecentsViewScroll(int scrollFromEdge, boolean gridEnabled) {
+ RecentsView recentsView = getRecentsView();
+ if (recentsView == null) {
+ return;
+ }
+
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
if (orientationSize == 0) {
return;
}
- float shift;
- if (mIsRtl) {
- shift = Math.min(scrollState.scrollFromEdge, orientationSize);
- } else {
- shift = Math.min(scrollState.scrollFromEdge,
- orientationSize + getGridTrans(mGridTranslationPrimary))
- - getGridTrans(mGridTranslationPrimary);
+ int leftEdgeScroll = recentsView.getLeftMostChildScroll();
+ int adjustedScrollFromEdge = scrollFromEdge - leftEdgeScroll;
+ float shift = Math.min(adjustedScrollFromEdge, orientationSize);
+ mNormalTranslationPrimary = mIsRtl ? -shift : shift;
+ if (!gridEnabled) {
+ mNormalTranslationPrimary += mSidePadding;
}
- mNormalTranslationPrimary = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift);
applyPrimaryTranslation();
- orientationHandler.getSecondaryViewTranslate().set(this,
- orientationHandler.getSecondaryValue(mOriginalTranslationX, mOriginalTranslationY));
+ applySecondaryTranslation();
mScrollAlpha = 1 - shift / orientationSize;
updateAlpha();
}
@@ -125,21 +128,48 @@
setClickable(Math.min(alpha, 1) == 1);
}
+ public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
+ mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
+ applyPrimaryTranslation();
+ }
+
public void setGridTranslationPrimary(float gridTranslationPrimary) {
mGridTranslationPrimary = gridTranslationPrimary;
applyPrimaryTranslation();
}
- public float getScrollAdjustment(boolean gridEnabled) {
+ public void setGridScrollOffset(float gridScrollOffset) {
+ mGridScrollOffset = gridScrollOffset;
+ }
+
+ public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
+ mScrollOffsetPrimary = scrollOffsetPrimary;
+ }
+
+ public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
- if (gridEnabled) {
- scrollAdjustment += mGridTranslationPrimary;
+ if (fullscreenEnabled) {
+ scrollAdjustment += mFullscreenTranslationPrimary;
}
+ if (gridEnabled) {
+ scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
+ }
+ scrollAdjustment += mScrollOffsetPrimary;
return scrollAdjustment;
}
- public float getOffsetAdjustment(boolean gridEnabled) {
- return getScrollAdjustment(gridEnabled);
+ public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
+ return getScrollAdjustment(fullscreenEnabled, gridEnabled);
+ }
+
+ /**
+ * Adjust translation when this TaskView is about to be shown fullscreen.
+ *
+ * @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
+ */
+ public void setFullscreenProgress(float progress) {
+ mFullscreenProgress = progress;
+ applyPrimaryTranslation();
}
/**
@@ -160,10 +190,34 @@
PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
orientationHandler.getPrimaryViewTranslate().set(this,
- mNormalTranslationPrimary + getGridTrans(mGridTranslationPrimary));
+ orientationHandler.getPrimaryValue(0f, getOriginalTranslationY())
+ + mNormalTranslationPrimary + getFullscreenTrans(
+ mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
+ }
+
+ private void applySecondaryTranslation() {
+ RecentsView recentsView = getRecentsView();
+ if (recentsView == null) {
+ return;
+ }
+
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ orientationHandler.getSecondaryViewTranslate().set(this,
+ orientationHandler.getSecondaryValue(0f, getOriginalTranslationY()));
+ }
+
+ private float getFullscreenTrans(float endTranslation) {
+ return mFullscreenProgress > 0 ? endTranslation : 0;
}
private float getGridTrans(float endTranslation) {
return mGridProgress > 0 ? endTranslation : 0;
}
+
+ /**
+ * Get the Y translation that is set in the original layout position, before scrolling.
+ */
+ private float getOriginalTranslationY() {
+ return mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx / 2.0f;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 25ae055..7c8041c 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -30,6 +30,7 @@
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.AppUsageLimit;
import android.graphics.Outline;
+import android.graphics.Paint;
import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
import android.icu.util.Measure;
@@ -48,6 +49,7 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.systemui.shared.recents.model.Task;
import java.time.Duration;
@@ -70,7 +72,6 @@
private View mBanner;
private ViewOutlineProvider mOldBannerOutlineProvider;
private float mBannerOffsetPercentage;
- private float mBannerAlpha = 1f;
private float mVerticalOffset = 0f;
public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) {
@@ -97,10 +98,6 @@
mTaskView.setContentDescription(
getContentDescriptionForTask(mTask, appUsageLimitTimeMs, appRemainingTimeMs));
- RecentsView rv = mTaskView.getRecentsView();
- if (rv != null) {
- rv.onDigitalWellbeingToastShown();
- }
}
public String getText() {
@@ -266,7 +263,6 @@
layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
mTaskView.getThumbnail().getLayoutParams()).bottomMargin;
mBanner.setTranslationY(mBannerOffsetPercentage * mBanner.getHeight());
- mBanner.setAlpha(mBannerAlpha);
mTaskView.addView(mBanner);
}
@@ -291,10 +287,16 @@
}
}
- void updateBannerAlpha(float alpha) {
- if (mBanner != null && mBannerAlpha != alpha) {
- mBannerAlpha = alpha;
- mBanner.setAlpha(alpha);
+ void setBannerColorTint(int color, float amount) {
+ if (mBanner == null) {
+ return;
}
+ if (amount == 0) {
+ mBanner.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ Paint layerPaint = new Paint();
+ layerPaint.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount));
+ mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint);
+ mBanner.setLayerPaint(layerPaint);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index 7cc00b7..5b0ade0 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -21,11 +21,7 @@
import android.util.AttributeSet;
import android.view.View;
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.FastBitmapDrawable;
-
-import java.util.ArrayList;
+import com.android.launcher3.Utilities;
/**
* A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
@@ -33,14 +29,8 @@
*/
public class IconView extends View {
- public interface OnScaleUpdateListener {
- public void onScaleUpdate(float scale);
- }
-
private Drawable mDrawable;
- private ArrayList<OnScaleUpdateListener> mScaleListeners;
-
public IconView(Context context) {
super(context);
}
@@ -94,16 +84,6 @@
}
@Override
- public void invalidateDrawable(@NonNull Drawable drawable) {
- super.invalidateDrawable(drawable);
- if (drawable instanceof FastBitmapDrawable && mScaleListeners != null) {
- for (OnScaleUpdateListener listener : mScaleListeners) {
- listener.onScaleUpdate(((FastBitmapDrawable) drawable).getScale());
- }
- }
- }
-
- @Override
protected void onDraw(Canvas canvas) {
if (mDrawable != null) {
mDrawable.draw(canvas);
@@ -115,22 +95,6 @@
return false;
}
- public void addUpdateScaleListener(OnScaleUpdateListener listener) {
- if (mScaleListeners == null) {
- mScaleListeners = new ArrayList<>();
- }
- mScaleListeners.add(listener);
- if (mDrawable instanceof FastBitmapDrawable) {
- listener.onScaleUpdate(((FastBitmapDrawable) mDrawable).getScale());
- }
- }
-
- public void removeUpdateScaleListener(OnScaleUpdateListener listener) {
- if (mScaleListeners != null) {
- mScaleListeners.remove(listener);
- }
- }
-
@Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
@@ -140,4 +104,16 @@
setVisibility(INVISIBLE);
}
}
+
+ /**
+ * Set the tint color of the icon, useful for scrimming or dimming.
+ *
+ * @param color to blend in.
+ * @param amount [0,1] 0 no tint, 1 full tint
+ */
+ public void setIconColorTint(int color, float amount) {
+ if (mDrawable != null) {
+ mDrawable.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount));
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index c62f3e2..f5a8ff8 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,20 +15,17 @@
*/
package com.android.quickstep.views;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
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.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -40,8 +37,8 @@
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.RecentsExtraCard;
@@ -50,7 +47,7 @@
* {@link RecentsView} used in Launcher activity
*/
@TargetApi(Build.VERSION_CODES.O)
-public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
+public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher, LauncherState>
implements StateListener<LauncherState> {
private RecentsExtraCard mRecentsExtraCardPlugin;
@@ -86,8 +83,8 @@
}
@Override
- public void init(OverviewActionsView actionsView) {
- super.init(actionsView);
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ super.init(actionsView, splitPlaceholderView);
setContentAlpha(0);
}
@@ -104,35 +101,10 @@
}
}
- /**
- * Animates adjacent tasks and translate hotseat off screen as well.
- */
- @Override
- public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
- AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv);
-
- if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
- // Hotseat doesn't move when opening recents with the button,
- // so don't animate it here either.
- return anim;
- }
-
- float allAppsProgressOffscreen = ALL_APPS_PROGRESS_OFF_SCREEN;
- LauncherState state = mActivity.getStateManager().getState();
- if ((state.getVisibleElements(mActivity) & ALL_APPS_HEADER_EXTRA) != 0) {
- float maxShiftRange = mActivity.getDeviceProfile().heightPx;
- float currShiftRange = mActivity.getAllAppsController().getShiftRange();
- allAppsProgressOffscreen = 1f + (maxShiftRange - currShiftRange) / maxShiftRange;
- }
- anim.play(ObjectAnimator.ofFloat(
- mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen));
- return anim;
- }
-
@Override
protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
- mActivity.getStateManager().goToState(NORMAL, false /* animate */);
+ mActivity.getStateManager().moveToRestState();
} else {
LauncherState state = mActivity.getStateManager().getState();
mActivity.getAllAppsController().setState(state);
@@ -150,6 +122,8 @@
@Override
public void onStateTransitionStart(LauncherState toState) {
setOverviewStateEnabled(toState.overviewUi);
+ setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
setFreezeViewVisibility(true);
}
@@ -160,8 +134,6 @@
reset();
}
setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
- setOverviewGridEnabled(finalState.displayOverviewTasksAsGrid(mActivity));
- setOverviewFullscreenEnabled(finalState.getOverviewFullscreenProgress() == 1);
setFreezeViewVisibility(false);
}
@@ -177,9 +149,10 @@
}
@Override
- protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
- return mActivity.getStateManager().getState().overviewUi
- && super.shouldStealTouchFromSiblingsBelow(ev);
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean result = super.onTouchEvent(ev);
+ // Do not let touch escape to siblings below this view.
+ return result || mActivity.getStateManager().getState().overviewUi;
}
@Override
@@ -265,4 +238,33 @@
}
}
}
+
+ @Override
+ protected void onDismissAnimationEnds() {
+ super.onDismissAnimationEnds();
+ if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // We want to keep the tasks translations in this temporary state
+ // after resetting the rest above
+ setTaskViewsResistanceTranslation(mTaskViewsSecondaryTranslation);
+ setTaskViewsPrimaryTranslation(mTaskViewsPrimaryTranslation);
+ }
+ }
+
+ @Override
+ public void initiateSplitSelect(TaskView taskView,
+ SplitConfigurationOptions.SplitPositionOption splitPositionOption) {
+ super.initiateSplitSelect(taskView, splitPositionOption);
+ mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ // If overview is in modal state when rotate, reset it to overview state without running
+ // animation.
+ if (mActivity.isInState(OVERVIEW_MODAL_TASK)) {
+ mActivity.getStateManager().goToState(LauncherState.OVERVIEW, false);
+ resetModalVisuals();
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 1241982..9adeea4 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,6 +16,7 @@
package com.android.quickstep.views;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SHARE;
import android.content.Context;
@@ -29,8 +30,10 @@
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.SysUINavigationMode;
@@ -75,6 +78,7 @@
private static final int INDEX_VISIBILITY_ALPHA = 1;
private static final int INDEX_FULLSCREEN_ALPHA = 2;
private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
+ private static final int INDEX_SCROLL_ALPHA = 4;
private final MultiValueAlpha mMultiValueAlpha;
@@ -86,6 +90,9 @@
protected T mCallbacks;
+ private float mModalness;
+ private float mModalTransformY;
+
public OverviewActionsView(Context context) {
this(context, null);
}
@@ -96,7 +103,7 @@
public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr, 0);
- mMultiValueAlpha = new MultiValueAlpha(this, 4);
+ mMultiValueAlpha = new MultiValueAlpha(this, 5);
mMultiValueAlpha.setUpdateVisibility(true);
}
@@ -144,6 +151,7 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+ updateHorizontalPadding();
}
public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
@@ -187,6 +195,14 @@
return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
}
+ public AlphaProperty getScrollAlpha() {
+ return mMultiValueAlpha.getProperty(INDEX_SCROLL_ALPHA);
+ }
+
+ private void updateHorizontalPadding() {
+ setPadding(mInsets.left, 0, mInsets.right, 0);
+ }
+
/** Updates vertical margins for different navigation mode or configuration changes. */
public void updateVerticalMargin(Mode mode) {
LayoutParams actionParams = (LayoutParams) findViewById(
@@ -196,6 +212,13 @@
getBottomVerticalMargin(mode));
}
+ /**
+ * Set the device profile for this view to draw with.
+ */
+ public void setDp(DeviceProfile dp) {
+ requestLayout();
+ }
+
protected int getBottomVerticalMargin(Mode mode) {
int bottomMargin;
int orientation = getResources().getConfiguration().orientation;
@@ -211,4 +234,27 @@
bottomMargin += mInsets.bottom;
return bottomMargin;
}
+
+ /**
+ * The current task is fully modal (modalness = 1) when it is shown on its own in a modal
+ * way. Modalness 0 means the task is shown in context with all the other tasks.
+ */
+ public void setTaskModalness(float modalness) {
+ mModalness = modalness;
+ applyTranslationY();
+ }
+
+ public void setModalTransformY(float modalTransformY) {
+ mModalTransformY = modalTransformY;
+ applyTranslationY();
+ }
+
+ private void applyTranslationY() {
+ setTranslationY(getModalTrans(mModalTransformY));
+ }
+
+ private float getModalTrans(float endTranslation) {
+ float progress = ACCEL_DEACCEL.getInterpolation(mModalness);
+ return Utilities.mapRange(progress, 0, endTranslation);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java
index 1ea6d4a..16bc3bc 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java
@@ -23,7 +23,7 @@
/**
* Empty view to house recents overview extra card
*/
-public class RecentsExtraViewContainer extends FrameLayout implements RecentsView.PageCallbacks {
+public class RecentsExtraViewContainer extends FrameLayout {
private boolean mScrollable = false;
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index bdd0a36..524bae1 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -20,12 +20,14 @@
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
+import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
@@ -33,12 +35,14 @@
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_2;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
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.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
@@ -47,6 +51,8 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
@@ -58,6 +64,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -74,6 +81,7 @@
import android.util.FloatProperty;
import android.util.Log;
import android.util.SparseBooleanArray;
+import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -84,6 +92,7 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
import android.widget.ListView;
import androidx.annotation.Nullable;
@@ -93,7 +102,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -104,15 +112,16 @@
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.OverScroller;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.ViewPool;
import com.android.quickstep.AnimatedFloat;
@@ -125,21 +134,26 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitScreenBounds;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.plugins.ResourceProvider;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.wm.shell.pip.IPipAnimationListener;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -148,13 +162,12 @@
* A list of recent tasks.
*/
@TargetApi(Build.VERSION_CODES.R)
-public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
- Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
+public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>,
+ STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
+ TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
SplitScreenBounds.OnChangeListener {
- private static final String TAG = RecentsView.class.getSimpleName();
-
public static final FloatProperty<RecentsView> CONTENT_ALPHA =
new FloatProperty<RecentsView>("contentAlpha") {
@Override
@@ -210,11 +223,17 @@
}
};
+ /**
+ * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+ * are currently both used to apply secondary translation. Should their use cases change to be
+ * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+ * offsetX/Y property
+ */
public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
new FloatProperty<RecentsView>("taskSecondaryTranslation") {
@Override
public void setValue(RecentsView recentsView, float v) {
- recentsView.setTaskViewsSecondaryTranslation(v);
+ recentsView.setTaskViewsResistanceTranslation(v);
}
@Override
@@ -223,6 +242,25 @@
}
};
+ /**
+ * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+ * are currently both used to apply secondary translation. Should their use cases change to be
+ * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+ * offsetX/Y property
+ */
+ public static final FloatProperty<RecentsView> TASK_PRIMARY_TRANSLATION =
+ new FloatProperty<RecentsView>("taskPrimaryTranslation") {
+ @Override
+ public void setValue(RecentsView recentsView, float v) {
+ recentsView.setTaskViewsPrimaryTranslation(v);
+ }
+
+ @Override
+ public Float get(RecentsView recentsView) {
+ return recentsView.mTaskViewsPrimaryTranslation;
+ }
+ };
+
/** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
new FloatProperty<RecentsView>("recentsScale") {
@@ -233,7 +271,8 @@
view.mLastComputedTaskPushOutDistance = null;
view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
view.updatePageOffsets();
- view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
+ view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
+ view.setTaskViewsPrimaryTranslation(view.mTaskViewsPrimaryTranslation);
}
@Override
@@ -256,7 +295,7 @@
};
protected final RecentsOrientedState mOrientationState;
- protected final BaseActivityInterface mSizeStrategy;
+ protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
protected RecentsAnimationController mRecentsAnimationController;
protected SurfaceTransactionApplier mSyncTransactionApplier;
protected int mTaskWidth;
@@ -264,28 +303,34 @@
protected final TransformParams mLiveTileParams = new TransformParams();
protected final TaskViewSimulator mLiveTileTaskViewSimulator;
protected final Rect mLastComputedTaskSize = new Rect();
+ protected final Rect mLastComputedGridSize = new Rect();
+ protected final Rect mLastComputedGridTaskSize = new Rect();
// How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
protected Float mLastComputedTaskPushOutDistance = null;
protected boolean mEnableDrawingLiveTile = false;
protected final Rect mTempRect = new Rect();
protected final RectF mTempRectF = new RectF();
private final PointF mTempPointF = new PointF();
+ private float mFullscreenScale;
private static final int DISMISS_TASK_DURATION = 300;
private static final int ADDITION_TASK_DURATION = 200;
// The threshold at which we update the SystemUI flags when animating from the task into the app
public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f;
- protected final T mActivity;
+ protected final ACTIVITY_TYPE mActivity;
private final float mFastFlingVelocity;
private final RecentsModel mModel;
- private final int mTaskTopMargin;
private final int mRowSpacing;
+ private final int mGridSideMargin;
private final ClearAllButton mClearAllButton;
private final Rect mClearAllButtonDeadZoneRect = new Rect();
private final Rect mTaskViewDeadZoneRect = new Rect();
+ /**
+ * Reflects if Recents is currently in the middle of a gesture
+ */
+ private boolean mGestureActive;
- private final ScrollState mScrollState = new ScrollState();
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
@@ -295,7 +340,6 @@
private final TaskOverlayFactory mTaskOverlayFactory;
- private boolean mDwbToastShown;
protected boolean mDisallowScrollToClearAll;
private boolean mOverlayEnabled;
protected boolean mFreezeViewVisibility;
@@ -303,10 +347,15 @@
private boolean mOverviewFullscreenEnabled;
private float mAdjacentPageOffset = 0;
- private float mTaskViewsSecondaryTranslation = 0;
+ protected float mTaskViewsSecondaryTranslation = 0;
+ protected float mTaskViewsPrimaryTranslation = 0;
// Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
private float mGridProgress = 0;
- private boolean mShowAsGrid;
+
+ // The GestureEndTarget that is still in progress.
+ private GestureState.GestureEndTarget mCurrentGestureEndTarget;
+
+ IntSet mTopIdSet = new IntSet();
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -376,7 +425,7 @@
}
};
- private final PinnedStackAnimationListener mIPinnedStackAnimationListener =
+ private final PinnedStackAnimationListener mIPipAnimationListener =
new PinnedStackAnimationListener();
// Used to keep track of the last requested task list id, so that we do not request to load the
@@ -387,9 +436,12 @@
protected int mRunningTaskId = -1;
protected boolean mRunningTaskTileHidden;
private Task mTmpRunningTask;
+ protected int mFocusedTaskId = -1;
+ private float mFocusedTaskRatio;
private boolean mRunningTaskIconScaledDown = false;
+ private final boolean mHasLightBackground;
private boolean mOverviewStateEnabled;
private boolean mHandleTaskStackChanges;
private boolean mSwipeDownShouldLaunchApp;
@@ -425,6 +477,28 @@
private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
private Layout mEmptyTextLayout;
+ /**
+ * Placeholder view indicating where the first split screen selected app will be placed
+ */
+ private SplitPlaceholderView mSplitPlaceholderView;
+ /**
+ * The first task that split screen selection was initiated with. When split select state is
+ * initialized, we create a
+ * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
+ * don't actually remove the task since the user might back out. As such, we also ensure this
+ * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
+ */
+ private TaskView mSplitHiddenTaskView;
+ /**
+ * Keeps track of the index of the TaskView that split screen was initialized with so we know
+ * where to insert it back into list of taskViews in case user backs out of entering split
+ * screen.
+ * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView,
+ * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/
+ * removed from recentsView
+ */
+ private int mSplitHiddenTaskViewIndex;
+
// Keeps track of the index where the first TaskView should be
private int mTaskViewStartIndex = 0;
private OverviewActionsView mActionsView;
@@ -469,9 +543,8 @@
mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
- mTaskTopMargin = getResources()
- .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
- mRowSpacing = (int) getResources().getDimension(R.dimen.recents_row_spacing);
+ mRowSpacing = getResources().getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
+ mGridSideMargin = getResources().getDimensionPixelSize(R.dimen.overview_grid_side_margin);
mSquaredTouchSlop = squaredTouchSlop(context);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -483,6 +556,7 @@
.getDimension(R.dimen.recents_empty_message_text_size));
mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
Typeface.NORMAL));
+ mEmptyMessagePaint.setAntiAlias(true);
mEmptyMessagePadding = getResources()
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
@@ -502,10 +576,7 @@
mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
- mShowAsGrid =
- mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
- mActivity.addOnDeviceProfileChangeListener(newDp ->
- mShowAsGrid = newDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
+ mHasLightBackground = Themes.getAttrBoolean(mActivity, android.R.attr.isLightTheme);
}
public OverScroller getScroller() {
@@ -564,9 +635,6 @@
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
- if (visibility != VISIBLE && LIVE_TILE.get()) {
- finishRecentsAnimation(true /* toRecents */, null);
- }
updateTaskStackListenerState();
}
@@ -576,13 +644,22 @@
return;
}
mModel.getIconCache().clear();
- unloadVisibleTaskData();
- loadVisibleTaskData();
+ unloadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
}
- public void init(OverviewActionsView actionsView) {
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+ mSplitPlaceholderView = splitPlaceholderView;
+ }
+
+ public SplitPlaceholderView getSplitPlaceholder() {
+ return mSplitPlaceholderView;
+ }
+
+ public boolean isSplitSelectionActive() {
+ return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
}
@Override
@@ -596,9 +673,9 @@
mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIdp.addOnChangeListener(this);
- mIPinnedStackAnimationListener.setActivity(mActivity);
+ mIPipAnimationListener.setActivity(mActivity);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
- mIPinnedStackAnimationListener);
+ mIPipAnimationListener);
mOrientationState.initListeners();
SplitScreenBounds.INSTANCE.addOnChangeListener(this);
mTaskOverlayFactory.initListeners();
@@ -617,7 +694,7 @@
mIdp.removeOnChangeListener(this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
- mIPinnedStackAnimationListener.setActivity(null);
+ mIPipAnimationListener.setActivity(null);
mOrientationState.destroyListeners();
mTaskOverlayFactory.removeListeners();
}
@@ -626,8 +703,9 @@
public void onViewRemoved(View child) {
super.onViewRemoved(child);
- // Clear the task data for the removed child if it was visible
- if (child instanceof TaskView) {
+ // Clear the task data for the removed child if it was visible unless it's the initial
+ // taskview for entering split screen, we only pretend to dismiss the task
+ if (child instanceof TaskView && child != mSplitHiddenTaskView) {
TaskView taskView = (TaskView) child;
mHasVisibleTaskData.delete(taskView.getTask().key.id);
mTaskViewPool.recycle(taskView);
@@ -654,6 +732,72 @@
super.draw(canvas);
}
+ public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
+ if (mRunningTaskId != -1 && mRunningTaskId == taskId &&
+ getLiveTileParams().getTargetSet().findTask(taskId) != null) {
+ launchSideTaskInLiveTileMode(taskId, getLiveTileParams().getTargetSet().apps);
+ }
+ }
+
+ public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) {
+ AnimatorSet anim = new AnimatorSet();
+ TaskView taskView = getTaskView(taskId);
+ if (taskView == null || !isTaskViewVisible(taskView)) {
+ // TODO: Refine this animation.
+ SurfaceTransactionApplier surfaceApplier =
+ new SurfaceTransactionApplier(mActivity.getDragLayer());
+ ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+ appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
+ appAnimator.setInterpolator(ACCEL_DEACCEL);
+ appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+ @Override
+ public void onUpdate(float percent) {
+ SurfaceParams.Builder builder = new SurfaceParams.Builder(
+ apps[apps.length - 1].leash);
+ Matrix matrix = new Matrix();
+ matrix.postScale(percent, percent);
+ matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2,
+ mActivity.getDeviceProfile().heightPx * (1 - percent) / 2);
+ builder.withAlpha(percent).withMatrix(matrix);
+ surfaceApplier.scheduleApply(builder.build());
+ }
+ });
+ anim.play(appAnimator);
+ } else {
+ TaskViewUtils.composeRecentsLaunchAnimator(
+ anim, taskView, apps,
+ mLiveTileParams.getTargetSet().wallpapers,
+ mLiveTileParams.getTargetSet().nonApps, true /* launcherClosing */,
+ mActivity.getStateManager(), this,
+ getDepthController());
+ }
+ anim.addListener(new AnimatorListenerAdapter(){
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ cleanUp(false);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ cleanUp(true);
+ }
+
+ private void cleanUp(boolean canceled) {
+ if (mRecentsAnimationController != null) {
+ finishRecentsAnimation(false /* toRecents */, null /* onFinishComplete */);
+ if (canceled) {
+ mRecentsAnimationController = null;
+ } else {
+ mSizeStrategy.onLaunchTaskSuccess();
+ }
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+ }
+ }
+ });
+ anim.start();
+ }
+
private void updateTaskStartIndex(View affectingView) {
if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) {
int childCount = getChildCount();
@@ -667,7 +811,7 @@
}
public boolean isTaskViewVisible(TaskView tv) {
- if (mShowAsGrid) {
+ if (showAsGrid()) {
int screenStart = mOrientationHandler.getPrimaryScroll(this);
int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
return isTaskViewWithinBounds(tv, screenStart, screenEnd);
@@ -679,9 +823,9 @@
private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
- mOverviewFullscreenEnabled, mOverviewGridEnabled);
+ mOverviewFullscreenEnabled, showAsGrid());
int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
- mOverviewFullscreenEnabled, mOverviewGridEnabled));
+ mOverviewFullscreenEnabled));
int taskEnd = taskStart + taskSize;
return (taskStart >= start && taskStart <= end) || (taskEnd >= start
&& taskEnd <= end);
@@ -705,12 +849,16 @@
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
mTmpRunningTask = null;
+ if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
+ cancelSplitSelect(false);
+ }
}
- }
- public void onDigitalWellbeingToastShown() {
- if (!mDwbToastShown) {
- mDwbToastShown = true;
+ if (enabled) {
+ mActivity.getSystemUiController().updateUiState(
+ UI_STATE_OVERVIEW, hasLightBackground());
+ } else {
+ mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
}
}
@@ -746,7 +894,7 @@
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
- if (mShowAsGrid) {
+ if (showAsGrid()) {
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
TaskView taskView = getTaskViewAt(i);
@@ -806,14 +954,12 @@
break;
}
-
- // Do not let touch escape to siblings below this view.
- return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev);
+ return isHandlingTouch();
}
@Override
protected boolean snapToPageInFreeScroll() {
- return !mShowAsGrid;
+ return !showAsGrid();
}
@Override
@@ -823,9 +969,6 @@
super.determineScrollingStart(ev, touchSlopScale);
}
}
- protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
- return true;
- }
protected void applyLoadPlan(ArrayList<Task> tasks) {
if (TestProtocol.sDebugTracing) {
@@ -846,7 +989,7 @@
}
// Unload existing visible task data
- unloadVisibleTaskData();
+ unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
TaskView ignoreResetTaskView =
mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
@@ -968,7 +1111,7 @@
updateCurveProperties();
// Update the set of visible task's data
- loadVisibleTaskData();
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
setTaskModalness(0);
}
@@ -978,6 +1121,7 @@
for (int i = 0; i < taskCount; i++) {
getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
}
+ mClearAllButton.setFullscreenProgress(fullscreenProgress);
// Fade out the actions view quickly (0.1 range)
mActionsView.getFullscreenAlpha().setValue(
@@ -999,7 +1143,9 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
resetPaddingFromTaskSize();
- mLiveTileTaskViewSimulator.setDp(mActivity.getDeviceProfile());
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ mLiveTileTaskViewSimulator.setDp(dp);
+ mActionsView.setDp(dp);
}
private void resetPaddingFromTaskSize() {
@@ -1008,27 +1154,82 @@
mTaskWidth = mTempRect.width();
mTaskHeight = mTempRect.height();
- mTempRect.top -= mTaskTopMargin;
+ mTempRect.top -= dp.overviewTaskThumbnailTopMarginPx;
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
+
+ mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(),
+ mLastComputedGridSize);
+ mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
+ mLastComputedGridTaskSize, mOrientationHandler);
+
// Force TaskView to update size from thumbnail
updateTaskSize();
+
+ // Update ActionsView position
+ FrameLayout.LayoutParams layoutParams =
+ (FrameLayout.LayoutParams) mActionsView.getLayoutParams();
+ if (dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+ layoutParams.gravity = Gravity.BOTTOM;
+ layoutParams.bottomMargin =
+ dp.heightPx - mInsets.bottom - mLastComputedGridSize.bottom;
+ layoutParams.leftMargin = mLastComputedTaskSize.left;
+ layoutParams.rightMargin = dp.widthPx - mLastComputedTaskSize.right;
+ // When in modal state, remove bottom margin to avoid covering content.
+ mActionsView.setModalTransformY(layoutParams.bottomMargin);
+ } else {
+ layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ layoutParams.bottomMargin = 0;
+ layoutParams.leftMargin = 0;
+ layoutParams.rightMargin = 0;
+ mActionsView.setModalTransformY(0);
+ }
+ mActionsView.setLayoutParams(layoutParams);
}
/**
* Updates TaskView scaling and translation required to support variable width.
*/
private void updateTaskSize() {
- float accumulatedTranslationX = 0;
final int taskCount = getTaskViewCount();
+ if (taskCount == 0) {
+ return;
+ }
+
+ float accumulatedTranslationX = 0;
+ float[] fullscreenTranslations = new float[taskCount];
+ int firstNonHomeTaskIndex = 0;
for (int i = 0; i < taskCount; i++) {
TaskView taskView = getTaskViewAt(i);
+ if (isHomeTask(taskView)) {
+ if (firstNonHomeTaskIndex == i) {
+ firstNonHomeTaskIndex++;
+ }
+ continue;
+ }
+
taskView.updateTaskSize();
- taskView.setAccumulatedFullscreenTranslationX(accumulatedTranslationX);
- accumulatedTranslationX += taskView.getFullscreenTranslationX();
+ fullscreenTranslations[i] += accumulatedTranslationX;
+ // Compensate space caused by TaskView scaling.
+ float widthDiff =
+ taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
+ // Compensate page spacing widening caused by RecentsView scaling.
+ widthDiff += mPageSpacing * (1 - 1 / mFullscreenScale);
+ float fullscreenTranslationX = mIsRtl ? widthDiff : -widthDiff;
+ accumulatedTranslationX += fullscreenTranslationX;
}
- updateGridProperties();
+
+ // We need to maintain first non-home task's full screen translation at 0, now shift
+ // translation of all the TaskViews to achieve that.
+ for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+ getTaskViewAt(i).setFullscreenTranslationX(
+ fullscreenTranslations[i] - fullscreenTranslations[firstNonHomeTaskIndex]);
+ }
+ mClearAllButton.setFullscreenTranslationPrimary(
+ accumulatedTranslationX - fullscreenTranslations[firstNonHomeTaskIndex]);
+
+ updateGridProperties(false);
}
public void getTaskSize(Rect outRect) {
@@ -1037,11 +1238,36 @@
mLastComputedTaskSize.set(outRect);
}
+ /**
+ * Returns the size of task selected to enter modal state.
+ */
+ public Point getSelectedTaskSize() {
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect,
+ mOrientationHandler);
+ int taskWidth = mTempRect.width();
+ int taskHeight = mTempRect.height();
+ if (mRunningTaskId != -1) {
+ int boxLength = Math.max(taskWidth, taskHeight);
+ if (mFocusedTaskRatio > 1) {
+ taskWidth = boxLength;
+ taskHeight = (int) (boxLength / mFocusedTaskRatio);
+ } else {
+ taskWidth = (int) (boxLength * mFocusedTaskRatio);
+ taskHeight = boxLength;
+ }
+ }
+ return new Point(taskWidth, taskHeight);
+ }
+
/** Gets the last computed task size */
public Rect getLastComputedTaskSize() {
return mLastComputedTaskSize;
}
+ public Rect getLastComputedGridTaskSize() {
+ return mLastComputedGridTaskSize;
+ }
+
/** Gets the task size for modal state. */
public void getModalTaskSize(Rect outRect) {
mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
@@ -1059,7 +1285,16 @@
}
// After scrolling, update the visible task's data
- loadVisibleTaskData();
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
+
+ // After scrolling, update ActionsView's visibility.
+ TaskView focusedTaskView = getFocusedTaskView();
+ if (focusedTaskView != null) {
+ float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
+ - mOrientationHandler.getPrimaryScroll(this));
+ float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
+ mActionsView.getScrollAlpha().setValue(Utilities.boundToRange(delta, 0, 1));
+ }
}
// Update the high res thumbnail loader state
@@ -1080,22 +1315,14 @@
if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
return;
}
- mOrientationHandler.getCurveProperties(this, mInsets, mScrollState);
- mScrollState.scrollFromEdge =
- mIsRtl ? mScrollState.scroll : (mMaxScroll - mScrollState.scroll);
-
- final int pageCount = getPageCount();
- for (int i = 0; i < pageCount; i++) {
- View page = getPageAt(i);
- mScrollState.updateInterpolation(mActivity.getDeviceProfile(),
- mOrientationHandler.getChildStartWithTranslation(page));
- ((PageCallbacks) page).onPageScroll(mScrollState);
- }
+ int scroll = mOrientationHandler.getPrimaryScroll(this);
+ int scrollFromEdge = mIsRtl ? scroll : (mMaxScroll - scroll);
+ mClearAllButton.onRecentsViewScroll(scrollFromEdge, mOverviewGridEnabled);
}
@Override
protected int getDestinationPage(int scaledScroll) {
- if (mGridProgress == 0) {
+ if (!showAsGrid()) {
return super.getDestinationPage(scaledScroll);
}
@@ -1122,7 +1349,7 @@
* Iterates through all the tasks, and loads the associated task data for newly visible tasks,
* and unloads the associated task data for tasks that are no longer visible.
*/
- public void loadVisibleTaskData() {
+ public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
// Skip loading visible task data if we've already left the overview state, or if the
// task list hasn't been loaded yet (the task views will not reflect the task list)
@@ -1133,7 +1360,7 @@
int upper = 0;
int visibleStart = 0;
int visibleEnd = 0;
- if (mShowAsGrid) {
+ if (showAsGrid()) {
int screenStart = mOrientationHandler.getPrimaryScroll(this);
int pageOrientedSize = mOrientationHandler.getMeasuredSize(this);
int halfScreenSize = pageOrientedSize / 2;
@@ -1153,7 +1380,7 @@
Task task = taskView.getTask();
int index = indexOfChild(taskView);
boolean visible;
- if (mShowAsGrid) {
+ if (showAsGrid()) {
visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd);
} else {
visible = lower <= index && index <= upper;
@@ -1164,12 +1391,18 @@
continue;
}
if (!mHasVisibleTaskData.get(task.key.id)) {
- taskView.onTaskListVisibilityChanged(true /* visible */);
+ // Ignore thumbnail update if it's current running task during the gesture
+ // We snapshot at end of gesture, it will update then
+ int changes = dataChanges;
+ if (taskView == getRunningTaskView() && mGestureActive) {
+ changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
+ }
+ taskView.onTaskListVisibilityChanged(true /* visible */, changes);
}
mHasVisibleTaskData.put(task.key.id, visible);
} else {
if (mHasVisibleTaskData.get(task.key.id)) {
- taskView.onTaskListVisibilityChanged(false /* visible */);
+ taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
}
mHasVisibleTaskData.delete(task.key.id);
}
@@ -1179,12 +1412,12 @@
/**
* Unloads any associated data from the currently visible tasks
*/
- private void unloadVisibleTaskData() {
+ private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
if (mHasVisibleTaskData.valueAt(i)) {
TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
if (taskView != null) {
- taskView.onTaskListVisibilityChanged(false /* visible */);
+ taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
}
}
}
@@ -1218,13 +1451,13 @@
setCurrentTask(-1);
mIgnoreResetTaskId = -1;
mTaskListChangeId = -1;
+ mFocusedTaskId = -1;
mRecentsAnimationController = null;
mLiveTileParams.setTargetSet(null);
- unloadVisibleTaskData();
+ unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
setCurrentPage(0);
- mDwbToastShown = false;
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
LayoutUtils.setViewEnabled(mActionsView, true);
if (mOrientationState.setGestureActive(false)) {
@@ -1244,6 +1477,17 @@
return getTaskIndexForId(mRunningTaskId);
}
+ public @Nullable TaskView getFocusedTaskView() {
+ return getTaskView(mFocusedTaskId);
+ }
+
+ /**
+ * Returns the width to height ratio of the focused {@link TaskView}.
+ */
+ public float getFocusedTaskRatio() {
+ return mFocusedTaskRatio;
+ }
+
/**
* Get the index of the task view whose id matches {@param taskId}.
* @return -1 if there is no task view for the task id, else the index of the task view.
@@ -1270,6 +1514,7 @@
* Called when a gesture from an app is starting.
*/
public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
+ mGestureActive = true;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
updateOrientationHandler();
@@ -1327,19 +1572,29 @@
for (int i = 0; i < getTaskViewCount(); i++) {
getTaskViewAt(i).setOrientationState(mOrientationState);
}
+ TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mActivity, TYPE_TASK_MENU);
+ if (tv != null) {
+ tv.onRotationChanged();
+ }
}
/**
* Called when a gesture from an app has finished, and an end target has been determined.
*/
public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
-
+ mCurrentGestureEndTarget = endTarget;
+ if (showAsGrid()) {
+ mFocusedTaskId = mRunningTaskId;
+ mFocusedTaskRatio =
+ mLastComputedTaskSize.width() / (float) mLastComputedTaskSize.height();
+ }
}
/**
* Called when a gesture from an app has finished, and the animation to the target has ended.
*/
public void onGestureAnimationEnd() {
+ mGestureActive = false;
if (mOrientationState.setGestureActive(false)) {
updateOrientationHandler();
}
@@ -1353,10 +1608,11 @@
setRunningTaskHidden(false);
animateUpRunningTaskIconScale();
- // TODO: This should be tied to whether there is a focus app on overview.
- if (!mShowAsGrid) {
+ if (!showAsGrid() || getFocusedTaskView() != null) {
animateActionsViewIn();
}
+
+ mCurrentGestureEndTarget = null;
}
/**
@@ -1447,29 +1703,6 @@
}
}
- public void showNextTask() {
- final TaskView runningTaskView = getRunningTaskView();
- final TaskView targetTask;
-
- if (runningTaskView == null) {
- // Launch the first task
- if (getTaskViewCount() > 0) {
- targetTask = getTaskViewAt(0);
- } else {
- return;
- }
- } else {
- final TaskView nextTask = getNextTaskView();
- if (nextTask != null) {
- targetTask = nextTask;
- } else {
- targetTask = runningTaskView;
- }
- }
- targetTask.setEndQuickswitchCuj(true);
- targetTask.launchTask(true);
- }
-
public void setRunningTaskIconScaledDown(boolean isScaledDown) {
if (mRunningTaskIconScaledDown != isScaledDown) {
mRunningTaskIconScaledDown = isScaledDown;
@@ -1495,6 +1728,13 @@
anim.start();
}
+ private void animateActionsViewOut() {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(
+ mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 1, 0);
+ anim.setDuration(TaskView.SCALE_ICON_DURATION);
+ anim.start();
+ }
+
public void animateUpRunningTaskIconScale() {
animateUpRunningTaskIconScale(0);
}
@@ -1514,94 +1754,95 @@
* This method only calculates the potential position and depends on {@link #setGridProgress} to
* apply the actual scaling and translation.
*/
- private void updateGridProperties() {
+ private void updateGridProperties(boolean isTaskDismissal) {
int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
}
- final int boxLength = Math.max(mTaskWidth, mTaskHeight);
-
- float availableHeight =
- mTaskTopMargin + mTaskHeight + mSizeStrategy.getOverviewActionsHeight(mContext);
- float rowHeight = (availableHeight - mRowSpacing) / 2;
- float gridScale = rowHeight / (boxLength + mTaskTopMargin);
-
- TaskView firstTask = getTaskViewAt(0);
- float firstTaskWidthOffset;
- if (mIsRtl) {
- // Move the first task to the right edge.
- firstTaskWidthOffset = mTaskWidth - firstTask.getLayoutParams().width * gridScale;
- } else {
- // Move the first task to the left edge.
- firstTaskWidthOffset = -firstTask.getLayoutParams().width * (1 - gridScale);
- }
+ final int boxLength = Math.max(mLastComputedGridTaskSize.width(),
+ mLastComputedGridTaskSize.height());
+ int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ float heightOffset = (boxLength + taskTopMargin) + mRowSpacing;
+ float taskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
int topRowWidth = 0;
int bottomRowWidth = 0;
float topAccumulatedTranslationX = 0;
float bottomAccumulatedTranslationX = 0;
IntSet topSet = new IntSet();
+ IntSet bottomSet = new IntSet();
float[] gridTranslations = new float[taskCount];
+ int firstNonHomeTaskIndex = 0;
+
+ if (!isTaskDismissal) {
+ mTopIdSet.clear();
+ }
for (int i = 0; i < taskCount; i++) {
TaskView taskView = getTaskViewAt(i);
- taskView.setGridScale(gridScale);
+ if (isHomeTask(taskView)) {
+ if (firstNonHomeTaskIndex == i) {
+ firstNonHomeTaskIndex++;
+ }
+ continue;
+ }
- float taskWidthDiff = mTaskWidth - taskView.getLayoutParams().width * gridScale;
- float taskWidthOffset = mIsRtl ? taskWidthDiff : -taskWidthDiff;
- // Visually we want to move all task by firstTaskWidthOffset, but calculate page scroll
- // according to right edge (or left in nonRtl) of TaskView.
- gridTranslations[i] = firstTaskWidthOffset - taskWidthOffset;
- taskView.setGridOffsetTranslationX(taskWidthOffset);
+ // Evenly distribute tasks between rows unless rearranging due to task dismissal, in
+ // which case keep tasks in their respective rows. For the running task, don't join
+ // the grid.
+ if (taskView.isRunningTask()) {
+ topRowWidth += taskView.getLayoutParams().width + mPageSpacing;
+ bottomRowWidth += taskView.getLayoutParams().width + mPageSpacing;
- // Off-set gap due to task scaling.
- float widthDiff = taskView.getLayoutParams().width * (1 - gridScale);
- float gridScaleTranslationX = mIsRtl ? widthDiff : -widthDiff;
- gridTranslations[i] += gridScaleTranslationX;
-
- // Visible offset caused by having scaling pivot on top-right.
- taskView.setNonRtlVisibleOffset(mIsRtl ? 0 : widthDiff);
-
- if (topRowWidth <= bottomRowWidth) {
+ // Center view vertically in case it's from different orientation.
+ taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
+ - taskView.getLayoutParams().height) / 2f);
+ } else if ((!isTaskDismissal && topRowWidth <= bottomRowWidth) || (isTaskDismissal
+ && mTopIdSet.contains(taskView.getTask().key.id))) {
gridTranslations[i] += topAccumulatedTranslationX;
- topRowWidth += taskView.getLayoutParams().width * gridScale + mPageSpacing;
+ topRowWidth += taskView.getLayoutParams().width + mPageSpacing;
topSet.add(i);
+ mTopIdSet.add(taskView.getTask().key.id);
- taskView.setGridTranslationY(0);
+ taskView.setGridTranslationY(taskGridVerticalDiff);
// Move horizontally into empty space.
float widthOffset = 0;
- for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
- widthOffset += getTaskViewAt(j).getLayoutParams().width * gridScale
- + mPageSpacing;
+ for (int j = i - 1; bottomSet.contains(j); j--) {
+ widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
}
float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
gridTranslations[i] += gridTranslationX;
- topAccumulatedTranslationX += gridTranslationX + gridScaleTranslationX;
- bottomAccumulatedTranslationX += gridScaleTranslationX;
+ topAccumulatedTranslationX += gridTranslationX;
} else {
gridTranslations[i] += bottomAccumulatedTranslationX;
- bottomRowWidth += taskView.getLayoutParams().width * gridScale + mPageSpacing;
+ bottomRowWidth += taskView.getLayoutParams().width + mPageSpacing;
+ bottomSet.add(i);
// Move into bottom row.
- float heightOffset = (boxLength + mTaskTopMargin) * gridScale + mRowSpacing;
- taskView.setGridTranslationY(heightOffset);
+ taskView.setGridTranslationY(heightOffset + taskGridVerticalDiff);
// Move horizontally into empty space.
float widthOffset = 0;
for (int j = i - 1; topSet.contains(j); j--) {
- widthOffset += getTaskViewAt(j).getLayoutParams().width * gridScale
- + mPageSpacing;
+ widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
}
float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
gridTranslations[i] += gridTranslationX;
- topAccumulatedTranslationX += gridScaleTranslationX;
- bottomAccumulatedTranslationX += gridTranslationX + gridScaleTranslationX;
+ bottomAccumulatedTranslationX += gridTranslationX;
}
}
+ // We need to maintain first non-home task's grid translation at 0, now shift translation
+ // of all the TaskViews to achieve that.
+ for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ taskView.setGridTranslationX(
+ gridTranslations[i] - gridTranslations[firstNonHomeTaskIndex]);
+ }
+
// Use the accumulated translation of the longer row.
float clearAllAccumulatedTranslation = mIsRtl ? Math.max(topAccumulatedTranslationX,
bottomAccumulatedTranslationX) : Math.min(topAccumulatedTranslationX,
@@ -1615,51 +1856,51 @@
shorterRowCompensation = bottomRowWidth - topRowWidth;
}
} else {
- if (!topSet.contains(taskCount - 1)) {
+ if (bottomSet.contains(taskCount - 1)) {
shorterRowCompensation = topRowWidth - bottomRowWidth;
}
}
float clearAllShorterRowCompensation =
mIsRtl ? -shorterRowCompensation : shorterRowCompensation;
- // If the total width is shorter than one task's width, move ClearAllButton further away
+ // If the total width is shorter than one grid's width, move ClearAllButton further away
// accordingly.
float clearAllShortTotalCompensation = 0;
float longRowWidth = Math.max(topRowWidth, bottomRowWidth);
- if (longRowWidth < mTaskWidth) {
- float shortTotalCompensation = mTaskWidth - longRowWidth;
+ if (longRowWidth < mLastComputedGridSize.width()) {
+ float shortTotalCompensation = mLastComputedGridSize.width() - longRowWidth;
clearAllShortTotalCompensation =
mIsRtl ? -shortTotalCompensation : shortTotalCompensation;
}
- float clearAllTotalTranslationX = firstTaskWidthOffset + clearAllAccumulatedTranslation
- + clearAllShorterRowCompensation + clearAllShortTotalCompensation;
+ float clearAllTotalTranslationX =
+ clearAllAccumulatedTranslation + clearAllShorterRowCompensation
+ + clearAllShortTotalCompensation;
- // We need to maintain first task's grid translation at 0, now shift translation of all
- // the TaskViews to achieve that.
- for (int i = 0; i < taskCount; i++) {
- getTaskViewAt(i).setGridTranslationX(gridTranslations[i] - gridTranslations[0]);
- }
- mClearAllButton.setGridTranslationPrimary(clearAllTotalTranslationX - gridTranslations[0]);
+ mClearAllButton.setGridTranslationPrimary(
+ clearAllTotalTranslationX - gridTranslations[firstNonHomeTaskIndex]);
+ mClearAllButton.setGridScrollOffset(
+ mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left
+ : mLastComputedTaskSize.right - mLastComputedGridSize.right);
setGridProgress(mGridProgress);
}
+ protected boolean isHomeTask(TaskView taskView) {
+ return false;
+ }
+
/**
* Moves TaskView and ClearAllButton between carousel and 2 row grid.
*
* @param gridProgress 0 = carousel; 1 = 2 row grid.
*/
- public void setGridProgress(float gridProgress) {
+ private void setGridProgress(float gridProgress) {
int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
}
- if (!mShowAsGrid) {
- gridProgress = 0;
- }
-
mGridProgress = gridProgress;
for (int i = 0; i < taskCount; i++) {
@@ -1687,7 +1928,7 @@
// When the unpinned task is added, snap to first page and disable transitions
if (view instanceof TaskView) {
snapToPage(0);
- disableLayoutTransitions();
+ setLayoutTransition(null);
}
}
@@ -1696,10 +1937,6 @@
setLayoutTransition(mLayoutTransition);
}
- private void disableLayoutTransitions() {
- setLayoutTransition(null);
- }
-
public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) {
mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp;
}
@@ -1708,42 +1945,6 @@
return mSwipeDownShouldLaunchApp;
}
- public interface PageCallbacks {
-
- /**
- * Updates the page UI based on scroll params.
- */
- default void onPageScroll(ScrollState scrollState) {}
- }
-
- public static class ScrollState extends CurveProperties {
-
- /**
- * The progress from 0 to 1, where 0 is the center
- * of the screen and 1 is the edge of the screen.
- */
- public float linearInterpolation;
-
- /**
- * The amount by which all the content is scrolled relative to the end of the list.
- */
- public float scrollFromEdge;
-
- /**
- * Updates linearInterpolation for the provided child position
- */
- public void updateInterpolation(DeviceProfile deviceProfile, float childStart) {
- float pageCenter = childStart + halfPageSize;
- float distanceFromScreenCenter = screenCenter - pageCenter;
- // How far the page has to move from the center to be offscreen, taking into account
- // the EDGE_SCALE_DOWN_FACTOR that will be applied at that position.
- float distanceToReachEdge = halfScreenSize
- + halfPageSize * (1 - TaskView.getEdgeScaleDownFactor(deviceProfile));
- linearInterpolation = Math.min(1,
- Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
- }
- }
-
public void setIgnoreResetTask(int taskId) {
mIgnoreResetTaskId = taskId;
}
@@ -1760,7 +1961,7 @@
// alpha is set to 0 so that it can be recycled in the view pool properly
anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
FloatProperty<TaskView> secondaryViewTranslate =
- taskView.getDismissTaskTranslationProperty();
+ taskView.getSecondaryDissmissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
@@ -1803,7 +2004,8 @@
if (animateTaskView) {
addDismissedTaskAnimations(taskView, duration, anim);
}
- } else if (!mShowAsGrid) { // Don't animate other tasks when dismissing in grid for now
+ } else if (!showAsGrid()) {
+ // For grid layout, don't animate other tasks when dismissing in grid for now.
// If we just take newScroll - oldScroll, everything to the right of dragged task
// translates to the left. We need to offset this in some cases:
// - In RTL, add page offset to all pages, since we want pages to move to the right
@@ -1823,10 +2025,23 @@
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
}
+
+ // Additional offset for fake landscape, if the pinning happens to the right or
+ // left, we need to scroll all the tasks away from the direction of the splaceholder
+ // view
+ if (isSplitSelectionActive()) {
+ int splitPosition = getSplitPlaceholder().getSplitController()
+ .getActiveSplitPositionOption().mStagePosition;
+ int direction = mOrientationHandler
+ .getSplitTranslationDirectionFactor(splitPosition);
+ int splitOffset = mOrientationHandler.getSplitAnimationTranslation(
+ mSplitPlaceholderView.getHeight(), mActivity.getDeviceProfile());
+ offset += direction * splitOffset;
+ }
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
FloatProperty translationProperty = child instanceof TaskView
- ? ((TaskView) child).getFillDismissGapTranslationProperty()
+ ? ((TaskView) child).getPrimaryDismissTranslationProperty()
: mOrientationHandler.getPrimaryViewTranslate();
ResourceProvider rp = DynamicResource.provider(mActivity);
@@ -1897,19 +2112,26 @@
} else {
snapToPageImmediately(pageToSnapTo);
// Grid got messed up, reapply.
- updateGridProperties();
+ updateGridProperties(true);
+ if (showAsGrid() && getFocusedTaskView() == null) {
+ animateActionsViewOut();
+ }
}
// Update the layout synchronously so that the position of next view is
// immediately available.
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
}
resetTaskVisuals();
+ onDismissAnimationEnds();
mPendingAnimation = null;
}
});
return anim;
}
+ protected void onDismissAnimationEnds() {
+ }
+
public PendingAnimation createAllTasksDismissAnimation(long duration) {
if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
throw new IllegalStateException("Another pending animation is still running");
@@ -2077,13 +2299,6 @@
if (mOrientationState.setRecentsRotation(rotation)) {
updateOrientationHandler();
}
-
- // If overview is in modal state when rotate, reset it to overview state without running
- // animation.
- if (mActivity.isInState(OVERVIEW_MODAL_TASK)) {
- mActivity.getStateManager().goToState(LauncherState.OVERVIEW, false);
- resetModalVisuals();
- }
}
public void setLayoutRotation(int touchRotation, int displayRotation) {
@@ -2188,7 +2403,7 @@
// Update the pivots such that when the task is scaled, it fills the full page
getTaskSize(mTempRect);
- getPagedViewOrientedState().getFullScreenScaleAndPivot(
+ mFullscreenScale = getPagedViewOrientedState().getFullScreenScaleAndPivot(
mTempRect, mActivity.getDeviceProfile(), mTempPointF);
setPivotX(mTempPointF.x);
setPivotY(mTempPointF.y);
@@ -2266,9 +2481,7 @@
mTempRectF.set(mLastComputedTaskSize);
RectF taskPosition = mTempRectF;
float desiredLeft = getWidth();
- float distanceToOffscreen = desiredLeft - taskPosition.left;
// Used to calculate the scale of the task view based on its new offset.
- float centerToOffscreenProgress = Math.abs(offsetProgress);
if (midpointIndex > -1) {
// When there is a midpoint reference task, adjacent tasks have less distance to travel
// to reach offscreen. Offset the task position to the task's starting point.
@@ -2278,10 +2491,8 @@
- mOrientationHandler.getChildStart(midpointChild)
+ getDisplacementFromScreenCenter(midpointIndex));
taskPosition.offset(distanceFromMidpoint, 0);
- centerToOffscreenProgress = Utilities.mapRange(centerToOffscreenProgress,
- distanceFromMidpoint / distanceToOffscreen, 1);
}
- distanceToOffscreen = desiredLeft - taskPosition.left;
+ float distanceToOffscreen = desiredLeft - taskPosition.left;
// Finally, we need to account for RecentsView scale, because it moves tasks based on its
// pivot. To do this, we move the task position to where it would be offscreen at scale = 1
// (computed above), then we apply the scale via getMatrix() to determine how much that
@@ -2295,7 +2506,7 @@
return distanceToOffscreen * offsetProgress;
}
- private void setTaskViewsSecondaryTranslation(float translation) {
+ protected void setTaskViewsResistanceTranslation(float translation) {
mTaskViewsSecondaryTranslation = translation;
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView task = getTaskViewAt(i);
@@ -2304,6 +2515,15 @@
mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
}
+ protected void setTaskViewsPrimaryTranslation(float translation) {
+ mTaskViewsPrimaryTranslation = translation;
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ TaskView task = getTaskViewAt(i);
+ task.getPrimaryDismissTranslationProperty().set(task, translation / getScaleY());
+ }
+ mLiveTileTaskViewSimulator.recentsViewPrimaryTranslation.value = translation;
+ }
+
/**
* TODO: Do not assume motion across X axis for adjacent page
*/
@@ -2321,6 +2541,118 @@
}
}
+ /**
+ * True if the background scrim of the recents view is light colored and the foreground elements
+ * should use dark colors.
+ */
+ public boolean hasLightBackground() {
+ return mHasLightBackground;
+ }
+
+ public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
+ mSplitHiddenTaskView = taskView;
+ mSplitPlaceholderView.getSplitController().setInitialTaskSelect(taskView,
+ splitPositionOption);
+ mSplitHiddenTaskViewIndex = indexOfChild(taskView);
+ }
+
+ public PendingAnimation createSplitSelectInitAnimation() {
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
+ }
+
+ public void confirmSplitSelect(TaskView taskView) {
+ mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
+ resetTaskVisuals();
+ setTranslationY(0);
+ }
+
+ public PendingAnimation cancelSplitSelect(boolean animate) {
+ mSplitPlaceholderView.getSplitController().resetState();
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ PendingAnimation pendingAnim = new PendingAnimation(duration);
+ if (!animate) {
+ resetFromSplitSelectionState();
+ return pendingAnim;
+ }
+
+ addViewInLayout(mSplitHiddenTaskView, mSplitHiddenTaskViewIndex,
+ mSplitHiddenTaskView.getLayoutParams());
+ mSplitHiddenTaskView.setAlpha(0);
+ int[] oldScroll = new int[getChildCount()];
+ getPageScrolls(oldScroll, false,
+ view -> view.getVisibility() != GONE && view != mSplitHiddenTaskView);
+
+ // x is correct, y is before tasks move up
+ int[] locationOnScreen = mSplitHiddenTaskView.getLocationOnScreen();
+ int[] newScroll = new int[getChildCount()];
+ getPageScrolls(newScroll, false, SIMPLE_SCROLL_LOGIC);
+
+ boolean needsCurveUpdates = false;
+ for (int i = mSplitHiddenTaskViewIndex; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (child == mSplitHiddenTaskView) {
+
+ int left = newScroll[i] + getPaddingStart();
+ int topMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ int top = -mSplitHiddenTaskView.getHeight() - locationOnScreen[1];
+ mSplitHiddenTaskView.layout(left, top,
+ left + mSplitHiddenTaskView.getWidth(),
+ top + mSplitHiddenTaskView.getHeight());
+ pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, TRANSLATION_Y,
+ -top + mSplitPlaceholderView.getHeight() - topMargin));
+ pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, ALPHA, 1));
+ } else {
+ // If insertion is on last index (furthest from clear all), we directly add the view
+ // else we translate all views to the right of insertion index further right,
+ // ignore views to left
+ int scrollDiff = newScroll[i] - oldScroll[i];
+ if (scrollDiff != 0) {
+ FloatProperty translationProperty = child instanceof TaskView
+ ? ((TaskView) child).getPrimaryDismissTranslationProperty()
+ : mOrientationHandler.getPrimaryViewTranslate();
+
+ ResourceProvider rp = DynamicResource.provider(mActivity);
+ SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
+ .setDampingRatio(
+ rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio))
+ .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness));
+ pendingAnim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff)
+ .setDuration(duration), ACCEL, sp);
+ needsCurveUpdates = true;
+ }
+ }
+ }
+
+ if (needsCurveUpdates) {
+ pendingAnim.addOnFrameCallback(this::updateCurveProperties);
+ }
+
+ pendingAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ resetFromSplitSelectionState();
+ }
+ });
+
+ return pendingAnim;
+ }
+
+ private void resetFromSplitSelectionState() {
+ mSplitHiddenTaskView.setTranslationY(0);
+ int pageToSnapTo = mCurrentPage;
+ if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
+ pageToSnapTo += 1;
+ } else {
+ pageToSnapTo = mSplitHiddenTaskViewIndex;
+ }
+ snapToPageImmediately(pageToSnapTo);
+ onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
+ resetTaskVisuals();
+ mSplitHiddenTaskView = null;
+ mSplitHiddenTaskViewIndex = -1;
+ }
+
private void updateDeadZoneRects() {
// Get the deadzone rect surrounding the clear all button to not dismiss overview to home
mClearAllButtonDeadZoneRect.setEmpty();
@@ -2460,10 +2792,13 @@
progressAnim.addUpdateListener(animator -> {
// Once we pass a certain threshold, update the sysui flags to match the target
// tasks' flags
- mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
- animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
- ? targetSysUiFlags
- : 0);
+ if (animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD) {
+ mActivity.getSystemUiController().updateUiState(
+ UI_STATE_OVERVIEW, targetSysUiFlags);
+ } else {
+ mActivity.getSystemUiController().updateUiState(
+ UI_STATE_OVERVIEW, hasLightBackground());
+ }
// Passing the threshold from taskview to fullscreen app will vibrate
final boolean passed = animator.getAnimatedFraction() >=
@@ -2494,17 +2829,11 @@
}
mPendingAnimation.addEndListener(isSuccess -> {
if (isSuccess) {
- Consumer<Boolean> onLaunchResult = (result) -> {
- onTaskLaunchAnimationEnd(result);
- if (!result) {
- tv.notifyTaskLaunchFailed(TAG);
- }
- };
if (LIVE_TILE.get()) {
finishRecentsAnimation(false /* toRecents */, null);
- onLaunchResult.accept(true /* success */);
+ onTaskLaunchAnimationEnd(true /* success */);
} else {
- tv.launchTask(false, onLaunchResult, getHandler());
+ tv.launchTask(this::onTaskLaunchAnimationEnd);
}
Task task = tv.getTask();
if (task != null) {
@@ -2528,7 +2857,7 @@
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
- loadVisibleTaskData();
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
updateEnabledOverlays();
}
@@ -2611,6 +2940,16 @@
}
public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
+ if (!toRecents && LIVE_TILE.get()) {
+ // Reset the minimized state since we force-toggled the minimized state when entering
+ // overview, but never actually finished the recents animation. This is a catch all for
+ // cases where we haven't already reset it.
+ SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
+ if (p != null) {
+ p.setSplitScreenMinimized(false);
+ }
+ }
+
if (mRecentsAnimationController == null) {
if (onFinishComplete != null) {
onFinishComplete.run();
@@ -2641,37 +2980,59 @@
@Override
protected int computeMinScroll() {
if (getTaskViewCount() > 0) {
- if (mDisallowScrollToClearAll) {
+ if (mIsRtl && mDisallowScrollToClearAll) {
// We aren't showing the clear all button,
// so use the leftmost task as the min scroll.
- if (mIsRtl) {
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
- }
- return getScrollForPage(mTaskViewStartIndex);
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
}
- if (mIsRtl) {
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
- }
- return getScrollForPage(mTaskViewStartIndex);
+ return getLeftMostChildScroll();
}
return super.computeMinScroll();
}
+ /**
+ * Returns page scroll of the left most child.
+ */
+ public int getLeftMostChildScroll() {
+ return getScrollForPage(mIsRtl ? indexOfChild(mClearAllButton) : mTaskViewStartIndex);
+ }
+
+ @Override
+ protected int computeMaxScroll() {
+ if (getTaskViewCount() > 0) {
+ if (!mIsRtl && mDisallowScrollToClearAll) {
+ // We aren't showing the clear all button,
+ // so use the rightmost task as the min scroll.
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
+ }
+ return getScrollForPage(mIsRtl ? mTaskViewStartIndex : indexOfChild(mClearAllButton));
+ }
+ return super.computeMaxScroll();
+ }
+
@Override
protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
ComputePageScrollsLogic scrollLogic) {
boolean pageScrollChanged = super.getPageScrolls(outPageScrolls, layoutChildren,
scrollLogic);
+ // Align ClearAllButton to the left (RTL) or right (non-RTL), which is different from other
+ // TaskViews. This must be called after laying out ClearAllButton.
+ if (layoutChildren) {
+ int clearAllWidthDiff = mTaskWidth - mClearAllButton.getWidth();
+ mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff);
+ }
+
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
float scrollDiff = 0;
if (child instanceof TaskView) {
scrollDiff = ((TaskView) child).getScrollAdjustment(mOverviewFullscreenEnabled,
- mOverviewGridEnabled);
+ showAsGrid());
} else if (child instanceof ClearAllButton) {
- scrollDiff = ((ClearAllButton) child).getScrollAdjustment(mOverviewGridEnabled);
+ scrollDiff = ((ClearAllButton) child).getScrollAdjustment(
+ mOverviewFullscreenEnabled, showAsGrid());
}
if (scrollDiff != 0) {
@@ -2688,9 +3049,10 @@
View child = getChildAt(index);
if (child instanceof TaskView) {
childOffset += ((TaskView) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
- mOverviewGridEnabled);
+ showAsGrid());
} else if (child instanceof ClearAllButton) {
- childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewGridEnabled);
+ childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
+ showAsGrid());
}
return childOffset;
}
@@ -2702,26 +3064,7 @@
return super.getChildVisibleSize(index);
}
return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
- mOverviewFullscreenEnabled, mOverviewGridEnabled));
- }
-
- @Override
- protected int computeMaxScroll() {
- if (getTaskViewCount() > 0) {
- if (mDisallowScrollToClearAll) {
- // We aren't showing the clear all button,
- // so use the rightmost task as the min scroll.
- if (mIsRtl) {
- return getScrollForPage(mTaskViewStartIndex);
- }
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
- }
- if (mIsRtl) {
- return getScrollForPage(mTaskViewStartIndex);
- }
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
- }
- return super.computeMaxScroll();
+ mOverviewFullscreenEnabled));
}
public ClearAllButton getClearAllButton() {
@@ -2876,6 +3219,7 @@
boolean inPlaceLandscape = !mOrientationState.canRecentsActivityRotate()
&& mOrientationState.getTouchRotation() != ROTATION_0;
mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
+ mActionsView.setTaskModalness(modalness);
}
@Nullable
@@ -2905,6 +3249,27 @@
}
/**
+ * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the
+ * tasks to be dimmed while other elements in the recents view are left alone.
+ */
+ public void showForegroundScrim(boolean show) {
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ getTaskViewAt(i).showColorTint(show);
+ }
+ }
+
+ private boolean showAsGrid() {
+ return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
+ && mSizeStrategy.stateFromGestureEndTarget(
+ mCurrentGestureEndTarget).displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ }
+
+ public boolean shouldShowOverviewActionsForState(STATE_TYPE state) {
+ return !state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())
+ || getFocusedTaskView() != null;
+ }
+
+ /**
* Used to register callbacks for when our empty message state changes.
*
* @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener)
@@ -2916,7 +3281,7 @@
}
private static class PinnedStackAnimationListener<T extends BaseActivity> extends
- IPinnedStackAnimationListener.Stub {
+ IPipAnimationListener.Stub {
private T mActivity;
public void setActivity(T activity) {
@@ -2924,10 +3289,14 @@
}
@Override
- public void onPinnedStackAnimationStarted() {
- // Needed for activities that auto-enter PiP, which will not trigger a remote
- // animation to be created
- mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ public void onPipAnimationStarted() {
+ MAIN_EXECUTOR.execute(() -> {
+ // Needed for activities that auto-enter PiP, which will not trigger a remote
+ // animation to be created
+ if (mActivity != null) {
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
deleted file mode 100644
index db04fc0..0000000
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.views;
-
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.Path.Op;
-import android.util.AttributeSet;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.uioverrides.states.OverviewState;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.ScrimView;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
-
-/**
- * Scrim used for all-apps and shelf in Overview
- * In transposed layout, it behaves as a simple color scrim.
- * In portrait layout, it draws a rounded rect such that
- * From normal state to overview state, the shelf just fades in and does not move
- * From overview state to all-apps state the shelf moves up and fades in to cover the screen
- */
-public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
- implements NavigationModeChangeListener {
-
- // If the progress is more than this, shelf follows the finger, otherwise it moves faster to
- // cover the whole screen
- private static final float SCRIM_CATCHUP_THRESHOLD = 0.2f;
-
- // Temporarily needed until android.R.attr.bottomDialogCornerRadius becomes public
- private static final float BOTTOM_CORNER_RADIUS_RATIO = 2f;
-
- // In transposed layout, we simply draw a flat color.
- private boolean mDrawingFlatColor;
-
- // For shelf mode
- private final int mEndAlpha;
- private final float mRadius;
- private final int mMaxScrimAlpha;
- private final Paint mPaint;
-
- // Mid point where the alpha changes
- private int mMidAlpha;
- private float mMidProgress;
-
- private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
- private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
-
- private float mShiftRange;
-
- private float mTopOffset;
- private float mShelfTop;
- private float mShelfTopAtThreshold;
-
- private int mShelfColor;
- private int mRemainingScreenColor;
-
- private final Path mTempPath = new Path();
- private final Path mRemainingScreenPath = new Path();
- private boolean mRemainingScreenPathValid = false;
-
- private Mode mSysUINavigationMode;
-
- public ShelfScrimView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mMaxScrimAlpha = Math.round(OVERVIEW.getOverviewScrimAlpha(mLauncher) * 255);
-
- mEndAlpha = Color.alpha(mEndScrim);
- mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- // Just assume the easiest UI for now, until we have the proper layout information.
- mDrawingFlatColor = true;
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mRemainingScreenPathValid = false;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(getContext())
- .addModeChangeListener(this));
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- SysUINavigationMode.INSTANCE.get(getContext()).removeModeChangeListener(this);
- }
-
- @Override
- public void onNavigationModeChanged(Mode newMode) {
- mSysUINavigationMode = newMode;
- // Note that these interpolators are inverted because progress goes 1 to 0.
- if (mSysUINavigationMode == Mode.NO_BUTTON) {
- // Show the shelf more quickly before reaching overview progress.
- mBeforeMidProgressColorInterpolator = ACCEL_2;
- mAfterMidProgressColorInterpolator = ACCEL;
- } else {
- mBeforeMidProgressColorInterpolator = ACCEL;
- mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
- }
- }
-
- @Override
- public void reInitUi() {
- DeviceProfile dp = mLauncher.getDeviceProfile();
- mDrawingFlatColor = dp.isVerticalBarLayout();
-
- if (!mDrawingFlatColor) {
- mRemainingScreenPathValid = false;
- mShiftRange = mLauncher.getAllAppsController().getShiftRange();
-
- // Fade in all apps background quickly to distinguish from swiping from nav bar.
- mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha);
- mMidProgress = 1 - (OverviewState.getDefaultSwipeHeight(mLauncher)
- / mLauncher.getAllAppsController().getShiftRange());
-
- mTopOffset = dp.getInsets().top;
- mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
- }
- updateColors();
- updateSysUiColors();
- invalidate();
- }
-
- @Override
- public void updateColors() {
- super.updateColors();
- if (mDrawingFlatColor) {
- return;
- }
-
- if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
- mShelfTop = mShiftRange * mProgress + mTopOffset;
- } else {
- mShelfTop = Utilities.mapRange(mProgress / SCRIM_CATCHUP_THRESHOLD, -mRadius,
- mShelfTopAtThreshold);
- }
-
- if (mProgress >= 1) {
- mRemainingScreenColor = 0;
- mShelfColor = 0;
- } else if (mProgress >= mMidProgress) {
- mRemainingScreenColor = 0;
-
- int alpha = Math.round(Utilities.mapToRange(
- mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator));
- mShelfColor = setColorAlphaBound(mEndScrim, alpha);
- } else {
- // Note that these ranges and interpolators are inverted because progress goes 1 to 0.
- int alpha = Math.round(
- Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
- (float) mMidAlpha, mAfterMidProgressColorInterpolator));
- mShelfColor = setColorAlphaBound(mEndScrim, alpha);
-
- int remainingScrimAlpha = Math.round(
- Utilities.mapToRange(mProgress, (float) 0, mMidProgress, mMaxScrimAlpha,
- (float) 0, LINEAR));
- mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha);
- }
- }
-
- @Override
- protected void updateSysUiColors() {
- if (mDrawingFlatColor) {
- super.updateSysUiColors();
- } else {
- // Use a light system UI (dark icons) if all apps is behind at least half of the
- // status bar.
- boolean forceChange = mShelfTop <= mLauncher.getDeviceProfile().getInsets().top / 2f;
- if (forceChange) {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
- } else {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
- }
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mDrawingFlatColor) {
- if (mCurrentFlatColor != 0) {
- canvas.drawColor(mCurrentFlatColor);
- }
- return;
- }
-
- if (Color.alpha(mShelfColor) == 0) {
- return;
- } else if (mProgress <= 0) {
- canvas.drawColor(mShelfColor);
- return;
- }
-
- int height = getHeight();
- int width = getWidth();
- // Draw the scrim over the remaining screen if needed.
- if (mRemainingScreenColor != 0) {
- if (!mRemainingScreenPathValid) {
- mTempPath.reset();
- // Using a arbitrary '+10' in the bottom to avoid any left-overs at the
- // corners due to rounding issues.
- mTempPath.addRoundRect(0, height - mRadius, width, height + mRadius + 10,
- mRadius, mRadius, Direction.CW);
- mRemainingScreenPath.reset();
- mRemainingScreenPath.addRect(0, 0, width, height, Direction.CW);
- mRemainingScreenPath.op(mTempPath, Op.DIFFERENCE);
- }
-
- float offset = height - mRadius - mShelfTop;
- canvas.translate(0, -offset);
- mPaint.setColor(mRemainingScreenColor);
- canvas.drawPath(mRemainingScreenPath, mPaint);
- canvas.translate(0, offset);
- }
-
- mPaint.setColor(mShelfColor);
- canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
new file mode 100644
index 0000000..fb9be81
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.View;
+
+import com.android.quickstep.util.SplitSelectStateController;
+
+public class SplitPlaceholderView extends View {
+
+ public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
+ new FloatProperty<SplitPlaceholderView>("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();
+ }
+ };
+
+ private SplitSelectStateController mSplitController;
+
+ public SplitPlaceholderView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void init(SplitSelectStateController controller) {
+ this.mSplitController = controller;
+ }
+
+ public SplitSelectStateController getSplitController() {
+ return mSplitController;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 2315147..658d71d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -23,12 +23,15 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -41,22 +44,21 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.TaskCornerRadius;
/**
* Contains options for a recent task when long-pressing its icon.
*/
-public class TaskMenuView extends AbstractFloatingView {
+public class TaskMenuView extends AbstractFloatingView implements OnScrollChangedListener {
private static final Rect sTempRect = new Rect();
private static final int REVEAL_OPEN_DURATION = 150;
private static final int REVEAL_CLOSE_DURATION = 100;
- private final float mThumbnailTopMargin;
private BaseDraggingActivity mActivity;
private TextView mTaskName;
private AnimatorSet mOpenCloseAnimator;
@@ -71,7 +73,7 @@
super(context, attrs, defStyleAttr);
mActivity = BaseDraggingActivity.fromContext(context);
- mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+ setClipToOutline(true);
}
@Override
@@ -108,15 +110,28 @@
return (type & TYPE_TASK_MENU) != 0;
}
- public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) {
- float adjustedY = y + mThumbnailTopMargin;
+ @Override
+ public ViewOutlineProvider getOutlineProvider() {
+ return new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
+ TaskCornerRadius.get(view.getContext()));
+ }
+ };
+ }
+
+ private void setPosition(float x, float y) {
+ PagedOrientationHandler pagedOrientationHandler = mTaskView.getPagedOrientationHandler();
+ int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ float adjustedY = y + taskTopMargin;
// Changing pivot to make computations easier
// NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
// which would render the X and Y position set here incorrect
setPivotX(0);
if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
// In tablet, set pivotY to original position without mThumbnailTopMargin adjustment.
- setPivotY(-mThumbnailTopMargin);
+ setPivotY(-taskTopMargin);
} else {
setPivotY(0);
}
@@ -137,11 +152,11 @@
}
}
- public static TaskMenuView showForTask(TaskView taskView) {
+ public static boolean showForTask(TaskView taskView) {
BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext());
final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
R.layout.task_menu, activity.getDragLayer(), false);
- return taskMenuView.populateAndShowForTask(taskView) ? taskMenuView : null;
+ return taskMenuView.populateAndShowForTask(taskView);
}
private boolean populateAndShowForTask(TaskView taskView) {
@@ -154,9 +169,16 @@
return false;
}
post(this::animateOpen);
+ mActivity.getRootView().getViewTreeObserver().addOnScrollChangedListener(this);
return true;
}
+ @Override
+ public void onScrollChanged() {
+ RecentsView rv = mTaskView.getRecentsView();
+ setPosition(mTaskView.getX() - rv.getScrollX(), mTaskView.getY() - rv.getScrollY());
+ }
+
/** @return true if successfully able to populate task view menu, false otherwise */
private boolean populateAndLayoutMenu() {
if (mTaskView.getTask().icon == null) {
@@ -172,7 +194,8 @@
mTaskName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
mTaskName.setOnClickListener(v -> close(true));
- TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption);
+ TaskOverlayFactory.getEnabledShortcuts(taskView, mActivity.getDeviceProfile())
+ .forEach(this::addMenuOption);
}
private void addMenuOption(SystemShortcut menuOption) {
@@ -182,6 +205,8 @@
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
+ menuOptionView.setEnabled(menuOption.isEnabled());
+ menuOptionView.setAlpha(menuOption.isEnabled() ? 1 : 0.5f);
menuOptionView.setOnClickListener(view -> {
if (LIVE_TILE.get()) {
RecentsView recentsView = mTaskView.getRecentsView();
@@ -211,8 +236,7 @@
.mOrientationState.isRecentsActivityRotationAllowed();
mOptionLayout.setOrientation(orientationHandler
.getTaskMenuLayoutOrientation(canActivityRotate, mOptionLayout));
- setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top,
- taskView.getPagedOrientationHandler());
+ setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top);
}
private void animateOpen() {
@@ -233,9 +257,11 @@
final Animator revealAnimator = createOpenCloseOutlineProvider()
.createRevealAnimator(this, closing);
revealAnimator.setInterpolator(Interpolators.DEACCEL);
- mOpenCloseAnimator.play(revealAnimator);
- mOpenCloseAnimator.play(ObjectAnimator.ofFloat(mTaskView.getThumbnail(), DIM_ALPHA,
- closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA));
+ mOpenCloseAnimator.playTogether(revealAnimator,
+ ObjectAnimator.ofFloat(
+ mTaskView.getThumbnail(), DIM_ALPHA,
+ closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
+ ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
@@ -249,7 +275,6 @@
}
}
});
- mOpenCloseAnimator.play(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION: REVEAL_OPEN_DURATION);
mOpenCloseAnimator.start();
}
@@ -257,10 +282,11 @@
private void closeComplete() {
mIsOpen = false;
mActivity.getDragLayer().removeView(this);
+ mActivity.getRootView().getViewTreeObserver().removeOnScrollChangedListener(this);
}
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
- float radius = Themes.getDialogCornerRadius(getContext());
+ float radius = TaskCornerRadius.get(mContext);
Rect fromRect = new Rect(0, 0, getWidth(), 0);
Rect toRect = new Rect(0, 0, getWidth(), getHeight());
return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 4c21745..685f725 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -28,8 +28,6 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -46,10 +44,10 @@
import android.view.View;
import androidx.annotation.RequiresApi;
+import androidx.core.graphics.ColorUtils;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -67,10 +65,6 @@
* A task in the Recents view.
*/
public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
-
- private static final ColorMatrix COLOR_MATRIX = new ColorMatrix();
- private static final ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
-
private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
new MainThreadInitializedObject<>(FullscreenDrawParams::new);
@@ -89,11 +83,11 @@
private final BaseActivity mActivity;
private TaskOverlay mOverlay;
- private final boolean mIsDarkTextTheme;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mClearPaint = new Paint();
private final Paint mDimmingPaintAfterClearing = new Paint();
+ private final int mDimColor;
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
private final Rect mPreviewRect = new Rect();
@@ -104,9 +98,8 @@
private ThumbnailData mThumbnailData;
protected BitmapShader mBitmapShader;
- private float mDimAlpha = 1f;
- private float mDimAlphaMultiplier = 1f;
- private float mSaturation = 1f;
+ /** How much this thumbnail is dimmed, 0 not dimmed at all, 1 totally dimmed. */
+ private float mDimAlpha = 0f;
private boolean mOverlayEnabled;
private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
@@ -124,11 +117,12 @@
mPaint.setFilterBitmap(true);
mBackgroundPaint.setColor(Color.WHITE);
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- mDimmingPaintAfterClearing.setColor(Color.BLACK);
mActivity = BaseActivity.fromContext(context);
- mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
// Initialize with placeholder value. It is overridden later by TaskView
mFullscreenParams = TEMP_PARAMS.get(context);
+
+ mDimColor = Themes.getColorBackgroundFloating(context);
+ mDimmingPaintAfterClearing.setColor(mDimColor);
}
/**
@@ -186,15 +180,12 @@
updateThumbnailPaintFilter();
}
- public void setDimAlphaMultipler(float dimAlphaMultipler) {
- mDimAlphaMultiplier = dimAlphaMultipler;
- setDimAlpha(mDimAlpha);
- }
-
/**
* Sets the alpha of the dim layer on top of this view.
* <p>
- * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be black.
+ * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be the
+ * extracted background color.
+ *
*/
public void setDimAlpha(float dimAlpha) {
mDimAlpha = dimAlpha;
@@ -212,13 +203,6 @@
return mDimAlpha;
}
- public Rect getInsets(Rect fallback) {
- if (mThumbnailData != null) {
- return mThumbnailData.insets;
- }
- return fallback;
- }
-
/**
* Get the scaled insets that are being used to draw the task view. This is a subsection of
* the full snapshot.
@@ -230,6 +214,10 @@
return Insets.NONE;
}
+ if (!TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ return Insets.NONE;
+ }
+
RectF bitmapRect = new RectF(
0, 0,
mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
@@ -362,15 +350,15 @@
}
private void updateThumbnailPaintFilter() {
- int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255);
- ColorFilter filter = getColorFilter(mul, mIsDarkTextTheme, mSaturation);
+ ColorFilter filter = getColorFilter(mDimAlpha);
mBackgroundPaint.setColorFilter(filter);
- mDimmingPaintAfterClearing.setAlpha(255 - mul);
+ int alpha = (int) (mDimAlpha * 255);
+ mDimmingPaintAfterClearing.setAlpha(alpha);
if (mBitmapShader != null) {
mPaint.setColorFilter(filter);
} else {
mPaint.setColorFilter(null);
- mPaint.setColor(Color.argb(255, mul, mul, mul));
+ mPaint.setColor(ColorUtils.blendARGB(Color.BLACK, mDimColor, alpha));
}
invalidate();
}
@@ -382,9 +370,10 @@
mThumbnailData.thumbnail.getHeight());
int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
.getRecentsActivityRotation();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
- currentRotation);
+ currentRotation, isRtl);
mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
mPaint.setShader(mBitmapShader);
@@ -403,35 +392,8 @@
updateThumbnailMatrix();
}
- /**
- * @param intensity multiplier for color values. 0 - make black (white if shouldLighten), 255 -
- * leave unchanged.
- */
- private static ColorFilter getColorFilter(int intensity, boolean shouldLighten,
- float saturation) {
- intensity = Utilities.boundToRange(intensity, 0, 255);
-
- if (intensity == 255 && saturation == 1) {
- return null;
- }
-
- final float intensityScale = intensity / 255f;
- COLOR_MATRIX.setScale(intensityScale, intensityScale, intensityScale, 1);
-
- if (saturation != 1) {
- SATURATION_COLOR_MATRIX.setSaturation(saturation);
- COLOR_MATRIX.postConcat(SATURATION_COLOR_MATRIX);
- }
-
- if (shouldLighten) {
- final float[] colorArray = COLOR_MATRIX.getArray();
- final int colorAdd = 255 - intensity;
- colorArray[4] = colorAdd;
- colorArray[9] = colorAdd;
- colorArray[14] = colorAdd;
- }
-
- return new ColorMatrixColorFilter(COLOR_MATRIX);
+ private ColorFilter getColorFilter(float dimAmount) {
+ return Utilities.makeColorTintingColorFilter(mDimColor, dimAmount);
}
public Bitmap getThumbnail() {
@@ -442,13 +404,14 @@
}
/**
- * Returns whether the snapshot is real.
+ * Returns whether the snapshot is real. If the device is locked for the user of the task,
+ * the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
*/
public boolean isRealSnapshot() {
if (mThumbnailData == null) {
return false;
}
- return mThumbnailData.isRealSnapshot;
+ return mThumbnailData.isRealSnapshot && !mTask.isLocked;
}
/**
@@ -459,7 +422,6 @@
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
private final RectF mClippedInsets = new RectF();
private final Matrix mMatrix = new Matrix();
- private float mClipBottom = -1;
private boolean mIsOrientationChanged;
public Matrix getMatrix() {
@@ -470,13 +432,15 @@
* Updates the matrix based on the provided parameters
*/
public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
- int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
+ int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
+ boolean isRtl) {
boolean isRotated = false;
boolean isOrientationDifferent;
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
- RectF thumbnailClipHint = new RectF(thumbnailData.insets);
+ RectF thumbnailClipHint = TaskView.CLIP_STATUS_AND_NAV_BARS
+ ? new RectF(thumbnailData.insets) : new RectF();
float scale = thumbnailData.scale;
final float thumbnailScale;
@@ -503,6 +467,17 @@
float availableHeight = surfaceHeight
- (thumbnailClipHint.top + thumbnailClipHint.bottom);
+ if (isRotated) {
+ float canvasAspect = canvasWidth / (float) canvasHeight;
+ float availableAspect = availableHeight / availableWidth;
+ // Do not rotate thumbnail if it would not improve fit
+ if (Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+ availableAspect, 0.1f)) {
+ isRotated = false;
+ isOrientationDifferent = false;
+ }
+ }
+
final float targetW, targetH;
if (isOrientationDifferent) {
targetW = canvasHeight;
@@ -538,21 +513,21 @@
}
}
- // Update the clip hints
- float halfExtraW = (availableWidth - croppedWidth) / 2;
- thumbnailClipHint.left += halfExtraW;
- thumbnailClipHint.right += halfExtraW;
- if (thumbnailClipHint.left < 0) {
- thumbnailClipHint.right += thumbnailClipHint.left;
- thumbnailClipHint.left = 0;
- } else if (thumbnailClipHint.right < 0) {
- thumbnailClipHint.left += thumbnailClipHint.right;
- thumbnailClipHint.right = 0;
+ // Update the clip hints. Align to 0,0, crop the remaining.
+ if (isRtl) {
+ thumbnailClipHint.left += availableWidth - croppedWidth;
+ if (thumbnailClipHint.right < 0) {
+ thumbnailClipHint.left += thumbnailClipHint.right;
+ thumbnailClipHint.right = 0;
+ }
+ } else {
+ thumbnailClipHint.right += availableWidth - croppedWidth;
+ if (thumbnailClipHint.left < 0) {
+ thumbnailClipHint.right += thumbnailClipHint.left;
+ thumbnailClipHint.left = 0;
+ }
}
-
- float halfExtraH = (availableHeight - croppedHeight) / 2;
- thumbnailClipHint.top += halfExtraH;
- thumbnailClipHint.bottom += halfExtraH;
+ thumbnailClipHint.bottom += availableHeight - croppedHeight;
if (thumbnailClipHint.top < 0) {
thumbnailClipHint.bottom += thumbnailClipHint.top;
thumbnailClipHint.top = 0;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 88545c6..8c71ab3 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -27,7 +27,9 @@
import static android.view.Surface.ROTATION_90;
import static android.widget.Toast.LENGTH_SHORT;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -37,9 +39,12 @@
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -52,7 +57,6 @@
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;
@@ -66,6 +70,10 @@
import android.widget.FrameLayout;
import android.widget.Toast;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
@@ -79,11 +87,16 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
@@ -92,14 +105,13 @@
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
-import com.android.quickstep.views.RecentsView.PageCallbacks;
-import com.android.quickstep.views.RecentsView.ScrollState;
import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
+import java.lang.annotation.Retention;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -107,16 +119,32 @@
/**
* A task in the Recents view.
*/
-public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
+public class TaskView extends FrameLayout implements Reusable {
private static final String TAG = TaskView.class.getSimpleName();
+ public static final int FLAG_UPDATE_ICON = 1;
+ public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
+
+ public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL;
+
/**
- * The alpha of a black scrim on a page in the carousel as it leaves the screen.
- * In the resting position of the carousel, the adjacent pages have about half this scrim.
+ * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
+ * granularity on which components of this task require an update
*/
+ @Retention(SOURCE)
+ @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
+ public @interface TaskDataChanges {}
+
+ /** 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 status and navigation bars in recents. When this
+ * is false the overview shows the whole screen scaled down instead.
+ */
+ public static final boolean CLIP_STATUS_AND_NAV_BARS = false;
+
private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
@@ -225,18 +253,16 @@
}
};
- private final OnAttachStateChangeListener mTaskMenuStateListener =
- new OnAttachStateChangeListener() {
+ private static final FloatProperty<TaskView> COLOR_TINT =
+ new FloatProperty<TaskView>("colorTint") {
@Override
- public void onViewAttachedToWindow(View view) {
+ public void setValue(TaskView taskView, float v) {
+ taskView.setColorTint(v);
}
@Override
- public void onViewDetachedFromWindow(View view) {
- if (mMenuView != null) {
- mMenuView.removeOnAttachStateChangeListener(this);
- mMenuView = null;
- }
+ public Float get(TaskView taskView) {
+ return taskView.getColorTint();
}
};
@@ -244,13 +270,11 @@
private Task mTask;
private TaskThumbnailView mSnapshotView;
- private TaskMenuView mMenuView;
private IconView mIconView;
private final DigitalWellBeingToast mDigitalWellBeingToast;
private float mFullscreenProgress;
private float mGridProgress;
private float mFullscreenScale = 1;
- private float mGridScale = 1;
private final FullscreenDrawParams mCurrentFullscreenParams;
private final StatefulActivity mActivity;
@@ -263,14 +287,10 @@
private float mTaskResistanceTranslationY;
// The following translation variables should only be used in the same orientation as Launcher.
private float mFullscreenTranslationX;
- private float mAccumulatedFullscreenTranslationX;
private float mBoxTranslationY;
// The following grid translations scales with mGridProgress.
private float mGridTranslationX;
private float mGridTranslationY;
- // Offset translation does not affect scroll calculation.
- private float mGridOffsetTranslationX;
- private float mNonRtlVisibleOffset;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
@@ -287,10 +307,14 @@
private boolean mEndQuickswitchCuj;
private View mContextualChipWrapper;
- private View mContextualChip;
private final float[] mIconCenterCoords = new float[2];
private final float[] mChipCenterCoords = new float[2];
+ // Colored tint for the task view and all its supplementary views (like the task icon and well
+ // being banner.
+ private final int mTintingColor;
+ private float mTintAmount;
+
private boolean mIsClickableAsLiveTile = true;
public TaskView(Context context) {
@@ -304,48 +328,16 @@
public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivity = StatefulActivity.fromContext(context);
- setOnClickListener((view) -> {
- if (getTask() == null) {
- return;
- }
- if (LIVE_TILE.get() && isRunningTask()) {
- if (!mIsClickableAsLiveTile) {
- return;
- }
-
- mIsClickableAsLiveTile = false;
- RecentsView recentsView = getRecentsView();
- RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
- recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
-
- AnimatorSet anim = new AnimatorSet();
- TaskViewUtils.composeRecentsLaunchAnimator(
- anim, this, targets.apps,
- targets.wallpapers, true /* launcherClosing */,
- mActivity.getStateManager(), recentsView,
- recentsView.getDepthController());
- anim.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animator) {
- recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
- recentsView.finishRecentsAnimation(false, null);
- mIsClickableAsLiveTile = true;
- }
- });
- anim.start();
- } else {
- launchTask(true /* animate */);
- }
- mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
- .log(LAUNCHER_TASK_LAUNCH_TAP);
- });
+ setOnClickListener(this::onClick);
mCurrentFullscreenParams = new FullscreenDrawParams(context);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
- mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
+ mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams,
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
setOutlineProvider(mOutlineProvider);
+
+ mTintingColor = Themes.getColorBackgroundFloating(context);
}
/**
@@ -431,19 +423,15 @@
}
mModalness = modalness;
mIconView.setAlpha(comp(modalness));
- if (mContextualChip != null) {
- mContextualChip.setScaleX(comp(modalness));
- mContextualChip.setScaleY(comp(modalness));
+ if (mContextualChipWrapper != null) {
+ mContextualChipWrapper.setScaleX(comp(modalness));
+ mContextualChipWrapper.setScaleY(comp(modalness));
}
mDigitalWellBeingToast.updateBannerOffset(modalness,
mCurrentFullscreenParams.mCurrentDrawnInsets.top
+ mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
}
- public TaskMenuView getMenuView() {
- return mMenuView;
- }
-
public DigitalWellBeingToast getDigitalWellBeingToast() {
return mDigitalWellBeingToast;
}
@@ -477,73 +465,126 @@
return mIconView;
}
- public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
- return getRecentsView().createTaskLaunchAnimation(
- this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR)
- .createPlaybackController();
- }
-
- public void launchTask(boolean animate) {
- launchTask(animate, false /* freezeTaskList */);
- }
-
- public void launchTask(boolean animate, boolean freezeTaskList) {
- launchTask(animate, freezeTaskList, (result) -> {
- if (!result) {
- notifyTaskLaunchFailed(TAG);
+ private void onClick(View view) {
+ if (getTask() == null) {
+ return;
+ }
+ if (LIVE_TILE.get() && isRunningTask()) {
+ if (!mIsClickableAsLiveTile) {
+ return;
}
- }, getHandler());
+
+ // Reset the minimized state since we force-toggled the minimized state when entering
+ // overview, but never actually finished the recents animation
+ SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
+ if (p != null) {
+ p.setSplitScreenMinimized(false);
+ }
+
+ mIsClickableAsLiveTile = false;
+ RecentsView recentsView = getRecentsView();
+ RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
+ recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
+
+ AnimatorSet anim = new AnimatorSet();
+ TaskViewUtils.composeRecentsLaunchAnimator(
+ anim, this, targets.apps,
+ targets.wallpapers, targets.nonApps, true /* launcherClosing */,
+ mActivity.getStateManager(), recentsView,
+ recentsView.getDepthController());
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
+ recentsView.finishRecentsAnimation(false, null);
+ mIsClickableAsLiveTile = true;
+ }
+ });
+ anim.start();
+ } else {
+ if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // User tapped to select second split screen app
+ getRecentsView().confirmSplitSelect(this);
+ } else {
+ launchTaskAnimated();
+ }
+ }
+ mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
+ .log(LAUNCHER_TASK_LAUNCH_TAP);
}
- public void launchTask(boolean animate, Consumer<Boolean> resultCallback,
- Handler resultCallbackHandler) {
- launchTask(animate, false /* freezeTaskList */, resultCallback, resultCallbackHandler);
- }
-
- public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
- Handler resultCallbackHandler) {
+ /**
+ * Starts the task associated with this view and animates the startup.
+ * @return CompletionStage to indicate the animation completion or null if the launch failed.
+ */
+ public RunnableList launchTaskAnimated() {
if (mTask != null) {
- final ActivityOptions opts;
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
- if (animate) {
- opts = mActivity.getActivityLaunchOptions(this);
- if (freezeTaskList) {
- ActivityOptionsCompat.setFreezeRecentTasksList(opts);
- }
- ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
- opts, resultCallback, resultCallbackHandler);
+ ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this);
+ if (ActivityManagerWrapper.getInstance()
+ .startActivityFromRecents(mTask.key, opts.options)) {
+ return opts.onEndCallback;
} else {
- opts = ActivityOptionsCompat.makeCustomAnimation(getContext(), 0, 0, () -> {
- if (resultCallback != null) {
- // Only post the animation start after the system has indicated that the
- // transition has started
- resultCallbackHandler.post(() -> resultCallback.accept(true));
- }
- }, resultCallbackHandler);
- if (freezeTaskList) {
- ActivityOptionsCompat.setFreezeRecentTasksList(opts);
- }
- UI_HELPER_EXECUTOR.execute(
- () -> ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
- mTask.key,
- opts,
- (success) -> {
- if (resultCallback != null && !success) {
- // If the call to start activity failed, then post the
- // result
- // immediately, otherwise, wait for the animation start
- // callback
- // from the activity options above
- resultCallbackHandler.post(
- () -> resultCallback.accept(false));
- }
- }, resultCallbackHandler));
+ notifyTaskLaunchFailed(TAG);
+ return null;
}
+ } else {
+ return null;
}
}
+ /**
+ * Starts the task associated with this view without any animation
+ */
+ public void launchTask(@NonNull Consumer<Boolean> callback) {
+ launchTask(callback, false /* freezeTaskList */);
+ }
+
+ /**
+ * Starts the task associated with this view without any animation
+ */
+ public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+ if (mTask != null) {
+ TestLogging.recordEvent(
+ 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());
+ if (freezeTaskList) {
+ ActivityOptionsCompat.setFreezeRecentTasksList(opts);
+ }
+ Task.TaskKey key = mTask.key;
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
+ // If the call to start activity failed, then post the result immediately,
+ // otherwise, wait for the animation start callback from the activity options
+ // above
+ MAIN_EXECUTOR.post(() -> {
+ notifyTaskLaunchFailed(TAG);
+ callback.accept(false);
+ });
+ }
+ });
+ } else {
+ callback.accept(false);
+ }
+ }
+
+ /**
+ * See {@link TaskDataChanges}
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
public void onTaskListVisibilityChanged(boolean visible) {
+ onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL);
+ }
+
+ /**
+ * See {@link TaskDataChanges}
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
+ public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {
if (mTask == null) {
return;
}
@@ -554,22 +595,37 @@
RecentsModel model = RecentsModel.INSTANCE.get(getContext());
TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
TaskIconCache iconCache = model.getIconCache();
- mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
- mTask, thumbnail -> mSnapshotView.setThumbnail(mTask, thumbnail));
- mIconLoadRequest = iconCache.updateIconInBackground(mTask,
- (task) -> {
- setIcon(task.icon);
- mDigitalWellBeingToast.initialize(mTask);
- });
+
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
+ mTask, thumbnail -> {
+ mSnapshotView.setThumbnail(mTask, thumbnail);
+ });
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ mIconLoadRequest = iconCache.updateIconInBackground(mTask,
+ (task) -> {
+ setIcon(task.icon);
+ mDigitalWellBeingToast.initialize(mTask);
+ });
+ }
} else {
- mSnapshotView.setThumbnail(null, null);
- setIcon(null);
- // Reset the task thumbnail reference as well (it will be fetched from the cache or
- // reloaded next time we need it)
- mTask.thumbnail = null;
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mSnapshotView.setThumbnail(null, null);
+ // Reset the task thumbnail reference as well (it will be fetched from the cache or
+ // reloaded next time we need it)
+ mTask.thumbnail = null;
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ setIcon(null);
+ }
}
}
+ private boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
+ return (dataChange & flag) == flag;
+ }
+
private void cancelPendingLoadTasks() {
if (mThumbnailLoadRequest != null) {
mThumbnailLoadRequest.cancel();
@@ -582,17 +638,19 @@
}
private boolean showTaskMenu() {
+ if (getRecentsView().mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // Don't show menu when selecting second split screen app
+ return true;
+ }
+
if (!getRecentsView().isClearAllHidden()) {
getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
+ return false;
} else {
- mMenuView = TaskMenuView.showForTask(this);
mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
- if (mMenuView != null) {
- mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
- }
+ return TaskMenuView.showForTask(this);
}
- return mMenuView != null;
}
private void setIcon(Drawable icon) {
@@ -614,27 +672,27 @@
PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
- int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
- int taskIconMargin = (int) getResources().getDimension(R.dimen.task_icon_top_margin);
- int taskIconHeight = (int) getResources().getDimension(R.dimen.task_thumbnail_icon_size);
- int iconTopMargin = taskIconMargin - taskIconHeight + thumbnailPadding;
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ int taskIconMargin = deviceProfile.overviewTaskMarginPx;
+ int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
switch (orientationHandler.getRotation()) {
case ROTATION_90:
iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
- iconParams.rightMargin = -thumbnailPadding;
+ iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2;
iconParams.leftMargin = 0;
iconParams.topMargin = snapshotParams.topMargin / 2;
break;
case ROTATION_180:
iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
- iconParams.bottomMargin = -thumbnailPadding;
+ iconParams.bottomMargin = -snapshotParams.topMargin;
iconParams.leftMargin = iconParams.rightMargin = 0;
- iconParams.topMargin = iconTopMargin;
+ iconParams.topMargin = taskIconMargin;
break;
case ROTATION_270:
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
- iconParams.leftMargin = -thumbnailPadding;
+ iconParams.leftMargin = -taskIconHeight - taskIconMargin / 2;
iconParams.rightMargin = 0;
iconParams.topMargin = snapshotParams.topMargin / 2;
break;
@@ -642,15 +700,15 @@
default:
iconParams.gravity = TOP | CENTER_HORIZONTAL;
iconParams.leftMargin = iconParams.rightMargin = 0;
- iconParams.topMargin = iconTopMargin;
+ iconParams.topMargin = taskIconMargin;
break;
}
+ mSnapshotView.setLayoutParams(snapshotParams);
+ iconParams.width = iconParams.height = taskIconHeight;
mIconView.setLayoutParams(iconParams);
mIconView.setRotation(orientationHandler.getDegreesRotated());
-
- if (mMenuView != null) {
- mMenuView.onRotationChanged();
- }
+ snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ mSnapshotView.setLayoutParams(snapshotParams);
}
private void setIconAndDimTransitionProgress(float progress, boolean invert) {
@@ -658,7 +716,6 @@
progress = 1 - progress;
}
mFocusTransitionProgress = progress;
- mSnapshotView.setDimAlphaMultipler(0);
float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
float lowerClamp = invert ? 1f - iconScalePercentage : 0;
float upperClamp = invert ? 1 : iconScalePercentage;
@@ -666,10 +723,10 @@
.getInterpolation(progress);
mIconView.setScaleX(scale);
mIconView.setScaleY(scale);
- if (mContextualChip != null && mContextualChipWrapper != null) {
+ if (mContextualChipWrapper != null && mContextualChipWrapper != null) {
mContextualChipWrapper.setAlpha(scale);
- mContextualChip.setScaleX(scale);
- mContextualChip.setScaleY(scale);
+ mContextualChipWrapper.setScaleX(Math.min(scale, comp(mModalness)));
+ mContextualChipWrapper.setScaleY(Math.min(scale, comp(mModalness)));
}
mDigitalWellBeingToast.updateBannerOffset(1f - scale,
mCurrentFullscreenParams.mCurrentDrawnInsets.top
@@ -717,6 +774,7 @@
setTranslationZ(0);
setAlpha(mStableAlpha);
setIconScaleAndDim(1);
+ setColorTint(0);
}
public void setStableAlpha(float parentAlpha) {
@@ -726,9 +784,7 @@
@Override
public void onRecycle() {
- mFullscreenTranslationX = mAccumulatedFullscreenTranslationX = mGridTranslationX =
- mGridTranslationY =
- mGridOffsetTranslationX = mBoxTranslationY = mNonRtlVisibleOffset = 0f;
+ mFullscreenTranslationX = mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
resetViewTransforms();
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
@@ -737,27 +793,6 @@
onTaskListVisibilityChanged(false);
}
- @Override
- public void onPageScroll(ScrollState scrollState) {
- // Don't do anything if it's modal.
- if (mModalness > 0) {
- return;
- }
-
- float dwbBannerAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation,
- 0f, 1f);
- mDigitalWellBeingToast.updateBannerAlpha(dwbBannerAlpha);
-
- if (mMenuView != null) {
- PagedOrientationHandler pagedOrientationHandler = getPagedOrientationHandler();
- RecentsView recentsView = getRecentsView();
- mMenuView.setPosition(getX() - recentsView.getScrollX(),
- getY() - recentsView.getScrollY(), pagedOrientationHandler);
- mMenuView.setScaleX(getScaleX());
- mMenuView.setScaleY(getScaleY());
- }
- }
-
/**
* Sets the contextual chip.
*
@@ -769,20 +804,18 @@
}
if (view != null) {
mContextualChipWrapper = view;
- LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams layoutParams = new LayoutParams(((View) getParent()).getMeasuredWidth(),
LayoutParams.WRAP_CONTENT);
layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
int expectedChipHeight = getExpectedViewHeight(view);
float chipOffset = getResources().getDimension(R.dimen.chip_hint_vertical_offset);
layoutParams.bottomMargin = -expectedChipHeight - (int) chipOffset;
- mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
- mContextualChip.setScaleX(0f);
- mContextualChip.setScaleY(0f);
+ mContextualChipWrapper.setScaleX(0f);
+ mContextualChipWrapper.setScaleY(0f);
addView(view, getChildCount(), layoutParams);
- if (mContextualChip != null) {
- mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
- }
if (mContextualChipWrapper != null) {
+ float scale = comp(mModalness);
+ mContextualChipWrapper.animate().scaleX(scale).scaleY(scale).setDuration(50);
mChipTouchDelegate = new TransformingTouchDelegate(mContextualChipWrapper);
}
}
@@ -803,7 +836,6 @@
}
View oldContextualChipWrapper = mContextualChipWrapper;
mContextualChipWrapper = null;
- mContextualChip = null;
mChipTouchDelegate = null;
return oldContextualChipWrapper;
}
@@ -812,7 +844,7 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
- setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? (right - left) : 0);
+ setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left);
setPivotY(mSnapshotView.getTop());
} else {
setPivotX((right - left) * 0.5f);
@@ -840,9 +872,8 @@
applyScale();
}
- public void setGridScale(float gridScale) {
- mGridScale = gridScale;
- applyScale();
+ public float getFullscreenScale() {
+ return mFullscreenScale;
}
/**
@@ -861,8 +892,6 @@
float scale = 1;
float fullScreenProgress = EXAGGERATED_EASE.getInterpolation(mFullscreenProgress);
scale *= Utilities.mapRange(fullScreenProgress, 1f, mFullscreenScale);
- float gridProgress = ACCEL_DEACCEL.getInterpolation(mGridProgress);
- scale *= Utilities.mapRange(gridProgress, 1f, mGridScale);
setScaleX(scale);
setScaleY(scale);
}
@@ -897,20 +926,11 @@
applyTranslationY();
}
- private void setFullscreenTranslationX(float fullscreenTranslationX) {
+ public void setFullscreenTranslationX(float fullscreenTranslationX) {
mFullscreenTranslationX = fullscreenTranslationX;
applyTranslationX();
}
- public float getFullscreenTranslationX() {
- return mFullscreenTranslationX;
- }
-
- public void setAccumulatedFullscreenTranslationX(float accumulatedFullscreenTranslationX) {
- mAccumulatedFullscreenTranslationX = accumulatedFullscreenTranslationX;
- applyTranslationX();
- }
-
public void setGridTranslationX(float gridTranslationX) {
mGridTranslationX = gridTranslationX;
applyTranslationX();
@@ -929,19 +949,10 @@
return mGridTranslationY;
}
- public void setGridOffsetTranslationX(float gridOffsetTranslationX) {
- mGridOffsetTranslationX = gridOffsetTranslationX;
- applyTranslationX();
- }
-
- public void setNonRtlVisibleOffset(float nonRtlVisibleOffset) {
- mNonRtlVisibleOffset = nonRtlVisibleOffset;
- }
-
public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
if (fullscreenEnabled) {
- scrollAdjustment += mFullscreenTranslationX + mAccumulatedFullscreenTranslationX;
+ scrollAdjustment += mFullscreenTranslationX;
}
if (gridEnabled) {
scrollAdjustment += mGridTranslationX;
@@ -950,21 +961,14 @@
}
public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
- float offsetAdjustment = getScrollAdjustment(fullscreenEnabled, gridEnabled);
- if (gridEnabled) {
- offsetAdjustment += mGridOffsetTranslationX + mNonRtlVisibleOffset;
- }
- return offsetAdjustment;
+ return getScrollAdjustment(fullscreenEnabled, gridEnabled);
}
- public float getSizeAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
+ public float getSizeAdjustment(boolean fullscreenEnabled) {
float sizeAdjustment = 1;
if (fullscreenEnabled) {
sizeAdjustment *= mFullscreenScale;
}
- if (gridEnabled) {
- sizeAdjustment *= mGridScale;
- }
return sizeAdjustment;
}
@@ -975,8 +979,8 @@
private void applyTranslationX() {
setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
- + getFullscreenTrans(mFullscreenTranslationX + mAccumulatedFullscreenTranslationX)
- + getGridTrans(mGridTranslationX + mGridOffsetTranslationX));
+ + getFullscreenTrans(mFullscreenTranslationX)
+ + getGridTrans(mGridTranslationX));
}
private void applyTranslationY() {
@@ -990,12 +994,12 @@
return Utilities.mapRange(progress, 0, endTranslation);
}
- public FloatProperty<TaskView> getFillDismissGapTranslationProperty() {
+ public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
- public FloatProperty<TaskView> getDismissTaskTranslationProperty() {
+ public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() {
return getPagedOrientationHandler().getSecondaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
@@ -1026,17 +1030,17 @@
private static final class TaskOutlineProvider extends ViewOutlineProvider {
- private final int mMarginTop;
+ private int mMarginTop;
private FullscreenDrawParams mFullscreenParams;
- TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
- mMarginTop = context.getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_top_margin);
+ TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin) {
+ mMarginTop = topMargin;
mFullscreenParams = fullscreenParams;
}
- public void setFullscreenParams(FullscreenDrawParams params) {
+ public void updateParams(FullscreenDrawParams params, int topMargin) {
mFullscreenParams = params;
+ mMarginTop = topMargin;
}
@Override
@@ -1073,7 +1077,8 @@
getContext().getText(R.string.accessibility_close)));
final Context context = getContext();
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile())) {
info.addAction(s.createAccessibilityAction(context));
}
@@ -1105,7 +1110,8 @@
return true;
}
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile())) {
if (s.hasHandlerForAction(action)) {
s.onClick(this);
return true;
@@ -1123,7 +1129,7 @@
return getRecentsView().mOrientationState.getOrientationHandler();
}
- public void notifyTaskLaunchFailed(String tag) {
+ private void notifyTaskLaunchFailed(String tag) {
String msg = "Failed to launch task";
if (mTask != null) {
msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
@@ -1157,7 +1163,9 @@
}
thumbnail.setFullscreenParams(mCurrentFullscreenParams);
- mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
+ mOutlineProvider.updateParams(
+ mCurrentFullscreenParams,
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
invalidateOutline();
}
@@ -1178,66 +1186,77 @@
*/
void updateTaskSize() {
ViewGroup.LayoutParams params = getLayoutParams();
+ float fullscreenScale;
+ float boxTranslationY;
+ int expectedWidth;
+ int expectedHeight;
if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
- final int thumbnailPadding = (int) getResources().getDimension(
- R.dimen.task_thumbnail_top_margin);
+ final int thumbnailPadding =
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
+ final int taskWidth = lastComputedTaskSize.width();
+ final int taskHeight = lastComputedTaskSize.height();
- Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
- int taskWidth = lastComputedTaskSize.width();
- int taskHeight = lastComputedTaskSize.height();
-
- int expectedWidth;
- int expectedHeight;
- float thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio() : 0f;
- if (isRunningTask() || thumbnailRatio == 0f) {
- expectedWidth = taskWidth;
- expectedHeight = taskHeight + thumbnailPadding;
+ int boxWidth;
+ int boxHeight;
+ float thumbnailRatio;
+ boolean isFocusedTask = isFocusedTask();
+ if (isFocusedTask || isRunningTask()) {
+ // Task will be focused and should use focused task size. Use runningTaskRatio
+ // that is associated with the original orientation of the focused task if possible.
+ boxWidth = taskWidth;
+ boxHeight = taskHeight;
+ thumbnailRatio = isFocusedTask ? getRecentsView().getFocusedTaskRatio() : 0;
} else {
- int boxLength = Math.max(taskWidth, taskHeight);
- if (thumbnailRatio > 1) {
- expectedWidth = boxLength;
- expectedHeight = (int) (boxLength / thumbnailRatio) + thumbnailPadding;
- } else {
- expectedWidth = (int) (boxLength * thumbnailRatio);
- expectedHeight = boxLength + thumbnailPadding;
- }
+ // Otherwise task is in grid, and should use lastComputedGridTaskSize.
+ Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
+ boxWidth = lastComputedGridTaskSize.width();
+ boxHeight = lastComputedGridTaskSize.height();
+ thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio(
+ TaskView.CLIP_STATUS_AND_NAV_BARS) : 0f;
+ }
+ int boxLength = Math.max(boxWidth, boxHeight);
+
+ // Bound width/height to the box size.
+ if (thumbnailRatio == 0f) {
+ expectedWidth = boxWidth;
+ expectedHeight = boxHeight + thumbnailPadding;
+ } else if (thumbnailRatio > 1) {
+ expectedWidth = boxLength;
+ expectedHeight = (int) (boxLength / thumbnailRatio) + thumbnailPadding;
+ } else {
+ expectedWidth = (int) (boxLength * thumbnailRatio);
+ expectedHeight = boxLength + thumbnailPadding;
}
- float heightDiff = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
- setBoxTranslationY(heightDiff);
+ // Scale to to fit task Rect.
+ fullscreenScale = taskWidth / (float) boxWidth;
- float fullscreenScale = 1f;
- if (expectedWidth > taskWidth) {
- // In full screen, expectedWidth should not be larger than taskWidth.
- fullscreenScale = taskWidth / (float) expectedWidth;
- } else if (expectedHeight - thumbnailPadding > taskHeight) {
- // In full screen, expectedHeight should not be larger than taskHeight.
- fullscreenScale = taskHeight / (float) (expectedHeight - thumbnailPadding);
+ // In full screen, scale back TaskView to original size.
+ if (expectedWidth > boxWidth) {
+ fullscreenScale *= boxWidth / (float) expectedWidth;
+ } else if (expectedHeight - thumbnailPadding > boxHeight) {
+ fullscreenScale *= boxHeight / (float) (expectedHeight - thumbnailPadding);
}
- setFullscreenScale(fullscreenScale);
- float widthDiff = params.width * (1 - mFullscreenScale);
- setFullscreenTranslationX(
- getLayoutDirection() == LAYOUT_DIRECTION_RTL ? -widthDiff : widthDiff);
-
- if (params.width != expectedWidth || params.height != expectedHeight) {
- params.width = expectedWidth;
- params.height = expectedHeight;
- setLayoutParams(params);
- }
+ // Align to top of task Rect.
+ boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
} else {
- setBoxTranslationY(0);
- setFullscreenTranslationX(0);
- setFullscreenScale(1);
- if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
- params.width = ViewGroup.LayoutParams.MATCH_PARENT;
- params.height = ViewGroup.LayoutParams.MATCH_PARENT;
- setLayoutParams(params);
- }
+ fullscreenScale = 1f;
+ boxTranslationY = 0f;
+ expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT;
+ expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ setFullscreenScale(fullscreenScale);
+ setBoxTranslationY(boxTranslationY);
+ if (params.width != expectedWidth || params.height != expectedHeight) {
+ params.width = expectedWidth;
+ params.height = expectedHeight;
+ setLayoutParams(params);
}
}
-
private float getFullscreenTrans(float endTranslation) {
float progress = ACCEL_DEACCEL.getInterpolation(mFullscreenProgress);
return Utilities.mapRange(progress, 0, endTranslation);
@@ -1250,6 +1269,13 @@
return this == getRecentsView().getRunningTaskView();
}
+ public boolean isFocusedTask() {
+ if (getRecentsView() == null) {
+ return false;
+ }
+ return this == getRecentsView().getFocusedTaskView();
+ }
+
public void setShowScreenshot(boolean showScreenshot) {
mShowScreenshot = showScreenshot;
}
@@ -1265,6 +1291,31 @@
mSnapshotView.setOverlayEnabled(overlayEnabled);
}
+ public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
+ AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU);
+ getRecentsView().initiateSplitSelect(this, splitPositionOption);
+ }
+
+ private void setColorTint(float amount) {
+ mSnapshotView.setDimAlpha(amount);
+ mIconView.setIconColorTint(mTintingColor, amount);
+ mDigitalWellBeingToast.setBannerColorTint(mTintingColor, amount);
+ }
+
+ private float getColorTint() {
+ return mTintAmount;
+ }
+
+ /**
+ * Show the task view with a color tint (animates value).
+ */
+ public void showColorTint(boolean enable) {
+ ObjectAnimator tintAnimator = ObjectAnimator.ofFloat(
+ this, COLOR_TINT, enable ? MAX_PAGE_SCRIM_ALPHA : 0);
+ tintAnimator.setAutoCancel(true);
+ tintAnimator.start();
+ }
+
/**
* We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
*/
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index a412b39..4f27e21 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -16,7 +16,15 @@
package com.android.quickstep;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.quickstep.views.RecentsView;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
@@ -31,4 +39,49 @@
outerRule(new NavigationModeSwitchRule(mLauncher)).
around(super.getRulesInsideActivityMonitor());
}
+
+ @Override
+ protected void onLauncherActivityClose(Launcher launcher) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ if (recentsView != null) {
+ recentsView.finishRecentsAnimation(true, null);
+ }
+ }
+
+ @Override
+ protected void checkLauncherState(Launcher launcher, ContainerType expectedContainerType,
+ boolean isResumed, boolean isStarted) {
+ if (!isInLiveTileMode(launcher, expectedContainerType)) {
+ super.checkLauncherState(launcher, expectedContainerType, isResumed, isStarted);
+ } else {
+ assertTrue("[Live Tile] hasBeenResumed() == isStarted(), hasBeenResumed(): "
+ + isResumed, isResumed != isStarted);
+ }
+ }
+
+ @Override
+ protected void checkLauncherStateInOverview(Launcher launcher,
+ ContainerType expectedContainerType, boolean isStarted, boolean isResumed) {
+ if (!isInLiveTileMode(launcher, expectedContainerType)) {
+ super.checkLauncherStateInOverview(launcher, expectedContainerType, isStarted,
+ isResumed);
+ } else {
+ assertTrue(
+ "[Live Tile] Launcher is not started or has been resumed in state: "
+ + expectedContainerType,
+ isStarted && !isResumed);
+ }
+ }
+
+ private boolean isInLiveTileMode(Launcher launcher,
+ LauncherInstrumentation.ContainerType expectedContainerType) {
+ if (!LIVE_TILE.get()
+ || expectedContainerType != LauncherInstrumentation.ContainerType.OVERVIEW) {
+ return false;
+ }
+
+ RecentsView recentsView = launcher.getOverviewPanel();
+ return recentsView.getSizeStrategy().isInLiveTileMode()
+ && recentsView.getRunningTaskId() != -1;
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 9732cdc..6e19436 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -100,6 +100,7 @@
// The test action.
mLauncher.getBackground().switchToOverview();
}
+ closeLauncherActivity();
mLauncher.pressHome();
}
}
diff --git a/res/color/overview_button.xml b/res/color/overview_button.xml
index 6ac36bf..aa48b78 100644
--- a/res/color/overview_button.xml
+++ b/res/color/overview_button.xml
@@ -2,10 +2,10 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:alpha="1"
- android:color="?attr/workspaceTextColor"
+ android:color="?android:attr/textColorPrimary"
android:state_enabled="true" />
<item
android:alpha="?android:disabledAlpha"
- android:color="?attr/workspaceTextColor"
+ android:color="?android:attr/textColorPrimary"
android:state_enabled="false" />
</selector>
\ No newline at end of file
diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
new file mode 100644
index 0000000..04bde8f
--- /dev/null
+++ b/res/drawable/add_item_dialog_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="?android:attr/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/add_item_dialog_button_background.xml b/res/drawable/add_item_dialog_button_background.xml
new file mode 100644
index 0000000..1b4591f
--- /dev/null
+++ b/res/drawable/add_item_dialog_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<inset
+ android:insetLeft="@dimen/pin_widget_button_inset_horizontal"
+ android:insetRight="@dimen/pin_widget_button_inset_horizontal"
+ android:insetTop="@dimen/pin_widget_button_inset_vertical"
+ android:insetBottom="@dimen/pin_widget_button_inset_vertical"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:tint="?android:attr/colorAccent" android:shape="rectangle">
+ <corners android:radius="18dp" />
+ <solid android:color="#FFFFFF" />
+ <padding
+ android:left="@dimen/pin_widget_button_padding_horizontal"
+ android:top="@dimen/pin_widget_button_padding_vertical"
+ android:right="@dimen/pin_widget_button_padding_horizontal"
+ android:bottom="@dimen/pin_widget_button_padding_vertical" />
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_searchbox.xml b/res/drawable/bg_widgets_searchbox.xml
index 81dd2aa..2a50a51 100644
--- a/res/drawable/bg_widgets_searchbox.xml
+++ b/res/drawable/bg_widgets_searchbox.xml
@@ -14,6 +14,6 @@
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <solid android:color="#FFFFF7" />
+ <solid android:color="?android:attr/textColorPrimaryInverse" />
<corners android:radius="24dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/gm_edit_24.xml b/res/drawable/gm_edit_24.xml
new file mode 100644
index 0000000..59a0dc2
--- /dev/null
+++ b/res/drawable/gm_edit_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0L3,16.82L3,21h4.18L20.41,7.77c0.79,-0.78 0.79,-2.05 0,-2.83zM6.41,19.06L5,19v-1.36l9.82,-9.82 1.41,1.41 -9.82,9.83z"/>
+</vector>
diff --git a/res/drawable/ic_expand_less.xml b/res/drawable/ic_expand_less.xml
index 8360cee..cc16083 100644
--- a/res/drawable/ic_expand_less.xml
+++ b/res/drawable/ic_expand_less.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?android:attr/textColorHint">
+ android:tint="?android:attr/textColorSecondary">
<path
android:fillColor="#FF000000"
android:pathData="M18.59,16.41L20,15l-8,-8 -8,8 1.41,1.41L12,9.83"/>
diff --git a/res/drawable/ic_expand_more.xml b/res/drawable/ic_expand_more.xml
index 49e24f6..ecbce7f 100644
--- a/res/drawable/ic_expand_more.xml
+++ b/res/drawable/ic_expand_more.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?android:attr/textColorHint">
+ android:tint="?android:attr/textColorSecondary">
<path
android:fillColor="#FF000000"
android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17"/>
diff --git a/res/drawable/ic_gm_close_24.xml b/res/drawable/ic_gm_close_24.xml
new file mode 100644
index 0000000..2c9c932
--- /dev/null
+++ b/res/drawable/ic_gm_close_24.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/textColorTertiary"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_split_screen.xml b/res/drawable/ic_split_screen.xml
similarity index 100%
rename from quickstep/res/drawable/ic_split_screen.xml
rename to res/drawable/ic_split_screen.xml
diff --git a/quickstep/overview_ui_overrides/res/values/config.xml b/res/drawable/middle_item_primary.xml
similarity index 68%
rename from quickstep/overview_ui_overrides/res/values/config.xml
rename to res/drawable/middle_item_primary.xml
index 0f09439..0c04ea1 100644
--- a/quickstep/overview_ui_overrides/res/values/config.xml
+++ b/res/drawable/middle_item_primary.xml
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <string name="task_overlay_factory_class" translatable="false"/>
-</resources>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/popupColorPrimary"/>
+ <corners android:radius="@dimen/popup_smaller_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/overview_ui_overrides/res/values/config.xml b/res/drawable/single_item_primary.xml
similarity index 68%
copy from quickstep/overview_ui_overrides/res/values/config.xml
copy to res/drawable/single_item_primary.xml
index 0f09439..1c0889b 100644
--- a/quickstep/overview_ui_overrides/res/values/config.xml
+++ b/res/drawable/single_item_primary.xml
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <string name="task_overlay_factory_class" translatable="false"/>
-</resources>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/popupColorPrimary"/>
+ <corners android:radius="@dimen/popup_single_item_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/overview_ui_overrides/res/values/config.xml b/res/drawable/single_item_secondary.xml
similarity index 68%
copy from quickstep/overview_ui_overrides/res/values/config.xml
copy to res/drawable/single_item_secondary.xml
index 0f09439..4edc481 100644
--- a/quickstep/overview_ui_overrides/res/values/config.xml
+++ b/res/drawable/single_item_secondary.xml
@@ -5,7 +5,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <string name="task_overlay_factory_class" translatable="false"/>
-</resources>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/popupColorSecondary"/>
+ <corners android:radius="@dimen/popup_single_item_radius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/widget_reconfigure_button_frame.xml b/res/drawable/widget_reconfigure_button_frame.xml
new file mode 100644
index 0000000..37d93ad
--- /dev/null
+++ b/res/drawable/widget_reconfigure_button_frame.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:width="@dimen/widget_reconfigure_button_size"
+ android:height="@dimen/widget_reconfigure_button_size">
+ <shape
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorAccent" />
+ <corners android:radius="@dimen/widget_reconfigure_button_corner_radius" />
+ </shape>
+ </item>
+ <item
+ android:gravity="center"
+ android:padding="@dimen/widget_reconfigure_button_padding"
+ android:drawable="@drawable/gm_edit_24"
+ android:color="?android:attr/colorPrimary" />
+</layer-list>
diff --git a/res/drawable/widgets_list_bottom_ripple.xml b/res/drawable/widgets_list_bottom_ripple.xml
new file mode 100644
index 0000000..3a26091
--- /dev/null
+++ b/res/drawable/widgets_list_bottom_ripple.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_middle_ripple.xml b/res/drawable/widgets_list_middle_ripple.xml
new file mode 100644
index 0000000..da025d7
--- /dev/null
+++ b/res/drawable/widgets_list_middle_ripple.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_single_item_ripple.xml b/res/drawable/widgets_list_single_item_ripple.xml
new file mode 100644
index 0000000..b8b6f42
--- /dev/null
+++ b/res/drawable/widgets_list_single_item_ripple.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_top_ripple.xml b/res/drawable/widgets_list_top_ripple.xml
new file mode 100644
index 0000000..6efc3e1
--- /dev/null
+++ b/res/drawable/widgets_list_top_ripple.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index b1a1efe..d5e7333 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -17,70 +17,51 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/add_item_confirmation"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:padding="24dp"
android:orientation="vertical">
- <ScrollView
+
+ <TextView
+ style="@style/TextHeadline"
+ android:id="@+id/widget_appName"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:maxLines="1" />
+
+ <include layout="@layout/widget_cell"
+ android:id="@+id/widget_cell"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:clipToPadding="false">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="20dp"
- android:paddingLeft="24dp"
- android:paddingRight="24dp"
- android:paddingTop="4dp"
- android:text="@string/add_item_request_drag_hint" />
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?android:attr/colorPrimaryDark"
- android:theme="?attr/widgetsTheme">
-
- <com.android.launcher3.widget.WidgetCell
- android:id="@+id/widget_cell"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_weight="1"
- android:background="?android:attr/colorPrimaryDark"
- android:focusable="true"
- android:gravity="center_horizontal"
- android:orientation="vertical" >
-
- <include layout="@layout/widget_cell_content" />
-
- </com.android.launcher3.widget.WidgetCell>
- </FrameLayout>
- </LinearLayout>
- </ScrollView>
+ android:layout_marginVertical="16dp" />
<LinearLayout
- style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
- android:paddingBottom="4dp"
- android:paddingEnd="12dp"
- android:paddingStart="12dp"
- android:paddingTop="4dp" >
+ android:padding="8dp"
+ android:orientation="horizontal">
<Button
- style="?android:attr/buttonBarButtonStyle"
+ style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onCancelClick"
android:text="@android:string/cancel" />
+
+ <Space
+ android:layout_width="4dp"
+ android:layout_height="wrap_content" />
+
<Button
- style="?android:attr/buttonBarButtonStyle"
+ style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onPlaceAutomaticallyClick"
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 8ed16c7..b570464 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -40,11 +40,10 @@
<include layout="@layout/floating_header_content" />
- <include layout="@layout/personal_work_tabs" />
+ <include layout="@layout/all_apps_personal_work_tabs" />
</com.android.launcher3.allapps.FloatingHeaderView>
<include
- android:id="@id/search_container_all_apps"
layout="@layout/search_container_all_apps"/>
<include layout="@layout/all_apps_fast_scroller" />
diff --git a/res/layout/personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml
similarity index 100%
rename from res/layout/personal_work_tabs.xml
rename to res/layout/all_apps_personal_work_tabs.xml
diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml
index c684881..2accd2d 100644
--- a/res/layout/all_apps_tabs.xml
+++ b/res/layout/all_apps_tabs.xml
@@ -26,6 +26,7 @@
android:clipChildren="true"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
+ android:paddingTop="@dimen/all_apps_header_top_padding"
launcher:pageIndicator="@+id/tabs" >
<include layout="@layout/all_apps_rv_layout" />
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
index dfce946..53db5ed 100644
--- a/res/layout/app_widget_resize_frame.xml
+++ b/res/layout/app_widget_resize_frame.xml
@@ -26,6 +26,7 @@
<!-- Frame -->
<ImageView
+ android:id="@+id/widget_resize_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
@@ -34,6 +35,7 @@
<!-- Left -->
<ImageView
+ android:id="@+id/widget_resize_left_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
@@ -43,6 +45,7 @@
<!-- Top -->
<ImageView
+ android:id="@+id/widget_resize_top_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
@@ -52,6 +55,7 @@
<!-- Right -->
<ImageView
+ android:id="@+id/widget_resize_right_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
@@ -61,6 +65,7 @@
<!-- Bottom -->
<ImageView
+ android:id="@+id/widget_resize_bottom_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
@@ -68,5 +73,17 @@
android:src="@drawable/ic_widget_resize_handle"
android:tint="?android:attr/colorAccent" />
+ <ImageButton
+ android:id="@+id/widget_reconfigure_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/widget_reconfigure_button_padding"
+ android:layout_gravity="bottom|end"
+ android:layout_marginBottom="@dimen/widget_reconfigure_button_margin"
+ android:layout_marginEnd="@dimen/widget_reconfigure_button_margin"
+ android:src="@drawable/widget_reconfigure_button_frame"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone" />
+
</FrameLayout>
</com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 840a8b7..0d11b50 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -19,6 +19,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="@dimen/bg_popup_item_height"
+ android:background="@drawable/middle_item_primary"
android:theme="@style/PopupItem" >
<com.android.launcher3.shortcuts.DeepShortcutTextView
@@ -31,6 +32,8 @@
android:paddingEnd="@dimen/popup_padding_end"
android:drawableEnd="@drawable/ic_drag_handle"
android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
+ android:singleLine="true"
+ android:ellipsize="end"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
launcher:layoutHorizontal="true"
@@ -45,12 +48,4 @@
android:layout_gravity="start|center_vertical"
android:background="@drawable/ic_deepshortcut_placeholder"/>
- <View
- android:id="@+id/divider"
- android:layout_width="@dimen/deep_shortcuts_divider_width"
- android:layout_height="@dimen/popup_item_divider_height"
- android:layout_gravity="end|bottom"
- android:visibility="gone"
- android:background="?attr/popupColorTertiary" />
-
</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index 8451b77..f34e685 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -62,9 +62,12 @@
android:id="@+id/drop_target_bar"
layout="@layout/drop_target_bar" />
- <include
+ <com.android.launcher3.views.ScrimView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:id="@+id/scrim_view"
- layout="@layout/scrim_view" />
+ android:background="?attr/allAppsScrimColor"
+ android:alpha="0" />
<include
android:id="@+id/apps_view"
diff --git a/res/layout/longpress_options_menu.xml b/res/layout/longpress_options_menu.xml
index 20bb5b8..3898365 100644
--- a/res/layout/longpress_options_menu.xml
+++ b/res/layout/longpress_options_menu.xml
@@ -18,7 +18,6 @@
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="?attr/popupColorPrimary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml
index d01be01..147aa30 100644
--- a/res/layout/notification_content.xml
+++ b/res/layout/notification_content.xml
@@ -96,14 +96,6 @@
</com.android.launcher3.notification.NotificationMainView>
- <!-- Divider -->
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="@dimen/popup_item_divider_height"
- android:layout_below="@id/main_view"
- android:background="?attr/popupColorTertiary" />
-
<!-- Footer -->
<com.android.launcher3.notification.NotificationFooterLayout
android:id="@+id/footer"
diff --git a/res/layout/notification_gutter.xml b/res/layout/notification_gutter.xml
index 10e7f7d..9a3e55a 100644
--- a/res/layout/notification_gutter.xml
+++ b/res/layout/notification_gutter.xml
@@ -16,6 +16,5 @@
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="4dp"
- android:layout_marginTop="4dp"
- android:background="@drawable/bg_notification_content" />
\ No newline at end of file
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/popup_margin" />
\ No newline at end of file
diff --git a/res/layout/overview_actions_container.xml b/res/layout/overview_actions_container.xml
index 5946bf6..f152c7c 100644
--- a/res/layout/overview_actions_container.xml
+++ b/res/layout/overview_actions_container.xml
@@ -14,6 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!-- NOTE! don't add dimensions for margins / gravity to root view in this file, they need to be
+ loaded at runtime. -->
<Space
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml
index c737407..04822fd 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -19,8 +19,15 @@
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="?attr/popupColorPrimary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical" />
\ No newline at end of file
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/notification_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:background="?attr/popupColorPrimary"
+ android:orientation="vertical"/>
+</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
diff --git a/res/layout/scrim_view.xml b/res/layout/search_container_hotseat.xml
similarity index 80%
rename from res/layout/scrim_view.xml
rename to res/layout/search_container_hotseat.xml
index a604d56..8f12ca0 100644
--- a/res/layout/scrim_view.xml
+++ b/res/layout/search_container_hotseat.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!--
+ Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.views.ScrimView
+<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/scrim_view" />
\ No newline at end of file
+ android:layout_height="0dp" />
\ No newline at end of file
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index c620e2a..9f45f30 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -19,6 +19,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="@dimen/bg_popup_item_height"
+ android:background="@drawable/middle_item_primary"
android:theme="@style/PopupItem" >
<com.android.launcher3.BubbleTextView
@@ -30,7 +31,8 @@
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
android:paddingEnd="@dimen/popup_padding_end"
android:textSize="14sp"
- android:maxLines="1"
+ android:singleLine="true"
+ android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
@@ -44,12 +46,4 @@
android:layout_gravity="start|center_vertical"
android:backgroundTint="?android:attr/textColorTertiary"/>
- <View
- android:id="@+id/divider"
- android:layout_width="@dimen/deep_shortcuts_divider_width"
- android:layout_height="@dimen/popup_item_divider_height"
- android:layout_gravity="end|bottom"
- android:visibility="gone"
- android:background="?attr/popupColorTertiary" />
-
</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
index a340f4f..f992248 100644
--- a/res/layout/system_shortcut_icons.xml
+++ b/res/layout/system_shortcut_icons.xml
@@ -21,7 +21,7 @@
android:layout_height="@dimen/system_shortcut_header_height"
android:orientation="horizontal"
android:gravity="end|center_vertical"
- android:background="?attr/popupColorSecondary"
+ android:background="@drawable/single_item_secondary"
android:clipToPadding="true">
<Space android:layout_width="0dp"
diff --git a/res/layout/scrim_view.xml b/res/layout/taskbar_view.xml
similarity index 76%
copy from res/layout/scrim_view.xml
copy to res/layout/taskbar_view.xml
index a604d56..96ae43d 100644
--- a/res/layout/scrim_view.xml
+++ b/res/layout/taskbar_view.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!--
+ Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.views.ScrimView
+<Space
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/scrim_view" />
\ No newline at end of file
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:visibility="gone" />
\ No newline at end of file
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index d38a77a..8b18857 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -19,7 +19,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/round_rect_folder"
- android:elevation="5dp"
android:orientation="vertical" >
<com.android.launcher3.folder.FolderPagedView
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 65a49ab..0f6fc6c 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -17,14 +17,31 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <!-- The image of the widget. This view does not support padding. Any placement adjustment
- should be done using margins. -->
- <com.android.launcher3.widget.WidgetImageView
- android:id="@+id/widget_preview"
- android:layout_width="wrap_content"
+ <com.android.launcher3.widget.WidgetCellPreview
+ android:id="@+id/widget_preview_container"
+ android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
- android:layout_marginVertical="8dp" />
+ android:importantForAccessibility="no"
+ android:layout_marginVertical="8dp">
+ <!-- The image of the widget. This view does not support padding. Any placement adjustment
+ should be done using margins. Width & height are set at runtime after scaling the
+ preview image. -->
+ <com.android.launcher3.widget.WidgetImageView
+ android:id="@+id/widget_preview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:layout_gravity="fill"/>
+
+ <ImageView
+ android:id="@+id/widget_badge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="no"
+ android:layout_gravity="end|bottom"
+ android:layout_margin="@dimen/profile_badge_margin"/>
+ </com.android.launcher3.widget.WidgetCellPreview>
<!-- The name of the widget. -->
<TextView
@@ -36,28 +53,29 @@
android:gravity="center_horizontal"
android:singleLine="true"
android:maxLines="1"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="14sp" />
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/widget_cell_font_size" />
- <!-- The original dimensions of the widget (can't be the same text as above due to different
- style. -->
+ <!-- The original dimensions of the widget -->
<TextView
android:id="@+id/widget_dims"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textColor="?android:attr/textColorSecondary"
- android:textSize="14sp"
- android:alpha="0.8" />
+ android:textSize="@dimen/widget_cell_font_size"
+ android:alpha="0.7" />
<TextView
android:id="@+id/widget_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:textSize="12sp"
+ android:textSize="@dimen/widget_cell_font_size"
+ android:textColor="?android:attr/textColorSecondary"
android:maxLines="2"
android:ellipsize="end"
- android:fadingEdge="horizontal" />
+ android:fadingEdge="horizontal"
+ android:alpha="0.7" />
</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index c1b2cbf..8002d1d 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -19,41 +19,12 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingTop="28dp"
+ android:paddingTop="16dp"
android:background="@drawable/top_round_rect_primary"
android:elevation="@dimen/deep_shortcuts_elevation"
android:layout_gravity="bottom"
android:theme="?attr/widgetsTheme">
- <TextView
- style="@style/TextHeadline"
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"/>
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:paddingTop="4dp"
- android:fontFamily="sans-serif"
- android:textColor="?android:attr/textColorTertiary"
- android:textSize="14sp"
- android:text="@string/long_press_widget_to_add"/>
-
- <ScrollView
- android:id="@+id/widgets_table_scroll_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="45dp"
- android:layout_marginBottom="40dp">
- <include layout="@layout/widgets_table_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal" />
- </ScrollView>
+ <include layout="@layout/widgets_bottom_sheet_content" />
</com.android.launcher3.widget.WidgetsBottomSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
new file mode 100644
index 0000000..a9d523a
--- /dev/null
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <View
+ android:layout_width="48dp"
+ android:layout_height="2dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="16dp"
+ android:background="?android:attr/textColorSecondary"/>
+ <TextView
+ style="@style/TextHeadline"
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"/>
+
+ <ScrollView
+ android:id="@+id/widgets_table_scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fadeScrollbars="false"
+ android:layout_marginVertical="16dp">
+ <include layout="@layout/widgets_table_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal" />
+ </ScrollView>
+</merge>
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 28a8c6f..172284b 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -15,6 +15,7 @@
-->
<com.android.launcher3.widget.picker.WidgetsFullSheet
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
@@ -24,9 +25,17 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?android:attr/colorPrimary"
+ android:background="?android:attr/colorBackgroundFloating"
android:elevation="4dp">
+ <TextView
+ android:id="@+id/no_widgets_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:visibility="gone"
+ tools:text="No widgets available" />
+
<!-- Fast scroller popup -->
<TextView
android:id="@+id/fast_scroller_popup"
@@ -42,5 +51,13 @@
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/fastscroll_end_margin" />
+
+ <com.android.launcher3.widget.picker.WidgetsRecyclerView
+ android:id="@+id/search_widgets_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:clipToPadding="false" />
+
</com.android.launcher3.views.TopRoundedCornerView>
</com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index 8125db8..e3d34f6 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -16,11 +16,6 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto">
- <include layout="@layout/personal_work_tabs"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="16dp" />
-
<com.android.launcher3.workprofile.PersonalWorkPagedView
android:id="@+id/widgets_view_pager"
android:layout_width="match_parent"
@@ -43,4 +38,8 @@
</com.android.launcher3.workprofile.PersonalWorkPagedView>
+ <include layout="@layout/widgets_personal_work_tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp" />
</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_search_and_recommendations.xml b/res/layout/widgets_full_sheet_search_and_recommendations.xml
index 9a6f922..e5df175 100644
--- a/res/layout/widgets_full_sheet_search_and_recommendations.xml
+++ b/res/layout/widgets_full_sheet_search_and_recommendations.xml
@@ -18,14 +18,17 @@
android:id="@+id/search_and_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="16dp"
+ android:paddingHorizontal="16dp"
+ android:layout_marginBottom="16dp"
android:orientation="vertical">
<View
android:id="@+id/collapse_handle"
android:layout_width="48dp"
android:layout_height="2dp"
+ android:layout_marginTop="16dp"
+ android:elevation="2dp"
android:layout_gravity="center_horizontal"
- android:background="@color/popup_color_primary_dark"/>
+ android:background="?android:attr/textColorSecondary"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -33,17 +36,14 @@
android:gravity="center_horizontal"
android:textSize="24sp"
android:layout_marginTop="16dp"
+ android:textColor="?android:attr/textColorSecondary"
android:text="@string/widget_button_text"/>
- <!-- Disable the search bar because it has not been implemented. -->
- <EditText
- android:id="@+id/widgets_search_bar"
+ <include layout="@layout/widgets_search_bar"/>
+
+ <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+ android:id="@+id/recommended_widget_table"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="gone"
android:layout_marginTop="16dp"
- android:background="@drawable/bg_widgets_searchbox"
- android:drawablePadding="8dp"
- android:drawableStart="@drawable/ic_allapps_search"
- android:hint="@string/widgets_full_sheet_search_bar_hint"
- android:padding="12dp" />
+ android:visibility="gone" />
</LinearLayout>
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index 041e007..598041c 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -18,8 +18,10 @@
android:id="@+id/widgets_list_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/selectableItemBackground"
- android:paddingVertical="20dp"
+ android:layout_marginHorizontal="8dp"
+ android:background="@drawable/widgets_list_middle_ripple"
+ android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"
+ android:paddingVertical="@dimen/widget_list_header_view_vertical_padding"
android:orientation="horizontal">
<ImageView
@@ -52,6 +54,10 @@
android:id="@+id/app_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:alpha="0.7"
tools:text="m widgets, n shortcuts" />
</LinearLayout>
@@ -66,6 +72,7 @@
android:layout_gravity="center_vertical"
android:layout_alignParentEnd="true"
android:clickable="false"
+ android:importantForAccessibility="no"
android:button="@drawable/widgets_tray_expand_button"/>
</com.android.launcher3.widget.picker.WidgetsListHeader>
\ No newline at end of file
diff --git a/res/layout/widgets_personal_work_tabs.xml b/res/layout/widgets_personal_work_tabs.xml
new file mode 100644
index 0000000..3d3ae6a
--- /dev/null
+++ b/res/layout/widgets_personal_work_tabs.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+
+<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_header_tab_height"
+ android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+ android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
+ android:orientation="horizontal"
+ android:elevation="2dp"
+ style="@style/TextHeadline">
+
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/widgets_full_sheet_personal_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/widgets_full_sheet_work_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
\ No newline at end of file
diff --git a/res/layout/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
new file mode 100644
index 0000000..e3836df
--- /dev/null
+++ b/res/layout/widgets_search_bar.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widgets_search_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="16dp"
+ android:background="@drawable/bg_widgets_searchbox"
+ android:elevation="2dp">
+
+ <com.android.launcher3.ExtendedEditText
+ android:id="@+id/widgets_search_bar_edit_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="12dp"
+ android:drawablePadding="8dp"
+ android:drawableStart="@drawable/ic_allapps_search"
+ android:background="@null"
+ android:hint="@string/widgets_full_sheet_search_bar_hint"
+ android:maxLines="1"
+ android:layout_weight="1"
+ android:inputType="text"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textColorHint="?android:attr/textColorTertiary"/>
+
+ <ImageButton
+ android:id="@+id/widgets_search_cancel_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:padding="8dp"
+ android:src="@drawable/ic_gm_close_24"
+ android:background="?android:selectableItemBackground"
+ android:layout_gravity="center"
+ android:contentDescription="@string/widgets_full_sheet_cancel_button_description"
+ android:visibility="gone"/>
+</com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar>
\ No newline at end of file
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
index c4dfe7e..0b5f0b9 100644
--- a/res/layout/widgets_table_container.xml
+++ b/res/layout/widgets_table_container.xml
@@ -19,4 +19,5 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
- android:background="?android:attr/colorPrimaryDark" />
+ android:background="@drawable/widgets_list_middle_ripple"
+ android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"/>
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index 510e1f4..07a5096 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -21,6 +21,8 @@
<style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
+ <item name="android:windowBackground">@drawable/add_item_dialog_background</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
\ No newline at end of file
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index 09bdaaf..d50115b 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -1,3 +1,6 @@
<resources>
<bool name="allow_rotation">true</bool>
+
+ <!-- Hotseat -->
+ <bool name="hotseat_transpose_layout_with_orientation">false</bool>
</resources>
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
index ec07591..33fc553 100644
--- a/res/values-sw720dp/config.xml
+++ b/res/values-sw720dp/config.xml
@@ -3,7 +3,4 @@
<!-- All Apps & Widgets -->
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
<integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
-
-<!-- Hotseat -->
- <bool name="hotseat_transpose_layout_with_orientation">false</bool>
</resources>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 6baf39e..24aac10 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -17,17 +17,17 @@
*/
-->
<resources>
- <color name="popup_color_primary_light">@android:color/system_primary_50</color>
- <color name="popup_color_secondary_light">@android:color/system_primary_100</color>
- <color name="popup_color_tertiary_light">@android:color/system_primary_300</color>
- <color name="popup_color_primary_dark">@android:color/system_primary_800</color>
- <color name="popup_color_secondary_dark">@android:color/system_primary_900</color>
- <color name="popup_color_tertiary_dark">@android:color/system_primary_700</color>
+ <color name="popup_color_primary_light">@android:color/system_neutral1_50</color>
+ <color name="popup_color_secondary_light">@android:color/system_neutral2_100</color>
+ <color name="popup_color_tertiary_light">@android:color/system_neutral2_300</color>
+ <color name="popup_color_primary_dark">@android:color/system_neutral1_800</color>
+ <color name="popup_color_secondary_dark">@android:color/system_neutral1_900</color>
+ <color name="popup_color_tertiary_dark">@android:color/system_neutral2_700</color>
- <color name="workspace_text_color_light">@android:color/system_primary_50</color>
- <color name="workspace_text_color_dark">@android:color/system_primary_900</color>
+ <color name="workspace_text_color_light">@android:color/system_neutral1_50</color>
+ <color name="workspace_text_color_dark">@android:color/system_neutral1_900</color>
- <color name="text_color_primary_dark">@android:color/system_primary_50</color>
- <color name="text_color_secondary_dark">@android:color/system_primary_200</color>
- <color name="text_color_tertiary_dark">@android:color/system_primary_400</color>
+ <color name="text_color_primary_dark">@android:color/system_neutral1_50</color>
+ <color name="text_color_secondary_dark">@android:color/system_neutral2_200</color>
+ <color name="text_color_tertiary_dark">@android:color/system_neutral2_400</color>
</resources>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4078ef4..381b0fe 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -33,7 +33,6 @@
<attr name="workspaceKeyShadowColor" format="color" />
<attr name="workspaceStatusBarScrim" format="reference" />
<attr name="widgetsTheme" format="reference" />
- <attr name="loadingIconColor" format="color" />
<attr name="iconOnlyShortcutColor" format="color" />
<attr name="eduHalfSheetBGColor" format="color" />
@@ -44,7 +43,6 @@
<attr name="folderTextColor" format="color" />
<attr name="folderHintColor" format="color" />
<attr name="workProfileOverlayTextColor" format="color" />
- <attr name="disabledIconAlpha" format="float" />
<!-- BubbleTextView specific attributes. -->
<declare-styleable name="BubbleTextView">
@@ -129,11 +127,13 @@
<!-- numHotseatIcons defaults to numColumns, if not specified -->
<attr name="numHotseatIcons" format="integer" />
<attr name="dbFile" format="string" />
- <!-- numAllAppsColumns defaults to numColumns, if not specified -->
- <attr name="numAllAppsColumns" format="integer" />
<attr name="defaultLayoutId" format="reference" />
<attr name="demoModeLayoutId" format="reference" />
<attr name="isScalable" format="boolean" />
+ <attr name="devicePaddingId" format="reference" />
+
+ <!-- whether the grid option is shown to the user -->
+ <attr name="visible" format="boolean" />
</declare-styleable>
@@ -162,6 +162,8 @@
<!-- landscapeIconSize defaults to iconSize, if not specified -->
<attr name="landscapeIconSize" format="float" />
<attr name="iconTextSize" format="float" />
+ <!-- landscapeIconTextSize defaults to iconTextSize, if not specified -->
+ <attr name="landscapeIconTextSize" format="float" />
<!-- If true, this display option is used to determine the default grid -->
<attr name="canBeDefault" format="boolean" />
@@ -170,6 +172,9 @@
<attr name="allAppsIconSize" format="float" />
<!-- allAppsIconTextSize defaults to iconTextSize, if not specified -->
<attr name="allAppsIconTextSize" format="float" />
+
+ <!-- numAllAppsColumns defaults to GridDisplayOption.numColumns, if not specified -->
+ <attr name="numAllAppsColumns" format="integer" />
</declare-styleable>
<declare-styleable name="CellLayout">
diff --git a/res/values/config.xml b/res/values/config.xml
index 89415b8..57f626c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -61,7 +61,6 @@
<!-- Various classes overriden by projects/build flavors. -->
<string name="folder_name_provider_class" translatable="false"></string>
<string name="stats_log_manager_class" translatable="false"></string>
- <string name="app_transition_manager_class" translatable="false"></string>
<string name="instant_app_resolver_class" translatable="false"></string>
<string name="main_process_initializer_class" translatable="false"></string>
<string name="app_launch_tracker_class" translatable="false"></string>
@@ -88,8 +87,7 @@
<!-- Default packages -->
<string name="wallpaper_picker_package" translatable="false"></string>
- <string name="calendar_component_name" translatable="false"></string>
- <string name="clock_component_name" translatable="false"></string>
+ <string name="local_colors_extraction_class" translatable="false"></string>
<!-- Accessibility actions -->
<item type="id" name="action_remove" />
@@ -188,4 +186,8 @@
</string-array>
<string-array name="filtered_components" ></string-array>
+
+ <!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
+ <string name="color_generator_class" translatable="false"/>
+
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index da43758..51dddab 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -29,6 +29,8 @@
<dimen name="dynamic_grid_cell_layout_padding">5.5dp</dimen>
<dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
+ <dimen name="two_panel_home_side_padding">18dp</dimen>
+
<!-- Hotseat -->
<dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
<dimen name="dynamic_grid_hotseat_bottom_padding">2dp</dimen>
@@ -54,6 +56,12 @@
<dimen name="resize_frame_background_padding">24dp</dimen>
<dimen name="resize_frame_margin">22dp</dimen>
+ <!-- App widget reconfigure button -->
+ <dimen name="widget_reconfigure_button_corner_radius">14dp</dimen>
+ <dimen name="widget_reconfigure_button_padding">6dp</dimen>
+ <dimen name="widget_reconfigure_button_margin">32dp</dimen>
+ <dimen name="widget_reconfigure_button_size">36dp</dimen>
+
<!-- Fast scroll -->
<dimen name="fastscroll_track_min_width">6dp</dimen>
<dimen name="fastscroll_track_max_width">8dp</dimen>
@@ -76,6 +84,7 @@
<dimen name="fastscroll_end_margin">-26dp</dimen>
<!-- All Apps -->
+ <dimen name="all_apps_open_vertical_translate">96dp</dimen>
<dimen name="all_apps_search_bar_field_height">48dp</dimen>
<dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
<dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
@@ -107,6 +116,14 @@
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
<dimen name="widget_cell_horizontal_padding">16dp</dimen>
+ <dimen name="widget_cell_font_size">14sp</dimen>
+
+
+ <dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
+ <dimen name="widget_list_content_corner_radius">4dp</dimen>
+
+ <dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
+ <dimen name="widget_list_entry_bottom_margin">2dp</dimen>
<dimen name="widget_preview_shadow_blur">0.5dp</dimen>
<dimen name="widget_preview_key_shadow_distance">1dp</dimen>
@@ -128,6 +145,12 @@
<dimen name="shortcut_preview_padding_right">0dp</dimen>
<dimen name="shortcut_preview_padding_top">0dp</dimen>
+<!-- Pin widget dialog -->
+ <dimen name="pin_widget_button_padding_horizontal">8dp</dimen>
+ <dimen name="pin_widget_button_padding_vertical">4dp</dimen>
+ <dimen name="pin_widget_button_inset_horizontal">4dp</dimen>
+ <dimen name="pin_widget_button_inset_vertical">6dp</dimen>
+
<!-- Dragging -->
<!-- Drag padding to add to the bottom of drop targets -->
<dimen name="drop_target_drag_padding">14dp</dimen>
@@ -174,16 +197,17 @@
<dimen name="pending_widget_elevation">2dp</dimen>
<!-- Deep shortcuts -->
- <dimen name="deep_shortcuts_elevation">9dp</dimen>
- <!-- also update deep_shortcuts_divider_width -->
- <dimen name="bg_popup_item_width">234dp</dimen>
+ <dimen name="deep_shortcuts_elevation">0dp</dimen>
+ <dimen name="bg_popup_item_width">216dp</dimen>
<dimen name="bg_popup_item_height">56dp</dimen>
- <dimen name="bg_popup_item_condensed_height">48dp</dimen>
<dimen name="pre_drag_view_scale">6dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
- <dimen name="deep_shortcut_icon_size">36dp</dimen>
- <dimen name="deep_shortcut_drawable_padding">8dp</dimen>
+ <dimen name="deep_shortcut_icon_size">32dp</dimen>
+ <dimen name="popup_margin">2dp</dimen>
+ <dimen name="popup_single_item_radius">100dp</dimen>
+ <dimen name="popup_smaller_radius">4dp</dimen>
+ <dimen name="deep_shortcut_drawable_padding">12dp</dimen>
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
<dimen name="popup_padding_start">10dp</dimen>
<dimen name="popup_padding_end">16dp</dimen>
@@ -192,16 +216,14 @@
<dimen name="popup_arrow_height">10dp</dimen>
<dimen name="popup_arrow_vertical_offset">-1dp</dimen>
<!-- popup_padding_start + deep_shortcut_icon_size / 2 -->
- <dimen name="popup_arrow_horizontal_center_offset">28dp</dimen>
+ <dimen name="popup_arrow_horizontal_center_offset">26dp</dimen>
<dimen name="popup_arrow_corner_radius">2dp</dimen>
<!-- popup_padding_start + icon_size + 10dp -->
- <dimen name="deep_shortcuts_text_padding_start">56dp</dimen>
- <!-- popup_item_width - deep_shortcuts_text_padding_start -->
- <dimen name="deep_shortcuts_divider_width">178dp</dimen>
+ <dimen name="deep_shortcuts_text_padding_start">52dp</dimen>
<dimen name="system_shortcut_icon_size">24dp</dimen>
- <!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
+ <!-- popup_arrow_horizontal_center_offset - system_shortcut_icon_size / 2 -->
<dimen name="system_shortcut_margin_start">16dp</dimen>
- <dimen name="system_shortcut_header_height">48dp</dimen>
+ <dimen name="system_shortcut_header_height">56dp</dimen>
<dimen name="system_shortcut_header_icon_touch_size">48dp</dimen>
<!-- (touch_size - icon_size) / 2 -->
<dimen name="system_shortcut_header_icon_padding">12dp</dimen>
@@ -268,4 +290,12 @@
<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="taskbar_size">0dp</dimen>
+ <!-- Size of the maximum radius for the enforced rounded rectangles. -->
+ <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
+
+<!-- Overview placeholder to compile in Launcer3 without Quickstep -->
+ <dimen name="task_thumbnail_icon_size">0dp</dimen>
+ <dimen name="task_thumbnail_icon_size_grid">0dp</dimen>
+ <dimen name="overview_task_margin">0dp</dimen>
+
</resources>
diff --git a/res/values/id.xml b/res/values/id.xml
new file mode 100644
index 0000000..39c49bd
--- /dev/null
+++ b/res/values/id.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <item type="id" name="view_type_widgets_list" />
+ <item type="id" name="view_type_widgets_header" />
+ <item type="id" name="view_type_widgets_search_header" />
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 44b5ee7..986180c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,11 +38,18 @@
<!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
<string name="home_screen">Home</string>
+ <!-- Options for recent tasks -->
+ <!-- Title for an option to enter split screen mode for a given app -->
+ <string name="recent_task_option_split_screen">Split screen</string>
+ <string translatable="false" name="split_screen_position_top">Pin to top</string>
+ <string translatable="false" name="split_screen_position_left">Pin to left</string>
+ <string translatable="false" name="split_screen_position_right">Pin to right</string>
+
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
- <string name="long_press_widget_to_add">Touch & hold to pick up a widget.</string>
+ <string name="long_press_widget_to_add">Touch & hold to move a widget.</string>
<!-- Accessibility spoken hint message in widget picker, which allows user to add a widget. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
- <string name="long_accessible_way_to_add">Double-tap & hold to pick up a widget or use custom actions.</string>
+ <string name="long_accessible_way_to_add">Double-tap & hold to move a widget or use custom actions.</string>
<!-- The format string for the dimensions of a widget in the drawer -->
<!-- There is a special version of this format string for Farsi -->
<string name="widget_dims_format">%1$d \u00d7 %2$d</string>
@@ -74,12 +81,23 @@
<!-- Search bar text shown in the popup view showing all available widgets installed on the
device. [CHAR_LIMIT=50] -->
<string name="widgets_full_sheet_search_bar_hint">Search</string>
+ <!-- Spoken text for screen readers. This text lets a user know that the button is used to clear
+ the text that the user entered in the search box. [CHAR_LIMIT=none] -->
+ <string name="widgets_full_sheet_cancel_button_description">Clear text from search box</string>
+ <!-- Text shown when there is no widgets shown in the popup view showing all available widgets
+ installed on the device. [CHAR_LIMIT=none] -->
+ <string name="no_widgets_available">No widgets available</string>
+ <!-- Text shown when there are no matching widget search results for user's search query.
+ [CHAR_LIMIT=none] -->
+ <string name="no_search_results">No search results</string>
+ <!-- Tab label. A user can tap this tab to access their personal widgets. [CHAR_LIMIT=25] -->
+ <string name="widgets_full_sheet_personal_tab">Personal</string>
+ <!-- Tab label. A user can tap this tab to access their work widgets. [CHAR_LIMIT=25] -->
+ <string name="widgets_full_sheet_work_tab">Work</string>
<!-- All Apps -->
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
<string name="all_apps_search_bar_hint">Search apps</string>
- <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
- <string name="all_apps_on_device_search_bar_hint">Search this phone and more…</string>
<!-- Loading apps text. [CHAR_LIMIT=50] -->
<string name="all_apps_loading_message">Loading apps…</string>
<!-- No-search-results text. [CHAR_LIMIT=50] -->
@@ -101,9 +119,9 @@
<!-- Drag and drop -->
<!-- Message to tell the user to press and hold on a shortcut to add it [CHAR_LIMIT=50] -->
- <string name="long_press_shortcut_to_add">Touch & hold to pick up a shortcut.</string>
+ <string name="long_press_shortcut_to_add">Touch & hold to move a shortcut.</string>
<!-- Accessibility spoken hint message in deep shortcut menu, which allows user to add a shortcut. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=200] -->
- <string name="long_accessible_way_to_add_shortcut">Double-tap & hold to pick up a shortcut or use custom actions.</string>
+ <string name="long_accessible_way_to_add_shortcut">Double-tap & hold to move a shortcut or use custom actions.</string>
<skip />
<!-- Error message when user has filled a home screen -->
@@ -258,8 +276,6 @@
<string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
<!-- Strings for widgets & more in the popup container/bottom sheet -->
- <!-- Title for a bottom sheet that shows widgets for a particular app -->
- <string name="widgets_bottom_sheet_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> widgets</string>
<!-- Accessibility title for the popup containing a list of widgets. [CHAR_LIMIT=50] -->
<string name="widgets_list">Widgets list</string>
@@ -368,9 +384,18 @@
<!--- heading shown when user opens work apps tab while work apps are paused -->
<string name="work_apps_paused_title">Work profile is paused</string>
<!--- body shown when user opens work apps tab while work apps are paused -->
- <string name="work_apps_paused_body">Work apps can\'t send you notifications, use your battery, or access your location</string>
+ <string name="work_apps_paused_body">Work apps can’t send you notifications, use your battery, or access your location</string>
<!-- content description for paused work apps list -->
- <string name="work_apps_paused_content_description">Work profile is paused. Work apps can\’t send you notifications, use your battery, or access your location</string>
+ <string name="work_apps_paused_content_description">Work profile is paused. Work apps can’t send you notifications, use your battery, or access your location</string>
+ <!-- string shown in educational banner about work profile -->
+ <string name="work_apps_paused_edu_banner">Work apps are badged and visible to your IT admin</string>
+ <!-- button string shown to dismiss work tab education -->
+ <string name="work_apps_paused_edu_accept">Got it</string>
+
+ <!-- button string shown pause work profile -->
+ <string name="work_apps_pause_btn_text">Pause work apps</string>
+ <!-- button string shown enable work profile -->
+ <string name="work_apps_enable_btn_text">Turn on</string>
<!-- A hint shown in launcher settings develop options filter box -->
<string name="developer_options_filter_hint">Filter</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index adc2238..fd77b80 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -145,10 +145,12 @@
<style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
+ <item name="android:windowBackground">@drawable/add_item_dialog_background</item>
+ <item name="android:windowNoTitle">true</item>
</style>
<style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
- <item name="android:navigationBarColor">@android:color/transparent</item>
+ <item name="android:navigationBarColor">?android:colorPrimaryDark</item>
<item name="preferenceTheme">@style/HomeSettingsPreferenceTheme</item>
</style>
@@ -281,4 +283,8 @@
<item name="android:colorControlHighlight">#DFE1E5</item>
<item name="android:colorForeground">@color/all_apps_bg_hand_fill_dark</item>
</style>
+
+ <style name="Widget.DeviceDefault.Button.Rounded.Colored" parent="@android:style/Widget.DeviceDefault.Button.Colored">
+ <item name="android:background">@drawable/add_item_dialog_button_background</item>
+ </style>
</resources>
diff --git a/res/xml/size_limits.xml b/res/xml/size_limits_80x104.xml
similarity index 95%
rename from res/xml/size_limits.xml
rename to res/xml/size_limits_80x104.xml
index ba57014..f375549 100644
--- a/res/xml/size_limits.xml
+++ b/res/xml/size_limits_80x104.xml
@@ -36,11 +36,11 @@
launcher:a="0"
launcher:b="16dp"/>
<workspaceBottomPadding
- launcher:a="0.50"
+ launcher:a="0.56"
launcher:b="0"
- launcher:c="-16dp"/>
+ launcher:c="16dp"/>
<hotseatBottomPadding
- launcher:a="0.50"
+ launcher:a="0.44"
launcher:b="0"
launcher:c="16dp"/>
</device-padding>
diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp
index c738df9..bf32362 100644
--- a/robolectric_tests/Android.bp
+++ b/robolectric_tests/Android.bp
@@ -16,31 +16,43 @@
// Launcher Robolectric test target.
//
// "robolectric_android-all-stub", not needed, we write our own stubs
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
+filegroup {
+ name: "launcher3-robolectric-resources",
+ path: "resources",
+ srcs: ["resources/*"],
+}
+
+filegroup {
+ name: "launcher3-robolectric-src",
+ srcs: ["src/**/*.java"],
+}
+
android_robolectric_test {
name: "LauncherRoboTests",
srcs: [
- "src/**/*.java",
+ ":launcher3-robolectric-src",
+ ":launcher3-test-src-common",
],
- java_resource_dirs: [
- "resources",
- "res",
- "config",
- ],
+ java_resources: [":launcher3-robolectric-resources"],
static_libs: [
"truth-prebuilt",
- "Launcher3TestCommon",
"androidx.test.runner",
"androidx.test.rules",
"mockito-robolectric-prebuilt",
],
- //robolectric_prebuilt_version: "4.4",
- libs: [
- "platform-robolectric-4.4-prebuilt",
- ],
+ robolectric_prebuilt_version: "4.5.1",
instrumentation_for: "Launcher3",
test_options: {
timeout: 36000,
},
}
-
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/resources/robolectric.properties
similarity index 98%
rename from robolectric_tests/config/robolectric.properties
rename to robolectric_tests/resources/robolectric.properties
index 1b170e1..abb6968 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/resources/robolectric.properties
@@ -1,4 +1,4 @@
-sdk=29
+sdk=30
shadows= \
com.android.launcher3.shadows.LShadowAppPredictionManager \
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
index 34cb2ad..4d151f1 100644
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -54,6 +54,7 @@
*/
@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
+@org.junit.Ignore
public class LauncherUIScrollTest {
private Context mTargetContext;
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
index b972c6f..cc36f63 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
@@ -221,6 +221,27 @@
assertThat(currentList).containsExactlyElementsIn(newList);
}
+ @Test
+ public void headersContentsMix_headerWidgetsModified_shouldInvokeCorrectCallbacks() {
+ // GIVEN the current list has app headers [A, B, E content].
+ ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
+ List.of(mHeaderA, mHeaderB, mContentE));
+ // GIVEN the new list has one of the headers widgets list modified.
+ List<WidgetsListBaseEntry> newList = List.of(
+ new WidgetsListHeaderEntry(
+ mHeaderA.mPkgItem, mHeaderA.mTitleSectionName,
+ mHeaderA.mWidgets.subList(0, 1)),
+ mHeaderB, mContentE);
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+ // THEN notify "A" has been changed.
+ verify(mAdapter).notifyItemChanged(/* position= */ 0);
+ // THEN the current list contains all elements from the new list.
+ assertThat(currentList).containsExactlyElementsIn(newList);
+ }
+
private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
int numOfWidgets) {
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index a7c8d92..6b5678c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -26,6 +26,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
+import android.os.Process;
+import android.os.UserHandle;
import android.view.LayoutInflater;
import androidx.recyclerview.widget.RecyclerView;
@@ -37,6 +39,7 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -67,6 +70,7 @@
private WidgetsListAdapter mAdapter;
private InvariantDeviceProfile mTestProfile;
+ private UserHandle mUserHandle;
private Context mContext;
@Before
@@ -76,8 +80,9 @@
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
+ mUserHandle = Process.myUserHandle();
mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
- mIconCache, null, null);
+ mIconCache, null, null, null);
mAdapter.registerAdapterDataObserver(mListener);
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
@@ -126,7 +131,8 @@
mAdapter.setWidgets(generateSampleMap(3));
// WHEN com.google.test.1 header is expanded.
- mAdapter.onHeaderClicked(/* isExpanded= */ true, TEST_PACKAGE_PLACEHOLDER + 1);
+ mAdapter.onHeaderClicked(/* showWidgets= */ true,
+ new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
// THEN the visible entries list becomes:
// [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
@@ -143,7 +149,8 @@
// GIVEN test com.google.test1 is expanded.
// Visible entries in the adapter are:
// [com.google.test0, com.google.test1, com.google.test1 content]
- mAdapter.onHeaderClicked(/* isExpanded= */ true, TEST_PACKAGE_PLACEHOLDER + 1);
+ mAdapter.onHeaderClicked(/* showWidgets= */ true,
+ new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
Mockito.reset(mListener);
// WHEN the adapter is updated with the same list of apps but com.google.test1 has 2 widgets
@@ -200,6 +207,30 @@
verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
}
+ @Test
+ public void setWidgetsOnSearch_expandedApp_shouldResetExpandedApp() {
+ // GIVEN a list of widgets entries:
+ // [com.google.test0, com.google.test0 content,
+ // com.google.test1, com.google.test1 content,
+ // com.google.test2, com.google.test2 content]
+ // The visible widgets entries: [com.google.test0, com.google.test1, com.google.test2].
+ ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(2);
+ mAdapter.setWidgetsOnSearch(allEntries);
+ // GIVEN com.google.test.1 header is expanded. The visible entries list becomes:
+ // [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
+ mAdapter.onHeaderClicked(/* showWidgets= */ true,
+ new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
+ Mockito.reset(mListener);
+
+ // WHEN same widget entries are set again.
+ mAdapter.setWidgetsOnSearch(allEntries);
+
+ // THEN expanded app is reset and the visible entries list becomes:
+ // [com.google.test0, com.google.test1, com.google.test2]
+ verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
+ verify(mListener).onItemRangeRemoved(/* positionStart= */ 2, /* itemCount= */ 1);
+ }
+
/**
* Generates a list of sample widget entries.
*
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 848630e..12a092d 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -18,7 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
@@ -26,25 +28,22 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
-import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListHeaderViewHolderBinder.OnHeaderClickListener;
import org.junit.After;
import org.junit.Before;
@@ -74,12 +73,15 @@
// testing.
private ActivityController<TestActivity> mActivityController;
private TestActivity mTestActivity;
- private FakeOnHeaderClickListener mFakeOnHeaderClickListener = new FakeOnHeaderClickListener();
@Mock
private IconCache mIconCache;
@Mock
private DeviceProfile mDeviceProfile;
+ @Mock
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+ @Mock
+ private OnHeaderClickListener mOnHeaderClickListener;
@Before
public void setUp() {
@@ -98,9 +100,15 @@
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
+ WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
LayoutInflater.from(mTestActivity),
- mFakeOnHeaderClickListener);
+ mWidgetPreviewLoader,
+ mIconCache,
+ /* iconClickListener= */ view -> {},
+ /* iconLongClickListener= */ view -> false,
+ /* searchBarUIHelper= */ null);
+ mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
+ LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
}
@After
@@ -117,7 +125,7 @@
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -125,6 +133,23 @@
assertThat(appSubtitle.getText()).isEqualTo("3 widgets");
}
+ @Test
+ public void bindViewHolder_shouldAttachOnHeaderClickListener() {
+ WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+ new FrameLayout(mTestActivity));
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ WidgetsListHeaderEntry entry = generateSampleAppHeader(
+ APP_NAME,
+ TEST_PACKAGE,
+ /* numOfWidgets= */ 3);
+
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ widgetsListHeader.callOnClick();
+
+ verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
+ eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
+ }
+
private WidgetsListHeaderEntry generateSampleAppHeader(String appName, String packageName,
int numOfWidgets) {
PackageItemInfo appInfo = new PackageItemInfo(packageName);
@@ -152,22 +177,4 @@
}
return widgetItems;
}
-
- private void assertWidgetCellWithLabel(View view, String label) {
- assertThat(view).isInstanceOf(WidgetCell.class);
- TextView widgetLabel = (TextView) view.findViewById(R.id.widget_name);
- assertThat(widgetLabel.getText()).isEqualTo(label);
- }
-
- private final class FakeOnHeaderClickListener implements OnHeaderClickListener {
-
- boolean mShowWidgets = false;
- @Nullable String mHeaderClickedPackage = null;
-
- @Override
- public void onHeaderClicked(boolean showWidgets, String packageName) {
- mShowWidgets = showWidgets;
- mHeaderClickedPackage = packageName;
- }
- }
}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
new file mode 100644
index 0000000..e090341
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.testing.TestActivity;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsListSearchHeaderViewHolderBinderTest {
+ private static final String TEST_PACKAGE = "com.google.test";
+ private static final String APP_NAME = "Test app";
+
+ private Context mContext;
+ private WidgetsListSearchHeaderViewHolderBinder mViewHolderBinder;
+ private InvariantDeviceProfile mTestProfile;
+ // Replace ActivityController with ActivityScenario, which is the recommended way for activity
+ // testing.
+ private ActivityController<TestActivity> mActivityController;
+ private TestActivity mTestActivity;
+
+ @Mock
+ private IconCache mIconCache;
+ @Mock
+ private DeviceProfile mDeviceProfile;
+ @Mock
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+ @Mock
+ private OnHeaderClickListener mOnHeaderClickListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+
+ mActivityController = Robolectric.buildActivity(TestActivity.class);
+ mTestActivity = mActivityController.setup().get();
+ mTestActivity.setDeviceProfile(mDeviceProfile);
+
+ doAnswer(invocation -> {
+ ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ return componentWithLabel.getComponent().getShortClassName();
+ }).when(mIconCache).getTitleNoCache(any());
+
+ WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
+ LayoutInflater.from(mTestActivity),
+ mWidgetPreviewLoader,
+ mIconCache,
+ /* iconClickListener= */ view -> {},
+ /* iconLongClickListener= */ view -> false,
+ /* searchBarUIHelper= */ null);
+ mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
+ LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
+ }
+
+ @After
+ public void tearDown() {
+ mActivityController.destroy();
+ }
+
+ @Test
+ public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
+ WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+ new FrameLayout(mTestActivity));
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
+ APP_NAME,
+ TEST_PACKAGE,
+ /* numOfWidgets= */ 3);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+
+ TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
+ TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
+ assertThat(appTitle.getText()).isEqualTo(APP_NAME);
+ assertThat(appSubtitle.getText())
+ .isEqualTo(".SampleWidget0, .SampleWidget1, .SampleWidget2");
+ }
+
+ @Test
+ public void bindViewHolder_shouldAttachOnHeaderClickListener() {
+ WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+ new FrameLayout(mTestActivity));
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
+ APP_NAME,
+ TEST_PACKAGE,
+ /* numOfWidgets= */ 3);
+
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ widgetsListHeader.callOnClick();
+
+ verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
+ eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
+ }
+
+ private WidgetsListSearchHeaderEntry generateSampleSearchHeader(String appName,
+ String packageName, int numOfWidgets) {
+ PackageItemInfo appInfo = new PackageItemInfo(packageName);
+ appInfo.title = appName;
+ appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+
+ return new WidgetsListSearchHeaderEntry(appInfo,
+ /* titleSectionName= */ "",
+ generateWidgetItems(packageName, numOfWidgets));
+ }
+
+ private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
+ ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+ ArrayList<WidgetItem> widgetItems = new ArrayList<>();
+ for (int i = 0; i < numOfWidgets; i++) {
+ ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
+ AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+ widgetInfo.provider = cn;
+ ReflectionHelpers.setField(widgetInfo, "providerInfo",
+ packageManager.addReceiverIfNotPresent(cn));
+
+ widgetItems.add(new WidgetItem(
+ LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
+ mTestProfile, mIconCache));
+ }
+ return widgetItems;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 8a0cf34..0935d1c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -106,12 +106,20 @@
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
+ WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
+ LayoutInflater.from(mTestActivity),
+ mWidgetPreviewLoader,
+ mIconCache,
+ /* iconClickListener= */ view -> {},
+ /* iconLongClickListener= */ view -> false,
+ /* searchBarUIHelper= */ null);
mViewHolderBinder = new WidgetsListTableViewHolderBinder(
mContext,
LayoutInflater.from(mTestActivity),
mOnIconClickListener,
mOnLongClickListener,
- mWidgetPreviewLoader);
+ mWidgetPreviewLoader,
+ widgetsListAdapter);
}
@After
@@ -127,7 +135,7 @@
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
shadowOf(getMainLooper()).idle();
// THEN the table container has one row, which contains 3 widgets.
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
new file mode 100644
index 0000000..36b6f01
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class SimpleWidgetsSearchAlgorithmTest {
+
+ @Mock private IconCache mIconCache;
+
+ private InvariantDeviceProfile mTestProfile;
+ private WidgetsListHeaderEntry mCalendarHeaderEntry;
+ private WidgetsListContentEntry mCalendarContentEntry;
+ private WidgetsListHeaderEntry mCameraHeaderEntry;
+ private WidgetsListContentEntry mCameraContentEntry;
+ private WidgetsListHeaderEntry mClockHeaderEntry;
+ private WidgetsListContentEntry mClockContentEntry;
+ private Context mContext;
+
+ private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm;
+ @Mock
+ private PopupDataProvider mDataProvider;
+ @Mock
+ private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ return componentWithLabel.getComponent().getShortClassName();
+ }).when(mIconCache).getTitleNoCache(any());
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+ mContext = RuntimeEnvironment.application;
+
+ mCalendarHeaderEntry =
+ createWidgetsHeaderEntry("com.example.android.Calendar", "Calendar", 2);
+ mCalendarContentEntry =
+ createWidgetsContentEntry("com.example.android.Calendar", "Calendar", 2);
+ mCameraHeaderEntry = createWidgetsHeaderEntry("com.example.android.Camera", "Camera", 11);
+ mCameraContentEntry = createWidgetsContentEntry("com.example.android.Camera", "Camera", 11);
+ mClockHeaderEntry = createWidgetsHeaderEntry("com.example.android.Clock", "Clock", 3);
+ mClockContentEntry = createWidgetsContentEntry("com.example.android.Clock", "Clock", 3);
+
+
+ mSimpleWidgetsSearchAlgorithm = new SimpleWidgetsSearchAlgorithm(mDataProvider);
+ doReturn(Collections.EMPTY_LIST).when(mDataProvider).getAllWidgets();
+ }
+
+ @Test
+ public void filter_shouldMatchOnAppName() {
+ doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+ mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
+ .when(mDataProvider)
+ .getAllWidgets();
+
+ assertEquals(List.of(
+ new WidgetsListSearchHeaderEntry(
+ mCalendarHeaderEntry.mPkgItem,
+ mCalendarHeaderEntry.mTitleSectionName,
+ mCalendarHeaderEntry.mWidgets),
+ mCalendarContentEntry,
+ new WidgetsListSearchHeaderEntry(
+ mCameraHeaderEntry.mPkgItem,
+ mCameraHeaderEntry.mTitleSectionName,
+ mCameraHeaderEntry.mWidgets),
+ mCameraContentEntry),
+ SimpleWidgetsSearchAlgorithm.getFilteredWidgets(mDataProvider, "Ca"));
+ }
+
+ @Test
+ public void filter_shouldMatchOnWidgetLabel() {
+ doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+ mCameraContentEntry))
+ .when(mDataProvider)
+ .getAllWidgets();
+
+ assertEquals(List.of(
+ new WidgetsListSearchHeaderEntry(
+ mCalendarHeaderEntry.mPkgItem,
+ mCalendarHeaderEntry.mTitleSectionName,
+ mCalendarHeaderEntry.mWidgets.subList(1, 2)),
+ new WidgetsListContentEntry(
+ mCalendarHeaderEntry.mPkgItem,
+ mCalendarHeaderEntry.mTitleSectionName,
+ mCalendarHeaderEntry.mWidgets.subList(1, 2)),
+ new WidgetsListSearchHeaderEntry(
+ mCameraHeaderEntry.mPkgItem,
+ mCameraHeaderEntry.mTitleSectionName,
+ mCameraHeaderEntry.mWidgets.subList(1, 3)),
+ new WidgetsListContentEntry(
+ mCameraHeaderEntry.mPkgItem,
+ mCameraHeaderEntry.mTitleSectionName,
+ mCameraHeaderEntry.mWidgets.subList(1, 3))),
+ SimpleWidgetsSearchAlgorithm.getFilteredWidgets(mDataProvider, "Widget1"));
+ }
+
+ @Test
+ public void doSearch_shouldInformCallback() {
+ doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+ mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
+ .when(mDataProvider)
+ .getAllWidgets();
+ mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback);
+ shadowOf(getMainLooper()).idle();
+ verify(mSearchCallback).onSearchResult(
+ matches("Ca"), argThat(a -> a != null && !a.isEmpty()));
+ }
+
+ private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
+ int numOfWidgets) {
+ List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+ PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+ widgetItems.get(0).user);
+
+ return new WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+ }
+
+ private WidgetsListContentEntry createWidgetsContentEntry(String packageName, String appName,
+ int numOfWidgets) {
+ List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+ PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+ widgetItems.get(0).user);
+
+ return new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+ }
+
+ private PackageItemInfo createPackageItemInfo(String packageName, String appName,
+ UserHandle userHandle) {
+ PackageItemInfo pInfo = new PackageItemInfo(packageName);
+ pInfo.title = appName;
+ pInfo.user = userHandle;
+ pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+ return pInfo;
+ }
+
+ private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
+ ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+ ArrayList<WidgetItem> widgetItems = new ArrayList<>();
+ for (int i = 0; i < numOfWidgets; i++) {
+ ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
+ AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+ widgetInfo.provider = cn;
+ ReflectionHelpers.setField(widgetInfo, "providerInfo",
+ packageManager.addReceiverIfNotPresent(cn));
+
+ WidgetItem widgetItem = new WidgetItem(
+ LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
+ mTestProfile, mIconCache);
+ widgetItems.add(widgetItem);
+ }
+ return widgetItems;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
new file mode 100644
index 0000000..4e6f17c
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class WidgetsSearchBarControllerTest {
+
+ private WidgetsSearchBarController mController;
+ private Context mContext;
+ private ExtendedEditText mEditText;
+ private ImageButton mCancelButton;
+ @Mock
+ private SearchModeListener mSearchModeListener;
+ @Mock
+ private SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mEditText = new ExtendedEditText(mContext);
+ mCancelButton = new ImageButton(mContext);
+ mController = new WidgetsSearchBarController(
+ mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener);
+ }
+
+ @Test
+ public void onSearchResult_shouldInformSearchModeListener() {
+ ArrayList<WidgetsListBaseEntry> entries = new ArrayList<>();
+ mController.onSearchResult("abc", entries);
+
+ verify(mSearchModeListener).onSearchResults(entries);
+ }
+
+ @Test
+ public void afterTextChanged_shouldInformSearchModeListenerToEnterSearch() {
+ mEditText.setText("abc");
+
+ verify(mSearchModeListener).enterSearchMode();
+ verifyNoMoreInteractions(mSearchModeListener);
+ }
+
+ @Test
+ public void afterTextChanged_shouldDoSearch() {
+ mEditText.setText("abc");
+
+ verify(mSearchAlgorithm).doSearch(eq("abc"), any());
+ }
+
+ @Test
+ public void afterTextChanged_shouldShowCancelButton() {
+ mEditText.setText("abc");
+
+ assertEquals(mCancelButton.getVisibility(), View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_empty_shouldInformSearchModeListenerToExitSearch() {
+ mEditText.setText("");
+
+ verify(mSearchModeListener).exitSearchMode();
+ verifyNoMoreInteractions(mSearchModeListener);
+ }
+
+ @Test
+ public void afterTextChanged_empty_shouldCancelSearch() {
+ mEditText.setText("");
+
+ verify(mSearchAlgorithm).cancel(true);
+ verifyNoMoreInteractions(mSearchAlgorithm);
+ }
+
+ @Test
+ public void afterTextChanged_empty_shouldHideCancelButton() {
+ mEditText.setText("");
+
+ assertEquals(mCancelButton.getVisibility(), View.GONE);
+ }
+
+ @Test
+ public void cancelSearch_shouldInformSearchModeListenerToClearResultsAndExitSearch() {
+ mCancelButton.performClick();
+
+ verify(mSearchModeListener).exitSearchMode();
+ }
+
+ @Test
+ public void cancelSearch_shouldCancelSearch() {
+ mCancelButton.performClick();
+
+ verify(mSearchAlgorithm).cancel(true);
+ verifyNoMoreInteractions(mSearchAlgorithm);
+ }
+
+ @Test
+ public void cancelSearch_shouldClearSearchBar() {
+ mCancelButton.performClick();
+
+ assertEquals(mEditText.getText().toString(), "");
+ }
+}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 9d6af9f..5d41bb5 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -14,13 +14,16 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.SizeF;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
import androidx.annotation.Nullable;
@@ -72,6 +75,7 @@
private LauncherAppWidgetHostView mWidgetView;
private CellLayout mCellLayout;
private DragLayer mDragLayer;
+ private ImageButton mReconfigureButton;
private Rect mWidgetPadding;
@@ -101,6 +105,8 @@
private int mRunningVInc;
private int mMinHSpan;
private int mMinVSpan;
+ private int mMaxHSpan;
+ private int mMaxVSpan;
private int mDeltaX;
private int mDeltaY;
private int mDeltaXAddOn;
@@ -139,10 +145,10 @@
protected void onFinishInflate() {
super.onFinishInflate();
- ViewGroup content = (ViewGroup) getChildAt(0);
- for (int i = 0; i < HANDLE_COUNT; i ++) {
- mDragHandles[i] = content.getChildAt(i);
- }
+ mDragHandles[INDEX_LEFT] = findViewById(R.id.widget_resize_left_handle);
+ mDragHandles[INDEX_TOP] = findViewById(R.id.widget_resize_top_handle);
+ mDragHandles[INDEX_RIGHT] = findViewById(R.id.widget_resize_right_handle);
+ mDragHandles[INDEX_BOTTOM] = findViewById(R.id.widget_resize_bottom_handle);
}
@Override
@@ -165,6 +171,15 @@
DragLayer dl = launcher.getDragLayer();
AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
.inflate(R.layout.app_widget_resize_frame, dl, false);
+ if (widget.hasEnforcedCornerRadius()) {
+ float enforcedCornerRadius = widget.getEnforcedCornerRadius();
+ ImageView imageView = frame.findViewById(R.id.widget_resize_frame);
+ Drawable d = imageView.getDrawable();
+ if (d instanceof GradientDrawable) {
+ GradientDrawable gd = (GradientDrawable) d.mutate();
+ gd.setCornerRadius(enforcedCornerRadius);
+ }
+ }
frame.setupForWidget(widget, cellLayout, dl);
((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
@@ -184,6 +199,8 @@
mMinHSpan = info.minSpanX;
mMinVSpan = info.minSpanY;
+ mMaxHSpan = info.maxSpanX;
+ mMaxVSpan = info.maxSpanY;
mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
widgetView.getAppWidgetInfo().provider, null);
@@ -196,6 +213,17 @@
mDragHandles[INDEX_RIGHT].setVisibility(GONE);
}
+ mReconfigureButton = (ImageButton) findViewById(R.id.widget_reconfigure_button);
+ if (info.isReconfigurable()) {
+ mReconfigureButton.setVisibility(VISIBLE);
+ mReconfigureButton.setOnClickListener(view -> mLauncher
+ .getAppWidgetHost()
+ .startConfigActivity(
+ mLauncher,
+ mWidgetView.getAppWidgetId(),
+ Launcher.REQUEST_RECONFIGURE_APPWIDGET));
+ }
+
// When we create the resize frame, we first mark all cells as unoccupied. The appropriate
// cells (same if not resized, or different) will be marked as occupied when the resize
// frame is dismissed.
@@ -315,7 +343,7 @@
// expandability.
mTempRange1.set(cellX, spanX + cellX);
int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
- hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
+ hSpanInc, mMinHSpan, mMaxHSpan, mCellLayout.getCountX(), mTempRange2);
cellX = mTempRange2.start;
spanX = mTempRange2.size();
if (hSpanDelta != 0) {
@@ -324,7 +352,7 @@
mTempRange1.set(cellY, spanY + cellY);
int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
- vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
+ vSpanInc, mMinVSpan, mMaxVSpan, mCellLayout.getCountY(), mTempRange2);
cellY = mTempRange2.start;
spanY = mTempRange2.size();
if (vSpanDelta != 0) {
@@ -567,6 +595,13 @@
return false;
}
+ private boolean isTouchOnReconfigureButton(MotionEvent ev) {
+ int xFrame = (int) ev.getX() - getLeft();
+ int yFrame = (int) ev.getY() - getTop();
+ mReconfigureButton.getHitRect(sTmpRect);
+ return sTmpRect.contains(xFrame, yFrame);
+ }
+
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
int action = ev.getAction();
@@ -594,6 +629,11 @@
if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
return true;
}
+ // Keep the resize frame open but let a click on the reconfigure button fall through to the
+ // button's OnClickListener.
+ if (isTouchOnReconfigureButton(ev)) {
+ return false;
+ }
close(false);
return false;
}
@@ -643,12 +683,15 @@
* @param minSize minimum size after with the moving edge should not be shifted any further.
* For eg, if delta = -3 when moving the endEdge brings the size to less than
* minSize, only delta = -2 will applied
+ * @param maxSize maximum size after with the moving edge should not be shifted any further.
+ * For eg, if delta = -3 when moving the endEdge brings the size to greater
+ * than maxSize, only delta = -2 will applied
* @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
* @return the amount of increase when endEdge was moves and the amount of decrease when
* the start edge was moved.
*/
public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
- int minSize, int maxEnd, IntRange out) {
+ int minSize, int maxSize, int maxEnd, IntRange out) {
applyDelta(moveStart, moveEnd, delta, out);
if (out.start < 0) {
out.start = 0;
@@ -663,6 +706,13 @@
out.end = out.start + minSize;
}
}
+ if (out.size() > maxSize) {
+ if (moveStart) {
+ out.start = out.end - maxSize;
+ } else if (moveEnd) {
+ out.end = out.start + maxSize;
+ }
+ }
return moveEnd ? out.size() - size() : size() - out.size();
}
}
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 062ab71..f77f7e8 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -279,7 +279,7 @@
/**
* Used to set the override visibility state, used only to handle the transition home with the
* recents animation.
- * @see QuickstepAppTransitionManagerImpl#createWallpaperOpenRunner
+ * @see QuickstepTransitionManager#createWallpaperOpenRunner
*/
public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
mForceInvisible |= flag;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 5bfde15..5ba03ed 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -27,6 +27,8 @@
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Process;
import android.os.StrictMode;
@@ -40,6 +42,7 @@
import android.view.WindowMetrics;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -52,10 +55,12 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.ActivityOptionsWrapper;
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.PackageManagerHelper;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.WindowBounds;
@@ -76,6 +81,7 @@
protected boolean mIsSafeModeEnabled;
private Runnable mOnStartCallback;
+ private RunnableList mOnResumeCallbacks = new RunnableList();
private int mThemeRes = R.style.AppTheme;
@@ -98,6 +104,16 @@
}
@Override
+ protected void onResume() {
+ super.onResume();
+ mOnResumeCallbacks.executeAllAndClear();
+ }
+
+ public void addOnResumeCallback(Runnable callback) {
+ mOnResumeCallbacks.add(callback);
+ }
+
+ @Override
public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
updateTheme();
}
@@ -149,20 +165,35 @@
return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
}
- public final Bundle getActivityLaunchOptionsAsBundle(View v) {
- ActivityOptions activityOptions = getActivityLaunchOptions(v);
- return activityOptions == null ? null : activityOptions.toBundle();
+ @NonNull
+ public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+ int left = 0, top = 0;
+ int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
+ if (v instanceof BubbleTextView) {
+ // Launch from center of icon, not entire view
+ Drawable icon = ((BubbleTextView) v).getIcon();
+ if (icon != null) {
+ Rect bounds = icon.getBounds();
+ left = (width - bounds.width()) / 2;
+ top = v.getPaddingTop();
+ width = bounds.width();
+ height = bounds.height();
+ }
+ }
+ ActivityOptions options =
+ ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+ RunnableList callback = new RunnableList();
+ addOnResumeCallback(callback::executeAllAndDestroy);
+ return new ActivityOptionsWrapper(options, callback);
}
- public abstract ActivityOptions getActivityLaunchOptions(View v);
-
public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {
if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return false;
}
- Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null;
+ Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v).toBundle() : null;
UserHandle user = item == null ? null : item.user;
// Prepare intent
@@ -299,6 +330,6 @@
* views
*/
public SearchAdapterProvider createSearchAdapterProvider(AllAppsContainerView allapps) {
- return new DefaultSearchAdapterProvider(this);
+ return new DefaultSearchAdapterProvider(this, allapps);
}
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e0be6de..d333b49 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@@ -50,7 +49,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
@@ -58,11 +56,12 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.PlaceHolderIconDrawable;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.PlaceHolderIconDrawable;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -81,7 +80,7 @@
* because we want to make the bubble taller than the text and TextView's clip is
* too aggressive.
*/
-public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
IconLabelDotView, DraggableView, Reorderable {
private static final int DISPLAY_WORKSPACE = 0;
@@ -93,12 +92,9 @@
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private static final float HIGHLIGHT_SCALE = 1.16f;
-
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
- private static final int ICON_UPDATE_ANIMATION_DURATION = 375;
-
private float mScaleForReorderBounce = 1f;
protected final Paint mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -336,7 +332,7 @@
@UiThread
protected void applyIconAndLabel(ItemInfoWithIcon info) {
- FastBitmapDrawable iconDrawable = newIcon(getContext(), info);
+ FastBitmapDrawable iconDrawable = info.newIcon(getContext());
mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
setIcon(iconDrawable);
@@ -431,13 +427,6 @@
}
}
- @Override
- public void onLauncherResume() {
- // Reset the pressed state of icon that was locked in the press state while activity
- // was launching
- setStayPressed(false);
- }
-
void clearPressedBackground() {
setPressed(false);
setStayPressed(false);
@@ -941,7 +930,7 @@
private void resetIconScale() {
if (mIcon instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) mIcon).setScale(1f);
+ ((FastBitmapDrawable) mIcon).resetScale();
}
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 459b9a8..4740079 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -21,14 +21,9 @@
import static com.android.launcher3.LauncherState.NORMAL;
import android.animation.AnimatorSet;
-import android.animation.FloatArrayEvaluator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -45,10 +40,7 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.Thunk;
/**
* Implements a DropTarget.
@@ -72,6 +64,7 @@
private static final int[] sTempCords = new int[2];
private static final int DRAG_VIEW_DROP_DURATION = 285;
+ private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f;
public static final int TOOLTIP_DEFAULT = 0;
public static final int TOOLTIP_LEFT = 1;
@@ -89,9 +82,6 @@
/** An item must be dragged at least this many pixels before this drop target is enabled. */
private final int mDragDistanceThreshold;
- /** The paint applied to the drag view on hover */
- protected int mHoverColor = 0;
-
protected CharSequence mText;
protected ColorStateList mOriginalTextColor;
protected Drawable mDrawable;
@@ -101,7 +91,6 @@
private int mToolTipLocation;
private AnimatorSet mCurrentColorAnim;
- @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter;
public ButtonDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -177,8 +166,7 @@
mToolTip.showAsDropDown(this, x, y);
}
- d.dragView.setColor(mHoverColor);
- animateTextColor(mHoverColor);
+ d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
if (d.stateAnnouncer != null) {
d.stateAnnouncer.cancel();
}
@@ -190,51 +178,15 @@
// Do nothing
}
- protected void resetHoverColor() {
- animateTextColor(mOriginalTextColor.getDefaultColor());
- }
-
- private void animateTextColor(int targetColor) {
- if (mCurrentColorAnim != null) {
- mCurrentColorAnim.cancel();
- }
-
- mCurrentColorAnim = new AnimatorSet();
- mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION);
-
- if (mSrcFilter == null) {
- mSrcFilter = new ColorMatrix();
- mDstFilter = new ColorMatrix();
- mCurrentFilter = new ColorMatrix();
- }
-
- int defaultTextColor = mOriginalTextColor.getDefaultColor();
- Themes.setColorChangeOnMatrix(defaultTextColor, getTextColor(), mSrcFilter);
- Themes.setColorChangeOnMatrix(defaultTextColor, targetColor, mDstFilter);
-
- ValueAnimator anim1 = ValueAnimator.ofObject(
- new FloatArrayEvaluator(mCurrentFilter.getArray()),
- mSrcFilter.getArray(), mDstFilter.getArray());
- anim1.addUpdateListener((anim) -> {
- mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
- invalidate();
- });
-
- mCurrentColorAnim.play(anim1);
- mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, TEXT_COLOR, targetColor));
- mCurrentColorAnim.start();
- }
-
@Override
public final void onDragExit(DragObject d) {
hideTooltip();
if (!d.dragComplete) {
d.dragView.setColor(0);
- resetHoverColor();
+ d.dragView.setAlpha(1f);
} else {
- // Restore the hover color
- d.dragView.setColor(mHoverColor);
+ d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
}
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index b8833cf..c16c44b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -19,7 +19,6 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -60,6 +59,7 @@
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -180,6 +180,7 @@
private final ArrayList<View> mIntersectingViews = new ArrayList<>();
private final Rect mOccupiedRect = new Rect();
private final int[] mDirectionVector = new int[2];
+
final int[] mPreviousReorderDirection = new int[2];
private static final int INVALID_DIRECTION = -100;
@@ -209,15 +210,17 @@
setWillNotDraw(false);
setClipToPadding(false);
mActivity = ActivityContext.lookupContext(context);
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- DeviceProfile grid = mActivity.getDeviceProfile();
+ mBorderSpacing = mContainerType == FOLDER
+ ? deviceProfile.folderCellLayoutBorderSpacingPx
+ : deviceProfile.cellLayoutBorderSpacingPx;
- mBorderSpacing = grid.cellLayoutBorderSpacingPx;
mCellWidth = mCellHeight = -1;
mFixedCellWidth = mFixedCellHeight = -1;
- mCountX = grid.inv.numColumns;
- mCountY = grid.inv.numRows;
+ mCountX = deviceProfile.inv.numColumns;
+ mCountY = deviceProfile.inv.numRows;
mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
@@ -234,7 +237,7 @@
mBackground.setCallback(this);
mBackground.setAlpha(0);
- mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
+ mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
// Initialize the data structures used for the drag visualization.
mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
@@ -601,7 +604,7 @@
if (child instanceof BubbleTextView) {
BubbleTextView bubbleChild = (BubbleTextView) child;
bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
- if (ENABLE_FOUR_COLUMNS.get()) {
+ if (mActivity.getDeviceProfile().isScalableGrid) {
bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
}
}
@@ -961,15 +964,18 @@
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
- if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
- return;
- }
-
- Bitmap dragOutline = outlineProvider.generatedDragOutline;
if (cellX != oldDragCellX || cellY != oldDragCellY) {
mDragCell[0] = cellX;
mDragCell[1] = cellY;
+ applyColorExtraction(dragObject, mDragCell, spanX, spanY);
+
+ if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
+ return;
+ }
+
+ Bitmap dragOutline = outlineProvider.generatedDragOutline;
+
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
@@ -1011,6 +1017,22 @@
}
}
+ /** Applies the local color extraction to a dragging widget object. */
+ private void applyColorExtraction(DropTarget.DragObject dragObject, int[] targetCell, int spanX,
+ int spanY) {
+ // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
+ Drawable drawable = dragObject.dragView.getDrawable();
+ if (drawable instanceof AppWidgetHostViewDrawable) {
+ Workspace workspace =
+ Launcher.getLauncher(dragObject.dragView.getContext()).getWorkspace();
+ int screenId = workspace.getIdForScreen(this);
+ int pageId = workspace.getPageIndexForScreenId(screenId);
+ AppWidgetHostViewDrawable hostViewDrawable = ((AppWidgetHostViewDrawable) drawable);
+ cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
+ hostViewDrawable.getAppWidgetHostView().handleDrag(mTempRect, pageId);
+ }
+ }
+
@SuppressLint("StringFormatMatches")
public String getItemMoveDescription(int cellX, int cellY) {
if (mContainerType == HOTSEAT) {
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index cc119c9..e46aad2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -53,9 +53,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- // Get the hover color
- mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
-
setDrawable(R.drawable.ic_remove_shadow);
}
diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java
index 4827f36..7c387b1 100644
--- a/src/com/android/launcher3/DevicePaddings.java
+++ b/src/com/android/launcher3/DevicePaddings.java
@@ -52,8 +52,8 @@
ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
- public DevicePaddings(Context context) {
- try (XmlResourceParser parser = context.getResources().getXml(R.xml.size_limits)) {
+ public DevicePaddings(Context context, int devicePaddingId) {
+ try (XmlResourceParser parser = context.getResources().getXml(devicePaddingId)) {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
@@ -94,16 +94,27 @@
if (workspaceTopPadding == null
|| workspaceBottomPadding == null
|| hotseatBottomPadding == null) {
- throw new RuntimeException("DevicePadding missing padding.");
+ if (Utilities.IS_DEBUG_DEVICE) {
+ throw new RuntimeException("DevicePadding missing padding.");
+ }
}
- mDevicePaddings.add(new DevicePadding(maxWidthPx, workspaceTopPadding,
- workspaceBottomPadding, hotseatBottomPadding));
+ DevicePadding dp = new DevicePadding(maxWidthPx, workspaceTopPadding,
+ workspaceBottomPadding, hotseatBottomPadding);
+ if (dp.isValid()) {
+ mDevicePaddings.add(dp);
+ } else {
+ Log.e(TAG, "Invalid device padding found.");
+ if (Utilities.IS_DEBUG_DEVICE) {
+ throw new RuntimeException("DevicePadding is invalid");
+ }
+ }
}
}
}
}
} catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failure parsing device padding layout.", e);
throw new RuntimeException(e);
}
@@ -128,6 +139,9 @@
*/
public static final class DevicePadding {
+ // One for each padding since they can each be off by 1 due to rounding errors.
+ private static final int ROUNDING_THRESHOLD_PX = 3;
+
private final int maxEmptySpacePx;
private final PaddingFormula workspaceTopPadding;
private final PaddingFormula workspaceBottomPadding;
@@ -143,6 +157,10 @@
this.hotseatBottomPadding = hotseatBottomPadding;
}
+ public int getMaxEmptySpacePx() {
+ return maxEmptySpacePx;
+ }
+
public int getWorkspaceTopPadding(int extraSpacePx) {
return workspaceTopPadding.calculate(extraSpacePx);
}
@@ -154,6 +172,22 @@
public int getHotseatBottomPadding(int extraSpacePx) {
return hotseatBottomPadding.calculate(extraSpacePx);
}
+
+ public boolean isValid() {
+ int workspaceTopPadding = getWorkspaceTopPadding(maxEmptySpacePx);
+ int workspaceBottomPadding = getWorkspaceBottomPadding(maxEmptySpacePx);
+ int hotseatBottomPadding = getHotseatBottomPadding(maxEmptySpacePx);
+ int sum = workspaceTopPadding + workspaceBottomPadding + hotseatBottomPadding;
+ int diff = Math.abs(sum - maxEmptySpacePx);
+ if (DEBUG) {
+ Log.d(TAG, "isValid: workspaceTopPadding=" + workspaceTopPadding
+ + ", workspaceBottomPadding=" + workspaceBottomPadding
+ + ", hotseatBottomPadding=" + hotseatBottomPadding
+ + ", sum=" + sum
+ + ", diff=" + diff);
+ }
+ return diff <= ROUNDING_THRESHOLD_PX;
+ }
}
/**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f2dd60e..35b0f27 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.ResourceUtils.pxFromDp;
+import static com.android.launcher3.Utilities.dpiFromPx;
import android.content.Context;
import android.content.res.Configuration;
@@ -38,6 +39,8 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.WindowBounds;
+import java.io.PrintWriter;
+
public class DeviceProfile {
private static final float TABLET_MIN_DPS = 600;
@@ -91,14 +94,17 @@
public float workspaceSpringLoadShrinkFactor;
public final int workspaceSpringLoadedBottomSpace;
+ private final int extraSpace;
public int workspaceTopPadding;
public int workspaceBottomPadding;
+ public int extraHotseatBottomPadding;
// Workspace page indicator
public final int workspacePageIndicatorHeight;
private final int mWorkspacePageIndicatorOverlapWorkspace;
// Workspace icons
+ public float iconScale;
public int iconSizePx;
public int iconTextSizePx;
public int iconDrawablePaddingPx;
@@ -109,7 +115,6 @@
public int workspaceCellPaddingXPx;
public int cellYPaddingPx;
- public int cellYPaddingOriginalPx;
// Folder
public float folderLabelTextScale;
@@ -118,6 +123,7 @@
public int folderIconOffsetYPx;
// Folder content
+ public int folderCellLayoutBorderSpacingPx;
public int folderContentPaddingLeftRight;
public int folderContentPaddingTop;
@@ -141,12 +147,18 @@
public final int hotseatBarSidePaddingEndPx;
// All apps
+ public int allAppsOpenVerticalTranslate;
public int allAppsCellHeightPx;
public int allAppsCellWidthPx;
public int allAppsIconSizePx;
public int allAppsIconDrawablePaddingPx;
public float allAppsIconTextSizePx;
+ // Overview
+ public int overviewTaskMarginPx;
+ public int overviewTaskIconSizePx;
+ public int overviewTaskThumbnailTopMarginPx;
+
// Widgets
public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
@@ -167,6 +179,8 @@
// Taskbar
public boolean isTaskbarPresent;
public int taskbarSize;
+ // How much of the bottom inset is due to Taskbar rather than other system elements.
+ public int nonOverlappingTaskbarInset;
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
Point minSize, Point maxSize, int width, int height, boolean isLandscape,
@@ -197,8 +211,7 @@
mInfo = info;
// Constants from resources
- float swDPs = Utilities.dpiFromPx(
- Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
+ float swDPs = dpiFromPx(Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
boolean allowRotation = context.getResources().getBoolean(R.bool.allow_rotation);
// Tablet UI is built with assumption that simulated landscape is disabled.
isTablet = allowRotation && swDPs >= TABLET_MIN_DPS;
@@ -221,7 +234,7 @@
WindowInsets windowInsets = DisplayController.INSTANCE.get(context).getHolder(mInfo.id)
.getDisplayContext().getSystemService(WindowManager.class)
.getCurrentWindowMetrics().getWindowInsets();
- int nonOverlappingTaskbarInset =
+ nonOverlappingTaskbarInset =
taskbarSize - windowInsets.getSystemWindowInsetBottom();
if (nonOverlappingTaskbarInset > 0) {
nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
@@ -236,6 +249,10 @@
: res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
desiredWorkspaceLeftRightOriginalPx = desiredWorkspaceLeftRightMarginPx;
+
+ allAppsOpenVerticalTranslate = res.getDimensionPixelSize(
+ R.dimen.all_apps_open_vertical_translate);
+
folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
folderContentPaddingLeftRight =
res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
@@ -243,13 +260,19 @@
setCellLayoutBorderSpacing(pxFromDp(inv.borderSpacing, mInfo.metrics, 1f));
cellLayoutBorderSpacingOriginalPx = cellLayoutBorderSpacingPx;
+ folderCellLayoutBorderSpacingPx = cellLayoutBorderSpacingPx;
int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
int cellLayoutPadding = isScalableGrid
? 0
: res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
- if (isLandscape) {
+
+ if (FeatureFlags.ENABLE_TWO_PANEL_HOME.get() && isTablet) {
+ cellLayoutPaddingLeftRightPx =
+ res.getDimensionPixelSize(R.dimen.two_panel_home_side_padding);
+ cellLayoutBottomPaddingPx = 0;
+ } else if (isLandscape) {
cellLayoutPaddingLeftRightPx = 0;
cellLayoutBottomPaddingPx = cellLayoutPadding;
} else {
@@ -287,23 +310,37 @@
: (hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx
+ (isScalableGrid ? 0 : hotseatExtraVerticalSize)));
- // Calculate all of the remaining variables.
- int extraSpace = updateAvailableDimensions(res);
- // Now that we have all of the variables calculated, we can tune certain sizes.
- if (isScalableGrid) {
- DevicePadding padding = inv.devicePaddings.getDevicePadding(extraSpace);
- workspaceTopPadding = padding.getWorkspaceTopPadding(extraSpace);
- workspaceBottomPadding = padding.getWorkspaceBottomPadding(extraSpace);
+ overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
+ overviewTaskIconSizePx =
+ isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get() ? res.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_size_grid) : res.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_size);
+ overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
- float hotseatBarBottomPadding = padding.getHotseatBottomPadding(extraSpace);
- hotseatBarSizePx += hotseatBarBottomPadding;
- hotseatBarBottomPaddingPx += hotseatBarBottomPadding;
+ // Calculate all of the remaining variables.
+ extraSpace = updateAvailableDimensions(res);
+ // Now that we have all of the variables calculated, we can tune certain sizes.
+ if (isScalableGrid && inv.devicePaddings != null) {
+ // Paddings were created assuming no scaling, so we first unscale the extra space.
+ int unscaledExtraSpace = (int) (extraSpace / iconScale);
+ DevicePadding padding = inv.devicePaddings.getDevicePadding(unscaledExtraSpace);
+
+ int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
+ int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
+ int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
+
+ workspaceTopPadding = Math.round(paddingWorkspaceTop * iconScale);
+ workspaceBottomPadding = Math.round(paddingWorkspaceBottom * iconScale);
+ extraHotseatBottomPadding = Math.round(paddingHotseatBottom * iconScale);
+
+ hotseatBarSizePx += extraHotseatBottomPadding;
+ hotseatBarBottomPaddingPx += extraHotseatBottomPadding;
} else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
// We increase the hotseat size when there is extra space.
// ie. For a display with a large aspect ratio, we can keep the icons on the workspace
// in portrait mode closer together by adding more height to the hotseat.
// Note: This calculation was created after noticing a pattern in the design spec.
- extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
+ int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
- workspacePageIndicatorHeight;
hotseatBarSizePx += extraSpace;
hotseatBarBottomPaddingPx += extraSpace;
@@ -322,13 +359,7 @@
}
private void setCellLayoutBorderSpacing(int borderSpacing) {
- if (isScalableGrid) {
- cellLayoutBorderSpacingPx = borderSpacing;
- folderContentPaddingLeftRight = borderSpacing;
- folderContentPaddingTop = borderSpacing;
- } else {
- cellLayoutBorderSpacingPx = 0;
- }
+ cellLayoutBorderSpacingPx = isScalableGrid ? borderSpacing : 0;
}
/**
@@ -374,12 +405,7 @@
.setMultiWindowMode(true)
.build();
- // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
- float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
- - iconDrawablePaddingPx - profile.iconTextSizePx;
- if (workspaceCellPaddingY < profile.iconDrawablePaddingPx * 2) {
- profile.adjustToHideWorkspaceLabels();
- }
+ profile.hideWorkspaceLabelsIfNotEnoughSpace();
// We use these scales to measure and layout the widgets using their full invariant profile
// sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
@@ -400,24 +426,32 @@
}
/**
- * Adjusts the profile so that the labels on the Workspace are hidden.
+ * Checks if there is enough space for labels on the workspace.
+ * If there is not, labels on the Workspace are hidden.
* It is important to call this method after the All Apps variables have been set.
*/
- private void adjustToHideWorkspaceLabels() {
- iconTextSizePx = 0;
- iconDrawablePaddingPx = 0;
- cellHeightPx = iconSizePx;
- autoResizeAllAppsCells();
+ private void hideWorkspaceLabelsIfNotEnoughSpace() {
+ float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
+ float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
+ - iconTextHeight;
+
+ // We want enough space so that the text is closer to its corresponding icon.
+ if (workspaceCellPaddingY < iconTextHeight) {
+ iconTextSizePx = 0;
+ iconDrawablePaddingPx = 0;
+ cellHeightPx = iconSizePx;
+ autoResizeAllAppsCells();
+ }
}
/**
* Re-computes the all-apps cell size to be independent of workspace
*/
public void autoResizeAllAppsCells() {
- int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
+ int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
+ int topBottomPadding = textHeight;
allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
- + Utilities.calculateTextHeight(allAppsIconTextSizePx)
- + topBottomPadding * 2;
+ + textHeight + (topBottomPadding * 2);
}
/**
@@ -468,11 +502,14 @@
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
*/
public void updateIconSize(float scale, Resources res) {
+ iconScale = scale;
+
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
- float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
+ float invIconSizeDp = isLandscape ? inv.landscapeIconSize : inv.iconSize;
iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mInfo.metrics, scale));
- iconTextSizePx = pxFromDp(inv.iconTextSize, mInfo.metrics, scale);
+ float invIconTextSizeSp = isLandscape ? inv.landscapeIconTextSize : inv.iconTextSize;
+ iconTextSizePx = (int) (Utilities.pxFromSp(invIconTextSizeSp, mInfo.metrics) * scale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
setCellLayoutBorderSpacing((int) (cellLayoutBorderSpacingOriginalPx * scale));
@@ -504,9 +541,7 @@
allAppsIconSizePx = pxFromDp(inv.allAppsIconSize, mInfo.metrics);
allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics);
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
- // We use 4 below to ensure labels are closer to their corresponding icon.
- allAppsCellHeightPx = Math.round(allAppsIconSizePx + allAppsIconTextSizePx
- + (4 * allAppsIconDrawablePaddingPx));
+ autoResizeAllAppsCells();
} else {
allAppsIconSizePx = iconSizePx;
allAppsIconTextSizePx = iconTextSizePx;
@@ -515,9 +550,8 @@
}
allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
- if (isVerticalBarLayout()) {
- // Always hide the Workspace text with vertical bar layout.
- adjustToHideWorkspaceLabels();
+ if (isVerticalLayout) {
+ hideWorkspaceLabelsIfNotEnoughSpace();
}
// Hotseat
@@ -555,14 +589,14 @@
// Check if the icons fit within the available height.
float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
- + ((inv.numFolderRows - 1) * cellLayoutBorderSpacingPx);
+ + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacingPx);
int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
- folderMargin - folderContentPaddingTop;
float scaleY = contentMaxHeight / contentUsedHeight;
// Check if the icons fit within the available width.
float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
- + ((inv.numFolderColumns - 1) * cellLayoutBorderSpacingPx);
+ + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacingPx);
int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin
- folderContentPaddingLeftRight * 2;
float scaleX = contentMaxWidth / contentUsedWidth;
@@ -580,11 +614,25 @@
folderLabelTextSizePx = (int) (folderChildTextSizePx * folderLabelTextScale);
int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
- int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
- int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
- folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
- folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
+ if (isScalableGrid) {
+ folderCellWidthPx = (int) (cellWidthPx * scale);
+ folderCellHeightPx = (int) (cellHeightPx * scale);
+
+ int borderSpacing = (int) (cellLayoutBorderSpacingOriginalPx * scale);
+ folderCellLayoutBorderSpacingPx = borderSpacing;
+ folderContentPaddingLeftRight = borderSpacing;
+ folderContentPaddingTop = borderSpacing;
+ } else {
+ int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
+ * scale);
+ int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding)
+ * scale);
+
+ folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
+ folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
+ }
+
folderChildDrawablePaddingPx = Math.max(0,
(folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
}
@@ -658,6 +706,10 @@
- (2 * inv.numRows * cellHeightPx) - hotseatVerticalPadding);
padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
+
+ if (FeatureFlags.ENABLE_TWO_PANEL_HOME.get()) {
+ padding.set(0, padding.top, 0, padding.bottom);
+ }
} else {
// Pad the top and bottom of the workspace with search/hotseat bar sizes
padding.set(desiredWorkspaceLeftRightMarginPx,
@@ -708,10 +760,11 @@
mInsets.top + availableHeightPx);
} else {
// Folders should only appear below the drop target bar and above the hotseat
+ int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
return new Rect(mInsets.left + edgeMarginPx,
mInsets.top + dropTargetBarSizePx + edgeMarginPx,
mInsets.left + availableWidthPx - edgeMarginPx,
- mInsets.top + availableHeightPx - hotseatBarSizePx
+ mInsets.top + availableHeightPx - hotseatTop
- workspacePageIndicatorHeight - edgeMarginPx);
}
}
@@ -782,6 +835,102 @@
}
}
+ private String pxToDpStr(String name, float value) {
+ return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mInfo.metrics) + "dp)";
+ }
+
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "DeviceProfile:");
+ writer.println(prefix + "\t1 dp = " + mInfo.metrics.density + " px");
+
+ writer.println(prefix + "\tisTablet:" + isTablet);
+ writer.println(prefix + "\tisLargeTablet:" + isLargeTablet);
+ writer.println(prefix + "\tisPhone:" + isPhone);
+ writer.println(prefix + "\ttransposeLayoutWithOrientation:"
+ + transposeLayoutWithOrientation);
+
+ writer.println(prefix + "\tisLandscape:" + isLandscape);
+ writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
+
+ writer.println(prefix + pxToDpStr("windowX", windowX));
+ writer.println(prefix + pxToDpStr("windowY", windowY));
+ writer.println(prefix + pxToDpStr("widthPx", widthPx));
+ writer.println(prefix + pxToDpStr("heightPx", heightPx));
+
+ writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx));
+ writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx));
+
+ writer.println(prefix + "\taspectRatio:" + aspectRatio);
+
+ writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
+
+ writer.println(prefix + "\tinv.minCellWidth:" + inv.minCellWidth + "dp");
+ writer.println(prefix + "\tinv.minCellHeight:" + inv.minCellHeight + "dp");
+
+ writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
+ writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
+
+ writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x));
+ writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y));
+
+ writer.println(prefix + "\tinv.iconSize:" + inv.iconSize + "dp");
+ writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
+ writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
+ writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx));
+
+ writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx));
+ writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx));
+ writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx));
+ writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
+ writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
+ folderChildDrawablePaddingPx));
+ writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacingPx",
+ folderCellLayoutBorderSpacingPx));
+
+ writer.println(prefix + pxToDpStr("cellLayoutBorderSpacingPx",
+ cellLayoutBorderSpacingPx));
+ writer.println(prefix + pxToDpStr("desiredWorkspaceLeftRightMarginPx",
+ desiredWorkspaceLeftRightMarginPx));
+
+ writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
+ writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
+ writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
+ allAppsIconDrawablePaddingPx));
+ writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
+
+ writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
+ writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
+ writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
+ writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
+ writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
+ hotseatBarSidePaddingStartPx));
+ writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
+ hotseatBarSidePaddingEndPx));
+
+ writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
+
+ writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize));
+ writer.println(prefix + pxToDpStr("nonOverlappingTaskbarInset",
+ nonOverlappingTaskbarInset));
+
+ writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
+ writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
+ writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
+ writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
+
+ writer.println(prefix + pxToDpStr("scaleToFit", iconScale));
+ writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
+
+ if (inv.devicePaddings != null) {
+ int unscaledExtraSpace = (int) (extraSpace / iconScale);
+ writer.println(prefix + pxToDpStr("maxEmptySpace",
+ inv.devicePaddings.getDevicePadding(unscaledExtraSpace).getMaxEmptySpacePx()));
+ }
+ writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
+ writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
+ writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding));
+ }
+
private static Context getContext(Context c, Info info, int orientation) {
Configuration config = new Configuration(c.getResources().getConfiguration());
config.orientation = orientation;
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 02c6162..c79dabe 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -131,10 +131,9 @@
public void reset() {
if (!TextUtils.isEmpty(getText())) {
setText("");
- } else {
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- return;
- }
+ }
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ return;
}
if (isFocused()) {
View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
deleted file mode 100644
index b1fe4a2..0000000
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.Property;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.graphics.PlaceHolderIconDrawable;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.util.Themes;
-
-
-public class FastBitmapDrawable extends Drawable {
-
- private static final float PRESSED_SCALE = 1.1f;
-
- private static final float DISABLED_DESATURATION = 1f;
- private static final float DISABLED_BRIGHTNESS = 0.5f;
-
- public static final int CLICK_FEEDBACK_DURATION = 200;
-
- private static ColorFilter sDisabledFColorFilter;
-
- protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
- protected Bitmap mBitmap;
- protected final int mIconColor;
-
- @Nullable private ColorFilter mColorFilter;
-
- private boolean mIsPressed;
- private boolean mIsDisabled;
- private float mDisabledAlpha = 1f;
-
- // Animator and properties for the fast bitmap drawable's scale
- private static final Property<FastBitmapDrawable, Float> SCALE
- = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
- @Override
- public Float get(FastBitmapDrawable fastBitmapDrawable) {
- return fastBitmapDrawable.mScale;
- }
-
- @Override
- public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
- fastBitmapDrawable.mScale = value;
- fastBitmapDrawable.invalidateSelf();
- }
- };
- private ObjectAnimator mScaleAnimation;
- private float mScale = 1;
-
- private int mAlpha = 255;
-
- public FastBitmapDrawable(Bitmap b) {
- this(b, Color.TRANSPARENT);
- }
-
- public FastBitmapDrawable(BitmapInfo info) {
- this(info.icon, info.color);
- }
-
- protected FastBitmapDrawable(Bitmap b, int iconColor) {
- this(b, iconColor, false);
- }
-
- protected FastBitmapDrawable(Bitmap b, int iconColor, boolean isDisabled) {
- mBitmap = b;
- mIconColor = iconColor;
- setFilterBitmap(true);
- setIsDisabled(isDisabled);
- }
-
- @Override
- public final void draw(Canvas canvas) {
- if (mScale != 1f) {
- int count = canvas.save();
- Rect bounds = getBounds();
- canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
- drawInternal(canvas, bounds);
- canvas.restoreToCount(count);
- } else {
- drawInternal(canvas, getBounds());
- }
- }
-
- protected void drawInternal(Canvas canvas, Rect bounds) {
- canvas.drawBitmap(mBitmap, null, bounds, mPaint);
- }
-
- @Override
- public void setColorFilter(ColorFilter cf) {
- mColorFilter = cf;
- updateFilter();
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int alpha) {
- if (mAlpha != alpha) {
- mAlpha = alpha;
- mPaint.setAlpha(alpha);
- invalidateSelf();
- }
- }
-
- @Override
- public void setFilterBitmap(boolean filterBitmap) {
- mPaint.setFilterBitmap(filterBitmap);
- mPaint.setAntiAlias(filterBitmap);
- }
-
- public int getAlpha() {
- return mAlpha;
- }
-
- public void setScale(float scale) {
- if (mScaleAnimation != null) {
- mScaleAnimation.cancel();
- mScaleAnimation = null;
- }
- mScale = scale;
- invalidateSelf();
- }
-
- public float getAnimatedScale() {
- return mScaleAnimation == null ? 1 : mScale;
- }
-
- public float getScale() {
- return mScale;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mBitmap.getWidth();
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mBitmap.getHeight();
- }
-
- @Override
- public int getMinimumWidth() {
- return getBounds().width();
- }
-
- @Override
- public int getMinimumHeight() {
- return getBounds().height();
- }
-
- @Override
- public boolean isStateful() {
- return true;
- }
-
- @Override
- public ColorFilter getColorFilter() {
- return mPaint.getColorFilter();
- }
-
- @Override
- protected boolean onStateChange(int[] state) {
- boolean isPressed = false;
- for (int s : state) {
- if (s == android.R.attr.state_pressed) {
- isPressed = true;
- break;
- }
- }
- if (mIsPressed != isPressed) {
- mIsPressed = isPressed;
-
- if (mScaleAnimation != null) {
- mScaleAnimation.cancel();
- mScaleAnimation = null;
- }
-
- if (mIsPressed) {
- // Animate when going to pressed state
- mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
- mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
- mScaleAnimation.setInterpolator(ACCEL);
- mScaleAnimation.start();
- } else {
- if (isVisible()) {
- mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f);
- mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
- mScaleAnimation.setInterpolator(DEACCEL);
- mScaleAnimation.start();
- } else {
- mScale = 1f;
- invalidateSelf();
- }
- }
- return true;
- }
- return false;
- }
-
- public void setIsDisabled(boolean isDisabled) {
- if (mIsDisabled != isDisabled) {
- mIsDisabled = isDisabled;
- updateFilter();
- }
- }
-
- protected boolean isDisabled() {
- return mIsDisabled;
- }
-
- private ColorFilter getDisabledColorFilter() {
- if (sDisabledFColorFilter == null) {
- ColorMatrix tempBrightnessMatrix = new ColorMatrix();
- ColorMatrix tempFilterMatrix = new ColorMatrix();
-
- tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
- float scale = 1 - DISABLED_BRIGHTNESS;
- int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
- float[] mat = tempBrightnessMatrix.getArray();
- mat[0] = scale;
- mat[6] = scale;
- mat[12] = scale;
- mat[4] = brightnessI;
- mat[9] = brightnessI;
- mat[14] = brightnessI;
- mat[18] = mDisabledAlpha;
- tempFilterMatrix.preConcat(tempBrightnessMatrix);
- sDisabledFColorFilter = new ColorMatrixColorFilter(tempFilterMatrix);
- }
- return sDisabledFColorFilter;
- }
-
- /**
- * Updates the paint to reflect the current brightness and saturation.
- */
- protected void updateFilter() {
- mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : mColorFilter);
- invalidateSelf();
- }
-
- @Override
- public ConstantState getConstantState() {
- return new FastBitmapConstantState(mBitmap, mIconColor, mIsDisabled);
- }
-
- protected static class FastBitmapConstantState extends ConstantState {
- protected final Bitmap mBitmap;
- protected final int mIconColor;
- protected final boolean mIsDisabled;
-
- public FastBitmapConstantState(Bitmap bitmap, int color, boolean isDisabled) {
- mBitmap = bitmap;
- mIconColor = color;
- mIsDisabled = isDisabled;
- }
-
- @Override
- public FastBitmapDrawable newDrawable() {
- return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
- }
-
- @Override
- public int getChangingConfigurations() {
- return 0;
- }
- }
-
- /**
- * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction
- */
- public interface Factory {
-
- /**
- * Called to create a new drawable
- */
- FastBitmapDrawable newDrawable();
- }
-
- /**
- * Returns a FastBitmapDrawable with the icon.
- */
- public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
- FastBitmapDrawable drawable = newIcon(context, info.bitmap);
- drawable.setIsDisabled(info.isDisabled());
- return drawable;
- }
-
- /**
- * Creates a drawable for the provided BitmapInfo
- */
- public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) {
- final FastBitmapDrawable drawable;
- if (info instanceof Factory) {
- drawable = ((Factory) info).newDrawable();
- } else if (info.isLowRes()) {
- drawable = new PlaceHolderIconDrawable(info, context);
- } else {
- drawable = new FastBitmapDrawable(info);
- }
- drawable.mDisabledAlpha = Themes.getFloat(context, R.attr.disabledIconAlpha, 1f);
- return drawable;
- }
-}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b2112ad..5429806 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -20,6 +20,7 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
@@ -28,8 +29,6 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.config.FeatureFlags;
-
import java.util.function.Consumer;
/**
@@ -37,6 +36,9 @@
*/
public class Hotseat extends CellLayout implements Insettable {
+ // Ratio of empty space, qsb should take up to appear visually centered.
+ public static final float QSB_CENTER_FACTOR = .325f;
+
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mHasVerticalHotseat;
private Workspace mWorkspace;
@@ -44,6 +46,12 @@
@Nullable
private Consumer<Boolean> mOnVisibilityAggregatedCallback;
+ private final View mQsb;
+ private final int mQsbHeight;
+
+ private final View mTaskbarView;
+ private final int mTaskbarViewHeight;
+
public Hotseat(Context context) {
this(context, null);
}
@@ -54,6 +62,15 @@
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+ mQsbHeight = mQsb.getLayoutParams().height;
+ addView(mQsb);
+
+ mTaskbarView = LayoutInflater.from(context).inflate(R.layout.taskbar_view, this, false);
+ mTaskbarViewHeight = mTaskbarView.getLayoutParams().height;
+ // We want taskbar in the back so its background applies to Hotseat as well.
+ addView(mTaskbarView, 0);
}
/**
@@ -77,9 +94,8 @@
if (hasVerticalHotseat) {
setGridSize(1, idp.numHotseatIcons);
} else {
- setGridSize(idp.numHotseatIcons, FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 2 : 1);
+ setGridSize(idp.numHotseatIcons, 1);
}
- showInlineQsb();
}
@Override
@@ -88,6 +104,7 @@
DeviceProfile grid = mActivity.getDeviceProfile();
if (grid.isVerticalBarLayout()) {
+ mQsb.setVisibility(View.GONE);
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
if (grid.isSeascape()) {
lp.gravity = Gravity.LEFT;
@@ -97,21 +114,20 @@
lp.width = grid.hotseatBarSizePx + insets.right;
}
} else {
+ mQsb.setVisibility(View.VISIBLE);
lp.gravity = Gravity.BOTTOM;
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
- lp.height = grid.isTaskbarPresent
- ? grid.taskbarSize
- : grid.hotseatBarSizePx + insets.bottom;
+ lp.height = (grid.isTaskbarPresent
+ ? grid.workspacePadding.bottom
+ : grid.hotseatBarSizePx)
+ + (grid.isTaskbarPresent ? grid.taskbarSize : insets.bottom);
}
+
if (!grid.isTaskbarPresent) {
// When taskbar is present, we set the padding separately to ensure a seamless visual
// handoff between taskbar and hotseat during drag and drop.
Rect padding = grid.getHotseatLayoutPadding();
- int paddingBottom = padding.bottom;
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !grid.isVerticalBarLayout()) {
- paddingBottom -= grid.hotseatBarBottomPaddingPx;
- }
- setPadding(padding.left, padding.top, padding.right, paddingBottom);
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
setLayoutParams(lp);
@@ -164,14 +180,61 @@
mOnVisibilityAggregatedCallback = callback;
}
- protected void showInlineQsb() {
- //Does nothing
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = getShortcutsAndWidgets().getMeasuredWidth();
+ mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
+ mTaskbarView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mTaskbarViewHeight, MeasureSpec.EXACTLY));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ int qsbWidth = mQsb.getMeasuredWidth();
+ int left = (r - l - qsbWidth) / 2;
+ int right = left + qsbWidth;
+
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ int freeSpace = dp.isTaskbarPresent
+ ? dp.workspacePadding.bottom
+ : dp.hotseatBarSizePx - dp.hotseatCellHeightPx - mQsbHeight;
+ int bottom = b - t
+ - (int) (freeSpace * QSB_CENTER_FACTOR)
+ - (dp.isTaskbarPresent ? dp.taskbarSize : dp.getInsets().bottom);
+ int top = bottom - mQsbHeight;
+ mQsb.layout(left, top, right, bottom);
+
+ int taskbarWidth = mTaskbarView.getMeasuredWidth();
+ left = (r - l - taskbarWidth) / 2;
+ right = left + taskbarWidth;
+ bottom = b - t;
+ top = bottom - mTaskbarViewHeight;
+ mTaskbarView.layout(left, top, right, bottom);
}
/**
- * Returns the first View for which the given itemOperator returns true, or null.
+ * Sets the alpha value of just our ShortcutAndWidgetContainer.
*/
- public View getFirstItemMatch(Workspace.ItemOperator itemOperator) {
- return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
+ public void setIconsAlpha(float alpha) {
+ getShortcutsAndWidgets().setAlpha(alpha);
+ }
+
+ /**
+ * Returns the QSB inside hotseat
+ */
+ public View getQsb() {
+ return mQsb;
+ }
+
+ /**
+ * Returns the Taskbar inside hotseat
+ */
+ public View getTaskbarView() {
+ return mTaskbarView;
}
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index bb60557..5612daf 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
@@ -105,6 +106,7 @@
public float iconSize;
public String iconShapePath;
public float landscapeIconSize;
+ public float landscapeIconTextSize;
public int iconBitmapSize;
public int fillResIconDpi;
public float iconTextSize;
@@ -131,6 +133,7 @@
* Do not query directly. see {@link DeviceProfile#isScalableGrid}.
*/
protected boolean isScalable;
+ public int devicePaddingId;
public String dbFile;
public int defaultLayoutId;
@@ -139,7 +142,7 @@
public DeviceProfile landscapeProfile;
public DeviceProfile portraitProfile;
- public DevicePaddings devicePaddings;
+ @Nullable public DevicePaddings devicePaddings;
public Point defaultWallpaperSize;
public Rect defaultWidgetPadding;
@@ -161,9 +164,11 @@
landscapeIconSize = p.landscapeIconSize;
iconBitmapSize = p.iconBitmapSize;
iconTextSize = p.iconTextSize;
+ landscapeIconTextSize = p.landscapeIconTextSize;
numHotseatIcons = p.numHotseatIcons;
numAllAppsColumns = p.numAllAppsColumns;
isScalable = p.isScalable;
+ devicePaddingId = p.devicePaddingId;
minCellHeight = p.minCellHeight;
minCellWidth = p.minCellWidth;
borderSpacing = p.borderSpacing;
@@ -224,17 +229,24 @@
.add(myDisplayOption);
result.iconSize = defaultDisplayOption.iconSize;
result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
- result.allAppsIconSize = Math.min(
- defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize);
+ if (defaultDisplayOption.allAppsIconSize < myDisplayOption.allAppsIconSize) {
+ result.allAppsIconSize = defaultDisplayOption.allAppsIconSize;
+ result.numAllAppsColumns = defaultDisplayOption.numAllAppsColumns;
+ } else {
+ result.allAppsIconSize = myDisplayOption.allAppsIconSize;
+ result.numAllAppsColumns = myDisplayOption.numAllAppsColumns;
+ }
result.minCellHeight = defaultDisplayOption.minCellHeight;
result.minCellWidth = defaultDisplayOption.minCellWidth;
result.borderSpacing = defaultDisplayOption.borderSpacing;
- devicePaddings = new DevicePaddings(context);
initGrid(context, myInfo, result);
}
public static String getCurrentGridName(Context context) {
+ if (ENABLE_TWO_PANEL_HOME.get()) {
+ return ENABLE_TWO_PANEL_HOME.key;
+ }
if (ENABLE_FOUR_COLUMNS.get()) {
return ENABLE_FOUR_COLUMNS.key;
}
@@ -258,7 +270,6 @@
ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
- devicePaddings = new DevicePaddings(context);
initGrid(context, displayInfo, displayOption);
return displayOption.grid.name;
}
@@ -274,8 +285,8 @@
demoModeLayoutId = closestProfile.demoModeLayoutId;
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
- numAllAppsColumns = closestProfile.numAllAppsColumns;
isScalable = closestProfile.isScalable;
+ devicePaddingId = closestProfile.devicePaddingId;
mExtraAttrs = closestProfile.extraAttrs;
@@ -284,11 +295,13 @@
landscapeIconSize = displayOption.landscapeIconSize;
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
iconTextSize = displayOption.iconTextSize;
+ landscapeIconTextSize = displayOption.landscapeIconTextSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
minCellHeight = displayOption.minCellHeight;
minCellWidth = displayOption.minCellWidth;
borderSpacing = displayOption.borderSpacing;
+ numAllAppsColumns = Math.round(displayOption.numAllAppsColumns);
if (Utilities.isGridOptionsEnabled(context)) {
allAppsIconSize = displayOption.allAppsIconSize;
@@ -298,6 +311,10 @@
allAppsIconTextSize = iconTextSize;
}
+ if (devicePaddingId != 0) {
+ devicePaddings = new DevicePaddings(context, devicePaddingId);
+ }
+
// If the partner customization apk contains any grid overrides, apply them
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, displayInfo.metrics);
@@ -605,12 +622,14 @@
private final int numHotseatIcons;
private final String dbFile;
- private final int numAllAppsColumns;
private final int defaultLayoutId;
private final int demoModeLayoutId;
private final boolean isScalable;
+ private final int devicePaddingId;
+
+ public final boolean visible;
private final SparseArray<TypedValue> extraAttrs;
@@ -632,11 +651,13 @@
R.styleable.GridDisplayOption_numFolderRows, numRows);
numFolderColumns = a.getInt(
R.styleable.GridDisplayOption_numFolderColumns, numColumns);
- numAllAppsColumns = a.getInt(
- R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
isScalable = a.getBoolean(
R.styleable.GridDisplayOption_isScalable, false);
+ devicePaddingId = a.getResourceId(
+ R.styleable.GridDisplayOption_devicePaddingId, 0);
+
+ visible = a.getBoolean(R.styleable.GridDisplayOption_visible, true);
a.recycle();
@@ -652,6 +673,7 @@
private final float minHeightDps;
private final boolean canBeDefault;
+ private float numAllAppsColumns;
private float minCellHeight;
private float minCellWidth;
private float borderSpacing;
@@ -659,6 +681,7 @@
private float iconSize;
private float iconTextSize;
private float landscapeIconSize;
+ private float landscapeIconTextSize;
private float allAppsIconSize;
private float allAppsIconTextSize;
@@ -672,6 +695,8 @@
minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
canBeDefault = a.getBoolean(
R.styleable.ProfileDisplayOption_canBeDefault, false);
+ numAllAppsColumns = a.getInt(R.styleable.ProfileDisplayOption_numAllAppsColumns,
+ grid.numColumns);
minCellHeight = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightDps, 0);
minCellWidth = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthDps, 0);
@@ -681,6 +706,8 @@
landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
iconSize);
iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+ landscapeIconTextSize = a.getFloat(
+ R.styleable.ProfileDisplayOption_landscapeIconTextSize, iconTextSize);
allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
iconSize);
@@ -698,16 +725,19 @@
minWidthDps = 0;
minHeightDps = 0;
canBeDefault = false;
+ numAllAppsColumns = 0;
minCellHeight = 0;
minCellWidth = 0;
borderSpacing = 0;
}
private DisplayOption multiply(float w) {
+ numAllAppsColumns *= w;
iconSize *= w;
landscapeIconSize *= w;
allAppsIconSize *= w;
iconTextSize *= w;
+ landscapeIconTextSize *= w;
allAppsIconTextSize *= w;
minCellHeight *= w;
minCellWidth *= w;
@@ -716,10 +746,12 @@
}
private DisplayOption add(DisplayOption p) {
+ numAllAppsColumns += p.numAllAppsColumns;
iconSize += p.iconSize;
landscapeIconSize += p.landscapeIconSize;
allAppsIconSize += p.allAppsIconSize;
iconTextSize += p.iconTextSize;
+ landscapeIconTextSize += p.landscapeIconTextSize;
allAppsIconTextSize += p.allAppsIconTextSize;
minCellHeight += p.minCellHeight;
minCellWidth += p.minCellWidth;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 80c80d7..5091543 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -34,7 +34,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET;
import static com.android.launcher3.LauncherState.NO_SCALE;
-import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
@@ -61,7 +60,10 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
-import android.app.ActivityOptions;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.ActivityNotFoundException;
@@ -114,7 +116,6 @@
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.allapps.search.LiveSearchManager;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -151,7 +152,6 @@
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.testing.TestLogging;
@@ -271,16 +271,10 @@
private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
- private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 1;
- private static final int SCRIM_VIEW_ALPHA_CHANNEL_INDEX = 0;
-
private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
- private LauncherAppTransitionManager mAppTransitionManager;
private Configuration mOldConfig;
- private LiveSearchManager mLiveSearchManager;
-
@Thunk
Workspace mWorkspace;
@Thunk
@@ -312,8 +306,6 @@
@Thunk
boolean mWorkspaceLoading = true;
- private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
-
// Used to notify when an activity launch has been deferred because launcher is not yet resumed
// TODO: See if we can remove this later
private Runnable mOnDeferredActivityLaunchCallback;
@@ -349,8 +341,6 @@
private RotationHelper mRotationHelper;
- private float mCurrentAssistantVisibility = 0f;
-
protected LauncherOverlayManager mOverlayManager;
// If true, overlay callbacks are deferred
private boolean mDeferOverlayCallbacks;
@@ -387,6 +377,44 @@
.build());
}
+ if (Utilities.IS_DEBUG_DEVICE && FeatureFlags.NOTIFY_CRASHES.get()) {
+ final String notificationChannelId = "com.android.launcher3.Debug";
+ final String notificationChannelName = "Debug";
+ final String notificationTag = "Debug";
+ final int notificationId = 0;
+
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ notificationChannelId, notificationChannelName,
+ NotificationManager.IMPORTANCE_HIGH));
+
+ Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> {
+ String stackTrace = Log.getStackTraceString(throwable);
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, stackTrace);
+ shareIntent = Intent.createChooser(shareIntent, null);
+ PendingIntent sharePendingIntent = PendingIntent.getActivity(
+ this, 0, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ Notification notification = new Notification.Builder(this, notificationChannelId)
+ .setSmallIcon(android.R.drawable.ic_menu_close_clear_cancel)
+ .setContentTitle("Launcher crash detected!")
+ .setStyle(new Notification.BigTextStyle().bigText(stackTrace))
+ .addAction(android.R.drawable.ic_menu_share, "Share", sharePendingIntent)
+ .build();
+ notificationManager.notify(notificationTag, notificationId, notification);
+
+ Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler =
+ Thread.getDefaultUncaughtExceptionHandler();
+ if (defaultUncaughtExceptionHandler != null) {
+ defaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
+ }
+ });
+ }
+
super.onCreate(savedInstanceState);
LauncherAppState app = LauncherAppState.getInstance(this);
@@ -405,8 +433,6 @@
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new StateManager<>(this, NORMAL);
- mLiveSearchManager = new LiveSearchManager(this);
-
mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
mAppWidgetManager = new WidgetManagerHelper(this);
@@ -419,9 +445,6 @@
crossFadeWithPreviousAppearance();
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
- mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
- mAppTransitionManager.registerRemoteAnimations();
-
boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
if (internalStateHandled) {
if (savedInstanceState != null) {
@@ -469,24 +492,6 @@
OverlayPlugin.class, false /* allowedMultiple */);
mRotationHelper.initialize();
-
- mStateManager.addStateListener(new StateListener<LauncherState>() {
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- float alpha = 1f - mCurrentAssistantVisibility;
- if (finalState == NORMAL) {
- mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
- } else if (finalState == OVERVIEW) {
- mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
- mScrimView.setAlpha(alpha);
- } else {
- mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f);
- mScrimView.setAlpha(1f);
- }
- }
- });
-
TraceHelper.INSTANCE.endSection(traceToken);
mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
@@ -497,10 +502,6 @@
}
}
- public LiveSearchManager getLiveSearchManager() {
- return mLiveSearchManager;
- }
-
protected LauncherOverlayManager getDefaultOverlay() {
return new LauncherOverlayManager() { };
}
@@ -579,15 +580,7 @@
}
public void onAssistantVisibilityChanged(float visibility) {
- mCurrentAssistantVisibility = visibility;
- float alpha = 1f - visibility;
- LauncherState state = mStateManager.getState();
- if (state == NORMAL) {
- mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
- } else if (state == OVERVIEW) {
- mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
- mScrimView.setAlpha(alpha);
- }
+ mHotseat.getQsb().setAlpha(1f - visibility);
}
private void initDeviceProfile(InvariantDeviceProfile idp) {
@@ -1043,7 +1036,19 @@
}
// When multiple pages are visible, show persistent page indicator
mWorkspace.getPageIndicator().setShouldAutoHide(!state.hasFlag(FLAG_MULTI_PAGE));
+
mPrevLauncherState = mStateManager.getCurrentStableState();
+ if (mPrevLauncherState != state && ALL_APPS.equals(state)
+ // Making sure mAllAppsSessionLogId is null to avoid double logging.
+ && mAllAppsSessionLogId == null) {
+ // creates new instance ID since new all apps session is started.
+ mAllAppsSessionLogId = new InstanceIdSequence().newInstanceId();
+ getStatsLogManager()
+ .logger()
+ .log(FeatureFlags.ENABLE_DEVICE_SEARCH.get()
+ ? LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH
+ : LAUNCHER_ALLAPPS_ENTRY);
+ }
}
@Override
@@ -1068,16 +1073,8 @@
getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
}
- if (ALL_APPS.equals(state)) {
- // creates new instance ID since new all apps session is started.
- mAllAppsSessionLogId = new InstanceIdSequence().newInstanceId();
- getStatsLogManager()
- .logger()
- .log(FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- ? LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH
- : LAUNCHER_ALLAPPS_ENTRY);
- } else if (ALL_APPS.equals(mPrevLauncherState)
- // Check if mLogInstanceId is not null to make sure exit event is logged only once.
+ if (mPrevLauncherState != state && !ALL_APPS.equals(state)
+ // Making sure mAllAppsSessionLogId is not null to avoid double logging.
&& mAllAppsSessionLogId != null) {
getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_EXIT);
mAllAppsSessionLogId = null;
@@ -1090,15 +1087,6 @@
TraceHelper.FLAG_UI_EVENT);
super.onResume();
- if (!mOnResumeCallbacks.isEmpty()) {
- final ArrayList<OnResumeCallback> resumeCallbacks = new ArrayList<>(mOnResumeCallbacks);
- mOnResumeCallbacks.clear();
- for (int i = resumeCallbacks.size() - 1; i >= 0; i--) {
- resumeCallbacks.get(i).onLauncherResume();
- }
- resumeCallbacks.clear();
- }
-
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
} else {
@@ -1203,8 +1191,7 @@
// Setup the drag controller (drop targets have to be added in reverse order in priority)
mDropTargetBar.setup(mDragController);
-
- mAllAppsController.setupViews(mAppsView, mScrimView);
+ mAllAppsController.setupViews(mAppsView);
}
/**
@@ -1609,10 +1596,7 @@
LauncherAppState.getIDP(this).removeOnChangeListener(this);
mOverlayManager.onActivityDestroyed(this);
- mAppTransitionManager.unregisterRemoteAnimations();
- mAppTransitionManager.unregisterRemoteTransitions();
mUserChangedCallbackCloseable.close();
- mLiveSearchManager.stop();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1809,9 +1793,7 @@
@Override
public Rect getFolderBoundingBox() {
// We need to bound the folder to the currently visible workspace area
- Rect folderBoundingBox = new Rect();
- getWorkspace().getPageAreaRelativeToDragLayer(folderBoundingBox);
- return folderBoundingBox;
+ return getWorkspace().getPageAreaRelativeToDragLayer();
}
@Override
@@ -1936,16 +1918,6 @@
@TargetApi(Build.VERSION_CODES.M)
@Override
- public ActivityOptions getActivityLaunchOptions(View v) {
- return mAppTransitionManager.getActivityLaunchOptions(this, v);
- }
-
- public LauncherAppTransitionManager getAppTransitionManager() {
- return mAppTransitionManager;
- }
-
- @TargetApi(Build.VERSION_CODES.M)
- @Override
protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
// Due to legacy reasons, direct call shortcuts require Launchers to have the
// corresponding permission. Show the appropriate permission prompt if that
@@ -1994,7 +1966,7 @@
// state when we return to launcher.
BubbleTextView btv = (BubbleTextView) v;
btv.setStayPressed(true);
- addOnResumeCallback(btv);
+ addOnResumeCallback(() -> btv.setStayPressed(false));
}
return success;
}
@@ -2038,10 +2010,6 @@
return result;
}
- public void addOnResumeCallback(OnResumeCallback callback) {
- mOnResumeCallbacks.add(callback);
- }
-
/**
* Persistant callback which notifies when an activity launch is deferred because the activity
* was not yet resumed.
@@ -2676,6 +2644,7 @@
mDragLayer.dump(prefix, writer);
mStateManager.dump(prefix, writer);
mPopupDataProvider.dump(prefix, writer);
+ mDeviceProfile.dump(prefix, writer);
try {
FileLog.flushAll(writer);
@@ -2804,6 +2773,13 @@
return new float[] {NO_SCALE, NO_OFFSET};
}
+ /**
+ * @see LauncherState#getTaskbarScale(Launcher)
+ */
+ public float getNormalTaskbarScale() {
+ return 1f;
+ }
+
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -2815,15 +2791,6 @@
return (T) activityContext;
}
-
- /**
- * Callback for listening for onResume
- */
- public interface OnResumeCallback {
-
- void onLauncherResume();
- }
-
/**
* Cross-fades the launcher's updated appearance with its previous appearance.
*
@@ -2866,6 +2833,10 @@
return false;
}
+ public boolean supportsAdaptiveIconAnimation(View clickedView) {
+ return false;
+ }
+
public DragOptions getDefaultWorkspaceDragOptions() {
return new DragOptions();
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 57d7600..4754558 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,8 +17,11 @@
package com.android.launcher3;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
-import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import android.content.ComponentName;
import android.content.Context;
@@ -37,10 +40,10 @@
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.widget.custom.CustomWidgetManager;
@@ -113,7 +116,7 @@
mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
mSettingsCache.register(NOTIFICATION_BADGING_URI,
mNotificationSettingsChangedListener);
- mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
+ onNotificationSettingsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));
}
public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -122,6 +125,34 @@
mContext = context;
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
+
+ // b/175329686 Temporary logic to gracefully migrate group of users to the new 4x5 grid.
+ String gridName = InvariantDeviceProfile.getCurrentGridName(context);
+ if (ENABLE_FOUR_COLUMNS.get()
+ || "reasonable".equals(gridName)
+ || ENABLE_FOUR_COLUMNS.key.equals(gridName)) {
+ // Reset flag and remove it from developer options to prevent it from being enabled
+ // again.
+ ENABLE_FOUR_COLUMNS.reset(context);
+ FeatureFlags.removeFlag(ENABLE_FOUR_COLUMNS);
+
+ // Force migration code to run
+ Utilities.getPrefs(context).edit()
+ .remove(KEY_MIGRATION_SRC_HOTSEAT_COUNT)
+ .remove(KEY_MIGRATION_SRC_WORKSPACE_SIZE)
+ .apply();
+
+ // We make an empty call here to ensure the database is created with the old IDP grid,
+ // so that when we set the new grid the migration can proceeds as expected.
+ LauncherSettings.Settings.call(context.getContentResolver(), "");
+
+ String newGridName = "practical";
+ Utilities.getPrefs(mContext).edit().putString("idp_grid_name", newGridName).commit();
+ mInvariantDeviceProfile.setCurrentGrid(context, "practical");
+ } else {
+ FeatureFlags.removeFlag(ENABLE_FOUR_COLUMNS);
+ }
+
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
deleted file mode 100644
index 0fa441a..0000000
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Manages the opening and closing app transitions from Launcher.
- */
-public class LauncherAppTransitionManager implements ResourceBasedOverride {
-
- public static LauncherAppTransitionManager newInstance(Context context) {
- return Overrides.getObject(LauncherAppTransitionManager.class,
- context, R.string.app_transition_manager_class);
- }
-
- public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
- int left = 0, top = 0;
- int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
- if (v instanceof BubbleTextView) {
- // Launch from center of icon, not entire view
- Drawable icon = ((BubbleTextView) v).getIcon();
- if (icon != null) {
- Rect bounds = icon.getBounds();
- left = (width - bounds.width()) / 2;
- top = v.getPaddingTop();
- width = bounds.width();
- height = bounds.height();
- }
- }
- return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
- }
-
- public boolean supportsAdaptiveIconAnimation(View clickedView) {
- return false;
- }
-
- /**
- * Registers remote animations for certain system transitions.
- */
- public void registerRemoteAnimations() {
- // Do nothing
- }
-
- /**
- * Unregisters all remote animations.
- */
- public void unregisterRemoteAnimations() {
- // Do nothing
- }
-
- /**
- * Registers remote transitions for certain system transitions.
- */
- public void registerRemoteTransitions() {
- // Do nothing
- }
-
- /**
- * Unregisters all remote transitions.
- */
- public void unregisterRemoteTransitions() {
- // Do nothing
- }
-}
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 25afb55..6c0daa4 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -15,6 +15,7 @@
private static final String XML = ".xml";
public static final String LAUNCHER_DB = "launcher.db";
+ public static final String LAUNCHER_4_BY_5_DB = "launcher_4_by_5.db";
public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db";
public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db";
public static final String LAUNCHER_2_BY_2_DB = "launcher_2_by_2.db";
@@ -30,6 +31,7 @@
public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
LAUNCHER_DB,
+ LAUNCHER_4_BY_5_DB,
LAUNCHER_4_BY_4_DB,
LAUNCHER_3_BY_3_DB,
LAUNCHER_2_BY_2_DB,
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 76c4518..83ddf64 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -41,8 +41,15 @@
}
private void handleSystemWindowInsets(Rect insets) {
- // Update device profile before notifying th children.
- mActivity.getDeviceProfile().updateInsets(insets);
+ DeviceProfile dp = mActivity.getDeviceProfile();
+
+ // Taskbar provides insets, but we don't want that for most Launcher elements so remove it.
+ mTempRect.set(insets);
+ insets = mTempRect;
+ insets.bottom = Math.max(0, insets.bottom - dp.nonOverlappingTaskbarInset);
+
+ // Update device profile before notifying the children.
+ dp.updateInsets(insets);
boolean resetState = !insets.equals(mInsets);
setInsets(insets);
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 21c40ef..0c509a1 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -17,11 +17,14 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
@@ -29,7 +32,6 @@
import android.content.Context;
import android.view.animation.Interpolator;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.HintState;
@@ -51,19 +53,13 @@
*/
public static final int NONE = 0;
public static final int HOTSEAT_ICONS = 1 << 0;
- public static final int HOTSEAT_SEARCH_BOX = 1 << 1;
- public static final int ALL_APPS_HEADER = 1 << 2;
- public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
- public static final int ALL_APPS_CONTENT = 1 << 4;
- public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
- public static final int OVERVIEW_ACTIONS = 1 << 6;
- public static final int TASKBAR = 1 << 7;
- public static final int CLEAR_ALL_BUTTON = 1 << 8;
- public static final int WORKSPACE_PAGE_INDICATOR = 1 << 9;
-
- /** Mask of all the items that are contained in the apps view. */
- public static final int APPS_VIEW_ITEM_MASK =
- HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+ public static final int ALL_APPS_CONTENT = 1 << 1;
+ public static final int VERTICAL_SWIPE_INDICATOR = 1 << 2;
+ public static final int OVERVIEW_ACTIONS = 1 << 3;
+ public static final int TASKBAR = 1 << 4;
+ public static final int CLEAR_ALL_BUTTON = 1 << 5;
+ public static final int WORKSPACE_PAGE_INDICATOR = 1 << 6;
+ public static final int SPLIT_PLACHOLDER_VIEW = 1 << 7;
// Flag indicating workspace has multiple pages visible.
public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
@@ -95,7 +91,7 @@
}
};
- private static final LauncherState[] sAllStates = new LauncherState[9];
+ private static final LauncherState[] sAllStates = new LauncherState[10];
/**
* TODO: Create a separate class for NORMAL state.
@@ -118,6 +114,8 @@
SPRING_LOADED_STATE_ORDINAL);
public static final LauncherState ALL_APPS = new AllAppsState(ALL_APPS_STATE_ORDINAL);
public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL);
+ public static final LauncherState HINT_STATE_TWO_BUTTON = new HintState(
+ HINT_STATE_TWO_BUTTON_ORDINAL, LAUNCHER_STATE_OVERVIEW);
public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
public static final LauncherState OVERVIEW_MODAL_TASK = OverviewState.newModalTaskState(
@@ -126,6 +124,8 @@
OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
public static final LauncherState BACKGROUND_APP =
OverviewState.newBackgroundState(BACKGROUND_APP_STATE_ORDINAL);
+ public static final LauncherState OVERVIEW_SPLIT_SELECT =
+ OverviewState.newSplitSelectState(OVERVIEW_SPLIT_SELECT_ORDINAL);
public final int ordinal;
@@ -180,8 +180,8 @@
return launcher.getNormalOverviewScaleAndOffset();
}
- public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
+ public float getTaskbarScale(Launcher launcher) {
+ return launcher.getNormalTaskbarScale();
}
public float getOverviewFullscreenProgress() {
@@ -189,15 +189,7 @@
}
public int getVisibleElements(Launcher launcher) {
- DeviceProfile deviceProfile = launcher.getDeviceProfile();
- int flags = WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
- if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !deviceProfile.isVerticalBarLayout()) {
- flags |= HOTSEAT_SEARCH_BOX;
- }
- if (!deviceProfile.isTaskbarPresent) {
- flags |= HOTSEAT_ICONS;
- }
- return flags;
+ return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
}
/**
@@ -217,11 +209,11 @@
return 1f;
}
- public float getWorkspaceScrimAlpha(Launcher launcher) {
+ public float getWorkspaceBackgroundAlpha(Launcher launcher) {
return 0;
}
- public float getOverviewScrimAlpha(Launcher launcher) {
+ public float getWorkspaceScrimAlpha(Launcher launcher) {
return 0;
}
@@ -234,10 +226,11 @@
}
/**
- * For this state, whether tasks should layout as a grid rather than a list.
+ * For this state, how much additional vertical translation there should be for each of the
+ * child TaskViews.
*/
- public boolean displayOverviewTasksAsGrid(Launcher launcher) {
- return false;
+ public float getOverviewSecondaryTranslation(Launcher launcher) {
+ return 0;
}
/**
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 50f1e44..01f7c71 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -63,6 +63,7 @@
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
+import java.util.List;
/**
* An abstraction of the original Workspace which supports browsing through a
@@ -282,7 +283,47 @@
private int validateNewPage(int newPage) {
newPage = ensureWithinScrollBounds(newPage);
// Ensure that it is clamped by the actual set of children in all cases
- return Utilities.boundToRange(newPage, 0, getPageCount() - 1);
+ newPage = Utilities.boundToRange(newPage, 0, getPageCount() - 1);
+
+ if (getPanelCount() > 1) {
+ // Always return left panel as new page
+ newPage = getLeftmostVisiblePageForIndex(newPage);
+ }
+ return newPage;
+ }
+
+ private int getLeftmostVisiblePageForIndex(int pageIndex) {
+ int panelCount = getPanelCount();
+ return (pageIndex / panelCount) * panelCount;
+ }
+
+ /**
+ * Returns the number of pages that are shown at the same time.
+ */
+ protected int getPanelCount() {
+ return 1;
+ }
+
+ /**
+ * Returns the currently visible pages.
+ */
+ public Iterable<View> getVisiblePages() {
+ int panelCount = getPanelCount();
+ List<View> visiblePages = new ArrayList<>(panelCount);
+ for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) {
+ View page = getPageAt(i);
+ if (page != null) {
+ visiblePages.add(page);
+ }
+ }
+ return visiblePages;
+ }
+
+ /**
+ * Returns true if the view is on one of the current pages, false otherwise.
+ */
+ public boolean isVisible(View child) {
+ return getLeftmostVisiblePageForIndex(indexOfChild(child)) == mCurrentPage;
}
/**
@@ -548,6 +589,10 @@
super.forceLayout();
}
+ private int getPageWidthSize(int widthSize) {
+ return (widthSize - mInsets.left - mInsets.right) / getPanelCount();
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getChildCount() == 0) {
@@ -578,7 +623,7 @@
if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
int myWidthSpec = MeasureSpec.makeMeasureSpec(
- widthSize - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
+ getPageWidthSize(widthSize), MeasureSpec.EXACTLY);
int myHeightSpec = MeasureSpec.makeMeasureSpec(
heightSize - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
@@ -670,11 +715,10 @@
final int primaryDimension = bounds.primaryDimension;
final int childPrimaryEnd = bounds.childPrimaryEnd;
- // In case the pages are of different width, align the page to left or right edge
- // based on the orientation.
- final int pageScroll = mIsRtl
- ? (childStart - scrollOffsetStart)
- : Math.max(0, childPrimaryEnd - scrollOffsetEnd);
+ // In case the pages are of different width, align the page to left edge for non-RTL
+ // or right edge for RTL.
+ final int pageScroll =
+ mIsRtl ? childPrimaryEnd - scrollOffsetEnd : childStart - scrollOffsetStart;
if (outPageScrolls[i] != pageScroll) {
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
@@ -682,6 +726,19 @@
childStart += primaryDimension + mPageSpacing + getChildGap();
}
}
+
+ int panelCount = getPanelCount();
+ if (panelCount > 1) {
+ for (int i = 0; i < childCount; i++) {
+ // In case we have multiple panels, always use left panel's page scroll for all
+ // panels on the screen.
+ int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
+ if (outPageScrolls[i] != adjustedScroll) {
+ outPageScrolls[i] = adjustedScroll;
+ pageScrollChanged = true;
+ }
+ }
+ }
return pageScrollChanged;
}
@@ -794,14 +851,16 @@
}
if (direction == View.FOCUS_LEFT) {
if (getCurrentPage() > 0) {
- snapToPage(getCurrentPage() - 1);
- getChildAt(getCurrentPage() - 1).requestFocus(direction);
+ int nextPage = validateNewPage(getCurrentPage() - 1);
+ snapToPage(nextPage);
+ getChildAt(nextPage).requestFocus(direction);
return true;
}
} else if (direction == View.FOCUS_RIGHT) {
if (getCurrentPage() < getPageCount() - 1) {
- snapToPage(getCurrentPage() + 1);
- getChildAt(getCurrentPage() + 1).requestFocus(direction);
+ int nextPage = validateNewPage(getCurrentPage() + 1);
+ snapToPage(nextPage);
+ getChildAt(nextPage).requestFocus(direction);
return true;
}
}
@@ -820,11 +879,13 @@
}
if (direction == View.FOCUS_LEFT) {
if (mCurrentPage > 0) {
- getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
+ int nextPage = validateNewPage(mCurrentPage - 1);
+ getPageAt(nextPage).addFocusables(views, direction, focusableMode);
}
- } else if (direction == View.FOCUS_RIGHT){
+ } else if (direction == View.FOCUS_RIGHT) {
if (mCurrentPage < getPageCount() - 1) {
- getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
+ int nextPage = validateNewPage(mCurrentPage + 1);
+ getPageAt(nextPage).addFocusables(views, direction, focusableMode);
}
}
}
@@ -1004,10 +1065,7 @@
// Try canceling the long press. It could also have been scheduled
// by a distant descendant, so use the mAllowLongPress flag to block
// everything
- final View currentPage = getPageAt(mCurrentPage);
- if (currentPage != null) {
- currentPage.cancelLongPress();
- }
+ getVisiblePages().forEach(View::cancelLongPress);
}
protected float getScrollProgress(int screenCenter, View v, int page) {
@@ -1255,12 +1313,14 @@
if (((isSignificantMove && !isDeltaLeft && !isFling) ||
(isFling && !isVelocityLeft)) && mCurrentPage > 0) {
- finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+ finalPage = returnToOriginalPage
+ ? mCurrentPage : mCurrentPage - getPanelCount();
snapToPageWithVelocity(finalPage, velocity);
} else if (((isSignificantMove && isDeltaLeft && !isFling) ||
(isFling && isVelocityLeft)) &&
mCurrentPage < getChildCount() - 1) {
- finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
+ finalPage = returnToOriginalPage
+ ? mCurrentPage : mCurrentPage + getPanelCount();
snapToPageWithVelocity(finalPage, velocity);
} else {
snapToDestination();
@@ -1449,17 +1509,16 @@
return getDestinationPage(mOrientationHandler.getPrimaryScroll(this));
}
- protected int getDestinationPage(int scaledScroll) {
- return getPageNearestToCenterOfScreen(scaledScroll);
+ protected int getDestinationPage(int primaryScroll) {
+ return getPageNearestToCenterOfScreen(primaryScroll);
}
public int getPageNearestToCenterOfScreen() {
return getPageNearestToCenterOfScreen(mOrientationHandler.getPrimaryScroll(this));
}
- private int getPageNearestToCenterOfScreen(int scaledScroll) {
- int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
- int screenCenter = scaledScroll + (pageOrientationSize / 2);
+ private int getPageNearestToCenterOfScreen(int primaryScroll) {
+ int screenCenter = getScreenCenter(primaryScroll);
int minDistanceFromScreenCenter = Integer.MAX_VALUE;
int minDistanceFromScreenCenterIndex = -1;
final int childCount = getChildCount();
@@ -1475,18 +1534,26 @@
}
private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) {
- int childSize = getChildVisibleSize(childIndex);
+ int childSize = Math.round(getChildVisibleSize(childIndex));
int halfChildSize = (childSize / 2);
int childCenter = getChildOffset(childIndex) + halfChildSize;
return childCenter - screenCenter;
}
protected int getDisplacementFromScreenCenter(int childIndex) {
- int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
- int screenCenter = mOrientationHandler.getPrimaryScroll(this) + (pageOrientationSize / 2);
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int screenCenter = getScreenCenter(primaryScroll);
return getDisplacementFromScreenCenter(childIndex, screenCenter);
}
+ private int getScreenCenter(int primaryScroll) {
+ float primaryScale = mOrientationHandler.getPrimaryScale(this);
+ float primaryPivot = mOrientationHandler.getPrimaryValue(getPivotX(), getPivotY());
+ int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
+ return Math.round(primaryScroll + (pageOrientationSize / 2f - primaryPivot) / primaryScale
+ + primaryPivot);
+ }
+
protected void snapToDestination() {
snapToPage(getDestinationPage(), getPageSnapDuration());
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 4fd87cb..858b72e 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -33,7 +33,6 @@
import android.view.View;
import android.widget.Toast;
-import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
@@ -45,7 +44,6 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import java.net.URISyntaxException;
@@ -110,15 +108,12 @@
mCurrentAccessibilityAction = action;
if (action == UNINSTALL) {
- mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
setDrawable(R.drawable.ic_uninstall_shadow);
updateText(R.string.uninstall_drop_target_label);
} else if (action == DISMISS_PREDICTION) {
- mHoverColor = Themes.getColorAccent(getContext());
setDrawable(R.drawable.ic_block_shadow);
updateText(R.string.dismiss_prediction_label);
} else if (action == RECONFIGURE) {
- mHoverColor = Themes.getColorAccent(getContext());
setDrawable(R.drawable.ic_setup_shadow);
updateText(R.string.gadget_setup_text);
}
@@ -228,7 +223,7 @@
DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
if (target != null) {
deferred.mPackageName = target.getPackageName();
- mLauncher.addOnResumeCallback(deferred);
+ mLauncher.addOnResumeCallback(deferred::onLauncherResume);
} else {
deferred.sendFailure();
}
@@ -311,7 +306,7 @@
* A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until
* {@link #onLauncherResume}
*/
- private class DeferredOnComplete implements DragSource, OnResumeCallback {
+ private class DeferredOnComplete implements DragSource {
private final DragSource mOriginal;
private final Context mContext;
@@ -330,7 +325,6 @@
mDragObject = d;
}
- @Override
public void onLauncherResume() {
// We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 1bbbb2b..5036104 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Log;
import androidx.annotation.WorkerThread;
@@ -36,6 +37,8 @@
*/
public class SessionCommitReceiver extends BroadcastReceiver {
+ private static final String LOG = "SessionCommitReceiver";
+
// Preference key for automatically adding icon to homescreen.
public static final String ADD_ICON_PREFERENCE_KEY = "pref_add_icon_to_home";
@@ -68,6 +71,11 @@
return;
}
+ Log.i(LOG,
+ "Adding package name to install queue. Package name: " + info.getAppPackageName()
+ + ", has app icon: " + (info.getAppIcon() != null)
+ + ", has app label: " + !TextUtils.isEmpty(info.getAppLabel()));
+
ItemInstallQueue.INSTANCE.get(context)
.queueItem(info.getAppPackageName(), user);
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index c440303..1776777 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -37,6 +37,8 @@
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.LightingColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
@@ -64,11 +66,13 @@
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
+import androidx.core.graphics.ColorUtils;
import androidx.core.os.BuildCompat;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.GridCustomizationsProvider;
import com.android.launcher3.graphics.TintedDrawableSpan;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShortcutCachingLogic;
@@ -690,6 +694,64 @@
};
}
+ /**
+ * Compares the ratio of two quantities and returns whether that ratio is greater than the
+ * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+ * of a percentage.
+ */
+ public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+ float bound) {
+ return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+ }
+
+ /**
+ * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
+ * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
+ * the final bounds.
+ */
+ public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
+ int delta) {
+ int rdelta = ((delta % 4) + 4) % 4;
+ int origLeft = inOutBounds.left;
+ switch (rdelta) {
+ case 0:
+ return;
+ case 1:
+ inOutBounds.left = inOutBounds.top;
+ inOutBounds.top = parentWidth - inOutBounds.right;
+ inOutBounds.right = inOutBounds.bottom;
+ inOutBounds.bottom = parentWidth - origLeft;
+ return;
+ case 2:
+ inOutBounds.left = parentWidth - inOutBounds.right;
+ inOutBounds.right = parentWidth - origLeft;
+ return;
+ case 3:
+ inOutBounds.left = parentHeight - inOutBounds.bottom;
+ inOutBounds.bottom = inOutBounds.right;
+ inOutBounds.right = parentHeight - inOutBounds.top;
+ inOutBounds.top = origLeft;
+ return;
+ }
+ }
+
+ /**
+ * Make a color filter that blends a color into the destination based on a scalable amout.
+ *
+ * @param color to blend in.
+ * @param tintAmount [0-1] 0 no tinting, 1 full color.
+ * @return ColorFilter for tinting, or {@code null} if no filter is needed.
+ */
+ public static ColorFilter makeColorTintingColorFilter(int color, float tintAmount) {
+ if (tintAmount == 0f) {
+ return null;
+ }
+ return new LightingColorFilter(
+ // This isn't blending in white, its making a multiplication mask for the base color
+ ColorUtils.blendARGB(Color.WHITE, 0, tintAmount),
+ ColorUtils.blendARGB(0, color, tintAmount));
+ }
+
private static class FixedSizeEmptyDrawable extends ColorDrawable {
private final int mSize;
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 446c873..f43452c 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -37,6 +37,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
@@ -540,20 +541,17 @@
c.setBitmap(preview);
c.drawColor(0, PorterDuff.Mode.CLEAR);
}
- RectF boxRect = drawBoxWithShadow(c, size, size);
+
+ drawBoxWithShadow(c, size, size);
LauncherIcons li = LauncherIcons.obtain(mContext);
- Bitmap icon = li.createBadgedIconBitmap(
+ Drawable icon = li.createBadgedIconBitmap(
mutateOnMainThread(info.getFullResIcon(mIconCache)),
- Process.myUserHandle(), 0).icon;
+ Process.myUserHandle(), 0).newIcon(launcher);
li.recycle();
- Rect src = new Rect(0, 0, icon.getWidth(), icon.getHeight());
-
- boxRect.set(0, 0, iconSize, iconSize);
- boxRect.offset(padding, padding);
- c.drawBitmap(icon, src, boxRect,
- new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
+ icon.setBounds(padding, padding, padding + iconSize, padding + iconSize);
+ icon.draw(c);
c.setBitmap(null);
return preview;
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 87fb6fb..981cf89 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -69,6 +69,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -82,6 +83,7 @@
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.graphics.WorkspaceDragScrim;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
@@ -110,6 +112,7 @@
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetManagerHelper;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
import java.util.ArrayList;
@@ -186,7 +189,6 @@
@Thunk final Launcher mLauncher;
@Thunk DragController mDragController;
- private final Rect mTempRect = new Rect();
private final int[] mTempXY = new int[2];
private final float[] mTempFXY = new float[2];
@Thunk float[] mDragViewVisualCenter = new float[2];
@@ -310,8 +312,12 @@
Rect padding = grid.workspacePadding;
setPadding(padding.left, padding.top, padding.right, padding.bottom);
mInsets.set(insets);
+ // Increase our bottom insets so we don't overlap with the taskbar.
+ mInsets.bottom += grid.nonOverlappingTaskbarInset;
- if (mWorkspaceFadeInAdjacentScreens) {
+ if (isTwoPanelEnabled()) {
+ setPageSpacing(0); // we have two pages and we don't want any spacing
+ } else if (mWorkspaceFadeInAdjacentScreens) {
// In landscape mode the page spacing is set to the default.
setPageSpacing(grid.edgeMarginPx);
} else {
@@ -323,12 +329,30 @@
setPageSpacing(Math.max(maxInsets, maxPadding));
}
-
int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
int paddingBottom = grid.cellLayoutBottomPaddingPx;
+ int twoPanelLandscapeSidePadding = paddingLeftRight * 2;
+ int twoPanelPortraitSidePadding = paddingLeftRight / 2;
+
+ int panelCount = getPanelCount();
for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
- mWorkspaceScreens.valueAt(i)
- .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
+ int paddingLeft = paddingLeftRight;
+ int paddingRight = paddingLeftRight;
+ if (panelCount > 1) {
+ if (i % panelCount == 0) { // left side panel
+ paddingLeft = grid.isLandscape ? twoPanelLandscapeSidePadding
+ : twoPanelPortraitSidePadding;
+ paddingRight = 0;
+ } else if (i % panelCount == panelCount - 1) { // right side panel
+ paddingLeft = 0;
+ paddingRight = grid.isLandscape ? twoPanelLandscapeSidePadding
+ : twoPanelPortraitSidePadding;
+ } else { // middle panel
+ paddingLeft = 0;
+ paddingRight = 0;
+ }
+ }
+ mWorkspaceScreens.valueAt(i).setPadding(paddingLeft, 0, paddingRight, paddingBottom);
}
}
@@ -367,10 +391,19 @@
}
public float getWallpaperOffsetForCenterPage() {
- int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen());
+ return getWallpaperOffsetForPage(getPageNearestToCenterOfScreen());
+ }
+
+ private float getWallpaperOffsetForPage(int page) {
+ int pageScroll = getScrollForPage(page);
return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
}
+ /** Returns the number of pages used for the wallpaper parallax. */
+ public int getNumPagesForWallpaperParallax() {
+ return mWallpaperOffset.getNumPagesForWallpaperParallax();
+ }
+
public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
Rect r = new Rect();
cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
@@ -388,15 +421,6 @@
layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
}
- if (mOutlineProvider != null) {
- if (dragObject.dragView != null) {
- Bitmap preview = dragObject.dragView.getPreviewBitmap();
-
- // The outline is used to visualize where the item will land if dropped
- mOutlineProvider.generateDragOutline(preview);
- }
- }
-
updateChildrenLayersEnabled();
// Do not add a new page if it is a accessible drag which was not started by the workspace.
@@ -435,6 +459,15 @@
.log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
}
+ private boolean isTwoPanelEnabled() {
+ return mLauncher.mDeviceProfile.isTablet && FeatureFlags.ENABLE_TWO_PANEL_HOME.get();
+ }
+
+ @Override
+ protected int getPanelCount() {
+ return isTwoPanelEnabled() ? 2 : super.getPanelCount();
+ }
+
public void deferRemoveExtraEmptyScreen() {
mDeferRemoveExtraEmptyScreen = true;
}
@@ -822,7 +855,7 @@
private boolean shouldConsumeTouch(View v) {
return !workspaceIconsCanBeDragged()
- || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
+ || (!workspaceInModalState() && !isVisible(v));
}
public boolean isSwitchingState() {
@@ -1423,7 +1456,7 @@
// TAPL can work only if UIDevice is set up as setCompressedLayoutHeirarchy(false).
// Hiding workspace from the tests when it's
// IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS.
- return null;
+ return AccessibilityNodeInfo.obtain();
}
return super.createAccessibilityNodeInfo();
}
@@ -1496,10 +1529,10 @@
draggableView = (DraggableView) child;
}
- // The drag bitmap follows the touch point around on the screen
- final Bitmap b = previewProvider.createDragBitmap();
+ // The draggable drawable follows the touch point around on the screen
+ final Drawable drawable = previewProvider.createDrawable();
int halfPadding = previewProvider.previewPadding / 2;
- float scale = previewProvider.getScaleAndPosition(b, mTempXY);
+ float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
int dragLayerX = mTempXY[0];
int dragLayerY = mTempXY[1];
@@ -1525,9 +1558,21 @@
}
}
- DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
- dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
- scale, dragOptions);
+ if (drawable instanceof AppWidgetHostViewDrawable) {
+ mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
+ }
+ DragView dv = mDragController.startDrag(
+ drawable,
+ draggableView,
+ dragLayerX,
+ dragLayerY,
+ source,
+ dragObject,
+ dragVisualizeOffset,
+ dragRect,
+ scale * iconScale,
+ scale,
+ dragOptions);
dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
return dv;
}
@@ -1955,16 +2000,26 @@
}
/**
- * Computes the area relative to dragLayer which is used to display a page.
+ * Computes and returns the area relative to dragLayer which is used to display a page.
+ * In case we have multiple pages displayed at the same time, we return the union of the areas.
*/
- public void getPageAreaRelativeToDragLayer(Rect outArea) {
- CellLayout child = (CellLayout) getChildAt(getNextPage());
- if (child == null) {
- return;
+ public Rect getPageAreaRelativeToDragLayer() {
+ Rect area = new Rect();
+ int nextPage = getNextPage();
+ int panelCount = getPanelCount();
+ for (int page = nextPage; page < nextPage + panelCount; page++) {
+ CellLayout child = (CellLayout) getChildAt(page);
+ if (child == null) {
+ break;
+ }
+
+ ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
+ Rect tmpRect = new Rect();
+ mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, tmpRect);
+ area.union(tmpRect);
}
- ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
- mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, outArea);
+ return area;
}
@Override
@@ -2249,19 +2304,27 @@
int nextPage = getNextPage();
if (layout == null && !isPageInTransition()) {
- // Check if the item is dragged over left page
+ // Check if the item is dragged over currentPage - 1 page
mTempTouchCoordinates[0] = Math.min(centerX, d.x);
mTempTouchCoordinates[1] = d.y;
layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates);
}
if (layout == null && !isPageInTransition()) {
- // Check if the item is dragged over right page
+ // Check if the item is dragged over currentPage + 1 page
mTempTouchCoordinates[0] = Math.max(centerX, d.x);
mTempTouchCoordinates[1] = d.y;
layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates);
}
+ // If two panel is enabled, users can also drag items to currentPage + 2
+ if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) {
+ // Check if the item is dragged over currentPage + 2 page
+ mTempTouchCoordinates[0] = Math.max(centerX, d.x);
+ mTempTouchCoordinates[1] = d.y;
+ layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), mTempTouchCoordinates);
+ }
+
// Always pick the current page.
if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
layout = (CellLayout) getChildAt(nextPage);
@@ -2575,7 +2638,11 @@
}
- public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+ private Drawable createWidgetDrawable(ItemInfo widgetInfo, View layout) {
+ if (layout instanceof LauncherAppWidgetHostView) {
+ return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) layout);
+ }
+
int[] unScaledSize = estimateItemSize(widgetInfo);
int visibility = layout.getVisibility();
layout.setVisibility(VISIBLE);
@@ -2587,7 +2654,7 @@
Bitmap b = BitmapRenderer.createHardwareBitmap(
unScaledSize[0], unScaledSize[1], layout::draw);
layout.setVisibility(visibility);
- return b;
+ return new FastBitmapDrawable(b);
}
private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
@@ -2657,8 +2724,8 @@
boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
- Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
- dragView.setCrossFadeBitmap(crossFadeBitmap);
+ Drawable crossFadeDrawable = createWidgetDrawable(info, finalView);
+ dragView.setCrossFadeDrawable(crossFadeDrawable);
dragView.crossFade((int) (duration * 0.8f));
} else if (isWidget && external) {
scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
@@ -2911,8 +2978,10 @@
* @param user The user of the app to match.
*/
public View getFirstMatchForAppClose(String packageName, UserHandle user) {
- final int curPage = getCurrentPage();
- final CellLayout currentPage = (CellLayout) getPageAt(curPage);
+ List<CellLayout> cellLayouts = new ArrayList<>(getPanelCount() + 1);
+ cellLayouts.add(getHotseat());
+ getVisiblePages().forEach(page -> cellLayouts.add((CellLayout) page));
+
final Workspace.ItemOperator packageAndUser = (ItemInfo info, View view) -> info != null
&& info.getTargetComponent() != null
&& TextUtils.equals(info.getTargetComponent().getPackageName(), packageName)
@@ -2933,13 +3002,11 @@
// Order: App icons, app in folder. Items in hotseat get returned first.
if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
- return getFirstMatch(new CellLayout[] { getHotseat(), currentPage },
- packageAndUserAndApp, packageAndUserAndAppInFolder);
+ return getFirstMatch(cellLayouts, packageAndUserAndApp, packageAndUserAndAppInFolder);
} else {
// Do not use Folder as a criteria, since it'll cause a crash when trying to draw
// FolderAdaptiveIcon as the background.
- return getFirstMatch(new CellLayout[] { getHotseat(), currentPage },
- packageAndUserAndApp);
+ return getFirstMatch(cellLayouts, packageAndUserAndApp);
}
}
@@ -2973,7 +3040,7 @@
* @param operators List of operators, in order starting from best matching operator.
* @return
*/
- View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
+ View getFirstMatch(Iterable<CellLayout> cellLayouts, final ItemOperator... operators) {
// This array is filled with the first match for each operator.
final View[] matches = new View[operators.length];
// For efficiency, the outer loop should be CellLayout.
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 660eeab..24de19f 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -34,11 +34,11 @@
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
-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_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
import android.animation.ValueAnimator;
@@ -47,7 +47,6 @@
import com.android.launcher3.LauncherState.PageAlphaProvider;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringAnimationBuilder;
@@ -96,7 +95,6 @@
ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
mLauncher);
- ScaleAndTranslation qsbScaleAndTranslation = state.getQsbScaleAndTranslation(mLauncher);
mNewScale = scaleAndTranslation.scale;
PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
final int childCount = mWorkspace.getChildCount();
@@ -108,56 +106,39 @@
int elements = state.getVisibleElements(mLauncher);
Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
pageAlphaProvider.interpolator);
- boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
Hotseat hotseat = mWorkspace.getHotseat();
- // Since we set the pivot relative to mWorkspace, we need to scale a sibling of Workspace.
- AllAppsContainerView qsbScaleView = mLauncher.getAppsView();
- View qsbView = qsbScaleView.getSearchView();
- if (playAtomicComponent) {
- Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
- LauncherState fromState = mLauncher.getStateManager().getState();
- boolean shouldSpring = propertySetter instanceof PendingAnimation
- && fromState == HINT_STATE && state == NORMAL;
- if (shouldSpring) {
- ((PendingAnimation) propertySetter).add(getSpringScaleAnimator(mLauncher,
- mWorkspace, mNewScale));
- } else {
- propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
- }
+ Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
+ LauncherState fromState = mLauncher.getStateManager().getState();
- setPivotToScaleWithWorkspace(hotseat);
- setPivotToScaleWithWorkspace(qsbScaleView);
- float hotseatScale = hotseatScaleAndTranslation.scale;
- if (shouldSpring) {
- PendingAnimation pa = (PendingAnimation) propertySetter;
- pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale));
- pa.add(getSpringScaleAnimator(mLauncher, qsbScaleView,
- qsbScaleAndTranslation.scale));
- } else {
- Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
- scaleInterpolator);
- propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
- hotseatScaleInterpolator);
- propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
- hotseatScaleInterpolator);
- }
-
- float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
- propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha,
- config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
- float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
- propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
- workspacePageIndicatorAlpha, fadeInterpolator);
+ boolean shouldSpring = propertySetter instanceof PendingAnimation
+ && fromState == HINT_STATE && state == NORMAL;
+ if (shouldSpring) {
+ ((PendingAnimation) propertySetter).add(getSpringScaleAnimator(mLauncher,
+ mWorkspace, mNewScale));
+ } else {
+ propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
}
- if (config.onlyPlayAtomicComponent()) {
- // Only the alpha and scale, handled above, are included in the atomic animation.
- return;
+ setPivotToScaleWithWorkspace(hotseat);
+ float hotseatScale = hotseatScaleAndTranslation.scale;
+ if (shouldSpring) {
+ PendingAnimation pa = (PendingAnimation) propertySetter;
+ pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale));
+ } else {
+ Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
+ scaleInterpolator);
+ propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
+ hotseatScaleInterpolator);
}
- Interpolator translationInterpolator = !playAtomicComponent
- ? LINEAR
- : config.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
+ float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
+ propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
+ float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
+ propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
+ workspacePageIndicatorAlpha, fadeInterpolator);
+
+ Interpolator translationInterpolator =
+ config.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_X,
scaleAndTranslation.translationX, translationInterpolator);
propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_Y,
@@ -169,10 +150,8 @@
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
propertySetter.setFloat(mWorkspace.getPageIndicator(), VIEW_TRANSLATE_Y,
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
- propertySetter.setFloat(qsbView, VIEW_TRANSLATE_Y,
- qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator);
- setScrim(propertySetter, state);
+ setScrim(propertySetter, state, config);
}
/**
@@ -187,14 +166,19 @@
- sibling.getLeft() - sibling.getTranslationX());
}
- public void setScrim(PropertySetter propertySetter, LauncherState state) {
+ public void setScrim(PropertySetter propertySetter, LauncherState state,
+ StateAnimationConfig config) {
WorkspaceDragScrim workspaceDragScrim = mLauncher.getDragLayer().getWorkspaceDragScrim();
propertySetter.setFloat(workspaceDragScrim, SCRIM_PROGRESS,
- state.getWorkspaceScrimAlpha(mLauncher), LINEAR);
+ state.getWorkspaceBackgroundAlpha(mLauncher), LINEAR);
SysUiScrim sysUiScrim = mLauncher.getDragLayer().getSysUiScrim();
propertySetter.setFloat(sysUiScrim, SYSUI_PROGRESS,
state.hasFlag(FLAG_HAS_SYS_UI_SCRIM) ? 1 : 0, LINEAR);
+
+ propertySetter.setViewAlpha(mLauncher.getScrimView(),
+ state.getWorkspaceScrimAlpha(mLauncher),
+ config.getInterpolator(ANIM_WORKSPACE_SCRIM_FADE, LINEAR));
}
public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
@@ -209,17 +193,12 @@
int drawableAlpha = state.hasFlag(FLAG_WORKSPACE_HAS_BACKGROUNDS)
? Math.round(pageAlpha * 255) : 0;
- if (!config.onlyPlayAtomicComponent()) {
- // Don't update the scrim during the atomic animation.
- propertySetter.setInt(cl.getScrimBackground(),
- DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
- }
- if (config.playAtomicOverviewScaleComponent()) {
- Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
- pageAlphaProvider.interpolator);
- propertySetter.setFloat(cl.getShortcutsAndWidgets(), VIEW_ALPHA,
- pageAlpha, fadeInterpolator);
- }
+ propertySetter.setInt(cl.getScrimBackground(),
+ DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
+ Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
+ pageAlphaProvider.interpolator);
+ propertySetter.setFloat(cl.getShortcutsAndWidgets(), VIEW_ALPHA,
+ pageAlpha, fadeInterpolator);
}
/**
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index db7fd3f..a5852ba 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -265,7 +265,7 @@
List<OptionItem> actions = getSupportedResizeActions(host, info);
Rect pos = new Rect();
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos);
- ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions);
+ ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions, false);
popup.requestFocus();
popup.setOnCloseCallback(host::requestFocus);
return true;
@@ -294,15 +294,17 @@
if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) {
if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) ||
layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) {
- actions.add(new OptionItem(
- R.string.action_increase_width, R.drawable.ic_widget_width_increase,
+ actions.add(new OptionItem(mLauncher,
+ R.string.action_increase_width,
+ R.drawable.ic_widget_width_increase,
IGNORE,
v -> performResizeAction(R.string.action_increase_width, host, info)));
}
if (info.spanX > info.minSpanX && info.spanX > 1) {
- actions.add(new OptionItem(
- R.string.action_decrease_width, R.drawable.ic_widget_width_decrease,
+ actions.add(new OptionItem(mLauncher,
+ R.string.action_decrease_width,
+ R.drawable.ic_widget_width_decrease,
IGNORE,
v -> performResizeAction(R.string.action_decrease_width, host, info)));
}
@@ -311,15 +313,17 @@
if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) ||
layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) {
- actions.add(new OptionItem(
- R.string.action_increase_height, R.drawable.ic_widget_height_increase,
+ actions.add(new OptionItem(mLauncher,
+ R.string.action_increase_height,
+ R.drawable.ic_widget_height_increase,
IGNORE,
v -> performResizeAction(R.string.action_increase_height, host, info)));
}
if (info.spanY > info.minSpanY && info.spanY > 1) {
- actions.add(new OptionItem(
- R.string.action_decrease_height, R.drawable.ic_widget_height_decrease,
+ actions.add(new OptionItem(mLauncher,
+ R.string.action_decrease_height,
+ R.drawable.ic_widget_height_decrease,
IGNORE,
v -> performResizeAction(R.string.action_decrease_height, host, info)));
}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index edd9a9f..406e785 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
@@ -63,11 +62,8 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
@@ -79,10 +75,9 @@
public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener {
- private static final float FLING_VELOCITY_MULTIPLIER = 135f;
+ private static final float FLING_VELOCITY_MULTIPLIER = 1000 * .8f;
// Starts the springs after at least 55% of the animation has passed.
private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
- private static final int ALPHA_CHANNEL_COUNT = 2;
protected final BaseDraggingActivity mLauncher;
protected final AdapterHolder[] mAH;
@@ -109,8 +104,6 @@
protected RecyclerViewFastScroller mTouchHandler;
protected final Point mFastScrollerOffset = new Point();
- private final MultiValueAlpha mMultiValueAlpha;
-
private Rect mInsets = new Rect();
SearchAdapterProvider mSearchAdapterProvider;
@@ -141,13 +134,6 @@
mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
mAllAppsStore.addUpdateListener(this::onAppsUpdated);
-
- addSpringView(R.id.all_apps_header);
- addSpringView(R.id.apps_list_view);
- addSpringView(R.id.all_apps_tabs_view_pager);
-
- mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
-
}
/**
@@ -163,25 +149,14 @@
return mAllAppsStore;
}
- public AlphaProperty getAlphaProperty(int index) {
- return mMultiValueAlpha.getProperty(index);
- }
-
public WorkModeSwitch getWorkModeSwitch() {
return mWorkModeSwitch;
}
-
- @Override
- protected void setDampedScrollShift(float shift) {
- // Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
- float maxShift = getSearchView().getHeight() / 2f;
- super.setDampedScrollShift(Utilities.boundToRange(shift, -maxShift, maxShift));
- }
-
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
for (AdapterHolder holder : mAH) {
+ holder.adapter.setAppsPerRow(dp.inv.numAllAppsColumns);
if (holder.recyclerView != null) {
// Remove all views and clear the pool, while keeping the data same. After this
// call, all the viewHolders will be recreated.
@@ -201,9 +176,9 @@
}
if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) {
rebindAdapters(hasWorkApps);
- }
- if (hasWorkApps) {
- resetWorkProfile();
+ if (hasWorkApps) {
+ resetWorkProfile();
+ }
}
}
@@ -246,11 +221,7 @@
hideInput();
return false;
}
- boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
- if (shouldScroll) {
- hideInput();
- }
- return shouldScroll;
+ return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
}
@Override
@@ -347,7 +318,7 @@
mSearchContainer = findViewById(R.id.search_container_all_apps);
mSearchUiManager = (SearchUiManager) mSearchContainer;
- mSearchUiManager.initialize(this);
+ mSearchUiManager.initializeSearch(this);
}
public SearchUiManager getSearchUiManager() {
@@ -395,7 +366,8 @@
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
if (Utilities.ATLEAST_Q) {
- mNavBarScrimHeight = insets.getTappableElementInsets().bottom;
+ mNavBarScrimHeight = insets.getTappableElementInsets().bottom
+ - mLauncher.getDeviceProfile().nonOverlappingTaskbarInset;
} else {
mNavBarScrimHeight = insets.getStableInsetBottom();
}
@@ -412,12 +384,6 @@
}
}
- @Override
- public int getCanvasClipTopForOverscroll() {
- // Do not clip if the QSB is attached to the spring, otherwise the QSB will get clipped.
- return mSpringViews.get(getSearchView().getId()) ? 0 : mHeader.getTop();
- }
-
private void rebindAdapters(boolean showTabs) {
rebindAdapters(showTabs, false /* force */);
}
@@ -567,37 +533,9 @@
/**
* Handles selection on focused view and returns success
*/
- public boolean selectFocusedView(View v) {
- ItemInfo headerItem = getHighlightedItemFromHeader();
- if (headerItem != null) {
- return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
- }
- AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
- if (focusedItem != null) {
- View focusedView = getActiveRecyclerView().getLayoutManager()
- .findViewByPosition(focusedItem.position);
- if (focusedView != null && mSearchAdapterProvider.onAdapterItemSelected(focusedItem,
- focusedView)) {
- return true;
- }
- }
- if (focusedItem != null && focusedItem.appInfo != null) {
- ItemInfo itemInfo = focusedItem.appInfo;
- return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
- }
- return false;
- }
-
- /**
- * Returns the ItemInfo of a focused view inside {@link FloatingHeaderView}
- */
- public ItemInfo getHighlightedItemFromHeader() {
- View view = getFloatingHeaderView().getFocusedChild();
- if (view != null && view.getTag() instanceof ItemInfo) {
- return ((ItemInfo) view.getTag());
- }
-
- return null;
+ public boolean launchHighlightedItem() {
+ if (mSearchAdapterProvider == null) return false;
+ return mSearchAdapterProvider.launchHighlightedItem();
}
public SearchAdapterProvider getSearchAdapterProvider() {
@@ -616,10 +554,6 @@
int padding = mHeader.getMaxTranslation();
for (int i = 0; i < mAH.length; i++) {
mAH[i].padding.top = padding;
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mUsingTabs) {
- //add extra space between tabs and recycler view
- mAH[i].padding.top += mLauncher.getDeviceProfile().edgeMarginPx;
- }
mAH[i].applyPadding();
}
}
@@ -676,11 +610,8 @@
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (shouldSpring
&& valueAnimator.getAnimatedFraction() >= FLING_ANIMATION_THRESHOLD) {
- int searchViewId = getSearchView().getId();
- addSpringView(searchViewId);
- finishWithShiftAndVelocity(1, velocity * FLING_VELOCITY_MULTIPLIER,
- (anim, canceled, value, velocity) -> removeSpringView(searchViewId));
-
+ absorbSwipeUpVelocity(Math.abs(
+ Math.round(velocity * FLING_VELOCITY_MULTIPLIER)));
shouldSpring = false;
}
}
@@ -736,8 +667,7 @@
applyPadding();
setupOverlay();
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- recyclerView.addItemDecoration(new AllAppsSectionDecorator(
- AllAppsContainerView.this));
+ recyclerView.addItemDecoration(mSearchAdapterProvider.getDecorator());
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index bb175ea..5b4c4c5 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -42,7 +42,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
-import com.android.launcher3.allapps.search.SectionDecorationInfo;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
@@ -108,7 +107,7 @@
// The index of this app not including sections
public int appIndex = -1;
// Search section associated to result
- public SectionDecorationInfo sectionDecorationInfo = null;
+ public DecorationInfo decorationInfo = null;
/**
* Factory method for AppIcon AdapterItem
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 14e3b51..3cc9ce6 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -23,8 +23,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.PagedView;
-import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
/**
@@ -43,10 +41,6 @@
public AllAppsPagedView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- int topPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0
- : context.getResources().getDimensionPixelOffset(
- R.dimen.all_apps_header_top_padding);
- setPadding(0, topPadding, 0, 0);
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 179cb77..66575eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,6 +31,7 @@
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowInsets;
import androidx.recyclerview.widget.RecyclerView;
@@ -188,6 +189,8 @@
case SCROLL_STATE_DRAGGING:
mgr.logger().sendToInteractionJankMonitor(
LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
+ requestFocus();
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
break;
case SCROLL_STATE_IDLE:
mgr.logger().sendToInteractionJankMonitor(
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
deleted file mode 100644
index f4d735e..0000000
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.graphics.ColorUtils;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
-import com.android.launcher3.allapps.search.SectionDecorationInfo;
-import com.android.launcher3.util.Themes;
-
-import java.util.List;
-
-/**
- * ItemDecoration class that groups items in {@link AllAppsRecyclerView}
- */
-public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
-
- private final AllAppsContainerView mAppsView;
-
- AllAppsSectionDecorator(AllAppsContainerView appsContainerView) {
- mAppsView = appsContainerView;
- }
-
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
- for (int i = 0; i < parent.getChildCount(); i++) {
- View view = parent.getChildAt(i);
- int position = parent.getChildAdapterPosition(view);
- AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
- if (adapterItem.sectionDecorationInfo != null) {
- SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
- SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
- if (decorationHandler != null) {
- decorationHandler.extendBounds(view);
- if (sectionInfo.isFocusedView()) {
- decorationHandler.onFocusDraw(c, view);
- } else {
- decorationHandler.onGroupDraw(c);
- }
- }
- }
- }
- }
-
- // Fallback logic in case non of the SearchTarget is labeled as focused item.
- private void drawDecoration(@NonNull Canvas c,
- @NonNull SectionDecorationHandler decorationHandler,
- @NonNull RecyclerView parent) {
- if (decorationHandler.mIsFullWidth) {
- decorationHandler.mBounds.left = parent.getPaddingLeft();
- decorationHandler.mBounds.right = parent.getWidth() - parent.getPaddingRight();
- }
- if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
- && mAppsView.getApps().getFocusedChild() != null) {
- int index = mAppsView.getApps().getFocusedChildIndex();
- AppsGridLayoutManager layoutManager = (AppsGridLayoutManager)
- mAppsView.getActiveRecyclerView().getLayoutManager();
- if (layoutManager.findFirstVisibleItemPosition() <= index
- && index < parent.getChildCount()) {
- RecyclerView.ViewHolder vh = parent.findViewHolderForAdapterPosition(index);
- if (vh != null) decorationHandler.onFocusDraw(c, vh.itemView);
- }
- }
- decorationHandler.reset();
- }
-
- /**
- * Handles grouping and drawing of items in the same all apps sections.
- */
- public static class SectionDecorationHandler {
- protected RectF mBounds = new RectF();
- private final boolean mIsFullWidth;
- private final float mRadius;
-
- protected final int mFocusColor; // main focused item color
- protected final int mFillcolor; // grouping color
-
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final boolean mIsTopRound;
- private final boolean mIsBottomRound;
- private float [] mCorners;
- private float mFillSpacing;
-
- public SectionDecorationHandler(Context context, boolean isFullWidth, int fillAlpha,
- boolean isTopRound, boolean isBottomRound) {
-
- mIsFullWidth = isFullWidth;
- int endScrim = Themes.getColorBackground(context);
- mFillcolor = ColorUtils.setAlphaComponent(endScrim, fillAlpha);
- mFocusColor = endScrim;
-
- mIsTopRound = isTopRound;
- mIsBottomRound = isBottomRound;
-
- mRadius = context.getResources().getDimensionPixelSize(
- R.dimen.search_decoration_corner_radius);
- mFillSpacing = context.getResources().getDimensionPixelSize(
- R.dimen.search_decoration_padding);
- mCorners = new float[]{
- mIsTopRound ? mRadius : 0, mIsTopRound ? mRadius : 0, // Top left radius in px
- mIsTopRound ? mRadius : 0, mIsTopRound ? mRadius : 0, // Top right radius in px
- mIsBottomRound ? mRadius : 0, mIsBottomRound ? mRadius : 0, // Bottom right
- mIsBottomRound ? mRadius : 0, mIsBottomRound ? mRadius : 0 // Bottom left
- };
-
- }
-
- /**
- * Extends current bounds to include the view.
- */
- public void extendBounds(View view) {
- if (mBounds.isEmpty()) {
- mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
- } else {
- mBounds.set(
- Math.min(mBounds.left, view.getLeft()),
- Math.min(mBounds.top, view.getTop()),
- Math.max(mBounds.right, view.getRight()),
- Math.max(mBounds.bottom, view.getBottom())
- );
- }
- }
-
- /**
- * Draw bounds onto canvas.
- */
- public void onGroupDraw(Canvas canvas) {
- mPaint.setColor(mFillcolor);
- onDraw(canvas);
- }
-
- /**
- * Draw the bound of the view to the canvas.
- */
- public void onFocusDraw(Canvas canvas, @Nullable View view) {
- if (view == null) {
- return;
- }
- mPaint.setColor(mFocusColor);
- mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
- onDraw(canvas);
- }
-
-
- private void onDraw(Canvas canvas) {
- final Path path = new Path();
- RectF finalBounds = new RectF(mBounds.left + mFillSpacing,
- mBounds.top + mFillSpacing,
- mBounds.right - mFillSpacing,
- mBounds.bottom - mFillSpacing);
- path.addRoundRect(finalBounds, mCorners, Path.Direction.CW);
- canvas.drawPath(path, mPaint);
- }
-
- /**
- * Reset view bounds to empty.
- */
- public void reset() {
- mBounds.setEmpty();
- }
- }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index abf63dc..0060b83 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -16,16 +16,11 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-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.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
@@ -48,7 +43,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.views.ScrimView;
/**
* Handles AllApps view transition.
@@ -77,10 +71,7 @@
}
};
- private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0;
-
private AllAppsContainerView mAppsView;
- private ScrimView mScrimView;
private final Launcher mLauncher;
private boolean mIsVerticalLayout;
@@ -115,7 +106,6 @@
setScrollRangeDelta(mScrollRangeDelta);
if (mIsVerticalLayout) {
- mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1);
mLauncher.getHotseat().setTranslationY(0);
mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
}
@@ -131,9 +121,7 @@
*/
public void setProgress(float progress) {
mProgress = progress;
-
- mScrimView.setProgress(progress);
- mAppsView.setTranslationY(progress * mShiftRange);
+ mAppsView.setTranslationY(mProgress * mShiftRange);
}
public float getProgress() {
@@ -160,19 +148,12 @@
StateAnimationConfig config, PendingAnimation builder) {
float targetProgress = toState.getVerticalProgress(mLauncher);
if (Float.compare(mProgress, targetProgress) == 0) {
- if (!config.onlyPlayAtomicComponent()) {
- setAlphas(toState, config, builder);
- }
+ setAlphas(toState, config, builder);
// Fail fast
onProgressAnimationEnd();
return;
}
- if (config.onlyPlayAtomicComponent()) {
- // There is no atomic component for the all apps transition, so just return early.
- return;
- }
-
Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
? config.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
: FAST_OUT_SLOW_IN;
@@ -194,34 +175,18 @@
*/
public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) {
int visibleElements = state.getVisibleElements(mLauncher);
- boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
- boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
-
Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
- Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
-
-
- setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
- setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
- mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
- hasAllAppsContent, setter, headerFade, allAppsFade);
-
- mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
-
- // Set visibility of the container at the very beginning or end of the transition.
- setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
- hasAnyVisibleItem ? INSTANT : FINAL_FRAME);
+ setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade);
}
public AnimatorListenerAdapter getProgressAnimatorListener() {
return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd);
}
- public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
+ public void setupViews(AllAppsContainerView appsView) {
mAppsView = appsView;
- mScrimView = scrimView;
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && Utilities.ATLEAST_R) {
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -235,10 +200,6 @@
void setScrollRangeDelta(float delta) {
mScrollRangeDelta = delta;
mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta;
-
- if (mScrimView != null) {
- mScrimView.reInitUi();
- }
}
/**
@@ -246,6 +207,7 @@
* TODO: This logic should go in {@link LauncherState}
*/
private void onProgressAnimationEnd() {
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index fefd97a..6957850 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -20,7 +20,6 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
-import com.android.launcher3.allapps.search.SectionDecorationInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -288,11 +287,6 @@
mFastScrollerSections.clear();
mAdapterItems.clear();
- SectionDecorationInfo appSection = new SectionDecorationInfo();
- appSection.setDecorationHandler(
- new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true,
- 0, false, false));
-
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections
@@ -313,9 +307,7 @@
if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
}
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- appItem.sectionDecorationInfo = appSection;
- }
+
mAdapterItems.add(appItem);
}
} else {
diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/src/com/android/launcher3/allapps/DecorationInfo.java
similarity index 69%
rename from src/com/android/launcher3/util/SafeCloseable.java
rename to src/com/android/launcher3/allapps/DecorationInfo.java
index ba8ee04..50b250c 100644
--- a/src/com/android/launcher3/util/SafeCloseable.java
+++ b/src/com/android/launcher3/allapps/DecorationInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,14 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.launcher3.allapps;
-package com.android.launcher3.util;
-
-/**
- * Extension of closeable which does not throw an exception
- */
-public interface SafeCloseable extends AutoCloseable {
-
- @Override
- void close();
+public class DecorationInfo {
}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index 31c6cc7..9bf6043 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -17,10 +17,8 @@
import android.graphics.Rect;
import android.view.View;
-import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.PropertySetter;
/**
* A abstract representation of a row in all-apps view
@@ -47,9 +45,6 @@
*/
boolean hasVisibleContent();
- void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade);
-
/**
* Scrolls the content vertically.
*/
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 9056e8a..733d867 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
@@ -26,7 +24,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
@@ -37,7 +34,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.AllAppsRow;
@@ -88,7 +84,6 @@
private int mSnappedScrolledY;
private int mTranslationY;
- private boolean mAllowTouchForwarding;
private boolean mForwardToRecyclerView;
protected boolean mTabsHidden;
@@ -111,8 +106,8 @@
public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
- mHeaderTopPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 :
- context.getResources().getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
+ mHeaderTopPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
}
@Override
@@ -130,7 +125,6 @@
}
}
mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
- setPadding(0, mHeaderTopPadding, 0, 0);
mAllRows = mFixedRows;
}
@@ -248,9 +242,7 @@
public int getMaxTranslation() {
if (mMaxTranslation == 0 && mTabsHidden) {
- int paddingOffset = getResources().getDimensionPixelSize(
- R.dimen.all_apps_search_bar_bottom_padding);
- return FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 : paddingOffset;
+ return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
} else if (mMaxTranslation > 0 && mTabsHidden) {
return mMaxTranslation + getPaddingTop();
} else {
@@ -350,10 +342,6 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (!mAllowTouchForwarding) {
- mForwardToRecyclerView = false;
- return super.onInterceptTouchEvent(ev);
- }
calcOffset(mTempOffset);
ev.offsetLocation(mTempOffset.x, mTempOffset.y);
mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
@@ -382,20 +370,6 @@
p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
}
- public void setContentVisibility(boolean hasHeader, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- for (FloatingHeaderRow row : mAllRows) {
- row.setContentVisibility(hasHeader, hasAllAppsContent, setter, headerFade, allAppsFade);
- }
-
- allowTouchForwarding(hasAllAppsContent);
- setter.setFloat(mTabLayout, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
- }
-
- protected void allowTouchForwarding(boolean allow) {
- mAllowTouchForwarding = allow;
- }
-
public boolean hasVisibleContent() {
for (FloatingHeaderRow row : mAllRows) {
if (row.hasVisibleContent()) {
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 13ddc12..16ae250 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -72,8 +72,9 @@
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
- mLauncher.getAllAppsController()
- .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
+ int allAppsStartingPositionY = mLauncher.getDeviceProfile().availableHeightPx
+ - mLauncher.getDeviceProfile().allAppsOpenVerticalTranslate;
+ mLauncher.getAllAppsController().setScrollRangeDelta(allAppsStartingPositionY);
}
@Override
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index cf7142c..5b5fbb7 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -18,14 +18,10 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
import android.graphics.Rect;
import android.view.View;
-import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.PropertySetter;
import com.android.systemui.plugins.AllAppsRow;
/**
@@ -65,13 +61,6 @@
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- // Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(mView, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
- }
-
- @Override
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
mView.setVisibility(isScrolledOut ? INVISIBLE : VISIBLE);
if (!isScrolledOut) {
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 0d42950..924a392 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -15,16 +15,11 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
-
-import android.graphics.Rect;
import android.view.KeyEvent;
-import android.view.animation.Interpolator;
import androidx.annotation.Nullable;
import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.anim.PropertySetter;
/**
* Interface for controlling the Apps search UI.
@@ -34,7 +29,7 @@
/**
* Initializes the search manager.
*/
- void initialize(AllAppsContainerView containerView);
+ void initializeSearch(AllAppsContainerView containerView);
/**
* Notifies the search manager to close any active search session.
@@ -45,31 +40,7 @@
* Called before dispatching a key event, in case the search manager wants to initialize
* some UI beforehand.
*/
- void preDispatchKeyEvent(KeyEvent keyEvent);
-
- /**
- * Returns the vertical shift for the all-apps view, so that it aligns with the hotseat.
- */
- float getScrollRangeDelta(Rect insets);
-
- /**
- * Called as part of state transition to update the content UI
- */
- void setContentVisibility(int visibleElements, PropertySetter setter,
- Interpolator interpolator);
-
- /**
- * Called when activity is destroyed. Used to close search system services
- */
- default void destroy() {
- }
-
- /**
- * Returns true if the QSB should be visible for the given set of visible elements
- */
- default boolean isQsbVisible(int visibleElements) {
- return (visibleElements & ALL_APPS_HEADER) != 0;
- }
+ default void preDispatchKeyEvent(KeyEvent keyEvent) { };
/**
* @return the edit text object
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 3319018..79718fb 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -35,7 +35,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.search.SearchCallback;
-import com.android.launcher3.util.PackageManagerHelper;
/**
* An interface to a search box that AllApps can command.
@@ -105,30 +104,14 @@
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
- mLauncher.getStatsLogManager().logger()
- .log(LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME);
- // selectFocusedView should return SearchTargetEvent that is passed onto onClick
- if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
- return true;
- }
- }
- }
- // Skip if it's not the right action
- if (actionId != EditorInfo.IME_ACTION_SEARCH) {
- return false;
+ if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
+ mLauncher.getStatsLogManager().logger()
+ .log(LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME);
+ // selectFocusedView should return SearchTargetEvent that is passed onto onClick
+ return Launcher.getLauncher(mLauncher).getAppsView().launchHighlightedItem();
}
-
- // Skip if the query is empty
- String query = v.getText().toString();
- if (query.isEmpty()) {
- return false;
- }
- return mLauncher.startActivitySafely(v,
- PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null
- );
+ return false;
}
@Override
@@ -144,7 +127,7 @@
@Override
public void onFocusChange(View view, boolean hasFocus) {
- if (!hasFocus) {
+ if (!hasFocus && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mInput.hideKeyboard();
}
}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 2261d51..a8185d6 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -31,21 +31,17 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
-import android.view.animation.Interpolator;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.search.SearchCallback;
import java.util.ArrayList;
@@ -134,11 +130,11 @@
}
@Override
- public void initialize(AllAppsContainerView appsView) {
+ public void initializeSearch(AllAppsContainerView appsView) {
mApps = appsView.getApps();
mAppsView = appsView;
mSearchBarController.initialize(
- new DefaultAppSearchAlgorithm(mLauncher, LauncherAppState.getInstance(mLauncher)),
+ new DefaultAppSearchAlgorithm(mLauncher),
this, mLauncher, this);
}
@@ -213,22 +209,6 @@
}
@Override
- public float getScrollRangeDelta(Rect insets) {
- if (mLauncher.getDeviceProfile().isVerticalBarLayout()
- || FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- return 0;
- } else {
- return insets.bottom + insets.top;
- }
- }
-
- @Override
- public void setContentVisibility(int visibleElements, PropertySetter setter,
- Interpolator interpolator) {
- setter.setViewAlpha(this, isQsbVisible(visibleElements) ? 1 : 0, interpolator);
- }
-
- @Override
public ExtendedEditText getEditText() {
return this;
}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
deleted file mode 100644
index f9fb22e..0000000
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import android.content.Context;
-import android.os.CancellationSignal;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
-import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
-import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.data.AppInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * A device search section for handling app searches
- */
-public class AppsSearchPipeline implements SearchPipeline {
-
- private static final int MAX_RESULTS_COUNT = 5;
-
- private final SectionDecorationInfo mSearchSectionInfo;
- private final LauncherAppState mLauncherAppState;
-
- public AppsSearchPipeline(Context context, LauncherAppState launcherAppState) {
- mLauncherAppState = launcherAppState;
- mSearchSectionInfo = new SectionDecorationInfo();
- mSearchSectionInfo.setDecorationHandler(
- new SectionDecorationHandler(context, true, 0, true, true));
- }
-
- @Override
- public void query(String input, Consumer<ArrayList<AdapterItem>> callback,
- CancellationSignal cancellationSignal) {
- mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
- List<AppInfo> matchingResults = getTitleMatchResult(apps.data, input);
- callback.accept(getAdapterItems(matchingResults));
- }
- });
- }
-
- /**
- * Filters {@link AppInfo}s matching specified query
- */
- public static ArrayList<AppInfo> getTitleMatchResult(List<AppInfo> apps, String query) {
- // Do an intersection of the words in the query and each title, and filter out all the
- // apps that don't match all of the words in the query.
- final String queryTextLower = query.toLowerCase();
- final ArrayList<AppInfo> result = new ArrayList<>();
- DefaultAppSearchAlgorithm.StringMatcher matcher =
- DefaultAppSearchAlgorithm.StringMatcher.getInstance();
- for (AppInfo info : apps) {
- if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) {
- result.add(info);
- }
- }
- return result;
- }
-
- private ArrayList<AdapterItem> getAdapterItems(List<AppInfo> matchingApps) {
- ArrayList<AdapterItem> items = new ArrayList<>();
- for (int i = 0; i < matchingApps.size() && i < MAX_RESULTS_COUNT; i++) {
- AdapterItem appItem = AdapterItem.asApp(i, "", matchingApps.get(i), i);
- appItem.sectionDecorationInfo = mSearchSectionInfo;
- items.add(appItem);
- }
-
- return items;
- }
-}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 4e213b0..1f854c6 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -15,28 +15,39 @@
*/
package com.android.launcher3.allapps.search;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.content.Context;
import android.os.Handler;
+import androidx.annotation.AnyThread;
+
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.search.StringMatcherUtility;
-import java.text.Collator;
+import java.util.ArrayList;
+import java.util.List;
/**
* The default search implementation.
*/
public class DefaultAppSearchAlgorithm implements SearchAlgorithm<AdapterItem> {
- protected final Handler mResultHandler;
- private final AppsSearchPipeline mAppsSearchPipeline;
+ private static final int MAX_RESULTS_COUNT = 5;
- public DefaultAppSearchAlgorithm(Context context, LauncherAppState launcherAppState) {
- mResultHandler = new Handler();
- mAppsSearchPipeline = new AppsSearchPipeline(context, launcherAppState);
+ private final LauncherAppState mAppState;
+ private final Handler mResultHandler;
+
+ public DefaultAppSearchAlgorithm(Context context) {
+ mAppState = LauncherAppState.getInstance(context);
+ mResultHandler = new Handler(MAIN_EXECUTOR.getLooper());
}
@Override
@@ -47,139 +58,38 @@
}
@Override
- public void doSearch(final String query,
- final SearchCallback<AdapterItem> callback) {
- mAppsSearchPipeline.query(query,
- results -> mResultHandler.post(
- () -> callback.onSearchResult(query, results)),
- null);
- }
-
- public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
- int queryLength = query.length();
-
- String title = info.title.toString();
- int titleLength = title.length();
-
- if (titleLength < queryLength || queryLength <= 0) {
- return false;
- }
-
- if (requestSimpleFuzzySearch(query)) {
- return title.toLowerCase().contains(query);
- }
-
- int lastType;
- int thisType = Character.UNASSIGNED;
- int nextType = Character.getType(title.codePointAt(0));
-
- int end = titleLength - queryLength;
- for (int i = 0; i <= end; i++) {
- lastType = thisType;
- thisType = nextType;
- nextType = i < (titleLength - 1) ?
- Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED;
- if (isBreak(thisType, lastType, nextType) &&
- matcher.matches(query, title.substring(i, i + queryLength))) {
- return true;
+ public void doSearch(String query, SearchCallback<AdapterItem> callback) {
+ mAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
+ mResultHandler.post(() -> callback.onSearchResult(query, result));
}
- }
- return false;
+ });
}
/**
- * Returns true if the current point should be a break point. Following cases
- * are considered as break points:
- * 1) Any non space character after a space character
- * 2) Any digit after a non-digit character
- * 3) Any capital character after a digit or small character
- * 4) Any capital character before a small character
+ * Filters {@link AppInfo}s matching specified query
*/
- private static boolean isBreak(int thisType, int prevType, int nextType) {
- switch (prevType) {
- case Character.UNASSIGNED:
- case Character.SPACE_SEPARATOR:
- case Character.LINE_SEPARATOR:
- case Character.PARAGRAPH_SEPARATOR:
- return true;
- }
- switch (thisType) {
- case Character.UPPERCASE_LETTER:
- if (nextType == Character.UPPERCASE_LETTER) {
- return true;
- }
- // Follow through
- case Character.TITLECASE_LETTER:
- // Break point if previous was not a upper case
- return prevType != Character.UPPERCASE_LETTER;
- case Character.LOWERCASE_LETTER:
- // Break point if previous was not a letter.
- return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
- case Character.DECIMAL_DIGIT_NUMBER:
- case Character.LETTER_NUMBER:
- case Character.OTHER_NUMBER:
- // Break point if previous was not a number
- return !(prevType == Character.DECIMAL_DIGIT_NUMBER
- || prevType == Character.LETTER_NUMBER
- || prevType == Character.OTHER_NUMBER);
- case Character.MATH_SYMBOL:
- case Character.CURRENCY_SYMBOL:
- case Character.OTHER_PUNCTUATION:
- case Character.DASH_PUNCTUATION:
- // Always a break point for a symbol
- return true;
- default:
- return false;
- }
- }
+ @AnyThread
+ public static ArrayList<AdapterItem> getTitleMatchResult(List<AppInfo> apps, String query) {
+ // Do an intersection of the words in the query and each title, and filter out all the
+ // apps that don't match all of the words in the query.
+ final String queryTextLower = query.toLowerCase();
+ final ArrayList<AdapterItem> result = new ArrayList<>();
+ StringMatcherUtility.StringMatcher matcher =
+ StringMatcherUtility.StringMatcher.getInstance();
- public static class StringMatcher {
-
- private static final char MAX_UNICODE = '\uFFFF';
-
- private final Collator mCollator;
-
- StringMatcher() {
- // On android N and above, Collator uses ICU implementation which has a much better
- // support for non-latin locales.
- mCollator = Collator.getInstance();
- mCollator.setStrength(Collator.PRIMARY);
- mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
- }
-
- /**
- * Returns true if {@param query} is a prefix of {@param target}
- */
- public boolean matches(String query, String target) {
- switch (mCollator.compare(query, target)) {
- case 0:
- return true;
- case -1:
- // The target string can contain a modifier which would make it larger than
- // the query string (even though the length is same). If the query becomes
- // larger after appending a unicode character, it was originally a prefix of
- // the target string and hence should match.
- return mCollator.compare(query + MAX_UNICODE, target) > -1;
- default:
- return false;
+ int resultCount = 0;
+ int total = apps.size();
+ for (int i = 0; i < total && resultCount < MAX_RESULTS_COUNT; i++) {
+ AppInfo info = apps.get(i);
+ if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
+ AdapterItem appItem = AdapterItem.asApp(resultCount, "", info, resultCount);
+ result.add(appItem);
+ resultCount++;
}
}
-
- public static StringMatcher getInstance() {
- return new StringMatcher();
- }
- }
-
- private static boolean requestSimpleFuzzySearch(String s) {
- for (int i = 0; i < s.length(); ) {
- int codepoint = s.codePointAt(i);
- i += Character.charCount(codepoint);
- switch (Character.UnicodeScript.of(codepoint)) {
- case HAN:
- //Character.UnicodeScript.HAN: use String.contains to match
- return true;
- }
- }
- return false;
+ return result;
}
}
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index 330ec68..ef62da4 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -15,25 +15,45 @@
*/
package com.android.launcher3.allapps.search;
+import android.graphics.Canvas;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.model.data.ItemInfo;
/**
* Provides views for local search results
*/
public class DefaultSearchAdapterProvider extends SearchAdapterProvider {
- public DefaultSearchAdapterProvider(BaseDraggingActivity launcher) {
- super(launcher);
+ private final RecyclerView.ItemDecoration mDecoration;
+ private View mHighlightedView;
+
+ public DefaultSearchAdapterProvider(BaseDraggingActivity launcher,
+ AllAppsContainerView appsContainerView) {
+ super(launcher, appsContainerView);
+ mDecoration = new RecyclerView.ItemDecoration() {
+ @Override
+ public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent,
+ @NonNull RecyclerView.State state) {
+ super.onDraw(c, parent, state);
+ }
+ };
}
@Override
public void onBindView(AllAppsGridAdapter.ViewHolder holder, int position) {
-
+ if (position == 0) {
+ mHighlightedView = holder.itemView;
+ }
}
@Override
@@ -48,7 +68,22 @@
}
@Override
- public boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem adapterItem, View view) {
+ public boolean launchHighlightedItem() {
+ if (mHighlightedView instanceof BubbleTextView
+ && mHighlightedView.getTag() instanceof ItemInfo) {
+ ItemInfo itemInfo = (ItemInfo) mHighlightedView.getTag();
+ return mLauncher.startActivitySafely(mHighlightedView, itemInfo.getIntent(), itemInfo);
+ }
return false;
}
+
+ @Override
+ public View getHighlightedItem() {
+ return mHighlightedView;
+ }
+
+ @Override
+ public RecyclerView.ItemDecoration getDecorator() {
+ return mDecoration;
+ }
}
diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
deleted file mode 100644
index 76099a6..0000000
--- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
-
-import android.app.Activity;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.lifecycle.Observer;
-import androidx.slice.Slice;
-import androidx.slice.SliceViewManager;
-import androidx.slice.SliceViewManager.SliceCallback;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Manages Lifecycle for Live search results
- */
-public class LiveSearchManager implements StateListener<LauncherState> {
-
- private static final String TAG = "LiveSearchManager";
-
- public static final int SEARCH_APPWIDGET_HOST_ID = 2048;
-
- private final Launcher mLauncher;
- private final HashMap<Uri, SliceLifeCycle> mUriSliceMap = new HashMap<>();
-
- private final HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders =
- new HashMap<>();
- private SearchWidgetHost mSearchWidgetHost;
-
- public LiveSearchManager(Launcher launcher) {
- mLauncher = launcher;
- mLauncher.getStateManager().addStateListener(this);
- }
-
- /**
- * Creates new {@link AppWidgetHostView} from {@link AppWidgetProviderInfo}. Caches views for
- * quicker result within the same search session
- */
- public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) {
- if (mSearchWidgetHost == null) {
- mSearchWidgetHost = new SearchWidgetHost(mLauncher);
- mSearchWidgetHost.startListening();
- }
-
- ComponentName provider = providerInfo.provider;
- UserHandle userHandle = providerInfo.getProfile();
-
- ComponentKey key = new ComponentKey(provider, userHandle);
- if (mWidgetPlaceholders.containsKey(key)) {
- return mWidgetPlaceholders.get(key);
- }
-
- LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
- mLauncher, providerInfo);
- PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo);
-
- Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo);
- int appWidgetId = mSearchWidgetHost.allocateAppWidgetId();
- boolean success = AppWidgetManager.getInstance(mLauncher)
- .bindAppWidgetIdIfAllowed(appWidgetId, userHandle, provider, options);
- if (!success) {
- mSearchWidgetHost.deleteAppWidgetId(appWidgetId);
- mWidgetPlaceholders.put(key, null);
- return null;
- }
-
- SearchWidgetInfoContainer view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(
- mLauncher, appWidgetId, providerInfo);
- view.setTag(pendingAddWidgetInfo);
- mWidgetPlaceholders.put(key, view);
- return view;
- }
-
- /**
- * Stop search session
- */
- public void stop() {
- clearWidgetHost();
- }
-
- private void clearWidgetHost() {
- if (mSearchWidgetHost != null) {
- mSearchWidgetHost.stopListening();
- mSearchWidgetHost.clearViews();
- mSearchWidgetHost.deleteHost();
- mWidgetPlaceholders.clear();
- mSearchWidgetHost = null;
- }
- }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState != ALL_APPS) {
- // Clear all search session related objects
- mUriSliceMap.values().forEach(SliceLifeCycle::destroy);
- mUriSliceMap.clear();
-
- clearWidgetHost();
- }
- }
-
- /**
- * Adds a new observer for the provided uri and returns a callback to cancel this observer
- */
- public SafeCloseable addObserver(Uri uri, Observer<Slice> listener) {
- SliceLifeCycle slc = mUriSliceMap.get(uri);
- if (slc == null) {
- slc = new SliceLifeCycle(uri, mLauncher);
- mUriSliceMap.put(uri, slc);
- }
- slc.addListener(listener);
-
- final SliceLifeCycle sliceLifeCycle = slc;
- return () -> sliceLifeCycle.removeListener(listener);
- }
-
- static class SearchWidgetHost extends AppWidgetHost {
- SearchWidgetHost(Context context) {
- super(context, SEARCH_APPWIDGET_HOST_ID);
- }
-
- @Override
- protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
- AppWidgetProviderInfo appWidget) {
- return new SearchWidgetInfoContainer(context);
- }
-
- @Override
- public void clearViews() {
- super.clearViews();
- }
- }
-
- private static class SliceLifeCycle
- implements ActivityLifecycleCallbacks, SliceCallback {
-
- private final Uri mUri;
- private final Launcher mLauncher;
- private final SliceViewManager mSliceViewManager;
- private final ArrayList<Observer<Slice>> mListeners = new ArrayList<>();
-
- private boolean mDestroyed = false;
- private boolean mWasListening = false;
-
- SliceLifeCycle(Uri uri, Launcher launcher) {
- mUri = uri;
- mLauncher = launcher;
- mSliceViewManager = SliceViewManager.getInstance(launcher);
- launcher.registerActivityLifecycleCallbacks(this);
-
- if (launcher.isDestroyed()) {
- onActivityDestroyed(launcher);
- } else if (launcher.isStarted()) {
- onActivityStarted(launcher);
- }
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- destroy();
- }
-
- @Override
- public void onActivityStarted(Activity activity) {
- updateListening();
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- updateListening();
- }
-
- private void updateListening() {
- boolean isListening = mDestroyed
- ? false
- : (mLauncher.isStarted() && !mListeners.isEmpty());
- UI_HELPER_EXECUTOR.execute(() -> uploadListeningBg(isListening));
- }
-
- @WorkerThread
- private void uploadListeningBg(boolean isListening) {
- if (mWasListening != isListening) {
- mWasListening = isListening;
- if (isListening) {
- mSliceViewManager.registerSliceCallback(mUri, MAIN_EXECUTOR, this);
- // Update slice one-time on the different thread so that we can display
- // multiple slices in parallel
- THREAD_POOL_EXECUTOR.execute(this::updateSlice);
- } else {
- mSliceViewManager.unregisterSliceCallback(mUri, this);
- }
- }
- }
-
- @UiThread
- private void addListener(Observer<Slice> listener) {
- mListeners.add(listener);
- updateListening();
- }
-
- @UiThread
- private void removeListener(Observer<Slice> listener) {
- mListeners.remove(listener);
- updateListening();
- }
-
- @WorkerThread
- private void updateSlice() {
- try {
- Slice s = mSliceViewManager.bindSlice(mUri);
- MAIN_EXECUTOR.execute(() -> onSliceUpdated(s));
- } catch (Exception e) {
- Log.d(TAG, "Error fetching slice", e);
- }
- }
-
- @UiThread
- @Override
- public void onSliceUpdated(@Nullable Slice s) {
- mListeners.forEach(l -> l.onChanged(s));
- }
-
- private void destroy() {
- if (mDestroyed) {
- return;
- }
- mDestroyed = true;
- mLauncher.unregisterActivityLifecycleCallbacks(this);
- mListeners.clear();
- }
-
- @Override
- public void onActivityCreated(Activity activity, Bundle bundle) { }
-
- @Override
- public void onActivityPaused(Activity activity) { }
-
- @Override
- public void onActivityResumed(Activity activity) { }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
- }
-}
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index fdacd3d..cefb8cb 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -16,11 +16,15 @@
package com.android.launcher3.allapps.search;
+import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
/**
@@ -30,7 +34,7 @@
protected final BaseDraggingActivity mLauncher;
- public SearchAdapterProvider(BaseDraggingActivity launcher) {
+ public SearchAdapterProvider(BaseDraggingActivity launcher, AllAppsContainerView appsView) {
mLauncher = launcher;
}
@@ -40,6 +44,12 @@
public abstract void onBindView(AllAppsGridAdapter.ViewHolder holder, int position);
/**
+ * Called from LiveSearchManager to notify slice status updates.
+ */
+ public void onSliceStatusUpdate(Uri sliceUri) {
+ }
+
+ /**
* Returns whether or not viewType can be handled by searchProvider
*/
public abstract boolean isSearchView(int viewType);
@@ -65,9 +75,18 @@
}
/**
- * handles selection event on search adapter item. Returns false if provider can not handle
+ * Handles selection event on search adapter item. Returns false if provider can not handle
* event
*/
- public abstract boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem adapterItem,
- View view);
+ public abstract boolean launchHighlightedItem();
+
+ /**
+ * Returns the current highlighted view
+ */
+ public abstract View getHighlightedItem();
+
+ /**
+ * Returns the item decorator.
+ */
+ public abstract RecyclerView.ItemDecoration getDecorator();
}
diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java
deleted file mode 100644
index 3516a41..0000000
--- a/src/com/android/launcher3/allapps/search/SearchPipeline.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import android.os.CancellationSignal;
-
-import com.android.launcher3.allapps.AllAppsGridAdapter;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * An interface for handling search within pipeline
- */
-// Remove when System Service API is added.
-public interface SearchPipeline {
-
- /**
- * Perform query
- */
- void query(String input,
- Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
- CancellationSignal cancellationSignal);
-}
diff --git a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java
deleted file mode 100644
index 8e5f8cb..0000000
--- a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-import android.widget.RemoteViews;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A placeholder {@link AppWidgetHostView} used for managing widget search results
- */
-public class SearchWidgetInfoContainer extends AppWidgetHostView {
- private int mAppWidgetId;
- private AppWidgetProviderInfo mProviderInfo;
- private RemoteViews mViews;
- private List<AppWidgetHostView> mListeners = new ArrayList<>();
-
- public SearchWidgetInfoContainer(Context context) {
- super(context);
- }
-
- @Override
- public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
- mAppWidgetId = appWidgetId;
- mProviderInfo = info;
- for (AppWidgetHostView listener : mListeners) {
- listener.setAppWidget(mAppWidgetId, mProviderInfo);
- }
- }
-
- @Override
- public void updateAppWidget(RemoteViews remoteViews) {
- mViews = remoteViews;
- for (AppWidgetHostView listener : mListeners) {
- listener.updateAppWidget(remoteViews);
- }
- }
-
- /**
- * Create a live {@link AppWidgetHostView} from placeholder
- */
- public void attachWidget(AppWidgetHostView hv) {
- hv.setTag(getTag());
- hv.setAppWidget(mAppWidgetId, mProviderInfo);
- hv.updateAppWidget(mViews);
- mListeners.add(hv);
- }
-
- /**
- * stops AppWidgetHostView from getting updates
- */
- public void detachWidget(AppWidgetHostView hostView) {
- mListeners.remove(hostView);
- }
-
-}
diff --git a/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
deleted file mode 100644
index 0b64fca..0000000
--- a/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
-
-/**
- * Info class for a search section that is primarily used for decoration.
- */
-public class SectionDecorationInfo {
-
- public static final int QUICK_LAUNCH = 1 << 0;
- public static final int GROUPING = 1 << 1;
-
- private String mSectionId;
- private boolean mFocused;
- private SectionDecorationHandler mDecorationHandler;
-
- public boolean isFocusedView() {
- return mFocused;
- }
-
- public void setFocusedView(boolean focused) {
- mFocused = focused;
- }
-
- public SectionDecorationInfo() {
- this(null);
- }
-
- public SectionDecorationInfo(String sectionId) {
- mSectionId = sectionId;
- }
-
- public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
- mDecorationHandler = sectionDecorationHandler;
- }
-
- public SectionDecorationHandler getDecorationHandler() {
- return mDecorationHandler;
- }
-
- /**
- * Returns the section's ID
- */
- public String getSectionId() {
- return mSectionId == null ? "" : mSectionId;
- }
-}
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 6b9ed09..7980138 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -77,6 +77,9 @@
public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+ public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL =
+ v -> ACCEL_DEACCEL.getInterpolation(TOUCH_RESPONSE_INTERPOLATOR.getInterpolation(v));
+
/**
* Inversion of ZOOM_OUT, compounded with an ease-out.
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 4e90c9e..9068331 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -117,11 +117,18 @@
* Adds a callback to be run on every frame of the animation
*/
public void addOnFrameCallback(Runnable runnable) {
+ addOnFrameListener(anim -> runnable.run());
+ }
+
+ /**
+ * Adds a listener to be run on every frame of the animation
+ */
+ public void addOnFrameListener(ValueAnimator.AnimatorUpdateListener listener) {
if (mProgressAnimator == null) {
mProgressAnimator = ValueAnimator.ofFloat(0, 1);
}
- mProgressAnimator.addUpdateListener(anim -> runnable.run());
+ mProgressAnimator.addUpdateListener(listener);
}
/**
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e406e9b..6331ef2 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -85,7 +85,7 @@
"ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
- "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
+ "ENABLE_QUICKSTEP_LIVE_TILE", true, "Enable live tile in Quickstep overview");
// Keep as DeviceFlag to allow remote disable in emergency.
public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
@@ -93,7 +93,11 @@
public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag(
- "ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
+ "ENABLE_DEVICE_SEARCH", true, "Allows on device search in all apps");
+
+ public static final BooleanFlag ENABLE_PEOPLE_TILE_PREVIEW = getDebugFlag(
+ "ENABLE_PEOPLE_TILE_PREVIEW", false,
+ "Experimental: Shows conversation shortcuts on home screen as search results");
public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
"FOLDER_NAME_SUGGEST", true,
@@ -140,6 +144,9 @@
public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
"ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
+ public static final BooleanFlag ENABLE_WIDGETS_PICKER_AIAI_SEARCH = new DeviceFlag(
+ "ENABLE_WIDGETS_PICKER_AIAI_SEARCH", false, "Enable AiAi search in the widgets picker");
+
public static final BooleanFlag ENABLE_OVERVIEW_SHARE = getDebugFlag(
"ENABLE_OVERVIEW_SHARE", false, "Show Share button in Overview Actions");
@@ -184,7 +191,7 @@
"EXPANDED_SMARTSPACE", false, "Expands smartspace height to two rows. "
+ "Any apps occupying the first row will be removed from workspace.");
- public static final BooleanFlag ENABLE_FOUR_COLUMNS = new DeviceFlag(
+ public static final DeviceFlag ENABLE_FOUR_COLUMNS = new DeviceFlag(
"ENABLE_FOUR_COLUMNS", false, "Uses 4 columns in launcher grid."
+ "Warning: This will permanently alter your home screen items and is not reversible.");
@@ -198,13 +205,26 @@
"ENABLE_APP_PREDICTIONS_WHILE_VISIBLE", true, "Allows app "
+ "predictions to be updated while they are visible to the user.");
- public static final BooleanFlag ENABLE_TASKBAR = new DeviceFlag(
+ public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
"ENABLE_TASKBAR", false, "Allows a system Taskbar to be shown on larger devices.");
- public static final BooleanFlag ENABLE_OVERVIEW_GRID = new DeviceFlag(
+ public static final BooleanFlag ENABLE_OVERVIEW_GRID = getDebugFlag(
"ENABLE_OVERVIEW_GRID", false, "Uses grid overview layout. "
+ "Only applicable on large screen devices.");
+ public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(
+ "ENABLE_TWO_PANEL_HOME", false,
+ "Uses two panel on home screen. Only applicable on large screen devices.");
+
+ public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
+ "ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
+
+ public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
+ "ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
+
+ public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag("NOTIFY_CRASHES", false,
+ "Sends a notification whenever launcher encounters an uncaught exception.");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
@@ -214,6 +234,12 @@
}
}
+ public static void removeFlag(DebugFlag flag) {
+ synchronized (sDebugFlags) {
+ sDebugFlags.remove(flag);
+ }
+ }
+
static List<DebugFlag> getDebugFlags() {
synchronized (sDebugFlags) {
return new ArrayList<>(sDebugFlags);
@@ -291,6 +317,15 @@
.getBoolean(key, defaultValue);
}
+ /**
+ * Resets value to default value.
+ */
+ public void reset(Context context) {
+ mCurrentValue = defaultValue;
+ context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+ .edit().putBoolean(key, defaultValue).apply();
+ }
+
@Override
protected StringBuilder appendProps(StringBuilder src) {
return super.appendProps(src).append(", mCurrentValue=").append(mCurrentValue);
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index c972cbb..b7a7366 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -37,11 +37,14 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
+import android.view.WindowManager;
+import android.widget.TextView;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.InvariantDeviceProfile;
@@ -127,6 +130,9 @@
if (savedInstanceState == null) {
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_START);
}
+
+ TextView widgetAppName = findViewById(R.id.widget_appName);
+ widgetAppName.setText(getApplicationInfo().labelRes);
}
@Override
@@ -142,7 +148,7 @@
// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
// we abort the drag.
- if (img.getBitmap() == null) {
+ if (img.getDrawable() == null) {
return false;
}
@@ -151,7 +157,7 @@
// Start home and pass the draw request params
PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
- img.getBitmap().getWidth(), img.getWidth());
+ img.getDrawable().getIntrinsicWidth(), img.getWidth());
// Start a system drag and drop. We use a transparent bitmap as preview for system drag
@@ -326,4 +332,15 @@
.withItemInfo((ItemInfo) mWidgetCell.getWidgetView().getTag())
.log(command);
}
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ View view = getWindow().getDecorView();
+ WindowManager.LayoutParams layoutParams =
+ (WindowManager.LayoutParams) view.getLayoutParams();
+ layoutParams.gravity = Gravity.BOTTOM;
+ layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
+ getWindowManager().updateViewLayout(view, layoutParams);
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
new file mode 100644
index 0000000..2135f5d
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.dragndrop;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/**
+ * A drawable which renders {@link LauncherAppWidgetHostView} to a canvas.
+ *
+ * TODO(b/183609936) Stop using that class and remove it.
+ */
+public final class AppWidgetHostViewDrawable extends Drawable {
+
+ private final LauncherAppWidgetHostView mAppWidgetHostView;
+ private Paint mPaint = new Paint();
+
+ public AppWidgetHostViewDrawable(LauncherAppWidgetHostView appWidgetHostView) {
+ mAppWidgetHostView = appWidgetHostView;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ int saveCount = canvas.saveLayer(0, 0, getIntrinsicWidth(), getIntrinsicHeight(), mPaint);
+ mAppWidgetHostView.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mAppWidgetHostView.getMeasuredWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mAppWidgetHostView.getMeasuredHeight();
+ }
+
+ @Override
+ public int getOpacity() {
+ // This is up to app widget provider. We don't know if the host view will cover anything
+ // behind the drawable.
+ return PixelFormat.UNKNOWN;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public int getAlpha() {
+ return mPaint.getAlpha();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ return mPaint.getColorFilter();
+ }
+
+ /** Returns the {@link LauncherAppWidgetHostView}. */
+ public LauncherAppWidgetHostView getAppWidgetHostView() {
+ return mAppWidgetHostView;
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 93df599..b7a70cb 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -25,9 +25,9 @@
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -129,7 +129,7 @@
* drop, it is the responsibility of the {@link DropTarget} to exit out of the spring loaded
* mode. If the drop was cancelled for some reason, the UI will automatically exit out of this mode.
*
- * @param b The bitmap to display as the drag image. It will be re-scaled to the
+ * @param drawable The drawable to be displayed in the drag view. It will be re-scaled to the
* enlarged size.
* @param originalView The source view (ie. icon, widget etc.) that is being dragged
* and which the DragView represents
@@ -140,9 +140,18 @@
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
- public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
- DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
- float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
+ public DragView startDrag(
+ Drawable drawable,
+ DraggableView originalView,
+ int dragLayerX,
+ int dragLayerY,
+ DragSource source,
+ ItemInfo dragInfo,
+ Point dragOffset,
+ Rect dragRegion,
+ float initialDragViewScale,
+ float dragViewScaleOnDrop,
+ DragOptions options) {
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
@@ -173,8 +182,14 @@
final Resources res = mLauncher.getResources();
final float scaleDps = mIsInPreDrag
? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
- final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
- registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
+ final DragView dragView = mDragObject.dragView = new DragView(
+ mLauncher,
+ drawable,
+ registrationX,
+ registrationY,
+ initialDragViewScale,
+ dragViewScaleOnDrop,
+ scaleDps);
dragView.setItemInfo(dragInfo);
mDragObject.dragComplete = false;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 7a6b4f9..c80fd90 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -46,7 +46,6 @@
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.graphics.WorkspaceDragScrim;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -84,7 +83,6 @@
// Related to adjacent page hints
private final ViewGroupFocusHelper mFocusIndicatorHelper;
- private final OverviewScrim mOverviewScrim;
private WorkspaceDragScrim mWorkspaceDragScrim;
private SysUiScrim mSysUiScrim;
private LauncherRootView mRootView;
@@ -103,7 +101,6 @@
setChildrenDrawingOrderEnabled(true);
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
- mOverviewScrim = new OverviewScrim(this);
}
public void setup(DragController dragController, Workspace workspace) {
@@ -118,8 +115,6 @@
mRootView = (LauncherRootView) getParent();
mSysUiScrim = new SysUiScrim(mRootView);
mRootView.setSysUiScrim(mSysUiScrim);
-
-
}
@Override
@@ -529,20 +524,8 @@
protected void dispatchDraw(Canvas canvas) {
// Draw the background below children.
mWorkspaceDragScrim.draw(canvas);
- mOverviewScrim.updateCurrentScrimmedView(this);
mFocusIndicatorHelper.draw(canvas);
super.dispatchDraw(canvas);
- if (mOverviewScrim.getScrimmedView() == null) {
- mOverviewScrim.draw(canvas);
- }
- }
-
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (child == mOverviewScrim.getScrimmedView()) {
- mOverviewScrim.draw(canvas);
- }
- return super.drawChild(canvas, child, drawingTime);
}
@Override
@@ -555,7 +538,6 @@
public void setInsets(Rect insets) {
super.setInsets(insets);
mSysUiScrim.onInsetsChanged(insets, mAllowSysuiScrims);
- mOverviewScrim.onInsetsChanged(insets);
}
public WorkspaceDragScrim getWorkspaceDragScrim() {
@@ -565,8 +547,4 @@
public SysUiScrim getSysUiScrim() {
return mSysUiScrim;
}
-
- public OverviewScrim getOverviewScrim() {
- return mOverviewScrim;
- }
}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 86b93d0..0f26ff4 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -44,7 +44,6 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.FirstFrameAnimatorHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
@@ -52,6 +51,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statemanager.StateManager.StateListener;
@@ -67,9 +67,9 @@
public static final int COLOR_CHANGE_DURATION = 120;
public static final int VIEW_ZOOM_DURATION = 150;
- private boolean mDrawBitmap = true;
- private Bitmap mBitmap;
- private Bitmap mCrossFadeBitmap;
+ private boolean mShouldDraw = true;
+ private Drawable mDrawable;
+ private Drawable mCrossFadeDrawable;
@Thunk Paint mPaint;
private final int mBlurSizeOutline;
private final int mRegistrationX;
@@ -114,19 +114,21 @@
* The registration point is the point inside our view that the touch events should
* be centered upon.
* @param launcher The Launcher instance
- * @param bitmap The view that we're dragging around. We scale it up when we draw it.
+ * @param drawable The view that we're dragging around. We scale it up when we draw it.
* @param registrationX The x coordinate of the registration point.
* @param registrationY The y coordinate of the registration point.
*/
- public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
- final float initialScale, final float scaleOnDrop, final float finalScaleDps) {
+ public DragView(Launcher launcher, Drawable drawable, int registrationX,
+ int registrationY, final float initialScale, final float scaleOnDrop,
+ final float finalScaleDps) {
super(launcher);
mLauncher = launcher;
mDragLayer = launcher.getDragLayer();
mDragController = launcher.getDragController();
mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
- final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
+ final float scale = (drawable.getIntrinsicWidth() + finalScaleDps)
+ / drawable.getIntrinsicWidth();
// Set the initial scale to avoid any jumps
setScaleX(initialScale);
@@ -144,8 +146,9 @@
}
});
- mBitmap = bitmap;
- setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
+ mDrawable = drawable;
+ setDragRegion(new Rect(0, 0, drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight()));
// The point in our scaled bitmap that the touch events are located
mRegistrationX = registrationX;
@@ -197,8 +200,8 @@
@Override
public void run() {
Object[] outObj = new Object[1];
- int w = mBitmap.getWidth();
- int h = mBitmap.getHeight();
+ int w = mDrawable.getIntrinsicWidth();
+ int h = mDrawable.getIntrinsicHeight();
Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
if (dr instanceof AdaptiveIconDrawable) {
@@ -214,11 +217,11 @@
mBadge.setBounds(badgeBounds);
// Do not draw the background in case of folder as its translucent
- mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
+ mShouldDraw = !(dr instanceof FolderAdaptiveIcon);
try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
Drawable nDr; // drawable to be normalized
- if (mDrawBitmap) {
+ if (mShouldDraw) {
nDr = dr;
} else {
// Since we just want the scale, avoid heavy drawing operations
@@ -308,7 +311,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
+ setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
}
/** Sets the scale of the view over the normal workspace icon size. */
@@ -352,29 +355,37 @@
return mDragRegion;
}
- public Bitmap getPreviewBitmap() {
- return mBitmap;
- }
-
@Override
protected void onDraw(Canvas canvas) {
mHasDrawn = true;
- if (mDrawBitmap) {
+ if (mShouldDraw) {
// Always draw the bitmap to mask anti aliasing due to clipPath
- boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+ boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeDrawable != null;
if (crossFade) {
int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
mPaint.setAlpha(alpha);
}
- canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+ mDrawable.setColorFilter(mPaint.getColorFilter());
+ mDrawable.setAlpha(mPaint.getAlpha());
+ mDrawable.setBounds(
+ new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+ mDrawable.getIntrinsicHeight()));
+ mDrawable.draw(canvas);
if (crossFade) {
mPaint.setAlpha((int) (255 * mCrossFadeProgress));
final int saveCount = canvas.save();
- float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
- float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+ float sX = ((float) mDrawable.getIntrinsicWidth())
+ / mCrossFadeDrawable.getIntrinsicWidth();
+ float sY = ((float) mDrawable.getIntrinsicHeight())
+ / mCrossFadeDrawable.getIntrinsicHeight();
canvas.scale(sX, sY);
- canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+ mCrossFadeDrawable.setColorFilter(mPaint.getColorFilter());
+ mCrossFadeDrawable.setAlpha(mPaint.getAlpha());
+ mDrawable.setBounds(
+ new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+ mDrawable.getIntrinsicHeight()));
+ mCrossFadeDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
@@ -390,8 +401,8 @@
}
}
- public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
- mCrossFadeBitmap = crossFadeBitmap;
+ public void setCrossFadeDrawable(Drawable crossFadeDrawable) {
+ mCrossFadeDrawable = crossFadeDrawable;
}
public void crossFade(int duration) {
@@ -469,8 +480,8 @@
// Start the pick-up animation
DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
- lp.width = mBitmap.getWidth();
- lp.height = mBitmap.getHeight();
+ lp.width = mDrawable.getIntrinsicWidth();
+ lp.height = mDrawable.getIntrinsicHeight();
lp.customPosition = true;
setLayoutParams(lp);
move(touchX, touchY);
@@ -550,6 +561,11 @@
return mInitialScale;
}
+ /** Returns the current {@link Drawable} that is rendered in this view. */
+ public Drawable getDrawable() {
+ return mDrawable;
+ }
+
private static class SpringFloatValue {
private static final FloatPropertyCompat<SpringFloatValue> VALUE =
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 2290473..9f12e6e 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -96,7 +96,7 @@
PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
- dragHelper.setPreview(getPreview(mRequest));
+ dragHelper.setRemoteViewsPreview(getPreview(mRequest));
}
return dragHelper;
}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index f543e47..29e7c18 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -28,7 +28,6 @@
import android.os.Build;
import android.os.Process;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherAppState;
@@ -78,7 +77,7 @@
Drawable d = mContext.getSystemService(LauncherApps.class)
.getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi);
if (d == null) {
- d = new FastBitmapDrawable(cache.getDefaultIcon(Process.myUserHandle()));
+ d = cache.getDefaultIcon(Process.myUserHandle()).newIcon(mContext);
}
return d;
}
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index 37200a6..6325877 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -56,9 +56,8 @@
if (mScreen != null) {
// Snap to the screen that we are hovering over now
Workspace w = mLauncher.getWorkspace();
- int page = w.indexOfChild(mScreen);
- if (page != w.getCurrentPage()) {
- w.snapToPage(page);
+ if (!w.isVisible(mScreen)) {
+ w.snapToPage(w.indexOfChild(mScreen));
}
} else {
mLauncher.getDragController().cancelDrag();
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 5954efa..c67efef 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -97,7 +97,7 @@
double thetaShift = 0;
if (curNumItems == 3) {
- thetaShift = Math.PI / 6;
+ thetaShift = Math.PI / 2;
} else if (curNumItems == 4) {
thetaShift = Math.PI / 4;
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index bcb3a54..a1b7997 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -695,6 +696,9 @@
mPageIndicator.stopAllAnimations();
startAnimation(anim);
+ // Because t=0 has the folder match the folder icon, we can skip the
+ // first frame and have the same movement one frame earlier.
+ anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration()));
// Make sure the folder picks up the last drag move even if the finger doesn't move.
if (mDragController.isDragging()) {
@@ -741,6 +745,10 @@
if (mIsAnimatingClosed) {
return;
}
+
+ mContent.completePendingPageChanges();
+ mContent.snapToPageImmediately(mContent.getDestinationPage());
+
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
mCurrentAnimator.cancel();
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index feb528c..ee6ea99 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -40,7 +40,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
@@ -177,7 +176,7 @@
Math.round((totalOffsetX + initialSize)),
Math.round((paddingOffsetY + initialSize)));
Rect endRect = new Rect(0, 0, lp.width, lp.height);
- float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics());
+ float finalRadius = mFolderBackground.getCornerRadius();
// Create the animators.
AnimatorSet a = new AnimatorSet();
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 0235dfa..c1f4643 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -483,7 +483,7 @@
icon.verifyHighRes();
// Set the callback back to the actual icon, in case
// it was captured by the FolderIcon
- Drawable d = icon.getCompoundDrawables()[1];
+ Drawable d = icon.getIcon();
if (d != null) {
d.setCallback(icon);
}
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 9ae7faf..8244f01 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -16,7 +16,6 @@
package com.android.launcher3.folder;
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
@@ -33,10 +32,10 @@
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
import android.view.View;
-import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -113,7 +112,7 @@
}
Drawable prepareCreateAnimation(final View destView) {
- Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
+ Drawable animateDrawable = ((BubbleTextView) destView).getIcon();
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
mReferenceDrawable = animateDrawable;
@@ -401,7 +400,7 @@
drawable.setLevel(item.getProgressLevel());
p.drawable = drawable;
} else {
- p.drawable = newIcon(mContext, item);
+ p.drawable = item.newIcon(mContext);
}
p.drawable.setBounds(0, 0, mIconSize, mIconSize);
p.item = item;
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 21822a3..0e61b98 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -33,8 +33,10 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -88,10 +90,14 @@
}
/**
- * Returns a new bitmap to show when the {@link #mView} is being dragged around.
- * Responsibility for the bitmap is transferred to the caller.
+ * Returns a new drawable to show when the {@link #mView} is being dragged around.
+ * Responsibility for the drawable is transferred to the caller.
*/
- public Bitmap createDragBitmap() {
+ public Drawable createDrawable() {
+ if (mView instanceof LauncherAppWidgetHostView) {
+ return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) mView);
+ }
+
int width = 0;
int height = 0;
// Assume scaleX == scaleY, which is always the case for workspace items.
@@ -105,8 +111,9 @@
height = mView.getHeight();
}
- return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, scale));
+ return new FastBitmapDrawable(
+ BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
+ height + blurSizeOutline, (c) -> drawDragView(c, scale)));
}
public final void generateDragOutline(Bitmap preview) {
@@ -129,7 +136,7 @@
return bounds;
}
- public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+ public float getScaleAndPosition(Drawable preview, int[] outPos) {
float scale = Launcher.getLauncher(mView.getContext())
.getDragLayer().getLocationInDragLayer(mView, outPos);
if (mView instanceof LauncherAppWidgetHostView) {
@@ -139,8 +146,8 @@
}
outPos[0] = Math.round(outPos[0] -
- (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
- outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
+ (preview.getIntrinsicWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
+ outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getIntrinsicHeight() / 2
- previewPadding / 2);
return scale;
}
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index cb42e7a..911f8c3 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -90,7 +90,10 @@
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if ((type == XmlPullParser.START_TAG)
&& GridOption.TAG_NAME.equals(parser.getName())) {
- result.add(new GridOption(getContext(), Xml.asAttributeSet(parser)));
+ GridOption option = new GridOption(getContext(), Xml.asAttributeSet(parser));
+ if (option.visible) {
+ result.add(option);
+ }
}
}
} catch (IOException | XmlPullParserException e) {
diff --git a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java b/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
deleted file mode 100644
index 5872689..0000000
--- a/src/com/android/launcher3/graphics/NinePatchDrawHelper.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.graphics;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-
-/**
- * Utility class which draws a bitmap by dissecting it into 3 segments and stretching
- * the middle segment.
- */
-public class NinePatchDrawHelper {
-
- // The extra width used for the bitmap. This portion of the bitmap is stretched to match the
- // width of the draw region. Randomly chosen, any value > 4 will be sufficient.
- public static final int EXTENSION_PX = 20;
-
- private final Rect mSrc = new Rect();
- private final RectF mDst = new RectF();
- // Enable filtering to always get a nice edge. This avoids jagged line, when bitmap is
- // translated by half pixel.
- public final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-
- /**
- * Draws the bitmap split into three parts horizontally, with the middle part having width
- * as {@link #EXTENSION_PX} in the center of the bitmap.
- */
- public void draw(Bitmap bitmap, Canvas canvas, float left, float top, float right) {
- int height = bitmap.getHeight();
-
- mSrc.top = 0;
- mSrc.bottom = height;
- mDst.top = top;
- mDst.bottom = top + height;
- draw3Patch(bitmap, canvas, left, right);
- }
-
-
- /**
- * Draws the bitmap split horizontally into 3 parts (same as {@link #draw}) and split
- * vertically into two parts, bottom part of size {@link #EXTENSION_PX} / 2 which is
- * stretched vertically.
- */
- public void drawVerticallyStretched(Bitmap bitmap, Canvas canvas, float left, float top,
- float right, float bottom) {
- draw(bitmap, canvas, left, top, right);
-
- // Draw bottom stretched region.
- int height = bitmap.getHeight();
- mSrc.top = height - EXTENSION_PX / 4;
- mSrc.bottom = height;
- mDst.top = top + height;
- mDst.bottom = bottom;
- draw3Patch(bitmap, canvas, left, right);
- }
-
-
-
- private void draw3Patch(Bitmap bitmap, Canvas canvas, float left, float right) {
- int width = bitmap.getWidth();
- int halfWidth = width / 2;
-
- // Draw left edge
- drawRegion(bitmap, canvas, 0, halfWidth, left, left + halfWidth);
-
- // Draw right edge
- drawRegion(bitmap, canvas, halfWidth, width, right - halfWidth, right);
-
- // Draw middle stretched region
- int halfExt = EXTENSION_PX / 4;
- drawRegion(bitmap, canvas, halfWidth - halfExt, halfWidth + halfExt,
- left + halfWidth, right - halfWidth);
- }
-
- private void drawRegion(Bitmap bitmap, Canvas c,
- int srcLeft, int srcRight, float dstLeft, float dstRight) {
- mSrc.left = srcLeft;
- mSrc.right = srcRight;
-
- mDst.left = dstLeft;
- mDst.right = dstRight;
- c.drawBitmap(bitmap, mSrc, mDst, paint);
- }
-}
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
deleted file mode 100644
index c0c3e5e..0000000
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ /dev/null
@@ -1,99 +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.graphics;
-
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.graphics.Rect;
-import android.util.FloatProperty;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * View scrim which draws behind overview (recent apps).
- */
-public class OverviewScrim extends Scrim {
-
- public static final FloatProperty<OverviewScrim> SCRIM_MULTIPLIER =
- new FloatProperty<OverviewScrim>("scrimMultiplier") {
- @Override
- public Float get(OverviewScrim scrim) {
- return scrim.mScrimMultiplier;
- }
-
- @Override
- public void setValue(OverviewScrim scrim, float v) {
- scrim.setScrimMultiplier(v);
- }
- };
-
- private @NonNull View mStableScrimmedView;
- // Might be higher up if mStableScrimmedView is invisible.
- private @Nullable View mCurrentScrimmedView;
-
- private float mScrimMultiplier = 1f;
-
- public OverviewScrim(View view) {
- super(view);
- mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
-
- onExtractedColorsChanged(mWallpaperColorInfo);
- }
-
- public void onInsetsChanged(Rect insets) {
- mStableScrimmedView = (OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0
- ? mLauncher.getHotseat()
- : mLauncher.getOverviewPanel();
- }
-
- public void updateCurrentScrimmedView(ViewGroup root) {
- // Find the lowest view that is at or above the view we want to show the scrim behind.
- mCurrentScrimmedView = mStableScrimmedView;
- int currentIndex = root.indexOfChild(mCurrentScrimmedView);
- final int childCount = root.getChildCount();
- while (mCurrentScrimmedView != null && mCurrentScrimmedView.getVisibility() != VISIBLE
- && currentIndex < childCount) {
- currentIndex++;
- mCurrentScrimmedView = root.getChildAt(currentIndex);
- }
- }
-
- /**
- * @return The view to draw the scrim behind, or null if all visible views should be scrimmed.
- */
- public @Nullable View getScrimmedView() {
- return mCurrentScrimmedView;
- }
-
- private void setScrimMultiplier(float scrimMultiplier) {
- if (Float.compare(mScrimMultiplier, scrimMultiplier) != 0) {
- mScrimMultiplier = scrimMultiplier;
- invalidate();
- }
- }
-
- @Override
- protected int getScrimAlpha() {
- return Math.round(super.getScrimAlpha() * mScrimMultiplier);
- }
-}
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
deleted file mode 100644
index b6d25c4..0000000
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ /dev/null
@@ -1,88 +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.graphics;
-
-import static androidx.core.graphics.ColorUtils.compositeColors;
-
-import static com.android.launcher3.graphics.IconShape.getShapePath;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Path;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-import androidx.core.graphics.ColorUtils;
-
-import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.util.Themes;
-
-/**
- * Subclass which draws a placeholder icon when the actual icon is not yet loaded
- */
-public class PlaceHolderIconDrawable extends FastBitmapDrawable {
-
- // Path in [0, 100] bounds.
- private final Path mProgressPath;
-
- public PlaceHolderIconDrawable(BitmapInfo info, Context context) {
- super(info);
-
- mProgressPath = getShapePath();
- mPaint.setColor(compositeColors(
- Themes.getAttrColor(context, R.attr.loadingIconColor), info.color));
- }
-
- @Override
- protected void drawInternal(Canvas canvas, Rect bounds) {
- int saveCount = canvas.save();
- canvas.translate(bounds.left, bounds.top);
- canvas.scale(bounds.width() / 100f, bounds.height() / 100f);
- canvas.drawPath(mProgressPath, mPaint);
- canvas.restoreToCount(saveCount);
- }
-
- /** Updates this placeholder to {@code newIcon} with animation. */
- public void animateIconUpdate(Drawable newIcon) {
- int placeholderColor = mPaint.getColor();
- int originalAlpha = Color.alpha(placeholderColor);
-
- ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0);
- iconUpdateAnimation.setDuration(375);
- iconUpdateAnimation.addUpdateListener(valueAnimator -> {
- int newAlpha = (int) valueAnimator.getAnimatedValue();
- int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha);
-
- newIcon.setColorFilter(new PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_ATOP));
- });
- iconUpdateAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- newIcon.setColorFilter(null);
- }
- });
- iconUpdateAnimation.start();
- }
-
-}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index ce824df..ac0ec5f 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -37,8 +37,8 @@
import android.util.SparseArray;
import android.view.ContextThemeWrapper;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.Themes;
diff --git a/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/src/com/android/launcher3/icons/ClockDrawableWrapper.java
deleted file mode 100644
index 1bd252b..0000000
--- a/src/com/android/launcher3/icons/ClockDrawableWrapper.java
+++ /dev/null
@@ -1,328 +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.icons;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.launcher3.FastBitmapDrawable;
-
-import java.util.Calendar;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
- * clock icons
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
-
- private static final String TAG = "ClockDrawableWrapper";
-
- private static final boolean DISABLE_SECONDS = true;
-
- // Time after which the clock icon should check for an update. The actual invalidate
- // will only happen in case of any change.
- public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L;
-
- private static final String LAUNCHER_PACKAGE = "com.android.launcher3";
- private static final String ROUND_ICON_METADATA_KEY = LAUNCHER_PACKAGE
- + ".LEVEL_PER_TICK_ICON_ROUND";
- private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX";
- private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
- + ".MINUTE_LAYER_INDEX";
- private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
- + ".SECOND_LAYER_INDEX";
- private static final String DEFAULT_HOUR_METADATA_KEY = LAUNCHER_PACKAGE
- + ".DEFAULT_HOUR";
- private static final String DEFAULT_MINUTE_METADATA_KEY = LAUNCHER_PACKAGE
- + ".DEFAULT_MINUTE";
- private static final String DEFAULT_SECOND_METADATA_KEY = LAUNCHER_PACKAGE
- + ".DEFAULT_SECOND";
-
- /* Number of levels to jump per second for the second hand */
- private static final int LEVELS_PER_SECOND = 10;
-
- public static final int INVALID_VALUE = -1;
-
- private final AnimationInfo mAnimationInfo = new AnimationInfo();
- private int mTargetSdkVersion;
-
- public ClockDrawableWrapper(AdaptiveIconDrawable base) {
- super(base.getBackground(), base.getForeground());
- }
-
- /**
- * Loads and returns the wrapper from the provided package, or returns null
- * if it is unable to load.
- */
- public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) {
- try {
- PackageManager pm = context.getPackageManager();
- ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
- final Bundle metadata = appInfo.metaData;
- if (metadata == null) {
- return null;
- }
- int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0);
- if (drawableId == 0) {
- return null;
- }
-
- Drawable drawable = pm.getResourcesForApplication(appInfo).getDrawableForDensity(
- drawableId, iconDpi).mutate();
- if (!(drawable instanceof AdaptiveIconDrawable)) {
- return null;
- }
-
- ClockDrawableWrapper wrapper =
- new ClockDrawableWrapper((AdaptiveIconDrawable) drawable);
- wrapper.mTargetSdkVersion = appInfo.targetSdkVersion;
- AnimationInfo info = wrapper.mAnimationInfo;
-
- info.baseDrawableState = drawable.getConstantState();
-
- info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE);
- info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE);
- info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE);
-
- info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0);
- info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0);
- info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0);
-
- LayerDrawable foreground = (LayerDrawable) wrapper.getForeground();
- int layerCount = foreground.getNumberOfLayers();
- if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) {
- info.hourLayerIndex = INVALID_VALUE;
- }
- if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) {
- info.minuteLayerIndex = INVALID_VALUE;
- }
- if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) {
- info.secondLayerIndex = INVALID_VALUE;
- } else if (DISABLE_SECONDS) {
- foreground.setDrawable(info.secondLayerIndex, null);
- info.secondLayerIndex = INVALID_VALUE;
- }
- return wrapper;
- } catch (Exception e) {
- Log.d(TAG, "Unable to load clock drawable info", e);
- }
- return null;
- }
-
- @Override
- public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) {
- iconFactory.disableColorExtraction();
- float [] scale = new float[1];
- AdaptiveIconDrawable background = new AdaptiveIconDrawable(
- getBackground().getConstantState().newDrawable(), null);
- BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background,
- Process.myUserHandle(), mTargetSdkVersion, false, scale);
-
- return new ClockBitmapInfo(bitmap, color, scale[0], mAnimationInfo, bitmapInfo.icon);
- }
-
- @Override
- public void prepareToDrawOnUi() {
- mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground());
- }
-
- private static class AnimationInfo {
-
- public ConstantState baseDrawableState;
-
- public int hourLayerIndex;
- public int minuteLayerIndex;
- public int secondLayerIndex;
- public int defaultHour;
- public int defaultMinute;
- public int defaultSecond;
-
- boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) {
- time.setTimeInMillis(System.currentTimeMillis());
-
- // We need to rotate by the difference from the default time if one is specified.
- int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12;
- int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60;
- int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60;
-
- boolean invalidate = false;
- if (hourLayerIndex != INVALID_VALUE) {
- final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex);
- if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) {
- invalidate = true;
- }
- }
-
- if (minuteLayerIndex != INVALID_VALUE) {
- final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex);
- if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) {
- invalidate = true;
- }
- }
-
- if (secondLayerIndex != INVALID_VALUE) {
- final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex);
- if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) {
- invalidate = true;
- }
- }
-
- return invalidate;
- }
- }
-
- private static class ClockBitmapInfo extends BitmapInfo implements FastBitmapDrawable.Factory {
-
- public final float scale;
- public final int offset;
- public final AnimationInfo animInfo;
- public final Bitmap mFlattenedBackground;
-
- ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo,
- Bitmap background) {
- super(icon, color);
- this.scale = scale;
- this.animInfo = animInfo;
- this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth());
- this.mFlattenedBackground = background;
- }
-
- @Override
- public FastBitmapDrawable newDrawable() {
- return new ClockIconDrawable(this);
- }
- }
-
- private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
-
- private final Calendar mTime = Calendar.getInstance();
-
- private final ClockBitmapInfo mInfo;
-
- private final AdaptiveIconDrawable mFullDrawable;
- private final LayerDrawable mForeground;
-
- ClockIconDrawable(ClockBitmapInfo clockInfo) {
- super(clockInfo);
-
- mInfo = clockInfo;
-
- mFullDrawable = (AdaptiveIconDrawable) mInfo.animInfo.baseDrawableState.newDrawable();
- mForeground = (LayerDrawable) mFullDrawable.getForeground();
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- mFullDrawable.setBounds(bounds);
- }
-
- @Override
- public void drawInternal(Canvas canvas, Rect bounds) {
- if (mInfo == null) {
- super.drawInternal(canvas, bounds);
- return;
- }
- // draw the background that is already flattened to a bitmap
- canvas.drawBitmap(mInfo.mFlattenedBackground, null, bounds, mPaint);
-
- // prepare and draw the foreground
- mInfo.animInfo.applyTime(mTime, mForeground);
-
- canvas.scale(mInfo.scale, mInfo.scale,
- bounds.exactCenterX() + mInfo.offset, bounds.exactCenterY() + mInfo.offset);
- canvas.clipPath(mFullDrawable.getIconMask());
- mForeground.draw(canvas);
-
- reschedule();
- }
-
- @Override
- protected void updateFilter() {
- super.updateFilter();
- mFullDrawable.setColorFilter(mPaint.getColorFilter());
- }
-
- @Override
- public void run() {
- if (mInfo.animInfo.applyTime(mTime, mForeground)) {
- invalidateSelf();
- } else {
- reschedule();
- }
- }
-
- @Override
- public boolean setVisible(boolean visible, boolean restart) {
- boolean result = super.setVisible(visible, restart);
- if (visible) {
- reschedule();
- } else {
- unscheduleSelf(this);
- }
- return result;
- }
-
- private void reschedule() {
- if (!isVisible()) {
- return;
- }
-
- unscheduleSelf(this);
- final long upTime = SystemClock.uptimeMillis();
- final long step = TICK_MS; /* tick every 200 ms */
- scheduleSelf(this, upTime - ((upTime % step)) + step);
- }
-
- @Override
- public ConstantState getConstantState() {
- return new ClockConstantState(mInfo, isDisabled());
- }
-
- private static class ClockConstantState extends FastBitmapConstantState {
-
- private final ClockBitmapInfo mInfo;
-
- ClockConstantState(ClockBitmapInfo info, boolean isDisabled) {
- super(info.icon, info.color, isDisabled);
- mInfo = info;
- }
-
- @Override
- public FastBitmapDrawable newDrawable() {
- ClockIconDrawable drawable = new ClockIconDrawable(mInfo);
- drawable.setIsDisabled(mIsDisabled);
- return drawable;
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 61f2c2a..988794c 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -131,6 +131,7 @@
/**
* Fetches high-res icon for the provided ItemInfo and updates the caller when done.
+ *
* @return a request ID that can be used to cancel the request.
*/
public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller,
@@ -139,7 +140,7 @@
if (mPendingIconRequestCount <= 0) {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
}
- mPendingIconRequestCount ++;
+ mPendingIconRequestCount++;
HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler,
() -> {
@@ -158,7 +159,7 @@
}
private void onIconRequestEnd() {
- mPendingIconRequestCount --;
+ mPendingIconRequestCount--;
if (mPendingIconRequestCount <= 0) {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
@@ -289,7 +290,8 @@
@NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
boolean usePkgIcon, boolean useLowResIcon) {
CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
- activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon);
+ activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
+ useLowResIcon);
applyCacheEntry(entry, infoInOut);
}
@@ -315,7 +317,8 @@
}
public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
- cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel());
+ cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(),
+ info.getAppLabel());
}
@Override
diff --git a/src/com/android/launcher3/icons/IconProvider.java b/src/com/android/launcher3/icons/IconProvider.java
deleted file mode 100644
index 1468b27..0000000
--- a/src/com/android/launcher3/icons/IconProvider.java
+++ /dev/null
@@ -1,251 +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.icons;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Process;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapInfo.Extender;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SafeCloseable;
-
-import java.util.Calendar;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-
-/**
- * Class to handle icon loading from different packages
- */
-public class IconProvider {
-
- private static final String TAG = "IconProvider";
- private static final boolean DEBUG = false;
-
- private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
-
- private static final String SYSTEM_STATE_SEPARATOR = " ";
-
- // Default value returned if there are problems getting resources.
- private static final int NO_ID = 0;
-
- private static final BiFunction<LauncherActivityInfo, Integer, Drawable> LAI_LOADER =
- LauncherActivityInfo::getIcon;
-
- private static final BiFunction<ActivityInfo, PackageManager, Drawable> AI_LOADER =
- ActivityInfo::loadUnbadgedIcon;
-
-
- private final Context mContext;
- private final ComponentName mCalendar;
- private final ComponentName mClock;
-
- public IconProvider(Context context) {
- mContext = context;
- mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
- mClock = parseComponentOrNull(context, R.string.clock_component_name);
- }
-
- /**
- * Adds any modification to the provided systemState for dynamic icons. This system state
- * is used by caches to check for icon invalidation.
- */
- public String getSystemStateForPackage(String systemState, String packageName) {
- if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
- return systemState + SYSTEM_STATE_SEPARATOR + getDay();
- } else {
- return systemState;
- }
- }
-
- /**
- * Loads the icon for the provided LauncherActivityInfo such that it can be drawn directly
- * on the UI
- */
- public Drawable getIconForUI(LauncherActivityInfo info, int iconDpi) {
- Drawable icon = getIcon(info, iconDpi);
- if (icon instanceof BitmapInfo.Extender) {
- ((Extender) icon).prepareToDrawOnUi();
- }
- return icon;
- }
-
- /**
- * Loads the icon for the provided LauncherActivityInfo
- */
- public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
- return getIcon(info.getApplicationInfo().packageName, info.getUser(),
- info, iconDpi, LAI_LOADER);
- }
-
- /**
- * Loads the icon for the provided activity info
- */
- public Drawable getIcon(ActivityInfo info, UserHandle user) {
- return getIcon(info.applicationInfo.packageName, user, info, mContext.getPackageManager(),
- AI_LOADER);
- }
-
- private <T, P> Drawable getIcon(String packageName, UserHandle user, T obj, P param,
- BiFunction<T, P, Drawable> loader) {
- Drawable icon = null;
- if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
- icon = loadCalendarDrawable(0);
- } else if (mClock != null
- && mClock.getPackageName().equals(packageName)
- && Process.myUserHandle().equals(user)) {
- icon = loadClockDrawable(0);
- }
- return icon == null ? loader.apply(obj, param) : icon;
- }
-
- private Drawable loadCalendarDrawable(int iconDpi) {
- PackageManager pm = mContext.getPackageManager();
- try {
- final Bundle metadata = pm.getActivityInfo(
- mCalendar,
- PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
- .metaData;
- final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
- final int id = getDynamicIconId(metadata, resources);
- if (id != NO_ID) {
- if (DEBUG) Log.d(TAG, "Got icon #" + id);
- return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
- }
- } catch (PackageManager.NameNotFoundException e) {
- if (DEBUG) {
- Log.d(TAG, "Could not get activityinfo or resources for package: "
- + mCalendar.getPackageName());
- }
- }
- return null;
- }
-
- private Drawable loadClockDrawable(int iconDpi) {
- return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
- }
-
- protected boolean isClockIcon(ComponentKey key) {
- return mClock != null && mClock.equals(key.componentName)
- && Process.myUserHandle().equals(key.user);
- }
-
- /**
- * @param metadata metadata of the default activity of Calendar
- * @param resources from the Calendar package
- * @return the resource id for today's Calendar icon; 0 if resources cannot be found.
- */
- private int getDynamicIconId(Bundle metadata, Resources resources) {
- if (metadata == null) {
- return NO_ID;
- }
- String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
- final int arrayId = metadata.getInt(key, NO_ID);
- if (arrayId == NO_ID) {
- return NO_ID;
- }
- try {
- return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID);
- } catch (Resources.NotFoundException e) {
- if (DEBUG) {
- Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
- }
- return NO_ID;
- }
- }
-
- /**
- * @return Today's day of the month, zero-indexed.
- */
- private int getDay() {
- return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
- }
-
-
- /**
- * Registers a callback to listen for calendar icon changes.
- * The callback receives the packageName for the calendar icon
- */
- public static SafeCloseable registerIconChangeListener(Context context,
- BiConsumer<String, UserHandle> callback, Handler handler) {
- ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name);
- ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
-
- if (calendar == null && clock == null) {
- return () -> { };
- }
-
- BroadcastReceiver receiver = new DateTimeChangeReceiver(callback);
- final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
- if (calendar != null) {
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_DATE_CHANGED);
- }
- context.registerReceiver(receiver, filter, null, handler);
-
- return () -> context.unregisterReceiver(receiver);
- }
-
- private static class DateTimeChangeReceiver extends BroadcastReceiver {
-
- private final BiConsumer<String, UserHandle> mCallback;
-
- DateTimeChangeReceiver(BiConsumer<String, UserHandle> callback) {
- mCallback = callback;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
- ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
- if (clock != null) {
- mCallback.accept(clock.getPackageName(), Process.myUserHandle());
- }
- }
-
- ComponentName calendar =
- parseComponentOrNull(context, R.string.calendar_component_name);
- if (calendar != null) {
- for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
- mCallback.accept(calendar.getPackageName(), user);
- }
- }
-
- }
- }
-
- private static ComponentName parseComponentOrNull(Context context, int resId) {
- String cn = context.getString(resId);
- return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
-
- }
-}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 56dbbd3..fd51ba8 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -20,6 +20,7 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller.SessionInfo;
import android.os.UserHandle;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
@@ -47,6 +48,8 @@
*/
public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
+ private static final String LOG = "AddWorkspaceItemsTask";
+
private final List<Pair<ItemInfo, Object>> mItemList;
/**
@@ -167,6 +170,8 @@
// Save the WorkspaceItemInfo for binding in the workspace
addedItemsFinal.add(itemInfo);
+
+ Log.i(LOG, "Adding item info to workspace: " + itemInfo);
}
}
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 70d1b48..e391d37 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.os.Process.myUserHandle;
import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
@@ -140,7 +142,7 @@
.putStringArrayListExtra(HOTSEAT_ITEM_EXTRA, new ArrayList<>(hotseatItems))
.putStringArrayListExtra(WIDGET_ITEM_EXTRA, new ArrayList<>(widgetItems))
.putExtra(VERIFICATION_TOKEN_EXTRA, PendingIntent.getActivity(context, 0,
- new Intent(), PendingIntent.FLAG_ONE_SHOT)));
+ new Intent(), FLAG_ONE_SHOT | FLAG_IMMUTABLE)));
}
private static String getPackageName(ItemInfo info) {
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index df8367f..836d804 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -63,6 +63,8 @@
*/
public class ItemInstallQueue {
+ private static final String LOG = "ItemInstallQueue";
+
public static final int FLAG_ACTIVITY_PAUSED = 1;
public static final int FLAG_LOADER_RUNNING = 2;
public static final int FLAG_DRAG_AND_DROP = 4;
@@ -183,7 +185,17 @@
private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
// Queue the item up for adding if launcher has not loaded properly yet
- MODEL_EXECUTOR.post(() -> addToQueue(info));
+ MODEL_EXECUTOR.post(() -> {
+ Pair<ItemInfo, Object> itemInfo = info.getItemInfo(mContext);
+ if (itemInfo == null) {
+ Log.i(LOG, "Adding PendingInstallShortcutInfo with no attached info to queue.");
+ } else {
+ Log.i(LOG, "Adding PendingInstallShortcutInfo to queue. Attached info: "
+ + itemInfo.first);
+ }
+
+ addToQueue(info);
+ });
flushInstallQueue();
}
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index d95e708..76b2ab0 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -23,6 +23,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.util.PackageManagerHelper;
@@ -216,4 +217,14 @@
* @return a copy of this
*/
public abstract ItemInfoWithIcon clone();
+
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public FastBitmapDrawable newIcon(Context context) {
+ FastBitmapDrawable drawable = bitmap.newIcon(context);
+ drawable.setIsDisabled(isDisabled());
+ return drawable;
+ }
}
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index 80eeb22..d27d8c7 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -22,7 +22,6 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -86,9 +85,8 @@
mIsIconLarge = true;
}
if (mIconDrawable == null) {
- mIconDrawable = new BitmapDrawable(context.getResources(), LauncherAppState
- .getInstance(context).getIconCache()
- .getDefaultIcon(statusBarNotification.getUser()).icon);
+ mIconDrawable = LauncherAppState.getInstance(context).getIconCache()
+ .getDefaultIcon(statusBarNotification.getUser()).newIcon(context);
}
intent = notification.contentIntent;
autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0;
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 0320aa3..e954480 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -21,10 +21,13 @@
import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.Outline;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewOutlineProvider;
import android.widget.TextView;
import com.android.launcher3.R;
@@ -43,7 +46,8 @@
private static final Rect sTempRect = new Rect();
private final Context mContext;
- private final PopupContainerWithArrow mContainer;
+ private final PopupContainerWithArrow mPopupContainer;
+ private final ViewGroup mRootView;
private final TextView mHeaderText;
private final TextView mHeaderCount;
@@ -53,7 +57,6 @@
private final View mIconView;
private final View mHeader;
- private final View mDivider;
private View mGutter;
@@ -61,8 +64,9 @@
private boolean mAnimatingNextIcon;
private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
- public NotificationItemView(PopupContainerWithArrow container) {
- mContainer = container;
+ public NotificationItemView(PopupContainerWithArrow container, ViewGroup rootView) {
+ mPopupContainer = container;
+ mRootView = rootView;
mContext = container.getContext();
mHeaderText = container.findViewById(R.id.notification_text);
@@ -72,17 +76,29 @@
mIconView = container.findViewById(R.id.popup_item_icon);
mHeader = container.findViewById(R.id.header);
- mDivider = container.findViewById(R.id.divider);
mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL);
mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
mMainView.setSwipeDetector(mSwipeDetector);
mFooter.setContainer(this);
+
+ float radius = Themes.getDialogCornerRadius(mContext);
+ rootView.setClipToOutline(true);
+ rootView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
+ }
+ });
+ }
+
+ public void updateBackgroundColor(int color) {
+ mMainView.updateBackgroundColor(color);
}
public void addGutter() {
if (mGutter == null) {
- mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter, mContainer);
+ mGutter = mPopupContainer.inflateAndAdd(R.layout.notification_gutter, mRootView);
}
}
@@ -94,9 +110,8 @@
}
public void removeFooter() {
- if (mContainer.indexOfChild(mFooter) >= 0) {
- mContainer.removeView(mFooter);
- mContainer.removeView(mDivider);
+ if (mRootView.indexOfChild(mFooter) >= 0) {
+ mRootView.removeView(mFooter);
}
}
@@ -108,16 +123,15 @@
}
public void removeAllViews() {
- mContainer.removeView(mMainView);
- mContainer.removeView(mHeader);
+ mRootView.removeView(mMainView);
+ mRootView.removeView(mHeader);
- if (mContainer.indexOfChild(mFooter) >= 0) {
- mContainer.removeView(mFooter);
- mContainer.removeView(mDivider);
+ if (mRootView.indexOfChild(mFooter) >= 0) {
+ mRootView.removeView(mFooter);
}
if (mGutter != null) {
- mContainer.removeView(mGutter);
+ mRootView.removeView(mGutter);
}
}
@@ -136,11 +150,11 @@
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
- mMainView.getRight(), mMainView.getBottom());
+ sTempRect.set(mRootView.getLeft(), mRootView.getTop(),
+ mRootView.getRight(), mRootView.getBottom());
mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
if (!mIgnoreTouch) {
- mContainer.getParent().requestDisallowInterceptTouchEvent(true);
+ mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true);
}
}
if (mIgnoreTouch) {
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 2905dc3..e58f5fa 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,9 +16,9 @@
package com.android.launcher3.notification;
-import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import android.annotation.TargetApi;
import android.app.Notification;
@@ -37,8 +37,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SettingsCache;
import java.util.ArrayList;
import java.util.Arrays;
@@ -213,7 +213,7 @@
mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
mSettingsCache.register(NOTIFICATION_BADGING_URI,
mNotificationSettingsChangedListener);
- mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
+ onNotificationSettingsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));
onNotificationFullRefresh();
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 9b06523..c995666 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -97,15 +97,24 @@
super.onFinishInflate();
mTextAndBackground = findViewById(R.id.text_and_background);
- ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
- mBackgroundColor = colorBackground.getColor();
- RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
- Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
- colorBackground, null);
- mTextAndBackground.setBackground(rippleBackground);
mTitleView = mTextAndBackground.findViewById(R.id.title);
mTextView = mTextAndBackground.findViewById(R.id.text);
mIconView = findViewById(R.id.popup_item_icon);
+
+ ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
+ updateBackgroundColor(colorBackground.getColor());
+ }
+
+ public void updateBackgroundColor(int color) {
+ mBackgroundColor = color;
+ RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
+ Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
+ new ColorDrawable(color), null);
+ mTextAndBackground.setBackground(rippleBackground);
+ if (mNotificationInfo != null) {
+ mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+ mBackgroundColor));
+ }
}
public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) {
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 6189dc9..a7cd10d 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -269,7 +269,7 @@
lp.leftMargin = lp.rightMargin = 0;
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
lp.bottomMargin = grid.isTaskbarPresent
- ? grid.workspacePadding.bottom + insets.bottom
+ ? grid.workspacePadding.bottom + grid.taskbarSize
: grid.hotseatBarSizePx + insets.bottom;
}
setLayoutParams(lp);
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 0091af1..72f1c58 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -29,6 +29,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -56,6 +57,8 @@
*/
public class InstallSessionHelper {
+ private static final String LOG = "InstallSessionHelper";
+
// Set<String> of session ids of promise icons that have been added to the home screen
// as FLAG_PROMISE_NEW_INSTALLS.
protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
@@ -219,6 +222,9 @@
&& !promiseIconAddedForId(sessionInfo.getSessionId())
&& !new PackageManagerHelper(mAppContext).isAppInstalled(
sessionInfo.getAppPackageName(), getUserHandle(sessionInfo))) {
+ Log.i(LOG, "Adding package name to install queue: "
+ + sessionInfo.getAppPackageName());
+
ItemInstallQueue.INSTANCE.get(mAppContext)
.queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 5a34d2a..3736538 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -17,10 +17,12 @@
package com.android.launcher3.popup;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
@@ -28,6 +30,9 @@
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.Gravity;
@@ -48,6 +53,7 @@
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
@@ -61,6 +67,9 @@
*/
public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
+ // +1 for system shortcut view
+ private static final int MAX_NUM_CHILDREN = MAX_SHORTCUTS + 1;
+
private final Rect mTempRect = new Rect();
protected final LayoutInflater mInflater;
@@ -75,6 +84,8 @@
private final int mArrowPointRadius;
private final View mArrow;
+ private final int mMargin;
+
protected boolean mIsLeftAligned;
protected boolean mIsAboveIcon;
private int mGravity;
@@ -84,8 +95,14 @@
private final Rect mStartRect = new Rect();
private final Rect mEndRect = new Rect();
+ private final GradientDrawable mRoundedTop;
+ private final GradientDrawable mRoundedBottom;
+
private Runnable mOnCloseCallback = () -> { };
+ private int mArrowColor;
+ private final int[] mColors;
+
public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mInflater = LayoutInflater.from(context);
@@ -103,6 +120,7 @@
// Initialize arrow view
final Resources resources = getResources();
+ mMargin = resources.getDimensionPixelSize(R.dimen.popup_margin);
mArrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
mArrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
mArrow = new View(context);
@@ -111,6 +129,25 @@
mArrowOffsetHorizontal = resources.getDimensionPixelSize(
R.dimen.popup_arrow_horizontal_center_offset) - (mArrowWidth / 2);
mArrowPointRadius = resources.getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+
+ int smallerRadius = resources.getDimensionPixelSize(R.dimen.popup_smaller_radius);
+ mRoundedTop = new GradientDrawable();
+ mRoundedTop.setCornerRadii(new float[] { mOutlineRadius, mOutlineRadius, mOutlineRadius,
+ mOutlineRadius, smallerRadius, smallerRadius, smallerRadius, smallerRadius});
+
+ mRoundedBottom = new GradientDrawable();
+ mRoundedBottom.setCornerRadii(new float[] { smallerRadius, smallerRadius, smallerRadius,
+ smallerRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius});
+
+ int primaryColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
+ int secondaryColor = Themes.getAttrColor(context, R.attr.popupColorSecondary);
+ ArgbEvaluator argb = new ArgbEvaluator();
+ mColors = new int[MAX_NUM_CHILDREN];
+ // Interpolate between the two colors, exclusive.
+ float step = 1f / (MAX_NUM_CHILDREN + 1);
+ for (int i = 0; i < mColors.length; ++i) {
+ mColors[i] = (int) argb.evaluate((i + 1) * step, primaryColor, secondaryColor);
+ }
}
public ArrowPopup(Context context, AttributeSet attrs) {
@@ -154,6 +191,77 @@
protected void onInflationComplete(boolean isReversed) { }
/**
+ * Set the margins and radius of backgrounds after views are properly ordered.
+ */
+ protected void assignMarginsAndBackgrounds() {
+ int count = getChildCount();
+ int totalVisibleShortcuts = 0;
+ for (int i = 0; i < count; i++) {
+ View view = getChildAt(i);
+ if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
+ totalVisibleShortcuts++;
+ }
+ }
+
+ int numVisibleShortcut = 0;
+ View lastView = null;
+ int numVisibleChild = 0;
+ for (int i = 0; i < count; i++) {
+ View view = getChildAt(i);
+ boolean isShortcut = view instanceof DeepShortcutView;
+ if (view.getVisibility() == VISIBLE) {
+ if (lastView != null) {
+ MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams();
+ mlp.bottomMargin = mMargin;
+ }
+ lastView = view;
+ MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams();
+ mlp.bottomMargin = 0;
+
+ if (isShortcut) {
+ if (totalVisibleShortcuts == 1) {
+ view.setBackgroundResource(R.drawable.single_item_primary);
+ } else if (totalVisibleShortcuts > 1) {
+ if (numVisibleShortcut == 0) {
+ view.setBackground(mRoundedTop);
+ } else if (numVisibleShortcut == (totalVisibleShortcuts - 1)) {
+ view.setBackground(mRoundedBottom);
+ } else {
+ view.setBackgroundResource(R.drawable.middle_item_primary);
+ }
+ numVisibleShortcut++;
+ }
+ }
+
+ int color = mColors[numVisibleChild % mColors.length];
+ setChildColor(view, color);
+
+ // Arrow color matches the first child or the last child.
+ if (!mIsAboveIcon && numVisibleChild == 0) {
+ mArrowColor = color;
+ } else if (mIsAboveIcon) {
+ mArrowColor = color;
+ }
+
+ numVisibleChild++;
+ }
+ }
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+
+ /**
+ * Sets the background color of the child.
+ */
+ protected void setChildColor(View view, int color) {
+ Drawable bg = view.getBackground();
+ if (bg instanceof GradientDrawable) {
+ ((GradientDrawable) bg.mutate()).setColor(color);
+ } else if (bg instanceof ColorDrawable) {
+ ((ColorDrawable) bg.mutate()).setColor(color);
+ }
+ }
+
+ /**
* Shows the popup at the desired location, optionally reversing the children.
* @param viewsToFlip number of views from the top to to flip in case of reverse order
*/
@@ -164,7 +272,10 @@
reverseOrder(viewsToFlip);
}
onInflationComplete(reverseOrder);
- addArrow();
+ assignMarginsAndBackgrounds();
+ if (shouldAddArrow()) {
+ addArrow();
+ }
animateOpen();
}
@@ -174,7 +285,10 @@
protected void show() {
setupForDisplay();
onInflationComplete(false);
- addArrow();
+ assignMarginsAndBackgrounds();
+ if (shouldAddArrow()) {
+ addArrow();
+ }
animateOpen();
}
@@ -199,8 +313,6 @@
for (int i = 0; i < count; i++) {
addView(allViews.get(i));
}
-
- orientAboutObject();
}
private int getArrowLeft() {
@@ -224,7 +336,7 @@
mOutlineRadius, getMeasuredWidth(), getMeasuredHeight(),
mArrowOffsetHorizontal, -mArrowOffsetVertical,
!mIsAboveIcon, mIsLeftAligned,
- Themes.getAttrColor(getContext(), R.attr.popupColorPrimary)));
+ mArrowColor));
mArrow.setElevation(getElevation());
}
@@ -233,6 +345,13 @@
}
/**
+ * Returns whether or not we should add the arrow.
+ */
+ protected boolean shouldAddArrow() {
+ return true;
+ }
+
+ /**
* Provide the location of the target object relative to the dragLayer.
*/
protected abstract void getTargetObjectLocation(Rect outPos);
@@ -263,10 +382,18 @@
private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- int width = getMeasuredWidth();
int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical
+ getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
- int height = getMeasuredHeight() + extraVerticalSpace;
+ // The margins are added after we call this method, so we need to account for them here.
+ int numVisibleChildren = 0;
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ if (getChildAt(i).getVisibility() == VISIBLE) {
+ numVisibleChildren++;
+ }
+ }
+ int childMargins = (numVisibleChildren - 1) * mMargin;
+ int height = getMeasuredHeight() + extraVerticalSpace + childMargins;
+ int width = getMeasuredWidth();
getTargetObjectLocation(mTempRect);
InsettableFrameLayout dragLayer = getPopupContainer();
@@ -392,13 +519,19 @@
return getChildCount() > 0 ? getChildAt(0) : this;
}
+ private int getArrowDuration() {
+ return shouldAddArrow()
+ ? getResources().getInteger(R.integer.config_popupArrowOpenCloseDuration)
+ : 0;
+ }
+
private void animateOpen() {
setVisibility(View.VISIBLE);
final AnimatorSet openAnim = new AnimatorSet();
final Resources res = getResources();
final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
- final long arrowDuration = res.getInteger(R.integer.config_popupArrowOpenCloseDuration);
+ final long arrowDuration = getArrowDuration();
final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
// Rectangular reveal.
@@ -460,7 +593,7 @@
final Resources res = getResources();
final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
final long revealDuration = res.getInteger(R.integer.config_popupOpenCloseDuration);
- final long arrowDuration = res.getInteger(R.integer.config_popupArrowOpenCloseDuration);
+ final long arrowDuration = getArrowDuration();
// Hide the arrow
Animator scaleArrow = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0)
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index a1ba747..c282ae8 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -60,6 +60,7 @@
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.notification.NotificationMainView;
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -91,6 +92,7 @@
private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
private int mNumNotifications;
+ private ViewGroup mNotificationContainer;
private ViewGroup mSystemShortcutContainer;
@@ -169,6 +171,14 @@
return false;
}
+ @Override
+ protected void setChildColor(View v, int color) {
+ super.setChildColor(v, color);
+ if (v.getId() == R.id.notification_container && mNotificationItemView != null) {
+ mNotificationItemView.updateBackgroundColor(color);
+ }
+ }
+
/**
* Returns true if we can show the container.
*/
@@ -222,20 +232,6 @@
if (isReversed && mNotificationItemView != null) {
mNotificationItemView.inverseGutterMargin();
}
-
- // Update dividers
- int count = getChildCount();
- DeepShortcutView lastView = null;
- for (int i = 0; i < count; i++) {
- View view = getChildAt(i);
- if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
- if (lastView != null) {
- lastView.setDividerVisibility(VISIBLE);
- }
- lastView = (DeepShortcutView) view;
- lastView.setDividerVisibility(INVISIBLE);
- }
- }
}
@TargetApi(Build.VERSION_CODES.P)
@@ -257,8 +253,12 @@
// Add views
if (mNumNotifications > 0) {
// Add notification entries
- View.inflate(getContext(), R.layout.notification_content, this);
- mNotificationItemView = new NotificationItemView(this);
+ if (mNotificationContainer == null) {
+ mNotificationContainer = findViewById(R.id.notification_container);
+ mNotificationContainer.setVisibility(VISIBLE);
+ }
+ View.inflate(getContext(), R.layout.notification_content, mNotificationContainer);
+ mNotificationItemView = new NotificationItemView(this, mNotificationContainer);
if (mNumNotifications == 1) {
mNotificationItemView.removeFooter();
}
@@ -342,34 +342,11 @@
private void updateHiddenShortcuts() {
int allowedCount = mNotificationItemView != null
? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
- int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
- int itemHeight = mNotificationItemView != null ?
- getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
- : originalHeight;
- float iconScale = ((float) itemHeight) / originalHeight;
int total = mShortcuts.size();
for (int i = 0; i < total; i++) {
DeepShortcutView view = mShortcuts.get(i);
view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
- view.getLayoutParams().height = itemHeight;
- view.getIconView().setScaleX(iconScale);
- view.getIconView().setScaleY(iconScale);
- }
- }
-
- private void updateDividers() {
- int count = getChildCount();
- DeepShortcutView lastView = null;
- for (int i = 0; i < count; i++) {
- View view = getChildAt(i);
- if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
- if (lastView != null) {
- lastView.setDividerVisibility(VISIBLE);
- }
- lastView = (DeepShortcutView) view;
- lastView.setDividerVisibility(INVISIBLE);
- }
}
}
@@ -591,8 +568,9 @@
// No more notifications, remove the notification views and expand all shortcuts.
mNotificationItemView.removeAllViews();
mNotificationItemView = null;
+ mNotificationContainer.setVisibility(GONE);
updateHiddenShortcuts();
- updateDividers();
+ assignMarginsAndBackgrounds();
} else {
mNotificationItemView.trimNotifications(
NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 7780894..6f9f0d7 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -40,6 +40,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -224,6 +225,7 @@
.map(recommendedWidget -> allWidgetItems.get(
new ComponentKey(recommendedWidget.getTargetComponent(),
recommendedWidget.user)))
+ .filter(Objects::nonNull)
.collect(Collectors.toList());
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 577fe4a..e5424cf 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -45,6 +45,11 @@
protected final T mTarget;
protected final ItemInfo mItemInfo;
+ /**
+ * Indicates if it's invokable or not through some disabled UI
+ */
+ private boolean isEnabled = true;
+
public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
mIconResId = iconResId;
mLabelResId = labelResId;
@@ -83,6 +88,14 @@
mAccessibilityActionId, context.getText(mLabelResId));
}
+ public void setEnabled(boolean enabled) {
+ isEnabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+
public boolean hasHandlerForAction(int action) {
return mAccessibilityActionId == action;
}
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
index 4653774..5b8d5bc 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -33,7 +33,7 @@
V newViewHolder(ViewGroup parent);
/** Populate UI references in {@link ViewHolder} with data. */
- void bindViewHolder(V viewHolder, T data);
+ void bindViewHolder(V viewHolder, T data, int position);
/**
* Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/search/SearchAlgorithm.java b/src/com/android/launcher3/search/SearchAlgorithm.java
index 1665354..a1720c7 100644
--- a/src/com/android/launcher3/search/SearchAlgorithm.java
+++ b/src/com/android/launcher3/search/SearchAlgorithm.java
@@ -31,4 +31,9 @@
* Cancels any active request.
*/
void cancel(boolean interruptActiveRequests);
+
+ /**
+ * Cleans up after search is no longer needed.
+ */
+ default void destroy() {};
}
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
new file mode 100644
index 0000000..acab52b
--- /dev/null
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.search;
+
+import java.text.Collator;
+
+/**
+ * Utilities for matching query string to target string.
+ */
+public class StringMatcherUtility {
+
+ /**
+ * Returns {@code true} is {@code query} is a prefix substring of a complete word/phrase in
+ * {@code target}.
+ */
+ public static boolean matches(String query, String target, StringMatcher matcher) {
+ int queryLength = query.length();
+
+ int targetLength = target.length();
+
+ if (targetLength < queryLength || queryLength <= 0) {
+ return false;
+ }
+
+ if (requestSimpleFuzzySearch(query)) {
+ return target.toLowerCase().contains(query);
+ }
+
+ int lastType;
+ int thisType = Character.UNASSIGNED;
+ int nextType = Character.getType(target.codePointAt(0));
+
+ int end = targetLength - queryLength;
+ for (int i = 0; i <= end; i++) {
+ lastType = thisType;
+ thisType = nextType;
+ nextType = i < (targetLength - 1)
+ ? Character.getType(target.codePointAt(i + 1)) : Character.UNASSIGNED;
+ if (isBreak(thisType, lastType, nextType)
+ && matcher.matches(query, target.substring(i, i + queryLength))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the current point should be a break point. Following cases
+ * are considered as break points:
+ * 1) Any non space character after a space character
+ * 2) Any digit after a non-digit character
+ * 3) Any capital character after a digit or small character
+ * 4) Any capital character before a small character
+ */
+ private static boolean isBreak(int thisType, int prevType, int nextType) {
+ switch (prevType) {
+ case Character.UNASSIGNED:
+ case Character.SPACE_SEPARATOR:
+ case Character.LINE_SEPARATOR:
+ case Character.PARAGRAPH_SEPARATOR:
+ return true;
+ }
+ switch (thisType) {
+ case Character.UPPERCASE_LETTER:
+ if (nextType == Character.UPPERCASE_LETTER) {
+ return true;
+ }
+ // Follow through
+ case Character.TITLECASE_LETTER:
+ // Break point if previous was not a upper case
+ return prevType != Character.UPPERCASE_LETTER;
+ case Character.LOWERCASE_LETTER:
+ // Break point if previous was not a letter.
+ return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
+ case Character.DECIMAL_DIGIT_NUMBER:
+ case Character.LETTER_NUMBER:
+ case Character.OTHER_NUMBER:
+ // Break point if previous was not a number
+ return !(prevType == Character.DECIMAL_DIGIT_NUMBER
+ || prevType == Character.LETTER_NUMBER
+ || prevType == Character.OTHER_NUMBER);
+ case Character.MATH_SYMBOL:
+ case Character.CURRENCY_SYMBOL:
+ case Character.OTHER_PUNCTUATION:
+ case Character.DASH_PUNCTUATION:
+ // Always a break point for a symbol
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Performs locale sensitive string comparison using {@link Collator}.
+ */
+ public static class StringMatcher {
+
+ private static final char MAX_UNICODE = '\uFFFF';
+
+ private final Collator mCollator;
+
+ StringMatcher() {
+ // On android N and above, Collator uses ICU implementation which has a much better
+ // support for non-latin locales.
+ mCollator = Collator.getInstance();
+ mCollator.setStrength(Collator.PRIMARY);
+ mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
+ }
+
+ /**
+ * Returns true if {@param query} is a prefix of {@param target}
+ */
+ public boolean matches(String query, String target) {
+ switch (mCollator.compare(query, target)) {
+ case 0:
+ return true;
+ case -1:
+ // The target string can contain a modifier which would make it larger than
+ // the query string (even though the length is same). If the query becomes
+ // larger after appending a unicode character, it was originally a prefix of
+ // the target string and hence should match.
+ return mCollator.compare(query + MAX_UNICODE, target) > -1;
+ default:
+ return false;
+ }
+ }
+
+ public static StringMatcher getInstance() {
+ return new StringMatcher();
+ }
+ }
+
+ /**
+ * Matching optimization to search in Chinese.
+ */
+ private static boolean requestSimpleFuzzySearch(String s) {
+ for (int i = 0; i < s.length(); ) {
+ int codepoint = s.codePointAt(i);
+ i += Character.charCount(codepoint);
+ switch (Character.UnicodeScript.of(codepoint)) {
+ case HAN:
+ //Character.UnicodeScript.HAN: use String.contains to match
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 0e8d4ae..f5e74b7 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -17,7 +17,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.app.ActivityOptions;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@@ -168,11 +167,6 @@
}
@Override
- public ActivityOptions getActivityLaunchOptions(View v) {
- return null;
- }
-
- @Override
protected void reapplyUi() { }
@Override
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index c9fb956..8d676c9 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -88,7 +88,6 @@
private PreferenceCategory mPluginsCategory;
private FlagTogglerPrefUi mFlagTogglerPrefUi;
-
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java
index a354169..afcf882 100644
--- a/src/com/android/launcher3/settings/NotificationDotsPreference.java
+++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java
@@ -17,6 +17,8 @@
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import static com.android.launcher3.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -24,6 +26,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.database.ContentObserver;
import android.os.Bundle;
import android.provider.Settings;
import android.util.AttributeSet;
@@ -49,6 +52,14 @@
/** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
+ private final ContentObserver mListenerListObserver =
+ new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateUI();
+ }
+ };
+
public NotificationDotsPreference(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
@@ -66,6 +77,29 @@
super(context);
}
+ @Override
+ public void onAttached() {
+ super.onAttached();
+ SettingsCache.INSTANCE.get(getContext()).register(NOTIFICATION_BADGING_URI, this);
+ getContext().getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS),
+ false, mListenerListObserver);
+ updateUI();
+ }
+
+ private void updateUI() {
+ onSettingsChanged(SettingsCache.INSTANCE.get(getContext())
+ .getValue(NOTIFICATION_BADGING_URI));
+ }
+
+ @Override
+ public void onDetached() {
+ super.onDetached();
+ SettingsCache.INSTANCE.get(getContext()).unregister(NOTIFICATION_BADGING_URI, this);
+ getContext().getContentResolver().unregisterContentObserver(mListenerListObserver);
+
+ }
+
private void setWidgetFrameVisible(boolean isVisible) {
if (mWidgetFrameVisible != isVisible) {
mWidgetFrameVisible = isVisible;
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index f03065c..5b42ac7 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,8 +18,6 @@
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
-import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
-import static com.android.launcher3.util.SettingsCache.NOTIFICATION_ENABLED_LISTENERS;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
@@ -45,7 +43,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
/**
@@ -124,11 +121,8 @@
*/
public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
- private SettingsCache mSettingsCache;
-
private String mHighLightKey;
private boolean mPreferenceHighlighted = false;
- private NotificationDotsPreference mNotificationSettingsChangedListener;
private Preference mDeveloperOptionPref;
@Override
@@ -172,22 +166,7 @@
protected boolean initPreference(Preference preference) {
switch (preference.getKey()) {
case NOTIFICATION_DOTS_PREFERENCE_KEY:
- if (WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS) {
- return false;
- }
-
- // Listen to system notification dot settings while this UI is active.
- mSettingsCache = SettingsCache.INSTANCE.get(getActivity());
- mNotificationSettingsChangedListener =
- ((NotificationDotsPreference) preference);
- mSettingsCache.register(NOTIFICATION_BADGING_URI,
- (NotificationDotsPreference) mNotificationSettingsChangedListener);
- // Also listen if notification permission changes
- mSettingsCache.register(NOTIFICATION_ENABLED_LISTENERS,
- mNotificationSettingsChangedListener);
- mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
- mSettingsCache.dispatchOnChange(NOTIFICATION_ENABLED_LISTENERS);
- return true;
+ return !WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS;
case ALLOW_ROTATION_PREFERENCE_KEY:
if (getResources().getBoolean(R.bool.allow_rotation)) {
@@ -269,16 +248,5 @@
}
});
}
-
- @Override
- public void onDestroy() {
- if (mSettingsCache != null) {
- mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
- mNotificationSettingsChangedListener);
- mSettingsCache.unregister(NOTIFICATION_ENABLED_LISTENERS,
- mNotificationSettingsChangedListener);
- }
- super.onDestroy();
- }
}
}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index e9b92e2..1c1418c 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -41,7 +41,6 @@
private BubbleTextView mBubbleText;
private View mIconView;
- private View mDivider;
private WorkspaceItemInfo mInfo;
private ShortcutInfo mDetail;
@@ -63,11 +62,6 @@
super.onFinishInflate();
mBubbleText = findViewById(R.id.bubble_text);
mIconView = findViewById(R.id.icon);
- mDivider = findViewById(R.id.divider);
- }
-
- public void setDividerVisibility(int visibility) {
- mDivider.setVisibility(visibility);
}
public BubbleTextView getBubbleText() {
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 3e59b61..cecbb0d 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -28,6 +28,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
/**
* Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size.
@@ -42,15 +43,16 @@
}
@Override
- public Bitmap createDragBitmap() {
+ public Drawable createDrawable() {
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
- return BitmapRenderer.createHardwareBitmap(
- size + blurSizeOutline,
- size + blurSizeOutline,
- (c) -> drawDragViewOnBackground(c, size));
+ return new FastBitmapDrawable(
+ BitmapRenderer.createHardwareBitmap(
+ size + blurSizeOutline,
+ size + blurSizeOutline,
+ (c) -> drawDragViewOnBackground(c, size)));
} else {
- return createDragBitmapLegacy();
+ return new FastBitmapDrawable(createDragBitmapLegacy());
}
}
@@ -81,7 +83,7 @@
}
@Override
- public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+ public float getScaleAndPosition(Drawable preview, int[] outPos) {
Launcher launcher = Launcher.getLauncher(mView.getContext());
int iconSize = getDrawableBounds(mView.getBackground()).width();
float scale = launcher.getDragLayer().getLocationInDragLayer(mView, outPos);
@@ -91,9 +93,10 @@
iconLeft = mView.getWidth() - iconSize - iconLeft;
}
- outPos[0] += Math.round(scale * iconLeft + (scale * iconSize - preview.getWidth()) / 2 +
- mPositionShift.x);
- outPos[1] += Math.round((scale * mView.getHeight() - preview.getHeight()) / 2
+ outPos[0] += Math.round(
+ scale * iconLeft + (scale * iconSize - preview.getIntrinsicWidth()) / 2
+ + mPositionShift.x);
+ outPos[1] += Math.round((scale * mView.getHeight() - preview.getIntrinsicHeight()) / 2
+ mPositionShift.y);
float size = launcher.getDeviceProfile().iconSizePx;
return scale * iconSize / size;
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index daec1d8..122573c 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -17,6 +17,8 @@
import android.content.Context;
+import com.android.launcher3.DeviceProfile;
+
/**
* Interface representing a state of a StatefulActivity
*/
@@ -52,4 +54,11 @@
* Returns if the state has the provided flag
*/
boolean hasFlag(int flagMask);
+
+ /**
+ * For this state, whether tasks should layout as a grid rather than a list.
+ */
+ default boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 2b51e97..14ef2dc 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -18,7 +18,7 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
@@ -77,6 +77,15 @@
return mCurrentStableState;
}
+ @Override
+ public String toString() {
+ return " StateManager(mLastStableState:" + mLastStableState
+ + ", mCurrentStableState:" + mCurrentStableState
+ + ", mState:" + mState
+ + ", mRestState:" + mRestState
+ + ", isInTransition:" + (mConfig.currentAnimation != null) + ")";
+ }
+
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "StateManager:");
writer.println(prefix + "\tmLastStableState:" + mLastStableState);
@@ -280,14 +289,14 @@
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
STATE_TYPE state, long duration) {
- return createAnimationToNewWorkspace(state, duration, ANIM_ALL_COMPONENTS);
+ return createAnimationToNewWorkspace(state, duration, 0 /* animFlags */);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(
- STATE_TYPE state, long duration, @AnimationFlags int animComponents) {
+ STATE_TYPE state, long duration, @AnimationFlags int animFlags) {
StateAnimationConfig config = new StateAnimationConfig();
config.duration = duration;
- config.animFlags = animComponents;
+ config.animFlags = animFlags;
return createAnimationToNewWorkspace(state, config);
}
@@ -303,7 +312,7 @@
private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
PendingAnimation builder = new PendingAnimation(mConfig.duration);
- if (mConfig.getAnimComponents() != 0) {
+ if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) {
for (StateHandler handler : getStateHandlers()) {
handler.setStateWithAnimation(state, mConfig, builder);
}
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index fd1d965..22c9d5b 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -31,7 +31,11 @@
| FLAG_HAS_SYS_UI_SCRIM;
public HintState(int id) {
- super(id, LAUNCHER_STATE_HOME, STATE_FLAGS);
+ this(id, LAUNCHER_STATE_HOME);
+ }
+
+ public HintState(int id, int statsLogOrdinal) {
+ super(id, statsLogOrdinal, STATE_FLAGS);
}
@Override
@@ -45,7 +49,7 @@
}
@Override
- public float getOverviewScrimAlpha(Launcher launcher) {
+ public float getWorkspaceScrimAlpha(Launcher launcher) {
return 0.4f;
}
@@ -53,10 +57,4 @@
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
return new ScaleAndTranslation(0.92f, 0, 0);
}
-
- @Override
- public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- // Treat the QSB as part of the hotseat so they move together.
- return getHotseatScaleAndTranslation(launcher);
- }
}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index fce8fff..39bcdc5 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -59,10 +59,11 @@
float scale = grid.workspaceSpringLoadShrinkFactor;
Rect insets = launcher.getDragLayer().getInsets();
+ int insetsBottom = grid.isTaskbarPresent ? grid.taskbarSize : insets.bottom;
float scaledHeight = scale * ws.getNormalChildHeight();
float shrunkTop = insets.top + grid.dropTargetBarSizePx;
- float shrunkBottom = ws.getMeasuredHeight() - insets.bottom
+ float shrunkBottom = ws.getMeasuredHeight() - insetsBottom
- grid.workspacePadding.bottom
- grid.workspaceSpringLoadedBottomSpace;
float totalShrunkSpace = shrunkBottom - shrunkTop;
@@ -87,7 +88,7 @@
}
@Override
- public float getWorkspaceScrimAlpha(Launcher launcher) {
+ public float getWorkspaceBackgroundAlpha(Launcher launcher) {
return 0.3f;
}
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index e4c67ee..0dbfb0b 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -27,34 +27,21 @@
*/
public class StateAnimationConfig {
- // We separate the state animations into "atomic" and "non-atomic" components. The atomic
- // components may be run atomically - that is, all at once, instead of user-controlled. However,
- // atomic components are not restricted to this purpose; they can be user-controlled alongside
- // non atomic components as well. Note that each gesture model has exactly one atomic component,
- // PLAY_ATOMIC_OVERVIEW_SCALE *or* PLAY_ATOMIC_OVERVIEW_PEEK.
@IntDef(flag = true, value = {
- PLAY_NON_ATOMIC,
- PLAY_ATOMIC_OVERVIEW_SCALE,
- PLAY_ATOMIC_OVERVIEW_PEEK,
+ SKIP_ALL_ANIMATIONS,
SKIP_OVERVIEW,
SKIP_DEPTH_CONTROLLER,
- SKIP_TASKBAR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationFlags {}
- public static final int PLAY_NON_ATOMIC = 1 << 0;
- public static final int PLAY_ATOMIC_OVERVIEW_SCALE = 1 << 1;
- public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
- public static final int SKIP_OVERVIEW = 1 << 3;
- public static final int SKIP_DEPTH_CONTROLLER = 1 << 4;
- public static final int SKIP_TASKBAR = 1 << 5;
+ public static final int SKIP_ALL_ANIMATIONS = 1 << 0;
+ public static final int SKIP_OVERVIEW = 1 << 1;
+ public static final int SKIP_DEPTH_CONTROLLER = 1 << 2;
public long duration;
public boolean userControlled;
- public @AnimationFlags int animFlags = ANIM_ALL_COMPONENTS;
+ public @AnimationFlags int animFlags = 0;
- public static final int ANIM_ALL_COMPONENTS = PLAY_NON_ATOMIC | PLAY_ATOMIC_OVERVIEW_SCALE
- | PLAY_ATOMIC_OVERVIEW_PEEK;
// Various types of animation state transition
@IntDef(value = {
@@ -69,13 +56,11 @@
ANIM_OVERVIEW_TRANSLATE_Y,
ANIM_OVERVIEW_FADE,
ANIM_ALL_APPS_FADE,
- ANIM_OVERVIEW_SCRIM_FADE,
+ ANIM_WORKSPACE_SCRIM_FADE,
ANIM_ALL_APPS_HEADER_FADE,
ANIM_OVERVIEW_MODAL,
ANIM_DEPTH,
ANIM_OVERVIEW_ACTIONS_FADE,
- ANIM_TASKBAR_FADE,
- ANIM_HOTSEAT_FADE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimType {}
@@ -90,15 +75,13 @@
public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8;
public static final int ANIM_OVERVIEW_FADE = 9;
public static final int ANIM_ALL_APPS_FADE = 10;
- public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
+ public static final int ANIM_WORKSPACE_SCRIM_FADE = 11;
public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
public static final int ANIM_OVERVIEW_MODAL = 13;
public static final int ANIM_DEPTH = 14;
public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15;
- public static final int ANIM_TASKBAR_FADE = 16;
- public static final int ANIM_HOTSEAT_FADE = 17; // if not set, falls back to ANIM_WORKSPACE_FADE
- private static final int ANIM_TYPES_COUNT = 18;
+ private static final int ANIM_TYPES_COUNT = 16;
protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
@@ -133,37 +116,9 @@
}
/**
- * @return Whether Overview is scaling as part of this animation. If this is the only
- * component (i.e. NON_ATOMIC_COMPONENT isn't included), then this scaling is happening
- * atomically, rather than being part of a normal state animation. StateHandlers can use
- * this to designate part of their animation that should scale with Overview.
- */
- public boolean playAtomicOverviewScaleComponent() {
- return hasAnimationFlag(StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE);
- }
-
- /**
- * @return Whether this animation will play atomically at the same time as a different,
- * user-controlled state transition. StateHandlers, which contribute to both animations, can
- * use this to avoid animating the same properties in both animations, since they'd conflict
- * with one another.
- */
- public boolean onlyPlayAtomicComponent() {
- return getAnimComponents() == StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE
- || getAnimComponents() == StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
- }
-
- /**
* Returns true if the config and any of the provided component flags
*/
public boolean hasAnimationFlag(@AnimationFlags int a) {
return (animFlags & a) != 0;
}
-
- /**
- * @return Only the flags that determine which animation components to play.
- */
- public @AnimationFlags int getAnimComponents() {
- return animFlags & StateAnimationConfig.ANIM_ALL_COMPONENTS;
- }
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 7cb6e34..943d129 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -32,6 +32,8 @@
public static final int ALL_APPS_STATE_ORDINAL = 5;
public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
public static final int HINT_STATE_ORDINAL = 7;
+ public static final int HINT_STATE_TWO_BUTTON_ORDINAL = 8;
+ public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 9;
public static final String TAPL_EVENTS_TAG = "TaplEvents";
public static final String SEQUENCE_MAIN = "Main";
public static final String SEQUENCE_TIS = "TIS";
@@ -55,6 +57,10 @@
return "Background";
case HINT_STATE_ORDINAL:
return "Hint";
+ case HINT_STATE_TWO_BUTTON_ORDINAL:
+ return "Hint2Button";
+ case OVERVIEW_SPLIT_SELECT_ORDINAL:
+ return "OverviewSplitSelect";
default:
return "Unknown";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 516fc74..a437293 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -27,30 +27,20 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.os.SystemClock;
-import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.TouchController;
@@ -60,13 +50,6 @@
public abstract class AbstractStateChangeTouchController
implements TouchController, SingleAxisSwipeDetector.Listener {
- /**
- * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
- * TODO: Remove the atomic animation altogether and just go to OVERVIEW directly (b/175137718).
- */
- public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 1f;
- protected final long ATOMIC_DURATION = getAtomicDuration();
-
protected final Launcher mLauncher;
protected final SingleAxisSwipeDetector mDetector;
protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
@@ -89,23 +72,7 @@
private float mProgressMultiplier;
private float mDisplacementShift;
private boolean mCanBlockFling;
- private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
-
- protected AnimatorSet mAtomicAnim;
- // True if we want to resume playing atomic components when mAtomicAnim completes.
- private boolean mScheduleResumeAtomicComponent;
- private AutoPlayAtomicAnimationInfo mAtomicAnimAutoPlayInfo;
-
- private boolean mPassedOverviewAtomicThreshold;
- // mAtomicAnim plays the atomic components of the state animations when we pass the threshold.
- // However, if we reinit to transition to a new state (e.g. OVERVIEW -> ALL_APPS) before the
- // atomic animation finishes, we only control the non-atomic components so that we don't
- // interfere with the atomic animation. When the atomic animation ends, we start controlling
- // the atomic components as well, using this controller.
- private AnimatorPlaybackController mAtomicComponentsController;
- private LauncherState mAtomicComponentsTargetState = NORMAL;
-
- private float mAtomicComponentsStartProgress;
+ private final FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
mLauncher = l;
@@ -113,10 +80,6 @@
mSwipeDirection = dir;
}
- protected long getAtomicDuration() {
- return 200;
- }
-
protected abstract boolean canInterceptTouch(MotionEvent ev);
@Override
@@ -182,7 +145,7 @@
protected abstract LauncherState getTargetState(LauncherState fromState,
boolean isDragTowardPositive);
- protected abstract float initCurrentAnimation(@AnimationFlags int animComponents);
+ protected abstract float initCurrentAnimation();
private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) {
LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState()
@@ -199,28 +162,10 @@
mToState = newToState;
mStartProgress = 0;
- mPassedOverviewAtomicThreshold = false;
if (mCurrentAnimation != null) {
mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
}
- int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
- ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS;
- mScheduleResumeAtomicComponent = false;
- if (mAtomicAnim != null) {
- animComponents = PLAY_NON_ATOMIC;
- // Control the non-atomic components until the atomic animation finishes, then control
- // the atomic components as well.
- mScheduleResumeAtomicComponent = true;
- }
- if (goingBetweenNormalAndOverview(mFromState, mToState)
- || mAtomicComponentsTargetState != mToState) {
- cancelAtomicComponentsController();
- }
-
- if (mAtomicComponentsController != null) {
- animComponents &= ~PLAY_ATOMIC_OVERVIEW_SCALE;
- }
- mProgressMultiplier = initCurrentAnimation(animComponents);
+ mProgressMultiplier = initCurrentAnimation();
mCurrentAnimation.dispatchOnStart();
return true;
}
@@ -231,13 +176,6 @@
protected void onReachedFinalState(LauncherState newToState) {
}
- protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
- LauncherState toState) {
- return (fromState == NORMAL || fromState == OVERVIEW)
- && (toState == NORMAL || toState == OVERVIEW)
- && mGoingBetweenStates;
- }
-
@Override
public void onDragStart(boolean start, float startDisplacement) {
mStartState = mLauncher.getStateManager().getState();
@@ -252,11 +190,6 @@
} else {
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
-
- mAtomicAnimAutoPlayInfo = null;
- if (mAtomicComponentsController != null) {
- mAtomicComponentsController.pause();
- }
}
mCanBlockFling = mFromState == NORMAL;
mFlingBlockCheck.unblockFling();
@@ -310,69 +243,6 @@
return;
}
mCurrentAnimation.setPlayFraction(fraction);
- if (mAtomicComponentsController != null) {
- // Make sure we don't divide by 0, and have at least a small runway.
- float start = Math.min(mAtomicComponentsStartProgress, 0.9f);
- mAtomicComponentsController.setPlayFraction((fraction - start) / (1 - start));
- }
- maybeUpdateAtomicAnim(mFromState, mToState, fraction);
- }
-
- /**
- * When going between normal and overview states, see if we passed the overview threshold and
- * play the appropriate atomic animation if so.
- */
- private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
- float progress) {
- if (!goingBetweenNormalAndOverview(fromState, toState)) {
- return;
- }
- boolean passedThreshold = progress >= ATOMIC_OVERVIEW_ANIM_THRESHOLD;
- if (passedThreshold != mPassedOverviewAtomicThreshold) {
- LauncherState atomicFromState = passedThreshold ? fromState: toState;
- LauncherState atomicToState = passedThreshold ? toState : fromState;
- mPassedOverviewAtomicThreshold = passedThreshold;
- if (mAtomicAnim != null) {
- mAtomicAnim.cancel();
- }
- mAtomicAnim = createAtomicAnimForState(atomicFromState, atomicToState, ATOMIC_DURATION);
- mAtomicAnim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mAtomicAnim = null;
- mScheduleResumeAtomicComponent = false;
- }
-
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (!mScheduleResumeAtomicComponent) {
- return;
- }
- cancelAtomicComponentsController();
-
- if (mCurrentAnimation != null) {
- mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction();
- long duration = (long) (getShiftRange() * 2);
- mAtomicComponentsController = AnimatorPlaybackController.wrap(
- createAtomicAnimForState(mFromState, mToState, duration), duration);
- mAtomicComponentsController.dispatchOnStart();
- mAtomicComponentsTargetState = mToState;
- maybeAutoPlayAtomicComponentsAnim();
- }
- }
- });
- mAtomicAnim.start();
- mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
- }
- }
-
- private AnimatorSet createAtomicAnimForState(LauncherState fromState, LauncherState targetState,
- long duration) {
- StateAnimationConfig config = getConfigForStates(fromState, targetState);
- config.animFlags = PLAY_ATOMIC_OVERVIEW_SCALE;
- config.duration = duration;
- return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, config);
}
/**
@@ -447,63 +317,18 @@
Math.min(progress, 1) - endProgress) * durationMultiplier;
}
}
-
+ if (targetState != mStartState) {
+ logReachedState(targetState);
+ }
mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState));
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(startProgress, endProgress);
- maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
- updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
- targetState, velocity, fling);
+ updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
mCurrentAnimation.dispatchOnStart();
if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
}
anim.start();
- mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, anim.getDuration());
- maybeAutoPlayAtomicComponentsAnim();
- }
-
- /**
- * Animates the atomic components from the current progress to the final progress.
- *
- * Note that this only applies when we are controlling the atomic components separately from
- * the non-atomic components, which only happens if we reinit before the atomic animation
- * finishes.
- */
- private void maybeAutoPlayAtomicComponentsAnim() {
- if (mAtomicComponentsController == null || mAtomicAnimAutoPlayInfo == null) {
- return;
- }
-
- final AnimatorPlaybackController controller = mAtomicComponentsController;
- ValueAnimator atomicAnim = controller.getAnimationPlayer();
- atomicAnim.setFloatValues(controller.getProgressFraction(),
- mAtomicAnimAutoPlayInfo.toProgress);
- long duration = mAtomicAnimAutoPlayInfo.endTime - SystemClock.elapsedRealtime();
- mAtomicAnimAutoPlayInfo = null;
- if (duration <= 0) {
- atomicAnim.start();
- atomicAnim.end();
- mAtomicComponentsController = null;
- } else {
- atomicAnim.setDuration(duration);
- atomicAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mAtomicComponentsController == controller) {
- mAtomicComponentsController = null;
- }
- }
- });
- atomicAnim.start();
- }
- }
-
- private long getRemainingAtomicDuration() {
- if (mAtomicAnim == null) {
- return 0;
- }
- return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
}
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
@@ -513,10 +338,6 @@
}
protected void onSwipeInteractionCompleted(LauncherState targetState) {
- if (mAtomicComponentsController != null) {
- mAtomicComponentsController.getAnimationPlayer().end();
- mAtomicComponentsController = null;
- }
onReachedFinalState(mToState);
clearState();
boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState);
@@ -526,9 +347,6 @@
}
protected void goToTargetState(LauncherState targetState) {
- if (targetState != mStartState) {
- logReachedState(targetState);
- }
if (!mLauncher.isInState(targetState)) {
// If we're already in the target state, don't jump to it at the end of the animation in
// case the user started interacting with it before the animation finished.
@@ -556,37 +374,12 @@
protected void clearState() {
cancelAnimationControllers();
- if (mAtomicAnim != null) {
- mAtomicAnim.cancel();
- mAtomicAnim = null;
- }
mGoingBetweenStates = true;
- mScheduleResumeAtomicComponent = false;
mDetector.finishedScrolling();
mDetector.setDetectableScrollConditions(0, false);
}
private void cancelAnimationControllers() {
mCurrentAnimation = null;
- cancelAtomicComponentsController();
- }
-
- private void cancelAtomicComponentsController() {
- if (mAtomicComponentsController != null) {
- mAtomicComponentsController.getAnimationPlayer().cancel();
- mAtomicComponentsController = null;
- }
- mAtomicAnimAutoPlayInfo = null;
- }
-
- private static class AutoPlayAtomicAnimationInfo {
-
- public final float toProgress;
- public final long endTime;
-
- AutoPlayAtomicAnimationInfo(float toProgress, long duration) {
- this.toProgress = toProgress;
- this.endTime = duration + SystemClock.elapsedRealtime();
- }
}
}
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index f9dcf2d..ab2652a 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -23,24 +23,18 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
/**
* TouchController to switch between NORMAL and ALL_APPS state.
*/
public class AllAppsSwipeController extends AbstractStateChangeTouchController {
- private MotionEvent mTouchDownEvent;
-
public AllAppsSwipeController(Launcher l) {
super(l, SingleAxisSwipeDetector.VERTICAL);
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mTouchDownEvent = ev;
- }
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
@@ -69,11 +63,11 @@
}
@Override
- protected float initCurrentAnimation(@AnimationFlags int animComponents) {
+ protected float initCurrentAnimation() {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, maxAccuracy, animComponents);
+ .createAnimationToNewWorkspace(mToState, maxAccuracy);
float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
float totalShift = endVerticalShift - startVerticalShift;
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 61bd30a..2e54904 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -180,7 +180,7 @@
LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
try {
launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
- launcher.getActivityLaunchOptionsAsBundle(v));
+ launcher.getActivityLaunchOptions(v).toBundle());
return;
} catch (Exception e) {
Log.e(TAG, "Unable to launch market intent for package=" + packageName, e);
@@ -304,7 +304,7 @@
intent.setPackage(null);
}
}
- if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation(v)) {
+ if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 8a64f3d..a241e63 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -21,6 +21,9 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+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.STAGE_TYPE_MAIN;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -36,8 +39,13 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.Collections;
+import java.util.List;
public class LandscapePagedViewHandler implements PagedOrientationHandler {
@@ -67,14 +75,6 @@
}
@Override
- public void getCurveProperties(PagedView view, Rect insets, CurveProperties out) {
- out.scroll = view.getScrollY();
- out.halfPageSize = view.getNormalChildHeight() / 2;
- out.halfScreenSize = view.getMeasuredHeight() / 2;
- out.screenCenter = insets.top + view.getPaddingTop() + out.scroll + out.halfPageSize;
- }
-
- @Override
public boolean isLayoutNaturalToLauncher() {
return false;
}
@@ -127,7 +127,7 @@
}
@Override
- public int getClearAllScrollOffset(View view, boolean isRtl) {
+ public int getClearAllSidePadding(View view, boolean isRtl) {
return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2;
}
@@ -212,6 +212,20 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ return translationOffset;
+ }
+
+ @Override
public float getTaskMenuX(float x, View thumbnailView) {
return thumbnailView.getMeasuredWidth() + x;
}
@@ -282,4 +296,18 @@
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return rect.left;
}
+
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ // Add "left" side of phone which is actually the top
+ return Collections.singletonList(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ }
+
+ @Override
+ public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile deviceProfile) {
+ return primary;
+ }
}
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index e1cec87..b85d08a 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -32,6 +32,10 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+
+import java.util.List;
/**
* Abstraction layer to separate horizontal and vertical specific implementations
@@ -62,7 +66,7 @@
float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
int getMeasuredSize(View view);
float getPrimarySize(RectF rect);
- int getClearAllScrollOffset(View view, boolean isRtl);
+ int getClearAllSidePadding(View view, boolean isRtl);
int getSecondaryDimension(View view);
FloatProperty<View> getPrimaryViewTranslate();
FloatProperty<View> getSecondaryViewTranslate();
@@ -75,6 +79,8 @@
int getScrollOffsetEnd(View view, Rect insets);
int getPrimaryTranslationDirectionFactor();
int getSecondaryTranslationDirectionFactor();
+ int getSplitTranslationDirectionFactor(@StagePosition int stagePosition);
+ int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp);
ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
void setMaxScroll(AccessibilityEvent event, int maxScroll);
boolean getRecentsRtlSetting(Resources resources);
@@ -87,7 +93,6 @@
void delegateScrollTo(PagedView pagedView, int primaryScroll);
void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y);
void scrollerStartScroll(OverScroller scroller, int newPosition);
- void getCurveProperties(PagedView view, Rect insets, CurveProperties out);
boolean isLayoutNaturalToLauncher();
float getTaskMenuX(float x, View thumbnailView);
float getTaskMenuY(float y, View thumbnailView);
@@ -95,6 +100,9 @@
int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout);
void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
+ List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+ FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile deviceProfile);
// The following are only used by TaskViewTouchHandler.
/** @return Either VERTICAL or HORIZONTAL. */
@@ -112,13 +120,6 @@
*/
void adjustFloatingIconStartVelocity(PointF velocity);
- class CurveProperties {
- public int scroll;
- public int halfPageSize;
- public int screenCenter;
- public int halfScreenSize;
- }
-
class ChildBounds {
public final int primaryDimension;
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index bcaf5f4..2fb5952 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -19,6 +19,9 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+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.STAGE_TYPE_MAIN;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -34,8 +37,13 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
public class PortraitPagedViewHandler implements PagedOrientationHandler {
@@ -65,14 +73,6 @@
}
@Override
- public void getCurveProperties(PagedView view, Rect insets, CurveProperties out) {
- out.scroll = view.getScrollX();
- out.halfPageSize = view.getNormalChildWidth() / 2;
- out.halfScreenSize = view.getMeasuredWidth() / 2;
- out.screenCenter = insets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize;
- }
-
- @Override
public boolean isLayoutNaturalToLauncher() {
return true;
}
@@ -123,7 +123,7 @@
}
@Override
- public int getClearAllScrollOffset(View view, boolean isRtl) {
+ public int getClearAllSidePadding(View view, boolean isRtl) {
return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
}
@@ -208,6 +208,23 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ if (dp.isLandscape) {
+ return translationOffset;
+ }
+ return 0;
+ }
+
+ @Override
public float getTaskMenuX(float x, View thumbnailView) {
return x;
}
@@ -277,4 +294,36 @@
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return dp.heightPx - rect.bottom;
}
+
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ List<SplitPositionOption> options = new ArrayList<>(1);
+ // TODO: Add in correct icons
+ if (dp.isSeascape()) { // or seascape
+ // Add left/right options
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ } else if (dp.isLandscape) {
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ } else {
+ // Only add top option
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_top,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ }
+ return options;
+ }
+
+ @Override
+ public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile dp) {
+ if (dp.isLandscape) { // or seascape
+ return primary;
+ } else {
+ return secondary;
+ }
+ }
}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 54af029..bd6e31b 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -17,6 +17,9 @@
package com.android.launcher3.touch;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+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.STAGE_TYPE_MAIN;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -25,7 +28,12 @@
import android.view.View;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.Collections;
+import java.util.List;
public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
@@ -35,6 +43,20 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ return translationOffset;
+ }
+
+ @Override
public boolean getRecentsRtlSetting(Resources resources) {
return Utilities.isRtl(resources);
}
@@ -71,6 +93,14 @@
return dp.widthPx - rect.right;
}
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ // Add "right" option which is actually the top
+ return Collections.singletonList(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ }
+
/* ---------- The following are only used by TaskViewTouchHandler. ---------- */
@Override
diff --git a/src/com/android/launcher3/util/ActivityOptionsWrapper.java b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
new file mode 100644
index 0000000..99cc1f7
--- /dev/null
+++ b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+
+import android.app.ActivityOptions;
+import android.os.Bundle;
+
+/**
+ * A wrapper around {@link ActivityOptions} to allow custom functionality in launcher
+ */
+public class ActivityOptionsWrapper {
+
+ public final ActivityOptions options;
+ public final RunnableList onEndCallback;
+
+ public ActivityOptionsWrapper(ActivityOptions options, RunnableList onEndCallback) {
+ this.options = options;
+ this.onEndCallback = onEndCallback;
+ }
+
+ /**
+ * @see {@link ActivityOptions#toBundle()}
+ */
+ public Bundle toBundle() {
+ return options.toBundle();
+ }
+}
diff --git a/src/com/android/launcher3/util/RunnableList.java b/src/com/android/launcher3/util/RunnableList.java
new file mode 100644
index 0000000..55add14
--- /dev/null
+++ b/src/com/android/launcher3/util/RunnableList.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to hold a list of runnable
+ */
+public class RunnableList {
+
+ private ArrayList<Runnable> mList = null;
+ private boolean mDestroyed = false;
+
+ /**
+ * Ads a runnable to this list
+ */
+ public void add(Runnable runnable) {
+ if (mDestroyed) {
+ runnable.run();
+ return;
+ }
+ if (mList == null) {
+ mList = new ArrayList<>();
+ }
+ mList.add(runnable);
+ }
+
+ /**
+ * Destroys the list, executing any pending callbacks. All new callbacks are
+ * immediately executed
+ */
+ public void executeAllAndDestroy() {
+ mDestroyed = true;
+ executeAllAndClear();
+ }
+
+ /**
+ * Executes all previously added runnable and clears the list
+ */
+ public void executeAllAndClear() {
+ if (mList != null) {
+ ArrayList<Runnable> list = mList;
+ mList = null;
+ int count = list.size();
+ for (int i = 0; i < count; i++) {
+ list.get(i).run();
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 22b4d38..10611c7 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -39,12 +39,11 @@
* {@link #unregister(Uri, OnChangeListener)} methods.
*
* This can be used as a normal cache without any listeners as well via the
- * {@link #getValue(Uri, int)} and {@link #dispatchOnChange(Uri)} to update (and subsequently call
+ * {@link #getValue(Uri, int)} and {@link #onChange)} to update (and subsequently call
* get)
*
* The cache will be invalidated/updated through the normal
* {@link ContentObserver#onChange(boolean)} calls
- * or can be force updated by calling {@link #dispatchOnChange(Uri)}.
*
* Cache will also be updated if a key queried is missing (even if it has no listeners registered).
*/
@@ -58,9 +57,6 @@
/** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
"swipe_bottom_to_notification_enabled";
- /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
- public static final Uri NOTIFICATION_ENABLED_LISTENERS =
- Settings.Secure.getUriFor("enabled_notification_listeners");
public static final Uri ROTATION_SETTING_URI =
Settings.System.getUriFor(ACCELEROMETER_ROTATION);
@@ -103,6 +99,14 @@
* Returns the value for this classes key from the cache. If not in cache, will call
* {@link #updateValue(Uri, int)} to fetch.
*/
+ public boolean getValue(Uri keySetting) {
+ return getValue(keySetting, 1);
+ }
+
+ /**
+ * Returns the value for this classes key from the cache. If not in cache, will call
+ * {@link #updateValue(Uri, int)} to fetch.
+ */
public boolean getValue(Uri keySetting, int defaultValue) {
if (mKeyCache.containsKey(keySetting)) {
return mKeyCache.get(keySetting);
@@ -140,14 +144,6 @@
}
/**
- * Force update a change for a given URI and have all listeners for that URI receive callbacks
- * even if the value is unchanged.
- */
- public void dispatchOnChange(Uri uri) {
- onChange(true, uri);
- }
-
- /**
* Call to stop receiving updates on the given {@param listener}.
* This Uri/Listener pair must correspond to the same pair called with for
* {@link #register(Uri, OnChangeListener)}
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
new file mode 100644
index 0000000..573c8bd
--- /dev/null
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+public final class SplitConfigurationOptions {
+
+ ///////////////////////////////////
+ // Taken from
+ // frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+ /**
+ * Stage position isn't specified normally meaning to use what ever it is currently set to.
+ */
+ public static final int STAGE_POSITION_UNDEFINED = -1;
+ /**
+ * Specifies that a stage is positioned at the top half of the screen if
+ * in portrait mode or at the left half of the screen if in landscape mode.
+ */
+ public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+ /**
+ * Specifies that a stage is positioned at the bottom half of the screen if
+ * in portrait mode or at the right half of the screen if in landscape mode.
+ */
+ public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+ @Retention(SOURCE)
+ @IntDef({STAGE_POSITION_UNDEFINED, STAGE_POSITION_TOP_OR_LEFT, STAGE_POSITION_BOTTOM_OR_RIGHT})
+ public @interface StagePosition {}
+
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ public static final int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ */
+ public static final int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ */
+ public static final int STAGE_TYPE_SIDE = 1;
+
+ @IntDef({STAGE_TYPE_UNDEFINED, STAGE_TYPE_MAIN, STAGE_TYPE_SIDE})
+ public @interface StageType {}
+ ///////////////////////////////////
+
+ public static class SplitPositionOption {
+ public final int mIconResId;
+ public final int mTextResId;
+ @StagePosition
+ public final int mStagePosition;
+
+ @StageType
+ public final int mStageType;
+
+ public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) {
+ mIconResId = iconResId;
+ mTextResId = textResId;
+ mStagePosition = stagePosition;
+ mStageType = stageType;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 512a286..e8a0635 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -28,6 +28,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
/**
@@ -92,10 +93,7 @@
}
public static int getAttrColor(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- return colorAccent;
+ return GraphicsUtils.getAttrColor(context, attr);
}
public static boolean getAttrBoolean(Context context, int attr) {
@@ -120,23 +118,6 @@
}
/**
- * Returns the alpha corresponding to the theme attribute {@param attr}, in the range [0, 255].
- */
- public static int getAlpha(Context context, int attr) {
- return (int) (255 * getFloat(context, attr, 0) + 0.5f);
- }
-
- /**
- * Returns the alpha corresponding to the theme attribute {@param attr}
- */
- public static float getFloat(Context context, int attr, float defValue) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
- float value = ta.getFloat(0, defValue);
- ta.recycle();
- return value;
- }
-
- /**
* Scales a color matrix such that, when applied to color R G B A, it produces R' G' B' A' where
* R' = r * R
* G' = g * G
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 5b33f18..e413d7f 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -58,7 +58,7 @@
Preconditions.assertUIThread();
Handler handler = new Handler();
- // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+ // LayoutInflater is not thread safe as it maintains a global variable 'mConstructorArgs'.
// Create a different copy to use on the background thread.
LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 2ad80cf..b8554e4 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -145,14 +145,17 @@
msg.sendToTarget();
}
- private void updateOffset() {
- int numPagesForWallpaperParallax;
+ /** Returns the number of pages used for the wallpaper parallax. */
+ public int getNumPagesForWallpaperParallax() {
if (mWallpaperIsLiveWallpaper) {
- numPagesForWallpaperParallax = mNumScreens;
+ return mNumScreens;
} else {
- numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
+ return Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
}
- Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0,
+ }
+
+ private void updateOffset() {
+ Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, getNumPagesForWallpaperParallax(), 0,
mWindowToken).sendToTarget();
}
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index e08f881..f79313f 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -216,7 +216,7 @@
return mLauncher.getDragLayer();
}
- protected static View createColorScrim(Context context, int bgColor) {
+ protected View createColorScrim(Context context, int bgColor) {
View view = new View(context);
view.forceHasOverlappingRendering(false);
view.setBackgroundColor(bgColor);
diff --git a/src/com/android/launcher3/views/AccessibilityActionsView.java b/src/com/android/launcher3/views/AccessibilityActionsView.java
index 0eacaa3..1d136c3 100644
--- a/src/com/android/launcher3/views/AccessibilityActionsView.java
+++ b/src/com/android/launcher3/views/AccessibilityActionsView.java
@@ -67,7 +67,7 @@
info.addAction(new AccessibilityAction(
R.string.all_apps_button_label, l.getText(R.string.all_apps_button_label)));
for (OptionItem item : OptionsPopupView.getOptions(l)) {
- info.addAction(new AccessibilityAction(item.labelRes, l.getText(item.labelRes)));
+ info.addAction(new AccessibilityAction(item.labelRes, item.label));
}
return info;
}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 505c6ce..71aa4ac 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Rect;
+import android.view.LayoutInflater;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.DeviceProfile;
@@ -67,13 +68,28 @@
}
/**
+ * Returns a LayoutInflater that is cloned in this Context, so that Views inflated by it will
+ * have the same Context. (i.e. {@link #lookupContext(Context)} will find this ActivityContext.)
+ */
+ default LayoutInflater getLayoutInflater() {
+ if (this instanceof Context) {
+ Context context = (Context) this;
+ return LayoutInflater.from(context).cloneInContext(context);
+ }
+ return null;
+ }
+
+ /**
* The root view to support drag-and-drop and popup support.
*/
BaseDragLayer getDragLayer();
DeviceProfile getDeviceProfile();
- static <T extends ActivityContext> T lookupContext(Context context) {
+ /**
+ * Returns the ActivityContext associated with the given Context.
+ */
+ static <T extends Context & ActivityContext> T lookupContext(Context context) {
if (context instanceof ActivityContext) {
return (T) context;
} else if (context instanceof ContextWrapper) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 899dcf7..98cc876 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
@@ -37,6 +38,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.core.content.ContextCompat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
@@ -62,6 +64,7 @@
private final ArrayMap<View, OptionItem> mItemMap = new ArrayMap<>();
private RectF mTargetRect;
+ private boolean mShouldAddArrow;
public OptionsPopupView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -113,23 +116,32 @@
return (type & TYPE_OPTIONS_POPUP) != 0;
}
+ public void setShouldAddArrow(boolean shouldAddArrow) {
+ mShouldAddArrow = shouldAddArrow;
+ }
+
+ @Override
+ protected boolean shouldAddArrow() {
+ return mShouldAddArrow;
+ }
+
@Override
protected void getTargetObjectLocation(Rect outPos) {
mTargetRect.roundOut(outPos);
}
public static OptionsPopupView show(
- Launcher launcher, RectF targetRect, List<OptionItem> items) {
+ Launcher launcher, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow) {
OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
.inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
popup.mTargetRect = targetRect;
+ popup.setShouldAddArrow(shouldAddArrow);
for (OptionItem item : items) {
DeepShortcutView view =
(DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
- view.getIconView().setBackgroundResource(item.iconRes);
- view.getBubbleText().setText(item.labelRes);
- view.setDividerVisibility(View.INVISIBLE);
+ view.getIconView().setBackgroundDrawable(item.icon);
+ view.getBubbleText().setText(item.label);
view.setOnClickListener(popup);
view.setOnLongClickListener(popup);
popup.mItemMap.put(view, item);
@@ -150,7 +162,7 @@
y = launcher.getDragLayer().getHeight() / 2;
}
RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
- show(launcher, target, getOptions(launcher));
+ show(launcher, target, getOptions(launcher), false);
}
/**
@@ -158,11 +170,15 @@
*/
public static ArrayList<OptionItem> getOptions(Launcher launcher) {
ArrayList<OptionItem> options = new ArrayList<>();
- options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
+ options.add(new OptionItem(launcher,
+ R.string.settings_button_text,
+ R.drawable.ic_setting,
LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
OptionsPopupView::startSettings));
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
- options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
+ options.add(new OptionItem(launcher,
+ R.string.widget_button_text,
+ R.drawable.ic_widget,
LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
OptionsPopupView::onWidgetsClicked));
}
@@ -170,7 +186,9 @@
R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
R.drawable.ic_palette : R.drawable.ic_wallpaper;
- options.add(new OptionItem(resString, resDrawable,
+ options.add(new OptionItem(launcher,
+ resString,
+ resDrawable,
IGNORE,
OptionsPopupView::startWallpaperPicker));
return options;
@@ -237,15 +255,28 @@
public static class OptionItem {
+ // Used to create AccessibilityNodeInfo in AccessibilityActionsView.java.
public final int labelRes;
- public final int iconRes;
+
+ public final CharSequence label;
+ public final Drawable icon;
public final EventEnum eventId;
public final OnLongClickListener clickListener;
- public OptionItem(int labelRes, int iconRes, EventEnum eventId,
- OnLongClickListener clickListener) {
+ public OptionItem(Context context, int labelRes, int iconRes, EventEnum eventId,
+ OnLongClickListener clickListener) {
this.labelRes = labelRes;
- this.iconRes = iconRes;
+ this.label = context.getText(labelRes);
+ this.icon = ContextCompat.getDrawable(context, iconRes);
+ this.eventId = eventId;
+ this.clickListener = clickListener;
+ }
+
+ public OptionItem(CharSequence label, Drawable icon, EventEnum eventId,
+ OnLongClickListener clickListener) {
+ this.labelRes = 0;
+ this.label = label;
+ this.icon = icon;
this.eventId = eventId;
this.clickListener = clickListener;
}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index c9bd284..c9424de 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -15,14 +15,9 @@
*/
package com.android.launcher3.views;
-import static androidx.core.graphics.ColorUtils.compositeColors;
-
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
@@ -32,110 +27,61 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.uioverrides.WallpaperColorInfo;
-import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
+import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
/**
* Simple scrim which draws a flat color
*/
-public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
+public class ScrimView extends View implements Insettable {
+ private static final float STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.9f;
- private static final float SCRIM_ALPHA = .95f;
- protected final T mLauncher;
- private final WallpaperColorInfo mWallpaperColorInfo;
- protected final int mEndScrim;
- protected final boolean mIsScrimDark;
-
- protected float mMaxScrimAlpha;
-
- protected float mProgress = 1;
- protected int mScrimColor;
-
- protected int mCurrentFlatColor;
- protected int mEndFlatColor;
- protected int mEndFlatColorAlpha;
+ private final boolean mIsScrimDark;
+ private SystemUiController mSystemUiController;
public ScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
- mLauncher = Launcher.cast(Launcher.getLauncher(context));
- mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
- int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- endScrim = Themes.getColorBackgroundFloating(context);
- endScrim = ColorUtils.setAlphaComponent(endScrim, (int) (255 * SCRIM_ALPHA));
- }
- mEndScrim = endScrim;
- mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
-
- mMaxScrimAlpha = 0.7f;
+ mIsScrimDark = ColorUtils.calculateLuminance(
+ Themes.getAttrColor(context, R.attr.allAppsScrimColor)) < 0.5f;
setFocusable(false);
}
@Override
public void setInsets(Rect insets) { }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mWallpaperColorInfo.addOnChangeListener(this);
- onExtractedColorsChanged(mWallpaperColorInfo);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mWallpaperColorInfo.removeOnChangeListener(this);
- }
-
@Override
public boolean hasOverlappingRendering() {
return false;
}
@Override
- public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
- mScrimColor = wallpaperColorInfo.getMainColor();
- mEndFlatColor = compositeColors(mEndScrim, setColorAlphaBound(
- mScrimColor, Math.round(mMaxScrimAlpha * 255)));
- mEndFlatColorAlpha = Color.alpha(mEndFlatColor);
- updateColors();
- invalidate();
- }
-
- public void setProgress(float progress) {
- if (mProgress != progress) {
- mProgress = progress;
- updateColors();
- updateSysUiColors();
- invalidate();
- }
- }
-
- public void reInitUi() { }
-
- protected void updateColors() {
- mCurrentFlatColor = mProgress >= 1 ? 0 : setColorAlphaBound(
- mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
- }
-
- protected void updateSysUiColors() {
- // Use a light system UI (dark icons) if all apps is behind at least half of the
- // status bar.
- boolean forceChange = mProgress <= 0.1f;
- if (forceChange) {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
- } else {
- mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
- }
+ protected boolean onSetAlpha(int alpha) {
+ updateSysUiColors();
+ return super.onSetAlpha(alpha);
}
@Override
- protected void onDraw(Canvas canvas) {
- if (mCurrentFlatColor != 0) {
- canvas.drawColor(mCurrentFlatColor);
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ updateSysUiColors();
+ }
+
+ private void updateSysUiColors() {
+ // Use a light system UI (dark icons) if all apps is behind at least half of the
+ // status bar.
+ boolean forceChange =
+ getVisibility() == VISIBLE && getAlpha() > STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD;
+ if (forceChange) {
+ getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark);
+ } else {
+ getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
}
}
+
+ private SystemUiController getSystemUiController() {
+ if (mSystemUiController == null) {
+ mSystemUiController = Launcher.getLauncher(getContext()).getSystemUiController();
+ }
+ return mSystemUiController;
+ }
}
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
index d0ec9d7..9701389 100644
--- a/src/com/android/launcher3/views/SpringRelativeLayout.java
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -15,51 +15,25 @@
*/
package com.android.launcher3.views;
-import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
-import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
-import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
-
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
-import android.util.SparseBooleanArray;
-import android.view.View;
import android.widget.EdgeEffect;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory;
+import com.android.launcher3.Utilities;
+
+/**
+ * View group to allow rendering overscroll effect in a child at the parent level
+ */
public class SpringRelativeLayout extends RelativeLayout {
- private static final float STIFFNESS = (STIFFNESS_MEDIUM + STIFFNESS_LOW) / 2;
- private static final float DAMPING_RATIO = DAMPING_RATIO_MEDIUM_BOUNCY;
- private static final float VELOCITY_MULTIPLIER = 0.3f;
-
- private static final FloatPropertyCompat<SpringRelativeLayout> DAMPED_SCROLL =
- new FloatPropertyCompat<SpringRelativeLayout>("value") {
-
- @Override
- public float getValue(SpringRelativeLayout object) {
- return object.mDampedScrollShift;
- }
-
- @Override
- public void setValue(SpringRelativeLayout object, float value) {
- object.setDampedScrollShift(value);
- }
- };
-
- protected final SparseBooleanArray mSpringViews = new SparseBooleanArray();
- private final SpringAnimation mSpring;
-
- private float mDampedScrollShift = 0;
- private SpringEdgeEffect mActiveEdge;
+ private final EdgeEffect mEdgeGlowTop;
+ private final EdgeEffect mEdgeGlowBottom;
public SpringRelativeLayout(Context context) {
this(context, null);
@@ -71,98 +45,73 @@
public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mSpring = new SpringAnimation(this, DAMPED_SCROLL, 0);
- mSpring.setSpring(new SpringForce(0)
- .setStiffness(STIFFNESS)
- .setDampingRatio(DAMPING_RATIO));
- }
-
- public void addSpringView(int id) {
- mSpringViews.put(id, true);
- }
-
- public void removeSpringView(int id) {
- mSpringViews.delete(id);
- invalidate();
- }
-
- /**
- * Used to clip the canvas when drawing child views during overscroll.
- */
- public int getCanvasClipTopForOverscroll() {
- return 0;
+ mEdgeGlowTop = Utilities.ATLEAST_S
+ ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
+ mEdgeGlowBottom = Utilities.ATLEAST_S
+ ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
+ setWillNotDraw(false);
}
@Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) {
- int saveCount = canvas.save();
-
- canvas.clipRect(0, getCanvasClipTopForOverscroll(), getWidth(), getHeight());
- canvas.translate(0, mDampedScrollShift);
- boolean result = super.drawChild(canvas, child, drawingTime);
-
- canvas.restoreToCount(saveCount);
-
- return result;
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (!mEdgeGlowTop.isFinished()) {
+ final int restoreCount = canvas.save();
+ canvas.translate(0, 0);
+ mEdgeGlowTop.setSize(getWidth(), getHeight());
+ if (mEdgeGlowTop.draw(canvas)) {
+ postInvalidateOnAnimation();
+ }
+ canvas.restoreToCount(restoreCount);
}
- return super.drawChild(canvas, child, drawingTime);
- }
-
- private void setActiveEdge(SpringEdgeEffect edge) {
- if (mActiveEdge != edge && mActiveEdge != null) {
- mActiveEdge.mDistance = 0;
- }
- mActiveEdge = edge;
- }
-
- protected void setDampedScrollShift(float shift) {
- if (shift != mDampedScrollShift) {
- mDampedScrollShift = shift;
- invalidate();
+ if (!mEdgeGlowBottom.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight();
+ canvas.translate(-width, height);
+ canvas.rotate(180, width, 0);
+ mEdgeGlowBottom.setSize(width, height);
+ if (mEdgeGlowBottom.draw(canvas)) {
+ postInvalidateOnAnimation();
+ }
+ canvas.restoreToCount(restoreCount);
}
}
- private void finishScrollWithVelocity(float velocity) {
- mSpring.setStartVelocity(velocity);
- mSpring.setStartValue(mDampedScrollShift);
- mSpring.start();
- }
- protected void finishWithShiftAndVelocity(float shift, float velocity,
- DynamicAnimation.OnAnimationEndListener listener) {
- setDampedScrollShift(shift);
- mSpring.addEndListener(listener);
- finishScrollWithVelocity(velocity);
+ /**
+ * Absorbs the velocity as a result for swipe-up fling
+ */
+ protected void absorbSwipeUpVelocity(int velocity) {
+ mEdgeGlowBottom.onAbsorb(velocity);
+ invalidate();
}
public EdgeEffectFactory createEdgeEffectFactory() {
- return new SpringEdgeEffectFactory();
+ return new ProxyEdgeEffectFactory();
}
- private class SpringEdgeEffectFactory extends EdgeEffectFactory {
+ private class ProxyEdgeEffectFactory extends EdgeEffectFactory {
@NonNull @Override
protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
switch (direction) {
case DIRECTION_TOP:
- return new SpringEdgeEffect(getContext(), +VELOCITY_MULTIPLIER);
+ return new EdgeEffectProxy(getContext(), mEdgeGlowTop);
case DIRECTION_BOTTOM:
- return new SpringEdgeEffect(getContext(), -VELOCITY_MULTIPLIER);
+ return new EdgeEffectProxy(getContext(), mEdgeGlowBottom);
}
return super.createEdgeEffect(view, direction);
}
}
- private class SpringEdgeEffect extends EdgeEffect {
+ private class EdgeEffectProxy extends EdgeEffect {
- private final float mVelocityMultiplier;
+ private final EdgeEffect mParent;
- private float mDistance;
-
- public SpringEdgeEffect(Context context, float velocityMultiplier) {
+ EdgeEffectProxy(Context context, EdgeEffect parent) {
super(context);
- mVelocityMultiplier = velocityMultiplier;
+ mParent = parent;
}
@Override
@@ -170,22 +119,44 @@
return false;
}
+ private void invalidateParentScrollEffect() {
+ if (!mParent.isFinished()) {
+ invalidate();
+ }
+ }
+
@Override
public void onAbsorb(int velocity) {
- finishScrollWithVelocity(velocity * mVelocityMultiplier);
+ mParent.onAbsorb(velocity);
+ invalidateParentScrollEffect();
+ }
+
+ @Override
+ public void onPull(float deltaDistance) {
+ mParent.onPull(deltaDistance);
+ invalidateParentScrollEffect();
}
@Override
public void onPull(float deltaDistance, float displacement) {
- setActiveEdge(this);
- mDistance += deltaDistance * (mVelocityMultiplier / 3f);
- setDampedScrollShift(mDistance * getHeight());
+ mParent.onPull(deltaDistance, displacement);
+ invalidateParentScrollEffect();
}
@Override
public void onRelease() {
- mDistance = 0;
- finishScrollWithVelocity(0);
+ mParent.onRelease();
+ invalidateParentScrollEffect();
+ }
+
+ @Override
+ public void finish() {
+ mParent.finish();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return mParent.isFinished();
}
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 03c58bb..95b887c 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.Point;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@@ -74,7 +75,18 @@
@Override
public final void onClick(View v) {
- mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+ Object tag = null;
+ if (v instanceof WidgetCell) {
+ tag = v.getTag();
+ } else if (v.getParent() instanceof WidgetCell) {
+ tag = ((WidgetCell) v.getParent()).getTag();
+ }
+ if (tag instanceof PendingAddShortcutInfo) {
+ mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
+ } else {
+ mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+ }
+
}
@Override
@@ -97,18 +109,29 @@
// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
// we abort the drag.
- if (image.getBitmap() == null) {
+ if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) {
return false;
}
PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
- dragHelper.setPreview(v.getPreview());
+ dragHelper.setRemoteViewsPreview(v.getPreview());
+ dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
- int[] loc = new int[2];
- getPopupContainer().getLocationInDragLayer(image, loc);
+ if (image.getDrawable() != null) {
+ int[] loc = new int[2];
+ getPopupContainer().getLocationInDragLayer(image, loc);
- dragHelper.startDrag(image.getBitmapBounds(), image.getBitmap().getWidth(),
- image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
+ dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
+ image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
+ } else {
+ View preview = v.getAppWidgetHostViewPreview();
+ int[] loc = new int[2];
+ getPopupContainer().getLocationInDragLayer(preview, loc);
+
+ Rect r = new Rect(0, 0, preview.getWidth(), preview.getHeight());
+ dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(),
+ new Point(loc[0], loc[1]), this, new DragOptions());
+ }
close(true);
return true;
}
@@ -158,4 +181,21 @@
toast.show();
return toast;
}
+
+ /**
+ * Show shortcut tap toast prompting user to drag instead.
+ */
+ private static Toast showShortcutToast(Context context, Toast toast) {
+ // Let the user know that they have to long press to add a widget
+ if (toast != null) {
+ toast.cancel();
+ }
+
+ CharSequence msg = Utilities.wrapForTts(
+ context.getText(R.string.long_press_shortcut_to_add),
+ context.getString(R.string.long_accessible_way_to_add_shortcut));
+ toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
+ toast.show();
+ return toast;
+ }
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 3285c18..687318f 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,35 +19,52 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.os.SystemClock;
+import android.util.Log;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AdapterView;
import android.widget.Advanceable;
import android.widget.RemoteViews;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
+
+import java.util.List;
/**
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
- implements TouchCompleteListener, View.OnLongClickListener {
+ implements TouchCompleteListener, View.OnLongClickListener,
+ LocalColorExtractor.Listener {
+
+ private static final String LOG_TAG = "LauncherAppWidgetHostView";
// Related to the auto-advancing of widgets
private static final long ADVANCE_INTERVAL = 20000;
@@ -60,18 +77,44 @@
private final CheckLongPressHelper mLongPressHelper;
protected final Launcher mLauncher;
+ private final Workspace mWorkspace;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mReinflateOnConfigChange;
+ // Maintain the color manager.
+ private final LocalColorExtractor mColorExtractor;
+
private boolean mIsScrollable;
private boolean mIsAttachedToWindow;
private boolean mIsAutoAdvanceRegistered;
+ private boolean mIsInDragMode = false;
private Runnable mAutoAdvanceRunnable;
+ private RectF mLastLocationRegistered = null;
+ @Nullable private AppWidgetHostViewDragListener mDragListener;
+
+ // Used to store the widget size during onLayout.
+ private final Rect mCurrentWidgetSize = new Rect();
+ private final Rect mWidgetSizeAtDrag = new Rect();
+ private final RectF mTempRectF = new RectF();
+ private final boolean mIsRtl;
+ private final Rect mEnforcedRectangle = new Rect();
+ private final float mEnforcedCornerRadius;
+ private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
+ outline.setEmpty();
+ } else {
+ outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
+ }
+ }
+ };
public LauncherAppWidgetHostView(Context context) {
super(context);
mLauncher = Launcher.getLauncher(context);
+ mWorkspace = mLauncher.getWorkspace();
mLongPressHelper = new CheckLongPressHelper(this, this);
mInflater = LayoutInflater.from(context);
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
@@ -81,12 +124,34 @@
if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
setOnLightBackground(true);
}
+ mIsRtl = Utilities.isRtl(context.getResources());
+ mColorExtractor = LocalColorExtractor.newInstance(getContext());
+ mColorExtractor.setListener(this);
+
+ mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
+ }
+
+ @Override
+ public void setColorResources(@Nullable SparseIntArray colors) {
+ if (colors == null) {
+ resetColorResources();
+ } else {
+ super.setColorResources(colors);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mIsInDragMode && mDragListener != null) {
+ mDragListener.onDragContentChanged();
+ }
}
@Override
public boolean onLongClick(View view) {
if (mIsScrollable) {
- DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+ DragLayer dragLayer = mLauncher.getDragLayer();
dragLayer.requestDisallowInterceptTouchEvent(false);
}
view.performLongClick();
@@ -121,7 +186,7 @@
if (viewGroup instanceof AdapterView) {
return true;
} else {
- for (int i=0; i < viewGroup.getChildCount(); i++) {
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup) {
if (checkScrollableRecursively((ViewGroup) child)) {
@@ -135,7 +200,7 @@
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+ DragLayer dragLayer = mLauncher.getDragLayer();
if (mIsScrollable) {
dragLayer.requestDisallowInterceptTouchEvent(true);
}
@@ -167,6 +232,7 @@
// state is updated. So isAttachedToWindow() will return true until next frame.
mIsAttachedToWindow = false;
checkIfAutoAdvance();
+ mColorExtractor.removeLocations();
}
@Override
@@ -213,6 +279,99 @@
}
mIsScrollable = checkScrollableRecursively(this);
+
+ if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) {
+ mCurrentWidgetSize.left = left;
+ mCurrentWidgetSize.top = top;
+ mCurrentWidgetSize.right = right;
+ mCurrentWidgetSize.bottom = bottom;
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+ int pageId = mWorkspace.getPageIndexForScreenId(info.screenId);
+ updateColorExtraction(mCurrentWidgetSize, pageId);
+ }
+
+ enforceRoundedCorners();
+ }
+
+ /** Starts the drag mode. */
+ public void startDrag(AppWidgetHostViewDragListener dragListener) {
+ mIsInDragMode = true;
+ mDragListener = dragListener;
+ }
+
+ /** Handles a drag event occurred on a workspace page, {@code pageId}. */
+ public void handleDrag(Rect rect, int pageId) {
+ mWidgetSizeAtDrag.set(rect);
+ updateColorExtraction(mWidgetSizeAtDrag, pageId);
+ }
+
+ /** Ends the drag mode. */
+ public void endDrag() {
+ mIsInDragMode = false;
+ mDragListener = null;
+ mWidgetSizeAtDrag.setEmpty();
+ requestLayout();
+ }
+
+ private void updateColorExtraction(Rect widgetLocation, int pageId) {
+ // If the widget hasn't been measured and laid out, we cannot do this.
+ if (widgetLocation.isEmpty()) {
+ return;
+ }
+ int screenWidth = mLauncher.getDeviceProfile().widthPx;
+ int screenHeight = mLauncher.getDeviceProfile().heightPx;
+ int numScreens = mWorkspace.getNumPagesForWallpaperParallax();
+ pageId = mIsRtl ? numScreens - pageId - 1 : pageId;
+ float relativeScreenWidth = 1f / numScreens;
+ float absoluteTop = widgetLocation.top;
+ float absoluteBottom = widgetLocation.bottom;
+ for (View v = (View) getParent();
+ v != null && v.getId() != R.id.launcher;
+ v = (View) v.getParent()) {
+ absoluteBottom += v.getTop();
+ absoluteTop += v.getTop();
+ }
+ float xOffset = 0;
+ View parentView = (View) getParent();
+ // The layout depends on the orientation.
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ int parentViewWidth = parentView == null ? 0 : parentView.getWidth();
+ xOffset = screenHeight - mWorkspace.getPaddingRight() - parentViewWidth;
+ } else {
+ int parentViewPaddingLeft = parentView == null ? 0 : parentView.getPaddingLeft();
+ xOffset = mWorkspace.getPaddingLeft() + parentViewPaddingLeft;
+ }
+ // This is the position of the widget relative to the wallpaper, as expected by the
+ // local color extraction of the WallpaperManager.
+ // The coordinate system is such that, on the horizontal axis, each screen has a
+ // distinct range on the [0,1] segment. So if there are 3 screens, they will have the
+ // ranges [0, 1/3], [1/3, 2/3] and [2/3, 1]. The position on the subrange should be
+ // the position of the widget relative to the screen. For the vertical axis, this is
+ // simply the location of the widget relative to the screen.
+ mTempRectF.left = ((widgetLocation.left + xOffset) / screenWidth + pageId)
+ * relativeScreenWidth;
+ mTempRectF.right = ((widgetLocation.right + xOffset) / screenWidth + pageId)
+ * relativeScreenWidth;
+ mTempRectF.top = absoluteTop / screenHeight;
+ mTempRectF.bottom = absoluteBottom / screenHeight;
+ if (mTempRectF.left < 0 || mTempRectF.right > 1 || mTempRectF.top < 0
+ || mTempRectF.bottom > 1) {
+ Log.e(LOG_TAG, " Error, invalid relative position");
+ return;
+ }
+ if (!mTempRectF.equals(mLastLocationRegistered)) {
+ if (mLastLocationRegistered != null) {
+ mColorExtractor.removeLocations();
+ }
+ mLastLocationRegistered = new RectF(mTempRectF);
+ mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+ }
+ }
+
+ @Override
+ public void onColorsChanged(RectF rectF, SparseIntArray colors) {
+ // setColorResources will reapply the view, which must happen in the UI thread.
+ post(() -> setColorResources(colors));
}
@Override
@@ -225,6 +384,14 @@
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
maybeRegisterAutoAdvance();
+
+ if (visibility == View.VISIBLE) {
+ if (mLastLocationRegistered != null) {
+ mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+ }
+ } else {
+ mColorExtractor.removeLocations();
+ }
}
private void checkIfAutoAdvance() {
@@ -321,4 +488,40 @@
}
return false;
}
+
+ @UiThread
+ private void resetRoundedCorners() {
+ setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+ setClipToOutline(false);
+ }
+
+ @UiThread
+ private void enforceRoundedCorners() {
+ if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+ resetRoundedCorners();
+ return;
+ }
+ View background = RoundedCornerEnforcement.findBackground(this);
+ if (background == null
+ || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+ resetRoundedCorners();
+ return;
+ }
+ RoundedCornerEnforcement.computeRoundedRectangle(this,
+ background,
+ mEnforcedRectangle);
+ setOutlineProvider(mCornerRadiusEnforcementOutline);
+ setClipToOutline(true);
+ }
+
+ /** Returns the corner radius currently enforced, in pixels. */
+ public float getEnforcedCornerRadius() {
+ return mEnforcedCornerRadius;
+ }
+
+ /** Returns true if the corner radius are enforced for this App Widget. */
+ public boolean hasEnforcedCornerRadius() {
+ return getClipToOutline();
+ }
+
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index ce97d2e..ad61495 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -1,5 +1,7 @@
package com.android.launcher3.widget;
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -33,6 +35,8 @@
public int spanY;
public int minSpanX;
public int minSpanY;
+ public int maxSpanX;
+ public int maxSpanY;
public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
AppWidgetProviderInfo info) {
@@ -78,15 +82,40 @@
|| !idp.portraitProfile.shouldInsetWidgets()) {
AppWidgetHostView.getDefaultPaddingForWidget(context, provider, widgetPadding);
}
- spanX = Math.max(1, (int) Math.ceil(
- (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
- spanY = Math.max(1, (int) Math.ceil(
- (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
- minSpanX = Math.max(1, (int) Math.ceil(
- (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
- minSpanY = Math.max(1, (int) Math.ceil(
- (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
+ minSpanX = getSpanX(widgetPadding, minResizeWidth, smallestCellWidth);
+ minSpanY = getSpanY(widgetPadding, minResizeHeight, smallestCellHeight);
+
+ // Use maxResizeWidth/Height if they are defined and we're on S or above.
+ maxSpanX =
+ (ATLEAST_S && maxResizeWidth > 0)
+ ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)
+ : idp.numColumns;
+ maxSpanY =
+ (ATLEAST_S && maxResizeHeight > 0)
+ ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)
+ : idp.numRows;
+
+ // Use targetCellWidth/Height if it is within the min/max ranges and we're on S or above.
+ // Otherwise, fall back to minWidth/Height.
+ if (ATLEAST_S && targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
+ && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
+ spanX = targetCellWidth;
+ spanY = targetCellHeight;
+ } else {
+ spanX = getSpanX(widgetPadding, minWidth, smallestCellWidth);
+ spanY = getSpanY(widgetPadding, minHeight, smallestCellHeight);
+ }
+ }
+
+ private int getSpanX(Rect widgetPadding, int widgetWidth, float cellWidth) {
+ return Math.max(1, (int) Math.ceil(
+ (widgetWidth + widgetPadding.left + widgetPadding.right) / cellWidth));
+ }
+
+ private int getSpanY(Rect widgetPadding, int widgetHeight, float cellHeight) {
+ return Math.max(1, (int) Math.ceil(
+ (widgetHeight + widgetPadding.top + widgetPadding.bottom) / cellHeight));
}
public String getLabel(PackageManager packageManager) {
@@ -110,6 +139,10 @@
}
}
+ public boolean isReconfigurable() {
+ return configure != null && (getWidgetFeatures() & WIDGET_FEATURE_RECONFIGURABLE) != 0;
+ }
+
@Override
public final ComponentName getComponent() {
return provider;
@@ -124,4 +157,4 @@
public Drawable getFullResIcon(IconCache cache) {
return cache.getFullResIcon(provider.getPackageName(), icon);
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java
new file mode 100644
index 0000000..097158b
--- /dev/null
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.graphics.RectF;
+import android.util.SparseIntArray;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.util.List;
+
+/** Extracts the colors we need from the wallpaper at given locations. */
+public class LocalColorExtractor implements ResourceBasedOverride {
+
+ /** Listener for color changes on a screen location. */
+ public interface Listener {
+ /**
+ * Method called when the colors on a registered location has changed.
+ *
+ * {@code extractedColors} maps the color resources {@code android.R.colors.system_*} to
+ * their value, in a format that can be passed directly to
+ * {@link AppWidgetHostView#setColorResources(SparseIntArray)}.
+ */
+ void onColorsChanged(RectF rect, SparseIntArray extractedColors);
+ }
+
+ static LocalColorExtractor newInstance(Context context) {
+ return Overrides.getObject(LocalColorExtractor.class, context.getApplicationContext(),
+ R.string.local_colors_extraction_class);
+ }
+
+ /** Sets the object that will receive the color changes. */
+ public void setListener(@Nullable Listener listener) {
+ // no-op
+ }
+
+ /** Adds a list of locations to track with this listener. */
+ public void addLocation(List<RectF> locations) {
+ // no-op
+ }
+
+ /** Stops tracking any locations. */
+ public void removeLocations() {
+ // no-op
+ }
+}
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 4b113d8..3308eec 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -16,7 +16,6 @@
package com.android.launcher3.widget;
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import android.content.Context;
@@ -37,8 +36,8 @@
import android.widget.RemoteViews;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.R;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -152,12 +151,12 @@
// 2) Preload icon in the center
// 3) Setup icon in the center and app icon in the top right corner.
if (mDisabledForSafeMode) {
- FastBitmapDrawable disabledIcon = newIcon(getContext(), info);
+ FastBitmapDrawable disabledIcon = info.newIcon(getContext());
disabledIcon.setIsDisabled(true);
mCenterDrawable = disabledIcon;
mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
- mCenterDrawable = newIcon(getContext(), info);
+ mCenterDrawable = info.newIcon(getContext());
mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
updateSettingColor(info.bitmap.color);
} else {
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 6e83836..e78d517 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -33,10 +33,14 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.RoundDrawableWrapper;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
/**
* Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
@@ -49,15 +53,29 @@
private final PendingAddItemInfo mAddInfo;
private int[] mEstimatedCellSize;
- @Nullable private RemoteViews mPreview;
+ @Nullable private RemoteViews mRemoteViewsPreview;
+ @Nullable private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
+ private final float mEnforcedRoundedCornersForWidget;
public PendingItemDragHelper(View view) {
super(view);
mAddInfo = (PendingAddItemInfo) view.getTag();
+ mEnforcedRoundedCornersForWidget = RoundedCornerEnforcement.computeEnforcedRadius(
+ view.getContext());
}
- public void setPreview(@Nullable RemoteViews preview) {
- mPreview = preview;
+ /**
+ * Sets a {@link RemoteViews} which shows an app widget preview provided by app developers in
+ * the pin widget flow.
+ */
+ public void setRemoteViewsPreview(@Nullable RemoteViews remoteViewsPreview) {
+ mRemoteViewsPreview = remoteViewsPreview;
+ }
+
+ /** Sets a {@link LauncherAppWidgetHostView} which shows a preview layout of an app widget. */
+ public void setAppWidgetHostViewPreview(
+ @Nullable LauncherAppWidgetHostView appWidgetHostViewPreview) {
+ mAppWidgetHostViewPreview = appWidgetHostViewPreview;
}
/**
@@ -74,7 +92,7 @@
final Launcher launcher = Launcher.getLauncher(mView.getContext());
LauncherAppState app = LauncherAppState.getInstance(launcher);
- Bitmap preview = null;
+ Drawable preview = null;
final float scale;
final Point dragOffset;
final Rect dragRegion;
@@ -90,13 +108,26 @@
int[] previewSizeBeforeScale = new int[1];
- if (mPreview != null) {
- preview = WidgetCell.generateFromRemoteViews(launcher, mPreview,
- createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
+ if (mRemoteViewsPreview != null) {
+ preview = new FastBitmapDrawable(
+ WidgetCell.generateFromRemoteViews(launcher, mRemoteViewsPreview,
+ createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
+ }
+ if (mAppWidgetHostViewPreview != null) {
+ preview = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
+ previewSizeBeforeScale[0] = mAppWidgetHostViewPreview.getMeasuredWidth();
+ launcher.getDragController()
+ .addDragListener(new AppWidgetHostViewDragListener(launcher));
}
if (preview == null) {
- preview = app.getWidgetCache().generateWidgetPreview(launcher,
- createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale).first;
+ Drawable p = new FastBitmapDrawable(
+ app.getWidgetCache().generateWidgetPreview(launcher,
+ createWidgetInfo.info, maxWidth, null,
+ previewSizeBeforeScale).first);
+ if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+ p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
+ }
+ preview = p;
}
if (previewSizeBeforeScale[0] < previewBitmapWidth) {
@@ -109,7 +140,7 @@
previewBounds.left += padding;
previewBounds.right -= padding;
}
- scale = previewBounds.width() / (float) preview.getWidth();
+ scale = previewBounds.width() / (float) preview.getIntrinsicWidth();
launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
dragOffset = null;
@@ -119,9 +150,10 @@
PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
LauncherIcons li = LauncherIcons.obtain(launcher);
- preview = li.createScaledBitmapWithoutShadow(icon, 0);
+ preview = new FastBitmapDrawable(
+ li.createScaledBitmapWithoutShadow(icon, 0));
li.recycle();
- scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
+ scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getIntrinsicWidth();
dragOffset = new Point(previewPadding / 2, previewPadding / 2);
@@ -149,9 +181,9 @@
launcher.getWorkspace().prepareDragWithProvider(this);
int dragLayerX = screenPos.x + previewBounds.left
- + (int) ((scale * preview.getWidth() - preview.getWidth()) / 2);
+ + (int) ((scale * preview.getIntrinsicWidth() - preview.getIntrinsicWidth()) / 2);
int dragLayerY = screenPos.y + previewBounds.top
- + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
+ + (int) ((scale * preview.getIntrinsicHeight() - preview.getIntrinsicHeight()) / 2);
// Start the drag
launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
new file mode 100644
index 0000000..1e46ffd
--- /dev/null
+++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities to compute the enforced the use of rounded corners on App Widgets.
+ */
+public class RoundedCornerEnforcement {
+ // This class is only a namespace and not meant to be instantiated.
+ private RoundedCornerEnforcement() {
+ }
+
+ /**
+ * Find the background view for a widget.
+ *
+ * @param appWidget the view containing the App Widget (typically the instance of
+ * {@link AppWidgetHostView}).
+ */
+ @Nullable
+ public static View findBackground(@NonNull View appWidget) {
+ List<View> backgrounds = findViewsWithId(appWidget, android.R.id.background);
+ if (backgrounds.size() == 1) {
+ return backgrounds.get(0);
+ }
+ // Really, the argument should contain the widget, so it cannot be the background.
+ if (appWidget instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) appWidget;
+ if (vg.getChildCount() > 0) {
+ return findUndefinedBackground(vg.getChildAt(0));
+ }
+ }
+ return appWidget;
+ }
+
+ /**
+ * Check whether the app widget has opted out of the enforcement.
+ */
+ public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) {
+ return background.getId() == android.R.id.background && background.getClipToOutline();
+ }
+
+ /** Check if the app widget is in the deny list. */
+ public static boolean isRoundedCornerEnabled() {
+ return Utilities.ATLEAST_S && FeatureFlags.ENABLE_ENFORCED_ROUNDED_CORNERS.get();
+ }
+
+ /**
+ * Computes the rounded rectangle needed for this app widget.
+ *
+ * @param appWidget View onto which the rounded rectangle will be applied.
+ * @param background Background view. This must be either {@code appWidget} or a descendant
+ * of {@code appWidget}.
+ * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame
+ * of {@code appWidget}.
+ */
+ public static void computeRoundedRectangle(@NonNull View appWidget, @NonNull View background,
+ @NonNull Rect outRect) {
+ outRect.left = 0;
+ outRect.right = background.getWidth();
+ outRect.top = 0;
+ outRect.bottom = background.getHeight();
+ while (background != appWidget) {
+ outRect.offset(background.getLeft(), background.getTop());
+ background = (View) background.getParent();
+ }
+ }
+
+ /**
+ * Computes the radius of the rounded rectangle that should be applied to a widget expanded
+ * in the given context.
+ */
+ public static float computeEnforcedRadius(@NonNull Context context) {
+ if (!Utilities.ATLEAST_S) {
+ return 0;
+ }
+ Resources res = context.getResources();
+ float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
+ float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
+ return Math.min(defaultRadius, systemRadius);
+ }
+
+ private static List<View> findViewsWithId(View view, @IdRes int viewId) {
+ List<View> output = new ArrayList<>();
+ accumulateViewsWithId(view, viewId, output);
+ return output;
+ }
+
+ // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+ private static void accumulateViewsWithId(View view, @IdRes int viewId, List<View> output) {
+ if (view.getId() == viewId) {
+ output.add(view);
+ return;
+ }
+ if (view instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) view;
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ accumulateViewsWithId(vg.getChildAt(i), viewId, output);
+ }
+ }
+ }
+
+ private static boolean isViewVisible(View view) {
+ if (view.getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ return !view.willNotDraw() || view.getForeground() != null || view.getBackground() != null;
+ }
+
+ @Nullable
+ private static View findUndefinedBackground(View current) {
+ if (current.getVisibility() != View.VISIBLE) {
+ return null;
+ }
+ if (isViewVisible(current)) {
+ return current;
+ }
+ View lastVisibleView = null;
+ // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+ // something, or a ViewGroup that contains more than one view.
+ if (current instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) current;
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ View visibleView = findUndefinedBackground(vg.getChildAt(i));
+ if (visibleView != null) {
+ if (lastVisibleView != null) {
+ return current; // At least two visible children
+ }
+ lastVisibleView = visibleView;
+ }
+ }
+ }
+ return lastVisibleView;
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
index 1ac5a33..9313266 100644
--- a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
+++ b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.widget;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.os.Parcel;
@@ -78,8 +81,22 @@
return true;
}
+ /**
+ * Checks whether the widget needs configuration.
+ *
+ * A widget needs configuration if (1) it has a configuration activity and (2)
+ * it's configuration is not optional.
+ *
+ * @return true if the widget needs configuration, false otherwise.
+ */
public boolean needsConfigure() {
- return mProviderInfo.configure != null;
+ int featureFlags = mProviderInfo.widgetFeatures;
+ // A widget's configuration is optional only if it's configuration is marked as optional AND
+ // it can be reconfigured later.
+ boolean configurationOptional = (featureFlags & WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0
+ && (featureFlags & WIDGET_FEATURE_RECONFIGURABLE) != 0;
+
+ return mProviderInfo.configure != null && !configurationOptional;
}
public LauncherAppWidgetProviderInfo getProviderInfo(Context context) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 229df50..5e7c961 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -18,23 +18,27 @@
import static com.android.launcher3.Utilities.ATLEAST_S;
-import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
import android.os.CancellationSignal;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.BaseActivity;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeviceProfile;
@@ -42,6 +46,8 @@
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.icons.RoundDrawableWrapper;
import com.android.launcher3.model.WidgetItem;
/**
@@ -70,8 +76,11 @@
protected int mPreviewHeight;
protected int mPresetPreviewSize;
private int mCellSize;
+ private float mPreviewScale = 1f;
+ private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
+ private ImageView mWidgetBadge;
private TextView mWidgetName;
private TextView mWidgetDims;
private TextView mWidgetDescription;
@@ -84,14 +93,15 @@
private boolean mAnimatePreview = true;
private boolean mApplyBitmapDeferred = false;
- private Bitmap mDeferredBitmap;
+ private Drawable mDeferredDrawable;
protected final BaseActivity mActivity;
protected final DeviceProfile mDeviceProfile;
private final CheckLongPressHelper mLongPressHelper;
+ private final float mEnforcedCornerRadius;
private RemoteViews mPreview;
- private AppWidgetHostView mPreviewAppWidgetHostView;
+ private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
public WidgetCell(Context context) {
this(context, null);
@@ -113,6 +123,7 @@
setWillNotDraw(false);
setClipToPadding(false);
setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
+ mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
}
private void setContainerWidth() {
@@ -125,7 +136,9 @@
protected void onFinishInflate() {
super.onFinishInflate();
+ mWidgetImageContainer = findViewById(R.id.widget_preview_container);
mWidgetImage = findViewById(R.id.widget_preview);
+ mWidgetBadge = findViewById(R.id.widget_badge);
mWidgetName = findViewById(R.id.widget_name);
mWidgetDims = findViewById(R.id.widget_dims);
mWidgetDescription = findViewById(R.id.widget_description);
@@ -147,7 +160,9 @@
Log.d(TAG, "reset called on:" + mWidgetName.getText());
}
mWidgetImage.animate().cancel();
- mWidgetImage.setBitmap(null, null);
+ mWidgetImage.setDrawable(null);
+ mWidgetImage.setVisibility(View.VISIBLE);
+ mWidgetBadge.setImageDrawable(null);
mWidgetName.setText(null);
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
@@ -159,7 +174,10 @@
mActiveRequest = null;
}
mPreview = null;
- mPreviewAppWidgetHostView = null;
+ if (mAppWidgetHostViewPreview != null) {
+ mWidgetImageContainer.removeView(mAppWidgetHostViewPreview);
+ }
+ mAppWidgetHostViewPreview = null;
}
public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
@@ -194,7 +212,7 @@
&& mPreview == null
&& item.widgetInfo != null
&& item.widgetInfo.previewLayout != Resources.ID_NULL) {
- mPreviewAppWidgetHostView = new AppWidgetHostView(getContext());
+ mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext());
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
item.widgetInfo.clone());
@@ -202,11 +220,18 @@
// rendering a preview layout for work profile apps yet. For non-work profile layout, a
// proper solution is to use RemoteViews(PackageName, LayoutId).
launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
- mPreviewAppWidgetHostView.setAppWidget(/* appWidgetId= */ -1,
+ mAppWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1,
launcherAppWidgetProviderInfo);
- mPreviewAppWidgetHostView.setPadding(/* left= */ 0, /* top= */0, /* right= */
+ mAppWidgetHostViewPreview.setPadding(/* left= */ 0, /* top= */0, /* right= */
0, /* bottom= */ 0);
- mPreviewAppWidgetHostView.updateAppWidget(/* remoteViews= */ null);
+ mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ null);
+ // Gravity 77 = "fill"
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT, /* gravity= */ 77);
+ mAppWidgetHostViewPreview.setLayoutParams(params);
+ mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
+ mWidgetImage.setVisibility(View.GONE);
}
}
@@ -214,6 +239,11 @@
return mWidgetImage;
}
+ @Nullable
+ public LauncherAppWidgetHostView getAppWidgetHostViewPreview() {
+ return mAppWidgetHostViewPreview;
+ }
+
/**
* Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
* will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
@@ -223,9 +253,9 @@
public void setApplyBitmapDeferred(boolean isDeferred) {
if (mApplyBitmapDeferred != isDeferred) {
mApplyBitmapDeferred = isDeferred;
- if (!mApplyBitmapDeferred && mDeferredBitmap != null) {
- applyPreview(mDeferredBitmap);
- mDeferredBitmap = null;
+ if (!mApplyBitmapDeferred && mDeferredDrawable != null) {
+ applyPreview(mDeferredDrawable);
+ mDeferredDrawable = null;
}
}
}
@@ -235,26 +265,46 @@
}
public void applyPreview(Bitmap bitmap) {
+ FastBitmapDrawable drawable = new FastBitmapDrawable(bitmap);
+ applyPreview(new RoundDrawableWrapper(drawable, mEnforcedCornerRadius));
+ }
+
+ private void applyPreview(Drawable drawable) {
if (mApplyBitmapDeferred) {
- mDeferredBitmap = bitmap;
+ mDeferredDrawable = drawable;
return;
}
- if (bitmap != null) {
- LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
- layoutParams.width = bitmap.getWidth();
- layoutParams.height = bitmap.getHeight();
- mWidgetImage.setLayoutParams(layoutParams);
-
- mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
- BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
- if (mAnimatePreview) {
- mWidgetImage.setAlpha(0f);
- ViewPropertyAnimator anim = mWidgetImage.animate();
- anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
- } else {
- mWidgetImage.setAlpha(1f);
+ if (drawable != null) {
+ setContainerSize(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ mWidgetImage.setDrawable(drawable);
+ mWidgetImage.setVisibility(View.VISIBLE);
+ if (mAppWidgetHostViewPreview != null) {
+ removeView(mAppWidgetHostViewPreview);
+ mAppWidgetHostViewPreview = null;
}
}
+ Drawable badge = mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+ BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx));
+ if (badge == null) {
+ mWidgetBadge.setVisibility(View.GONE);
+ } else {
+ mWidgetBadge.setVisibility(View.VISIBLE);
+ mWidgetBadge.setImageDrawable(badge);
+ }
+ if (mAnimatePreview) {
+ mWidgetImageContainer.setAlpha(0f);
+ ViewPropertyAnimator anim = mWidgetImageContainer.animate();
+ anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+ } else {
+ mWidgetImageContainer.setAlpha(1f);
+ }
+ }
+
+ private void setContainerSize(int width, int height) {
+ LayoutParams layoutParams = (LayoutParams) mWidgetImageContainer.getLayoutParams();
+ layoutParams.width = (int) (width * mPreviewScale);
+ layoutParams.height = (int) (height * mPreviewScale);
+ mWidgetImageContainer.setLayoutParams(layoutParams);
}
public void ensurePreview() {
@@ -262,18 +312,19 @@
Bitmap preview = generateFromRemoteViews(
mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
if (preview != null) {
- applyPreview(preview);
+ applyPreview(new FastBitmapDrawable(preview));
return;
}
}
- if (mPreviewAppWidgetHostView != null) {
- Bitmap preview = generateFromView(mActivity, mPreviewAppWidgetHostView,
- mItem.widgetInfo, mPreviewWidth, new int[1]);
- if (preview != null) {
- applyPreview(preview);
- return;
- }
+ if (mAppWidgetHostViewPreview != null) {
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ int viewWidth = dp.cellWidthPx * mItem.spanX;
+ int viewHeight = dp.cellHeightPx * mItem.spanY;
+
+ setContainerSize(viewWidth, viewHeight);
+ applyPreview((Drawable) null);
+ return;
}
if (mActiveRequest != null) {
return;
@@ -284,10 +335,16 @@
/** Sets the widget preview image size in number of cells. */
public void setPreviewSize(int spanX, int spanY) {
+ setPreviewSize(spanX, spanY, 1f);
+ }
+
+ /** Sets the widget preview image size, in number of cells, and preview scale. */
+ public void setPreviewSize(int spanX, int spanY, float previewScale) {
int padding = 2 * getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
mPreviewWidth = mDeviceProfile.cellWidthPx * spanX + padding;
mPreviewHeight = mDeviceProfile.cellHeightPx * spanY + padding;
+ mPreviewScale = previewScale;
}
@Override
diff --git a/src/com/android/launcher3/widget/WidgetCellPreview.java b/src/com/android/launcher3/widget/WidgetCellPreview.java
new file mode 100644
index 0000000..ad3a61a
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetCellPreview.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+/**
+ * View group managing the widget preview: either using a {@link WidgetImageView} or an actual
+ * {@link LauncherAppWidgetHostView}.
+ */
+public class WidgetCellPreview extends FrameLayout {
+ public WidgetCellPreview(Context context) {
+ this(context, null);
+ }
+
+ public WidgetCellPreview(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WidgetCellPreview(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ super.onInterceptTouchEvent(ev);
+ return true;
+ }
+
+}
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index df2bcff..11f4485 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -17,9 +17,7 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
@@ -27,7 +25,6 @@
import android.view.View;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
/**
* View that draws a bitmap horizontally centered. If the image width is greater than the view
@@ -35,12 +32,10 @@
*/
public class WidgetImageView extends View {
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final RectF mDstRectF = new RectF();
private final int mBadgeMargin;
- private Bitmap mBitmap;
- private Drawable mBadge;
+ private Drawable mDrawable;
public WidgetImageView(Context context) {
this(context, null);
@@ -57,26 +52,22 @@
.getDimensionPixelSize(R.dimen.profile_badge_margin);
}
- public void setBitmap(Bitmap bitmap, Drawable badge) {
- mBitmap = bitmap;
- mBadge = badge;
+ /** Set the drawable to use for this view. */
+ public void setDrawable(Drawable drawable) {
+ mDrawable = drawable;
invalidate();
}
- public Bitmap getBitmap() {
- return mBitmap;
+ public Drawable getDrawable() {
+ return mDrawable;
}
@Override
protected void onDraw(Canvas canvas) {
- if (mBitmap != null) {
+ if (mDrawable != null) {
updateDstRectF();
- canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
-
- // Only draw the badge if a preview was drawn.
- if (mBadge != null) {
- mBadge.draw(canvas);
- }
+ mDrawable.setBounds(getBitmapBounds());
+ mDrawable.draw(canvas);
}
}
@@ -91,11 +82,11 @@
private void updateDstRectF() {
float myWidth = getWidth();
float myHeight = getHeight();
- float bitmapWidth = mBitmap.getWidth();
+ float bitmapWidth = mDrawable.getIntrinsicWidth();
final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
float scaledWidth = bitmapWidth * scale;
- float scaledHeight = mBitmap.getHeight() * scale;
+ float scaledHeight = mDrawable.getIntrinsicHeight() * scale;
mDstRectF.left = (myWidth - scaledWidth) / 2;
mDstRectF.right = (myWidth + scaledWidth) / 2;
@@ -107,17 +98,6 @@
mDstRectF.top = (myHeight - scaledHeight) / 2;
mDstRectF.bottom = (myHeight + scaledHeight) / 2;
}
-
- if (mBadge != null) {
- Rect bounds = mBadge.getBounds();
- int left = Utilities.boundToRange(
- (int) (mDstRectF.right + mBadgeMargin - bounds.width()),
- mBadgeMargin, getWidth() - bounds.width());
- int top = Utilities.boundToRange(
- (int) (mDstRectF.bottom + mBadgeMargin - bounds.height()),
- mBadgeMargin, getHeight() - bounds.height());
- mBadge.setBounds(left, top, bounds.width() + left, bounds.height() + top);
- }
}
/**
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index e6d54a9..bbb0d92 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -26,6 +26,7 @@
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
@@ -119,8 +120,7 @@
public void populateAndShow(ItemInfo itemInfo) {
mOriginalItemInfo = itemInfo;
- ((TextView) findViewById(R.id.title)).setText(getContext().getString(
- R.string.widgets_bottom_sheet_title, mOriginalItemInfo.title));
+ ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
onWidgetsBound();
attachToContainer();
@@ -140,7 +140,7 @@
WidgetsTableUtils.groupWidgetItemsIntoTable(widgets, mMaxHorizontalSpan).forEach(row -> {
TableRow tableRow = new TableRow(getContext());
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
+ tableRow.setGravity(Gravity.TOP);
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
@@ -153,6 +153,19 @@
});
}
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = false;
+ ScrollView scrollView = findViewById(R.id.widgets_table_scroll_view);
+ if (getPopupContainer().isEventOverView(scrollView, ev)
+ && scrollView.getScrollY() > 0) {
+ mNoIntercept = true;
+ }
+ }
+ return super.onControllerInterceptTouchEvent(ev);
+ }
+
protected WidgetCell addItemCell(ViewGroup parent) {
WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
.inflate(R.layout.widget_cell, parent, false);
diff --git a/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java b/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
new file mode 100644
index 0000000..66bb363
--- /dev/null
+++ b/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.dragndrop;
+
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/** A drag listener of {@link LauncherAppWidgetHostView}. */
+public final class AppWidgetHostViewDragListener implements DragController.DragListener {
+ private final Launcher mLauncher;
+ private DropTarget.DragObject mDragObject;
+ private AppWidgetHostViewDrawable mAppWidgetHostViewDrawable;
+ private LauncherAppWidgetHostView mAppWidgetHostView;
+
+ public AppWidgetHostViewDragListener(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions unused) {
+ if (dragObject.dragView.getDrawable() instanceof AppWidgetHostViewDrawable) {
+ mDragObject = dragObject;
+ mAppWidgetHostViewDrawable =
+ (AppWidgetHostViewDrawable) mDragObject.dragView.getDrawable();
+ mAppWidgetHostView = mAppWidgetHostViewDrawable.getAppWidgetHostView();
+ mAppWidgetHostView.startDrag(this);
+ } else {
+ mLauncher.getDragController().removeDragListener(this);
+ }
+ }
+
+ @Override
+ public void onDragEnd() {
+ mAppWidgetHostView.endDrag();
+ mLauncher.getDragController().removeDragListener(this);
+ }
+
+ /** Notifies when there is a content change in the drag view. */
+ public void onDragContentChanged() {
+ if (mDragObject.dragView != null) {
+ mDragObject.dragView.invalidate();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index 09517e1..73bae6f 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -20,10 +20,14 @@
import androidx.annotation.IntDef;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.widget.WidgetItemComparator;
import java.lang.annotation.Retention;
+import java.util.List;
+import java.util.stream.Collectors;
/** Holder class to store the package information of an entry shown in the widgets list. */
public abstract class WidgetsListBaseEntry {
@@ -35,9 +39,14 @@
*/
public final String mTitleSectionName;
- public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName) {
+ public final List<WidgetItem> mWidgets;
+
+ public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items) {
mPkgItem = pkgItem;
mTitleSectionName = titleSectionName;
+ this.mWidgets =
+ items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
}
/**
@@ -51,10 +60,11 @@
public abstract int getRank();
@Retention(SOURCE)
- @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_CONTENT})
+ @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT})
public @interface Rank {
}
public static final int RANK_WIDGETS_LIST_HEADER = 1;
- public static final int RANK_WIDGETS_LIST_CONTENT = 2;
+ public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 2;
+ public static final int RANK_WIDGETS_LIST_CONTENT = 3;
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
index b0cb8c7..0328cf6 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -17,10 +17,8 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.widget.WidgetItemComparator;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Holder class to store all the information related to a list of widgets from the same app which is
@@ -28,18 +26,14 @@
*/
public final class WidgetsListContentEntry extends WidgetsListBaseEntry {
- public final List<WidgetItem> mWidgets;
-
public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName,
List<WidgetItem> items) {
- super(pkgItem, titleSectionName);
- this.mWidgets =
- items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
+ super(pkgItem, titleSectionName, items);
}
@Override
public String toString() {
- return mPkgItem.packageName + ":" + mWidgets.size();
+ return "Content:" + mPkgItem.packageName + ":" + mWidgets.size();
}
@Override
@@ -47,4 +41,12 @@
public int getRank() {
return RANK_WIDGETS_LIST_CONTENT;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WidgetsListContentEntry)) return false;
+ WidgetsListContentEntry otherEntry = (WidgetsListContentEntry) obj;
+ return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+ && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+ }
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index 6899647..1fdc399 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -18,7 +18,7 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import java.util.Collection;
+import java.util.List;
/** An information holder for an app which has widgets or/and shortcuts. */
public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry {
@@ -30,8 +30,8 @@
private boolean mHasEntryUpdated = false;
public WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
- Collection<WidgetItem> items) {
- super(pkgItem, titleSectionName);
+ List<WidgetItem> items) {
+ super(pkgItem, titleSectionName, items);
widgetsCount = (int) items.stream().filter(item -> item.widgetInfo != null).count();
shortcutsCount = Math.max(0, items.size() - widgetsCount);
}
@@ -57,8 +57,21 @@
}
@Override
+ public String toString() {
+ return "Header:" + mPkgItem.packageName + ":" + mWidgets.size();
+ }
+
+ @Override
@Rank
public int getRank() {
return RANK_WIDGETS_LIST_HEADER;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WidgetsListHeaderEntry)) return false;
+ WidgetsListHeaderEntry otherEntry = (WidgetsListHeaderEntry) obj;
+ return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+ && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+ }
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
new file mode 100644
index 0000000..2aec3f8
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.model;
+
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+
+import java.util.List;
+
+/** An information holder for an app which has widgets or/and shortcuts, to be shown in search. */
+public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry {
+
+ private boolean mIsWidgetListShown = false;
+ private boolean mHasEntryUpdated = false;
+
+ public WidgetsListSearchHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items) {
+ super(pkgItem, titleSectionName, items);
+ }
+
+ /** Sets if the widgets list associated with this header is shown. */
+ public void setIsWidgetListShown(boolean isWidgetListShown) {
+ if (mIsWidgetListShown != isWidgetListShown) {
+ this.mIsWidgetListShown = isWidgetListShown;
+ mHasEntryUpdated = true;
+ } else {
+ mHasEntryUpdated = false;
+ }
+ }
+
+ /** Returns {@code true} if the widgets list associated with this header is shown. */
+ public boolean isWidgetListShown() {
+ return mIsWidgetListShown;
+ }
+
+ /** Returns {@code true} if this entry has been updated due to user interactions. */
+ public boolean hasEntryUpdated() {
+ return mHasEntryUpdated;
+ }
+
+ @Override
+ public String toString() {
+ return "SearchHeader:" + mPkgItem.packageName + ":" + mWidgets.size();
+ }
+
+ @Override
+ @Rank
+ public int getRank() {
+ return RANK_WIDGETS_LIST_SEARCH_HEADER;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WidgetsListSearchHeaderEntry)) return false;
+ WidgetsListSearchHeaderEntry otherEntry = (WidgetsListSearchHeaderEntry) obj;
+ return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+ && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java b/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java
new file mode 100644
index 0000000..7372751
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import com.android.launcher3.util.PackageUserKey;
+
+/**
+ * A listener to be invoked when a header is clicked.
+ */
+public interface OnHeaderClickListener {
+ /**
+ * Calls when a header is clicked to show / hide widgets for a package.
+ */
+ void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey);
+}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index 95fa05f..7f84077 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -34,6 +34,8 @@
private final boolean mHasWorkProfile;
private final SearchAndRecommendationViewHolder mViewHolder;
private final WidgetsRecyclerView mPrimaryRecyclerView;
+ private final WidgetsRecyclerView mSearchRecyclerView;
+ private final int mTabsHeight;
// The following are only non null if mHasWorkProfile is true.
@Nullable private final WidgetsRecyclerView mWorkRecyclerView;
@@ -41,57 +43,94 @@
@Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
private WidgetsRecyclerView mCurrentRecyclerView;
- private int mMaxCollapsibleHeight = 0;
+
+ /**
+ * The vertical distance, in pixels, until the search is pinned at the top of the screen when
+ * the user scrolls down the recycler view.
+ */
+ private int mCollapsibleHeightForSearch = 0;
+ /**
+ * The vertical distance, in pixels, until the recommendation table disappears from the top of
+ * the screen when the user scrolls down the recycler view.
+ */
+ private int mCollapsibleHeightForRecommendation = 0;
+ /**
+ * The vertical distance, in pixels, until the tabs is pinned at the top of the screen when the
+ * user scrolls down the recycler view.
+ *
+ * <p>Always 0 if there is no work profile.
+ */
+ private int mCollapsibleHeightForTabs = 0;
SearchAndRecommendationsScrollController(
boolean hasWorkProfile,
+ int tabsHeight,
SearchAndRecommendationViewHolder viewHolder,
WidgetsRecyclerView primaryRecyclerView,
@Nullable WidgetsRecyclerView workRecyclerView,
+ WidgetsRecyclerView searchRecyclerView,
@Nullable View personalWorkTabsView,
@Nullable PersonalWorkPagedView primaryWorkViewPager) {
mHasWorkProfile = hasWorkProfile;
mViewHolder = viewHolder;
mPrimaryRecyclerView = primaryRecyclerView;
+ mCurrentRecyclerView = mPrimaryRecyclerView;
mWorkRecyclerView = workRecyclerView;
+ mSearchRecyclerView = searchRecyclerView;
mPrimaryWorkTabsView = personalWorkTabsView;
mPrimaryWorkViewPager = primaryWorkViewPager;
mCurrentRecyclerView = mPrimaryRecyclerView;
+ mTabsHeight = tabsHeight;
}
/** Sets the current active {@link WidgetsRecyclerView}. */
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
mCurrentRecyclerView = currentRecyclerView;
+ mCurrentRecyclerView = currentRecyclerView;
+ mViewHolder.mHeaderTitle.setTranslationY(0);
+ mViewHolder.mRecommendedWidgetsTable.setTranslationY(0);
+ mViewHolder.mSearchBar.setTranslationY(0);
+
+ if (mHasWorkProfile) {
+ mPrimaryWorkTabsView.setTranslationY(0);
+ }
}
/**
* Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
+ *
+ * @return {@code true} if margins or/and padding of views in the search and recommendations
+ * container have been updated.
*/
- public void updateMarginAndPadding() {
- // The maximum vertical distance, in pixels, until the last collapsible element is not
- // visible from the screen when the user scrolls down the recycler view.
- mMaxCollapsibleHeight = mViewHolder.mContainer.getPaddingTop()
- + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
- + measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
+ public boolean updateMarginAndPadding() {
+ boolean hasMarginOrPaddingUpdated = false;
+ mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
+ mCollapsibleHeightForRecommendation =
+ measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
+ + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
+ + measureHeightWithVerticalMargins((View) mViewHolder.mSearchBar)
+ + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
if (mHasWorkProfile) {
+ mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
+ + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
// In a work profile setup, the full widget sheet contains the following views:
- // ------- -|
- // Widgets -|---> LinearLayout for search & recommendations
- // Search bar -|
- // Personal | Work
+ // ------- (pinned) -|
+ // Widgets (collapsible) -|---> LinearLayout for search & recommendations
+ // Search bar (pinned) -|
+ // Widgets recommendation (collapsible)-|
+ // Personal | Work (pinned)
// View Pager
//
// Views after the search & recommendations are not bound by RelativelyLayout param.
// To position them on the expected location, padding & margin are added to these views
// Tabs should have a padding of the height of the search & recommendations container.
- mPrimaryWorkTabsView.setPadding(
- mPrimaryWorkTabsView.getPaddingLeft(),
- topContainerHeight,
- mPrimaryWorkTabsView.getPaddingRight(),
- mPrimaryWorkTabsView.getPaddingBottom());
+ RelativeLayout.LayoutParams tabsLayoutParams =
+ (RelativeLayout.LayoutParams) mPrimaryWorkTabsView.getLayoutParams();
+ tabsLayoutParams.topMargin = topContainerHeight;
+ mPrimaryWorkTabsView.setLayoutParams(tabsLayoutParams);
// Instead of setting the top offset directly, we split the top offset into two values:
// 1. topOffsetAfterAllViewsCollapsed: this is the top offset after all collapsible
@@ -121,34 +160,52 @@
//
// When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
// mMaxCollapsibleDistance should equal to the top container height.
- int tabsViewActualHeight = measureHeightWithVerticalMargins(mPrimaryWorkTabsView)
- - mPrimaryWorkTabsView.getPaddingTop();
int topOffsetAfterAllViewsCollapsed =
- topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
+ topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
- RelativeLayout.LayoutParams layoutParams =
+ RelativeLayout.LayoutParams viewPagerLayoutParams =
(RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
- layoutParams.setMargins(0, topOffsetAfterAllViewsCollapsed, 0, 0);
- mPrimaryWorkViewPager.setLayoutParams(layoutParams);
- mPrimaryWorkViewPager.requestLayout();
+ if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
+ viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
+ mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
+ hasMarginOrPaddingUpdated = true;
+ }
- mPrimaryRecyclerView.setPadding(
- mPrimaryRecyclerView.getPaddingLeft(),
- mMaxCollapsibleHeight,
- mPrimaryRecyclerView.getPaddingRight(),
- mPrimaryRecyclerView.getPaddingBottom());
- mWorkRecyclerView.setPadding(
- mWorkRecyclerView.getPaddingLeft(),
- mMaxCollapsibleHeight,
- mWorkRecyclerView.getPaddingRight(),
- mWorkRecyclerView.getPaddingBottom());
+ if (mPrimaryRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
+ mPrimaryRecyclerView.setPadding(
+ mPrimaryRecyclerView.getPaddingLeft(),
+ mCollapsibleHeightForTabs,
+ mPrimaryRecyclerView.getPaddingRight(),
+ mPrimaryRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
+ if (mWorkRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
+ mWorkRecyclerView.setPadding(
+ mWorkRecyclerView.getPaddingLeft(),
+ mCollapsibleHeightForTabs,
+ mWorkRecyclerView.getPaddingRight(),
+ mWorkRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
} else {
- mPrimaryRecyclerView.setPadding(
- mPrimaryRecyclerView.getPaddingLeft(),
- topContainerHeight,
- mPrimaryRecyclerView.getPaddingRight(),
- mPrimaryRecyclerView.getPaddingBottom());
+ if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
+ mPrimaryRecyclerView.setPadding(
+ mPrimaryRecyclerView.getPaddingLeft(),
+ topContainerHeight,
+ mPrimaryRecyclerView.getPaddingRight(),
+ mPrimaryRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
}
+ if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
+ mSearchRecyclerView.setPadding(
+ mSearchRecyclerView.getPaddingLeft(),
+ topContainerHeight,
+ mSearchRecyclerView.getPaddingRight(),
+ mSearchRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
+ return hasMarginOrPaddingUpdated;
}
/**
@@ -160,13 +217,22 @@
// Always use the recycler view offset because fast scroller offset has a different scale.
int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
if (recyclerViewYOffset < 0) return;
- if (mMaxCollapsibleHeight > 0) {
- int yDisplacement = Math.max(-recyclerViewYOffset, -mMaxCollapsibleHeight);
+
+ if (mCollapsibleHeightForRecommendation > 0) {
+ int yDisplacement = Math.max(-recyclerViewYOffset,
+ -mCollapsibleHeightForRecommendation);
mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
- mViewHolder.mSearchBar.setTranslationY(yDisplacement);
- if (mHasWorkProfile) {
- mPrimaryWorkTabsView.setTranslationY(yDisplacement);
- }
+ mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
+ }
+
+ if (mCollapsibleHeightForSearch > 0) {
+ int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch);
+ mViewHolder.mSearchBar.setTranslationY(searchYDisplacement);
+ }
+
+ if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
+ int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs);
+ mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
}
}
@@ -181,6 +247,9 @@
/** private the height, in pixel, + the vertical margins of a given view. */
private static int measureHeightWithVerticalMargins(View view) {
+ if (view.getVisibility() != View.VISIBLE) {
+ return 0;
+ }
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+ marginLayoutParams.topMargin;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
index dbd1bdf..2366609 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
@@ -25,6 +25,7 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
import java.util.ArrayList;
@@ -113,7 +114,7 @@
// or did the header view changed due to user interactions?
// or did the widget size and desc, span, etc change?
if (!isSamePackageItemInfo(orgRowEntry.mPkgItem, newRowEntry.mPkgItem)
- || hasHeaderUpdated(newRowEntry)
+ || hasHeaderUpdated(orgRowEntry, newRowEntry)
|| hasWidgetsListChanged(orgRowEntry, newRowEntry)) {
index = currentEntries.indexOf(orgRowEntry);
currentEntries.set(index, newRowEntry);
@@ -174,12 +175,16 @@
* Returns {@code true} if {@code newRow} is {@link WidgetsListHeaderEntry} and its content has
* been changed due to user interactions.
*/
- private boolean hasHeaderUpdated(WidgetsListBaseEntry newRow) {
- if (!(newRow instanceof WidgetsListHeaderEntry)) {
- return false;
+ private boolean hasHeaderUpdated(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow) {
+ if (newRow instanceof WidgetsListHeaderEntry && curRow instanceof WidgetsListHeaderEntry) {
+ return ((WidgetsListHeaderEntry) newRow).hasEntryUpdated() || !curRow.equals(newRow);
}
- WidgetsListHeaderEntry newRowEntry = (WidgetsListHeaderEntry) newRow;
- return newRowEntry.hasEntryUpdated();
+ if (newRow instanceof WidgetsListSearchHeaderEntry
+ && curRow instanceof WidgetsListSearchHeaderEntry) {
+ return ((WidgetsListSearchHeaderEntry) newRow).hasEntryUpdated()
+ || !curRow.equals(newRow);
+ }
+ return false;
}
private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index bf9b849..a4257a2 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -23,6 +23,7 @@
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.pm.LauncherApps;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Process;
import android.os.UserHandle;
@@ -32,9 +33,10 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
@@ -48,14 +50,20 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.TopRoundedCornerView;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.search.SearchModeListener;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
+import com.android.launcher3.widget.util.WidgetsTableUtils;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
@@ -64,11 +72,17 @@
*/
public class WidgetsFullSheet extends BaseWidgetSheet
implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
- WidgetsRecyclerView.HeaderViewDimensionsProvider {
+ WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener,
+ WidgetsSearchBarUIHelper {
+ private static final String TAG = WidgetsFullSheet.class.getSimpleName();
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
private static final float VERTICAL_START_POSITION = 0.3f;
+ // The widget recommendation table can easily take over the entire screen on devices with small
+ // resolution or landscape on phone. This ratio defines the max percentage of content area that
+ // the table can display.
+ private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
private final Rect mInsets = new Rect();
private final boolean mHasWorkProfile;
@@ -78,10 +92,14 @@
mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
mPrimaryWidgetsFilter.negate();
+ private final int mTabsHeight;
+ private final int mWidgetCellHorizontalPadding;
@Nullable private PersonalWorkPagedView mViewPager;
- private int mInitialTabsHeight = 0;
+ private boolean mIsInSearchMode;
+ private int mMaxSpansPerRow = 4;
private View mTabsView;
+ private TextView mNoWidgetsView;
private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
@@ -90,6 +108,13 @@
mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
+ mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
+ mTabsHeight = mHasWorkProfile
+ ? getContext().getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
+ : 0;
+ mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
+ R.dimen.widget_cell_horizontal_padding);
}
public WidgetsFullSheet(Context context, AttributeSet attrs) {
@@ -119,51 +144,81 @@
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> mViewPager.snapToPage(1));
fastScroller.setIsRecyclerViewFirstChildInParent(false);
- springLayout.addSpringView(R.id.primary_widgets_list_view);
- springLayout.addSpringView(R.id.work_widgets_list_view);
} else {
mViewPager = null;
- springLayout.addSpringView(R.id.primary_widgets_list_view);
}
layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
true);
- springLayout.addSpringView(R.id.search_and_recommendations_container);
-
mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
findViewById(R.id.search_and_recommendations_container));
mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
mHasWorkProfile,
+ mTabsHeight,
mSearchAndRecommendationViewHolder,
findViewById(R.id.primary_widgets_list_view),
mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
+ findViewById(R.id.search_widgets_list_view),
mTabsView,
mViewPager);
fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
+ mNoWidgetsView = findViewById(R.id.no_widgets_text);
+
+ onRecommendedWidgetsBound();
onWidgetsBound();
+
+ mSearchAndRecommendationViewHolder.mSearchBar.initialize(
+ mLauncher.getPopupDataProvider(), /* searchModeListener= */ this);
}
@Override
public void onActivePageChanged(int currentActivePage) {
+ AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
WidgetsRecyclerView currentRecyclerView =
mAdapters.get(currentActivePage).mWidgetsRecyclerView;
- currentRecyclerView.bindFastScrollbar();
- mSearchAndRecommendationsScrollController.setCurrentRecyclerView(currentRecyclerView);
+ updateNoWidgetsView(currentAdapterHolder);
+ attachScrollbarToRecyclerView(currentRecyclerView);
+ resetExpandedHeaders();
+ }
+
+ private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
+ recyclerView.bindFastScrollbar();
+ mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
reset();
}
+ private void updateNoWidgetsView(AdapterHolder adapterHolder) {
+ boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
+ adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
+
+ // Always resets the text in case this is updated by search.
+ mNoWidgetsView.setText(R.string.no_widgets_available);
+ mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
+ }
+
+ private void updateNoSearchResultsView(boolean isVisible) {
+ mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
+ if (isVisible) {
+ mNoWidgetsView.setText(R.string.no_search_results);
+ }
+ }
+
private void reset() {
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
if (mHasWorkProfile) {
mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
}
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
mSearchAndRecommendationsScrollController.reset();
}
@VisibleForTesting
public WidgetsRecyclerView getRecyclerView() {
+ if (mIsInSearchMode) {
+ return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
+ }
if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
}
@@ -194,6 +249,7 @@
mInsets.set(insets);
setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
+ setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
if (mHasWorkProfile) {
setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
}
@@ -217,6 +273,22 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ if (updateMaxSpansPerRow()) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ }
+
+ private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
int widthUsed;
if (mInsets.bottom > 0) {
@@ -232,22 +304,29 @@
widthUsed, heightMeasureSpec, heightUsed);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
+ }
- int paddingPx = 2 * getResources().getDimensionPixelOffset(
- R.dimen.widget_cell_horizontal_padding);
- int maxSpansPerRow = getMeasuredWidth() / (deviceProfile.cellWidthPx + paddingPx);
- mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
- maxSpansPerRow);
- if (mHasWorkProfile) {
- mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
- maxSpansPerRow);
+ /** Returns {@code true} if the max spans have been updated. */
+ private boolean updateMaxSpansPerRow() {
+ if (getMeasuredWidth() == 0) return false;
+
+ int previousMaxSpansPerRow = mMaxSpansPerRow;
+ mMaxSpansPerRow = getMeasuredWidth()
+ / (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
+
+ if (previousMaxSpansPerRow != mMaxSpansPerRow) {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ mMaxSpansPerRow);
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ mMaxSpansPerRow);
+ if (mHasWorkProfile) {
+ mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ mMaxSpansPerRow);
+ }
+ onRecommendedWidgetsBound();
+ return true;
}
-
- if (mInitialTabsHeight == 0 && mTabsView != null) {
- mInitialTabsHeight = measureHeightWithVerticalMargins(mTabsView);
- }
-
- mSearchAndRecommendationsScrollController.updateMarginAndPadding();
+ return false;
}
@Override
@@ -275,7 +354,11 @@
AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
+ AdapterHolder searchAdapterHolder = mAdapters.get(AdapterHolder.SEARCH);
+ searchAdapterHolder.setup(findViewById(R.id.search_widgets_list_view));
primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+ updateNoWidgetsView(primaryUserAdapterHolder);
+
if (mHasWorkProfile) {
AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
@@ -284,6 +367,73 @@
}
}
+ @Override
+ public void enterSearchMode() {
+ if (mIsInSearchMode) return;
+ setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
+ attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
+ resetExpandedHeaders();
+ }
+
+ @Override
+ public void exitSearchMode() {
+ onSearchResults(new ArrayList<>());
+ setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
+ if (mHasWorkProfile) {
+ mViewPager.snapToPage(AdapterHolder.PRIMARY);
+ }
+ attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
+
+ mSearchAndRecommendationsScrollController.updateMarginAndPadding();
+ }
+
+ @Override
+ public void onSearchResults(List<WidgetsListBaseEntry> entries) {
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
+ updateNoSearchResultsView(
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.getItemCount() == 0);
+ }
+
+ private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
+ mIsInSearchMode = isInSearchMode;
+ mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
+ .setVisibility(isInSearchMode ? GONE : VISIBLE);
+ if (mHasWorkProfile) {
+ mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
+ mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
+ } else {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
+ .setVisibility(isInSearchMode ? GONE : VISIBLE);
+ }
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
+ .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
+ mNoWidgetsView.setVisibility(GONE);
+ }
+
+ private void resetExpandedHeaders() {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
+ mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
+ }
+
+ @Override
+ public void onRecommendedWidgetsBound() {
+ List<WidgetItem> recommendedWidgets =
+ mLauncher.getPopupDataProvider().getRecommendedWidgets();
+ WidgetsRecommendationTableLayout table =
+ mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
+ if (recommendedWidgets.size() > 0) {
+ float maxTableHeight =
+ (mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
+ * RECOMMENDATION_TABLE_HEIGHT_RATIO;
+ List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
+ WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
+ mMaxSpansPerRow);
+ table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
+ } else {
+ table.setVisibility(GONE);
+ }
+ }
+
private void open(boolean animate) {
if (animate) {
if (getPopupContainer().getInsets().bottom > 0) {
@@ -369,23 +519,55 @@
@Override
public int getHeaderViewHeight() {
- // No need to check work profile here because mInitialTabHeight is always 0 if there is no
- // work profile.
- return mInitialTabsHeight
- + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mContainer);
+ return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
+ + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
+ + measureHeightWithVerticalMargins(
+ (View) mSearchAndRecommendationViewHolder.mSearchBar);
}
/** private the height, in pixel, + the vertical margins of a given view. */
private static int measureHeightWithVerticalMargins(View view) {
+ if (view.getVisibility() != VISIBLE) {
+ return 0;
+ }
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+ marginLayoutParams.topMargin;
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mIsInSearchMode) {
+ mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ }
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ if (mIsInSearchMode) {
+ mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ return true;
+ }
+ return super.onBackPressed();
+ }
+
+ @Override
+ public void onDragStart(boolean start, float startDisplacement) {
+ super.onDragStart(start, startDisplacement);
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+
+ @Override
+ public void clearSearchBarFocus() {
+ mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
+ }
+
/** A holder class for holding adapters & their corresponding recycler view. */
private final class AdapterHolder {
static final int PRIMARY = 0;
static final int WORK = 1;
+ static final int SEARCH = 2;
private final int mAdapterType;
private final WidgetsListAdapter mWidgetsListAdapter;
@@ -403,32 +585,54 @@
apps.getWidgetCache(),
apps.getIconCache(),
/* iconClickListener= */ WidgetsFullSheet.this,
- /* iconLongClickListener= */ WidgetsFullSheet.this);
- mWidgetsListAdapter.setFilter(
- mAdapterType == PRIMARY ? mPrimaryWidgetsFilter : mWorkWidgetsFilter);
+ /* iconLongClickListener= */ WidgetsFullSheet.this,
+ /* WidgetsSearchBarUIHelper= */
+ mAdapterType == SEARCH ? WidgetsFullSheet.this : null);
+ mWidgetsListAdapter.setHasStableIds(true);
+ switch (mAdapterType) {
+ case PRIMARY:
+ mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
+ break;
+ case WORK:
+ mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
+ break;
+ default:
+ break;
+ }
}
void setup(WidgetsRecyclerView recyclerView) {
mWidgetsRecyclerView = recyclerView;
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
+ // Disables animation because it disrupts the item focus upon adapter item change.
+ mWidgetsRecyclerView.setItemAnimator(null);
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
mWidgetsRecyclerView.setEdgeEffectFactory(
((TopRoundedCornerView) mContent).createEdgeEffectFactory());
mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
+ mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
}
}
final class SearchAndRecommendationViewHolder {
- final View mContainer;
+ final ViewGroup mContainer;
final View mCollapseHandle;
- final EditText mSearchBar;
+ final WidgetsSearchBar mSearchBar;
final TextView mHeaderTitle;
+ final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
- SearchAndRecommendationViewHolder(View searchAndRecommendationContainer) {
+ SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
mContainer = searchAndRecommendationContainer;
mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
mHeaderTitle = mContainer.findViewById(R.id.title);
+ mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
+ mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
+ getRecyclerView().onTouchEvent(event);
+ return false;
+ });
+ mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
+ mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
}
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 8b49d1e..d9c9d4d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -16,6 +16,7 @@
package com.android.launcher3.widget.picker;
import android.content.Context;
+import android.os.Process;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -34,13 +35,16 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.LabelComparator;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListHeaderViewHolderBinder.OnHeaderClickListener;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
@@ -62,9 +66,11 @@
private static final boolean DEBUG = false;
/** Uniquely identifies widgets list view type within the app. */
- private static final int VIEW_TYPE_WIDGETS_LIST = R.layout.widgets_list_row_view;
- private static final int VIEW_TYPE_WIDGETS_HEADER = R.layout.widgets_list_row_header;
+ private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
+ private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
+ private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
+ @Nullable private final WidgetsSearchBarUIHelper mSearchBarUIHelper;
private final WidgetsDiffReporter mDiffReporter;
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
@@ -73,22 +79,33 @@
private List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
- @Nullable private String mWidgetsContentVisiblePackage = null;
+ @Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null;
private Predicate<WidgetsListBaseEntry> mHeaderAndSelectedContentFilter = entry ->
entry instanceof WidgetsListHeaderEntry
- || entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage);
+ || entry instanceof WidgetsListSearchHeaderEntry
+ || new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+ .equals(mWidgetsContentVisiblePackageUserKey);
@Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
- OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
+ OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
+ @Nullable WidgetsSearchBarUIHelper searchBarUIHelper) {
+ mSearchBarUIHelper = searchBarUIHelper;
mDiffReporter = new WidgetsDiffReporter(iconCache, this);
mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
- layoutInflater, iconClickListener, iconLongClickListener, widgetPreviewLoader);
+ layoutInflater, iconClickListener, iconLongClickListener,
+ widgetPreviewLoader, /* listAdapter= */ this);
mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
- mViewHolderBinders.put(VIEW_TYPE_WIDGETS_HEADER,
- new WidgetsListHeaderViewHolderBinder(layoutInflater, this::onHeaderClicked));
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_HEADER,
+ new WidgetsListHeaderViewHolderBinder(
+ layoutInflater, /* onHeaderClickListener= */this, /* listAdapter= */ this));
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_SEARCH_HEADER,
+ new WidgetsListSearchHeaderViewHolderBinder(
+ layoutInflater, /*onHeaderClickListener=*/ this, /* listAdapter= */ this));
}
public void setFilter(Predicate<WidgetsListBaseEntry> filter) {
@@ -122,23 +139,40 @@
return mVisibleEntries.size();
}
+ /** Returns all items that will be drawn in a recycler view. */
+ public List<WidgetsListBaseEntry> getItems() {
+ return mVisibleEntries;
+ }
+
/** Gets the section name for {@link com.android.launcher3.views.RecyclerViewFastScroller}. */
public String getSectionName(int pos) {
return mVisibleEntries.get(pos).mTitleSectionName;
}
- /** Updates the widget list. */
+ /** Updates the widget list based on {@code tempEntries}. */
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
mAllEntries = tempEntries.stream().sorted(mRowComparator)
.collect(Collectors.toList());
updateVisibleEntries();
}
+ /** Updates the widget list based on {@code searchResults}. */
+ public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
+ // Forget the expanded package every time widget list is refreshed in search mode.
+ mWidgetsContentVisiblePackageUserKey = null;
+ setWidgets(searchResults);
+ }
+
private void updateVisibleEntries() {
mAllEntries.forEach(entry -> {
if (entry instanceof WidgetsListHeaderEntry) {
((WidgetsListHeaderEntry) entry).setIsWidgetListShown(
- entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage));
+ new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+ .equals(mWidgetsContentVisiblePackageUserKey));
+ } else if (entry instanceof WidgetsListSearchHeaderEntry) {
+ ((WidgetsListSearchHeaderEntry) entry).setIsWidgetListShown(
+ new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+ .equals(mWidgetsContentVisiblePackageUserKey));
}
});
List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
@@ -148,10 +182,18 @@
mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
}
+ /**
+ * Resets any expanded widget header.
+ */
+ public void resetExpandedHeader() {
+ mWidgetsContentVisiblePackageUserKey = null;
+ updateVisibleEntries();
+ }
+
@Override
public void onBindViewHolder(ViewHolder holder, int pos) {
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
- viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos));
+ viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
}
@Override
@@ -179,7 +221,9 @@
@Override
public long getItemId(int pos) {
- return pos;
+ return Arrays.hashCode(new Object[]{
+ mVisibleEntries.get(pos).mPkgItem.hashCode(),
+ getItemViewType(pos)});
}
@Override
@@ -189,17 +233,22 @@
return VIEW_TYPE_WIDGETS_LIST;
} else if (entry instanceof WidgetsListHeaderEntry) {
return VIEW_TYPE_WIDGETS_HEADER;
+ } else if (entry instanceof WidgetsListSearchHeaderEntry) {
+ return VIEW_TYPE_WIDGETS_SEARCH_HEADER;
}
throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
}
@Override
- public void onHeaderClicked(boolean showWidgets, String expandedPackage) {
+ public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) {
+ if (mSearchBarUIHelper != null) {
+ mSearchBarUIHelper.clearSearchBarFocus();
+ }
if (showWidgets) {
- mWidgetsContentVisiblePackage = expandedPackage;
+ mWidgetsContentVisiblePackageUserKey = packageUserKey;
updateVisibleEntries();
- } else if (expandedPackage.equals(mWidgetsContentVisiblePackage)) {
- mWidgetsContentVisiblePackage = null;
+ } else if (packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) {
+ mWidgetsContentVisiblePackageUserKey = null;
updateVisibleEntries();
}
}
@@ -229,7 +278,14 @@
@Override
public int compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b) {
- return mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+ int i = mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+ if (i != 0) {
+ return i;
+ }
+ // Prioritize entries from current user over other users if the entries are same.
+ if (a.mPkgItem.user.equals(b.mPkgItem.user)) return 0;
+ if (a.mPkgItem.user.equals(Process.myUserHandle())) return -1;
+ return 1;
}
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 070a9aa..497c72e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.widget.picker;
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
-
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -29,18 +27,22 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
-import com.android.launcher3.graphics.PlaceHolderIconDrawable;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.PlaceHolderIconDrawable;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import java.util.stream.Collectors;
/**
* A UI represents a header of an app shown in the full widgets tray.
@@ -55,6 +57,7 @@
@Nullable private HandlerRunnable mIconLoadRequest;
@Nullable private Drawable mIconDrawable;
private final int mIconSize;
+ private final int mBottomMarginSize;
private ImageView mAppIcon;
private TextView mTitle;
@@ -80,6 +83,8 @@
R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
grid.iconSizePx);
+ mBottomMarginSize =
+ getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin);
}
@Override
@@ -110,6 +115,13 @@
public void setExpanded(boolean isExpanded) {
this.mIsExpanded = isExpanded;
mExpandToggle.setChecked(isExpanded);
+ if (getLayoutParams() instanceof RecyclerView.LayoutParams) {
+ int bottomMargin = isExpanded ? 0 : mBottomMarginSize;
+ RecyclerView.LayoutParams layoutParams =
+ ((RecyclerView.LayoutParams) getLayoutParams());
+ layoutParams.bottomMargin = bottomMargin;
+ setLayoutParams(layoutParams);
+ }
}
/** Apply app icon, labels and tag using a generic {@link WidgetsListHeaderEntry}. */
@@ -131,7 +143,7 @@
}
private void setIcon(PackageItemInfo info) {
- FastBitmapDrawable icon = newIcon(getContext(), info);
+ FastBitmapDrawable icon = info.newIcon(getContext());
applyDrawables(icon);
mIconDrawable = icon;
if (mIconDrawable != null) {
@@ -173,7 +185,7 @@
shortcutsCount);
} else if (entry.widgetsCount > 0) {
subtitle = resources.getQuantityString(R.plurals.widgets_count,
- entry.widgetsCount, entry.widgetsCount);
+ entry.widgetsCount, entry.widgetsCount);
} else {
subtitle = resources.getQuantityString(R.plurals.shortcuts_count,
entry.shortcutsCount, entry.shortcutsCount);
@@ -182,6 +194,32 @@
mSubtitle.setVisibility(VISIBLE);
}
+ /** Apply app icon, labels and tag using a generic {@link WidgetsListSearchHeaderEntry}. */
+ @UiThread
+ public void applyFromItemInfoWithIcon(WidgetsListSearchHeaderEntry entry) {
+ applyIconAndLabel(entry);
+ }
+
+ @UiThread
+ private void applyIconAndLabel(WidgetsListSearchHeaderEntry entry) {
+ PackageItemInfo info = entry.mPkgItem;
+ setIcon(info);
+ setTitles(entry);
+ setExpanded(entry.isWidgetListShown());
+
+ super.setTag(info);
+
+ verifyHighRes();
+ }
+
+ private void setTitles(WidgetsListSearchHeaderEntry entry) {
+ mTitle.setText(entry.mPkgItem.title);
+
+ mSubtitle.setText(entry.mWidgets.stream()
+ .map(item -> item.label).sorted().collect(Collectors.joining(", ")));
+ mSubtitle.setVisibility(VISIBLE);
+ }
+
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) {
if (getTag() == info) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index ed53e6f..e57f4d8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -20,6 +20,7 @@
import com.android.launcher3.R;
import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
/**
@@ -29,11 +30,14 @@
ViewHolderBinder<WidgetsListHeaderEntry, WidgetsListHeaderHolder> {
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
+ private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
- OnHeaderClickListener onHeaderClickListener) {
+ OnHeaderClickListener onHeaderClickListener,
+ WidgetsListAdapter listAdapter) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
+ mWidgetsListAdapter = listAdapter;
}
@Override
@@ -45,17 +49,24 @@
}
@Override
- public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data) {
+ public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
+ int position) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ if (mWidgetsListAdapter.getItemCount() == 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_single_item_ripple);
+ } else if (position == 0) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
+ } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+ } else {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+ }
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
- mOnHeaderClickListener.onHeaderClicked(isExpanded, data.mPkgItem.packageName));
- }
-
- /** A listener to be invoked when {@link WidgetsListHeader} is clicked. */
- public interface OnHeaderClickListener {
- /** Calls when {@link WidgetsListHeader} is clicked to show / hide widgets for a package. */
- void onHeaderClicked(boolean showWidgets, String packageName);
+ mOnHeaderClickListener.onHeaderClicked(
+ isExpanded,
+ new PackageUserKey(data.mPkgItem.packageName, data.mPkgItem.user)
+ ));
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
new file mode 100644
index 0000000..9562af3
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * A {@link ViewHolder} for {@link WidgetsListHeader} of an app, which renders the app icon, the app
+ * name, label and a button for showing / hiding widgets.
+ */
+public final class WidgetsListSearchHeaderHolder extends ViewHolder {
+ final WidgetsListHeader mWidgetsListHeader;
+
+ public WidgetsListSearchHeaderHolder(WidgetsListHeader view) {
+ super(view);
+
+ mWidgetsListHeader = view;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
new file mode 100644
index 0000000..b98f5e1
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.launcher3.R;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+/**
+ * Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
+ */
+public final class WidgetsListSearchHeaderViewHolderBinder implements
+ ViewHolderBinder<WidgetsListSearchHeaderEntry, WidgetsListSearchHeaderHolder> {
+ private final LayoutInflater mLayoutInflater;
+ private final OnHeaderClickListener mOnHeaderClickListener;
+ private final WidgetsListAdapter mWidgetsListAdapter;
+
+ public WidgetsListSearchHeaderViewHolderBinder(LayoutInflater layoutInflater,
+ OnHeaderClickListener onHeaderClickListener,
+ WidgetsListAdapter listAdapter) {
+ mLayoutInflater = layoutInflater;
+ mOnHeaderClickListener = onHeaderClickListener;
+ mWidgetsListAdapter = listAdapter;
+ }
+
+ @Override
+ public WidgetsListSearchHeaderHolder newViewHolder(ViewGroup parent) {
+ WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
+ R.layout.widgets_list_row_header, parent, false);
+
+ return new WidgetsListSearchHeaderHolder(header);
+ }
+
+ @Override
+ public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
+ WidgetsListSearchHeaderEntry data, int position) {
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ if (mWidgetsListAdapter.getItemCount() == 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_single_item_ripple);
+ } else if (position == 0) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
+ } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+ } else {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+ }
+ widgetsListHeader.applyFromItemInfoWithIcon(data);
+ widgetsListHeader.setExpanded(data.isWidgetListShown());
+ widgetsListHeader.setOnExpandChangeListener(isExpanded ->
+ mOnHeaderClickListener.onHeaderClicked(isExpanded,
+ new PackageUserKey(data.mPkgItem.packageName, data.mPkgItem.user)));
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 47fa71a..1524ab3 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -31,7 +31,6 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetImageView;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.util.WidgetsTableUtils;
@@ -52,6 +51,7 @@
private final OnClickListener mIconClickListener;
private final OnLongClickListener mIconLongClickListener;
private final WidgetPreviewLoader mWidgetPreviewLoader;
+ private final WidgetsListAdapter mWidgetsListAdapter;
private boolean mApplyBitmapDeferred = false;
public WidgetsListTableViewHolderBinder(
@@ -59,12 +59,14 @@
LayoutInflater layoutInflater,
OnClickListener iconClickListener,
OnLongClickListener iconLongClickListener,
- WidgetPreviewLoader widgetPreviewLoader) {
+ WidgetPreviewLoader widgetPreviewLoader,
+ WidgetsListAdapter listAdapter) {
mLayoutInflater = layoutInflater;
mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
mWidgetPreviewLoader = widgetPreviewLoader;
+ mWidgetsListAdapter = listAdapter;
}
/**
@@ -97,13 +99,22 @@
}
@Override
- public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry) {
+ public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
+ int position) {
TableLayout table = holder.mTableContainer;
if (DEBUG) {
Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
entry.mWidgets.size(), table.getChildCount()));
}
+ if (position == mWidgetsListAdapter.getItemCount() - 1) {
+ table.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+ } else {
+ // WidgetsListContentEntry is never shown in position 0. There must be a header above
+ // it.
+ table.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+ }
+
List<ArrayList<WidgetItem>> widgetItemsTable =
WidgetsTableUtils.groupWidgetItemsIntoTable(entry.mWidgets, mMaxSpansPerRow);
recycleTableBeforeBinding(table, widgetItemsTable);
@@ -146,7 +157,7 @@
tableRow = (TableRow) table.getChildAt(i);
} else {
tableRow = new TableRow(table.getContext());
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
+ tableRow.setGravity(Gravity.TOP);
table.addView(tableRow);
}
if (tableRow.getChildCount() > widgetItems.size()) {
@@ -158,7 +169,7 @@
WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
R.layout.widget_cell, tableRow, false);
// set up touch.
- WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+ View preview = widget.findViewById(R.id.widget_preview_container);
preview.setOnClickListener(mIconClickListener);
preview.setOnLongClickListener(mIconLongClickListener);
tableRow.addView(widget);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
new file mode 100644
index 0000000..b95bb16
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.widget.WidgetCell;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A {@link TableLayout} for showing recommended widgets. */
+public final class WidgetsRecommendationTableLayout extends TableLayout {
+ private static final String TAG = "WidgetsRecommendationTableLayout";
+ private static final float DOWN_SCALE_RATIO = 0.9f;
+ private static final float MAX_DOWN_SCALE_RATIO = 0.5f;
+ private final DeviceProfile mDeviceProfile;
+ private final float mWidgetCellTextViewsHeight;
+
+ private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
+ @Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
+ @Nullable private OnClickListener mWidgetCellOnClickListener;
+ @Nullable private OnTouchListener mWidgetCellOnTouchListener;
+
+ public WidgetsRecommendationTableLayout(Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+ // There are 1 row for title, 1 row for dimension and 2 rows for description.
+ mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
+ }
+
+ /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
+ public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
+ mWidgetCellOnLongClickListener = onLongClickListener;
+ }
+
+ /** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */
+ public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) {
+ mWidgetCellOnClickListener = widgetCellOnClickListener;
+ }
+
+ /** Sets a {@link android.view.View.OnTouchListener} for all widget cells in this table. */
+ public void setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener) {
+ mWidgetCellOnTouchListener = widgetCellOnTouchListener;
+ }
+
+ /**
+ * Sets a list of recommended widgets that would like to be displayed in this table within the
+ * desired {@code recommendationTableMaxHeight}.
+ *
+ * <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
+ * last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
+ * row still doesn't fit, we scale down the preview image.
+ */
+ public void setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+ float recommendationTableMaxHeight) {
+ mRecommendationTableMaxHeight = recommendationTableMaxHeight;
+ RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
+ recommendedWidgets);
+ bindData(data);
+ }
+
+ private void bindData(RecommendationTableData data) {
+ if (data.mRecommendationTable.size() == 0) {
+ setVisibility(GONE);
+ return;
+ }
+
+ removeAllViews();
+
+ for (int i = 0; i < data.mRecommendationTable.size(); i++) {
+ List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
+ TableRow tableRow = new TableRow(getContext());
+ tableRow.setGravity(Gravity.TOP);
+
+ for (WidgetItem widgetItem : widgetItems) {
+ WidgetCell widgetCell = addItemCell(tableRow);
+ widgetCell.setPreviewSize(widgetItem.spanX, widgetItem.spanY, data.mPreviewScale);
+ widgetCell.applyFromCellItem(widgetItem,
+ LauncherAppState.getInstance(getContext()).getWidgetCache());
+ widgetCell.ensurePreview();
+ }
+ addView(tableRow);
+ }
+ setVisibility(VISIBLE);
+ }
+
+ private WidgetCell addItemCell(ViewGroup parent) {
+ WidgetCell widget = (WidgetCell) LayoutInflater.from(
+ getContext()).inflate(R.layout.widget_cell, parent, false);
+
+ widget.setOnTouchListener(mWidgetCellOnTouchListener);
+ View previewContainer = widget.findViewById(R.id.widget_preview_container);
+ previewContainer.setOnClickListener(mWidgetCellOnClickListener);
+ previewContainer.setOnLongClickListener(mWidgetCellOnLongClickListener);
+ widget.setAnimatePreview(false);
+
+ parent.addView(widget);
+ return widget;
+ }
+
+ private RecommendationTableData fitRecommendedWidgetsToTableSpace(
+ float previewScale,
+ List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
+ if (previewScale < MAX_DOWN_SCALE_RATIO) {
+ Log.w(TAG, "Hide recommended widgets. Can't down scale previews to " + previewScale);
+ return new RecommendationTableData(List.of(), previewScale);
+ }
+ // A naive estimation of the widgets recommendation table height without inflation.
+ float totalHeight = 0;
+ for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
+ List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
+ float rowHeight = 0;
+ for (int j = 0; j < widgetItems.size(); j++) {
+ float previewHeight = widgetItems.get(j).spanY * mDeviceProfile.allAppsCellHeightPx
+ * previewScale;
+ rowHeight = Math.max(rowHeight, previewHeight + mWidgetCellTextViewsHeight);
+ }
+ totalHeight += rowHeight;
+ }
+
+ if (totalHeight < mRecommendationTableMaxHeight) {
+ return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
+ }
+
+ if (recommendedWidgetsInTable.size() > 1) {
+ // We don't want to scale down widgets preview unless we really need to. Reduce the
+ // num of row by 1 to see if it fits.
+ return fitRecommendedWidgetsToTableSpace(
+ previewScale,
+ recommendedWidgetsInTable.subList(/* fromIndex= */0,
+ /* toIndex= */recommendedWidgetsInTable.size() - 1));
+ }
+
+ float nextPreviewScale = previewScale * DOWN_SCALE_RATIO;
+ return fitRecommendedWidgetsToTableSpace(nextPreviewScale, recommendedWidgetsInTable);
+ }
+
+ /** Data class for the widgets recommendation table and widgets preview scaling. */
+ private class RecommendationTableData {
+ private final List<ArrayList<WidgetItem>> mRecommendationTable;
+ private final float mPreviewScale;
+
+ RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
+ float previewScale) {
+ mRecommendationTable = recommendationTable;
+ mPreviewScale = previewScale;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index d65a809..b016b4f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -21,13 +21,20 @@
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.widget.TableLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
/**
* The widgets recycler view.
@@ -39,8 +46,10 @@
private final int mScrollbarTop;
private final Point mFastScrollerOffset = new Point();
+ private final int mEstimatedWidgetListHeaderHeight;
private boolean mTouchDownOnScroller;
private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
+ private int mLastVisibleWidgetContentTableHeight = 0;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -55,6 +64,12 @@
super(context, attrs, defStyleAttr);
mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
addOnItemTouchListener(this);
+
+ ActivityContext activity = ActivityContext.lookupContext(getContext());
+ DeviceProfile grid = activity.getDeviceProfile();
+ mEstimatedWidgetListHeaderHeight = grid.iconSizePx
+ + 2 * context.getResources().getDimensionPixelSize(
+ R.dimen.widget_list_header_view_vertical_padding);
}
@Override
@@ -123,21 +138,32 @@
View child = getChildAt(0);
int rowIndex = getChildPosition(child);
- int y = (child.getMeasuredHeight() * rowIndex);
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ if (view instanceof TableLayout) {
+ // This assumes there is ever only one content shown in this recycler view.
+ mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
+ }
+ }
+
+ int scrollPosition = getItemsHeight(rowIndex);
int offset = getLayoutManager().getDecoratedTop(child);
- return getPaddingTop() + y - offset;
+ return getPaddingTop() + scrollPosition - offset;
}
/**
- * Returns the available scroll height:
- * AvailableScrollHeight = Total height of the all items - last page height
+ * Returns the available scroll height, in pixel.
+ *
+ * <p>If the recycler view can't be scrolled, returns 0.
*/
@Override
protected int getAvailableScrollHeight() {
- View child = getChildAt(0);
- return child.getMeasuredHeight() * mAdapter.getItemCount() + getScrollBarTop()
- + getPaddingBottom() - mScrollbar.getHeight();
+ // AvailableScrollHeight = Total height of the all items - first page height
+ int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+ int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ mAdapter.getItemCount());
+ int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
+ return Math.max(0, availableScrollHeight);
}
private boolean isModelNotReady() {
@@ -181,6 +207,32 @@
}
/**
+ * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
+ * {@code untilIndex}.
+ *
+ * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+ * sum of all items' height.
+ */
+ private int getItemsHeight(int untilIndex) {
+ if (untilIndex > mAdapter.getItems().size()) {
+ untilIndex = mAdapter.getItems().size();
+ }
+ int totalItemsHeight = 0;
+ for (int i = 0; i < untilIndex; i++) {
+ WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
+ if (entry instanceof WidgetsListHeaderEntry
+ || entry instanceof WidgetsListSearchHeaderEntry) {
+ totalItemsHeight += mEstimatedWidgetListHeaderHeight;
+ } else if (entry instanceof WidgetsListContentEntry) {
+ totalItemsHeight += mLastVisibleWidgetContentTableHeight;
+ } else {
+ throw new UnsupportedOperationException("Can't estimate height for " + entry);
+ }
+ }
+ return totalItemsHeight;
+ }
+
+ /**
* Provides dimensions of the header view that is shown at the top of a
* {@link WidgetsRecyclerView}.
*/
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
new file mode 100644
index 0000000..42f1bb2
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.R;
+import com.android.launcher3.popup.PopupDataProvider;
+
+/**
+ * View for a search bar with an edit text with a cancel button.
+ */
+public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSearchBar {
+ private WidgetsSearchBarController mController;
+ private ExtendedEditText mEditText;
+ private ImageButton mCancelButton;
+
+ public LauncherWidgetsSearchBar(Context context) {
+ this(context, null, 0);
+ }
+
+ public LauncherWidgetsSearchBar(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LauncherWidgetsSearchBar(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener) {
+ mController = new WidgetsSearchBarController(
+ new SimpleWidgetsSearchAlgorithm(dataProvider),
+ mEditText, mCancelButton, searchModeListener);
+ }
+
+ @Override
+ public void reset() {
+ mController.clearSearchResult();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mEditText = findViewById(R.id.widgets_search_bar_edit_text);
+ mCancelButton = findViewById(R.id.widgets_search_cancel_button);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mController.onDestroy();
+ }
+
+ @Override
+ public void clearSearchBarFocus() {
+ mController.clearFocus();
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
similarity index 62%
rename from src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
rename to src/com/android/launcher3/widget/picker/search/SearchModeListener.java
index d12782c..cee7d67 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
+++ b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
@@ -13,32 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3.widget.picker.search;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.List;
-import java.util.function.Consumer;
/**
- * An interface for a pipeline to handle widgets search.
+ * A listener to help with widgets picker search.
*/
-public interface WidgetsPickerSearchPipeline {
+public interface SearchModeListener {
+ /**
+ * Notifies the subscriber when user enters widget picker search mode.
+ */
+ void enterSearchMode();
/**
- * Performs a search query asynchronically. Invokes {@code callback} when the search is
- * complete.
+ * Notifies the subscriber when user exits widget picker search mode.
*/
- void query(String input, Consumer<List<WidgetsListBaseEntry>> callback);
+ void exitSearchMode();
/**
- * Cancels any ongoing search request.
+ * Notifies the subscriber with search results.
*/
- default void cancel() {};
-
- /**
- * Cleans up after search is no longer needed.
- */
- default void destroy() {};
+ void onSearchResults(List<WidgetsListBaseEntry> entries);
}
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
new file mode 100644
index 0000000..9be3b5f
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static com.android.launcher3.search.StringMatcherUtility.matches;
+
+import android.os.Handler;
+
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of {@link SearchAlgorithm} that posts a task to query on the main thread.
+ */
+public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<WidgetsListBaseEntry> {
+
+ private final Handler mResultHandler;
+ private final PopupDataProvider mDataProvider;
+
+ public SimpleWidgetsSearchAlgorithm(PopupDataProvider dataProvider) {
+ mResultHandler = new Handler();
+ mDataProvider = dataProvider;
+ }
+
+ @Override
+ public void doSearch(String query, SearchCallback<WidgetsListBaseEntry> callback) {
+ ArrayList<WidgetsListBaseEntry> result = getFilteredWidgets(mDataProvider, query);
+ mResultHandler.post(() -> callback.onSearchResult(query, result));
+ }
+
+ @Override
+ public void cancel(boolean interruptActiveRequests) {
+ if (interruptActiveRequests) {
+ mResultHandler.removeCallbacksAndMessages(/*token= */null);
+ }
+ }
+
+ /**
+ * Returns entries for all matched widgets
+ */
+ public static ArrayList<WidgetsListBaseEntry> getFilteredWidgets(
+ PopupDataProvider dataProvider, String input) {
+ ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
+ dataProvider.getAllWidgets().stream()
+ .filter(entry -> entry instanceof WidgetsListHeaderEntry)
+ .forEach(headerEntry -> {
+ List<WidgetItem> matchedWidgetItems = filterWidgetItems(
+ input, headerEntry.mPkgItem.title.toString(), headerEntry.mWidgets);
+ if (matchedWidgetItems.size() > 0) {
+ results.add(new WidgetsListSearchHeaderEntry(headerEntry.mPkgItem,
+ headerEntry.mTitleSectionName, matchedWidgetItems));
+ results.add(new WidgetsListContentEntry(headerEntry.mPkgItem,
+ headerEntry.mTitleSectionName, matchedWidgetItems));
+ }
+ });
+ return results;
+ }
+
+ private static List<WidgetItem> filterWidgetItems(String query, String packageTitle,
+ List<WidgetItem> items) {
+ StringMatcher matcher = StringMatcher.getInstance();
+ if (matches(query, packageTitle, matcher)) {
+ return items;
+ }
+ return items.stream()
+ .filter(item -> matches(query, item.label, matcher))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
new file mode 100644
index 0000000..0ac47ce
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import com.android.launcher3.popup.PopupDataProvider;
+
+/**
+ * Interface for a widgets picker search bar.
+ */
+public interface WidgetsSearchBar {
+ /**
+ * Attaches a controller to the search bar which interacts with {@code searchModeListener}.
+ */
+ void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener);
+
+ /**
+ * Clears search bar.
+ */
+ void reset();
+
+ /**
+ * Clears focus from search bar.
+ */
+ void clearSearchBarFocus();
+
+ /**
+ * Sets the vertical location, in pixels, of this search bar relative to its top position.
+ */
+ void setTranslationY(float translationY);
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
new file mode 100644
index 0000000..d35a75b
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Controller for a search bar with an edit text and a cancel button.
+ */
+public class WidgetsSearchBarController implements TextWatcher,
+ SearchCallback<WidgetsListBaseEntry>, ExtendedEditText.OnBackKeyListener,
+ View.OnKeyListener {
+ private static final String TAG = "WidgetsSearchBarController";
+ private static final boolean DEBUG = false;
+
+ protected SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
+ protected ExtendedEditText mInput;
+ protected ImageButton mCancelButton;
+ protected SearchModeListener mSearchModeListener;
+ protected String mQuery;
+
+ public WidgetsSearchBarController(
+ SearchAlgorithm<WidgetsListBaseEntry> algo, ExtendedEditText editText,
+ ImageButton cancelButton, SearchModeListener searchModeListener) {
+ mSearchAlgorithm = algo;
+ mInput = editText;
+ mInput.addTextChangedListener(this);
+ mInput.setOnBackKeyListener(this);
+ mInput.setOnKeyListener(this);
+ mCancelButton = cancelButton;
+ mCancelButton.setOnClickListener(v -> clearSearchResult());
+ mSearchModeListener = searchModeListener;
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ mQuery = s.toString();
+ if (mQuery.isEmpty()) {
+ mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
+ mSearchModeListener.exitSearchMode();
+ mCancelButton.setVisibility(GONE);
+ } else {
+ mSearchAlgorithm.cancel(/* interruptActiveRequests= */ false);
+ mSearchModeListener.enterSearchMode();
+ mSearchAlgorithm.doSearch(mQuery, this);
+ mCancelButton.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onSearchResult(String query, ArrayList<WidgetsListBaseEntry> items) {
+ if (DEBUG) {
+ Log.d(TAG, "onSearchResult query: " + query + " items: " + items);
+ }
+ mSearchModeListener.onSearchResults(items);
+ }
+
+ @Override
+ public void onAppendSearchResult(String query, ArrayList<WidgetsListBaseEntry> items) {
+ // Not needed.
+ }
+
+ @Override
+ public void clearSearchResult() {
+ mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
+ mInput.getText().clear();
+ clearFocus();
+ mSearchModeListener.exitSearchMode();
+ }
+
+ /**
+ * Cleans up after search is no longer needed.
+ */
+ public void onDestroy() {
+ mSearchAlgorithm.destroy();
+ }
+
+ @Override
+ public boolean onBackKey() {
+ clearFocus();
+ return true;
+ }
+
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
+ clearFocus();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Clears focus from edit text.
+ */
+ public void clearFocus() {
+ mInput.clearFocus();
+ mInput.hideKeyboard();
+ }
+}
diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
similarity index 66%
copy from src/com/android/launcher3/util/SafeCloseable.java
copy to src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
index ba8ee04..edfdc65 100644
--- a/src/com/android/launcher3/util/SafeCloseable.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.launcher3.widget.picker.search;
/**
- * Extension of closeable which does not throw an exception
+ * UI helper for {@link WidgetsSearchBar}.
*/
-public interface SafeCloseable extends AutoCloseable {
-
- @Override
- void close();
+public interface WidgetsSearchBarUIHelper {
+ /**
+ * Clears focus from the search bar.
+ */
+ void clearSearchBarFocus();
}
diff --git a/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
deleted file mode 100644
index f8a9a04..0000000
--- a/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.plugins;
-
-import android.os.Parcelable;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-import java.util.List;
-
-/**
- * Interface to provide SmartspaceTargets to BcSmartspace.
- */
-@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION)
-public interface BcSmartspaceDataPlugin extends Plugin {
- String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
- int VERSION = 1;
-
- /** Register a listener to get Smartspace data. */
- void registerListener(SmartspaceTargetListener listener);
-
- /** Unregister a listener. */
- void unregisterListener(SmartspaceTargetListener listener);
-
- /** Provides Smartspace data to registered listeners. */
- interface SmartspaceTargetListener {
- /** Each Parcelable is a SmartspaceTarget that represents a card. */
- void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
- }
-}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 3ea4766..f82f2cc 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -132,7 +132,7 @@
widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
updatedItems.add(info);
}
- setWidgetsAndShortcuts(widgetsAndShortcuts, app);
+ setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
} catch (Exception e) {
if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) {
// the returned value may be incomplete and will not be refreshed until the next
@@ -149,7 +149,7 @@
}
private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
- LauncherAppState app) {
+ LauncherAppState app, @Nullable PackageUserKey packageUser) {
if (DEBUG) {
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}
@@ -158,8 +158,12 @@
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
HashMap<PackageUserKey, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
- // clear the lists.
- mWidgetsList.clear();
+ // Clear the lists only if this is an update on all widgets and shortcuts. If packageUser
+ // isn't null, only updates the shortcuts and widgets for the app represented in
+ // packageUser.
+ if (packageUser == null) {
+ mWidgetsList.clear();
+ }
// add and update.
mWidgetsList.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index a4e53a1..53748b7 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -56,7 +56,7 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return ALL_APPS_HEADER | ALL_APPS_CONTENT;
+ return ALL_APPS_CONTENT;
}
@Override
@@ -74,4 +74,9 @@
public float getVerticalProgress(Launcher launcher) {
return 0f;
}
+
+ @Override
+ public float getWorkspaceScrimAlpha(Launcher launcher) {
+ return 1;
+ }
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index da5a94f..e85e505 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -49,4 +49,11 @@
public static OverviewState newModalTaskState(int id) {
return new OverviewState(id);
}
+
+ /**
+ * New Overview substate that represents the overview in modal mode (one task shown on its own)
+ */
+ public static OverviewState newSplitSelectState(int id) {
+ return new OverviewState(id);
+ }
}
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 0000000..da55c28
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
+filegroup {
+ name: "launcher3-test-src-common",
+ srcs: ["src_common/**/*.java"],
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 744dee0..7f6c8f8 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -60,6 +60,17 @@
android:resource="@xml/appwidget_with_config"/>
</receiver>
+ <receiver
+ android:name="com.android.launcher3.testcomponent.AppWidgetDynamicColors"
+ android:exported="true"
+ android:label="Dynamic Colors">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_dynamic_colors"/>
+ </receiver>
+
<activity
android:name="com.android.launcher3.testcomponent.WidgetConfigActivity"
android:exported="true">
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
new file mode 100644
index 0000000..3fff622
--- /dev/null
+++ b/tests/Launcher3Tests.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs Launcher3 tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="set-test-harness" value="true" />
+ <option name="run-command" value="am force-stop com.android.launcher3" />
+ <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher" />
+ <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.out_of_proc_tests" />
+ <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.tests" />
+ <option name="run-command" value="pm disable com.google.android.googlequicksearchbox" />
+
+ <option name="run-command" value="input keyevent 82" />
+ <option name="run-command" value="settings delete secure assistant" />
+ <option name="run-command" value="settings put global airplane_mode_on 1" />
+ <option name="run-command" value="am broadcast -a android.intent.action.AIRPLANE_MODE" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="Launcher3Tests.apk" />
+ <option name="test-file-name" value="Launcher3.apk" />
+ </target_preparer>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.launcher3/files" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.launcher3.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/res/layout/test_layout_appwidget_dynamic_colors.xml b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..56c343e
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="?android:attr/colorBackground"
+ android:padding="8dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation = "horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="neut1"/>
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:background="@android:color/system_neutral1_500"/>
+ </LinearLayout>
+ <LinearLayout
+ android:orientation = "horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="accent1"/>
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:background="@android:color/system_accent1_500"/>
+ </LinearLayout>
+ <LinearLayout
+ android:orientation = "horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="accent2"/>
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:background="@android:color/system_accent2_500"/>
+ </LinearLayout>
+
+ </LinearLayout>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_dynamic_colors.xml b/tests/res/xml/appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..f6b9a04
--- /dev/null
+++ b/tests/res/xml/appwidget_dynamic_colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="1dp"
+ android:minHeight="1dp"
+ android:updatePeriodMillis="0"
+ android:initialLayout="@layout/test_layout_appwidget_dynamic_colors"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
deleted file mode 100644
index 39709a9..0000000
--- a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.model.data.AppInfo;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link DefaultAppSearchAlgorithm}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DefaultAppSearchAlgorithmTest {
- private static final DefaultAppSearchAlgorithm.StringMatcher MATCHER =
- DefaultAppSearchAlgorithm.StringMatcher.getInstance();
-
- @Test
- public void testMatches() {
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("white cow"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whiteCow"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whiteCOW"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecowCOW"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("white2cow"), "cow", MATCHER));
-
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitecow"), "cow", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitEcow"), "cow", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecowCow"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecow cow"), "cow", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitecowcow"), "cow", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whit ecowcow"), "cow", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&dogs"), "dog", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&Dogs"), "dog", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&Dogs"), "&", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("2+43"), "43", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("2+43"), "3", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("Q"), "q", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo(" Q"), "q", MATCHER));
-
- // match lower case words
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("elephant"), "e", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "电", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "电子", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "子", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "邮件", MATCHER));
-
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("Bot"), "ba", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("bot"), "ba", MATCHER));
- }
-
- @Test
- public void testMatchesVN() {
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드"), "다", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("드라이브"), "드", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷ", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("운로 드라이브"), "ㄷ", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("abc"), "åbç", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("Alpha"), "ål", MATCHER));
-
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷㄷ", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("로드라이브"), "ㄷ", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("abc"), "åç", MATCHER));
- }
-
- private AppInfo getInfo(String title) {
- AppInfo info = new AppInfo();
- info.title = title;
- info.componentName = new ComponentName("Test", title);
- return info;
- }
-}
diff --git a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
new file mode 100644
index 0000000..413f404
--- /dev/null
+++ b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.search;
+
+import static com.android.launcher3.search.StringMatcherUtility.matches;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link StringMatcherUtility}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StringMatcherUtilityTest {
+ private static final StringMatcher MATCHER =
+ StringMatcher.getInstance();
+
+ @Test
+ public void testMatches() {
+ assertTrue(matches("white ", "white cow", MATCHER));
+ assertTrue(matches("white c", "white cow", MATCHER));
+ assertTrue(matches("cow", "white cow", MATCHER));
+ assertTrue(matches("cow", "whiteCow", MATCHER));
+ assertTrue(matches("cow", "whiteCOW", MATCHER));
+ assertTrue(matches("cow", "whitecowCOW", MATCHER));
+ assertTrue(matches("cow", "white2cow", MATCHER));
+
+ assertFalse(matches("cow", "whitecow", MATCHER));
+ assertFalse(matches("cow", "whitEcow", MATCHER));
+
+ assertTrue(matches("cow", "whitecowCow", MATCHER));
+ assertTrue(matches("cow", "whitecow cow", MATCHER));
+ assertFalse(matches("cow", "whitecowcow", MATCHER));
+ assertFalse(matches("cow", "whit ecowcow", MATCHER));
+
+ assertTrue(matches("dog", "cats&dogs", MATCHER));
+ assertTrue(matches("dog", "cats&Dogs", MATCHER));
+ assertTrue(matches("&", "cats&Dogs", MATCHER));
+
+ assertTrue(matches("43", "2+43", MATCHER));
+ assertFalse(matches("3", "2+43", MATCHER));
+
+ assertTrue(matches("q", "Q", MATCHER));
+ assertTrue(matches("q", " Q", MATCHER));
+
+ // match lower case words
+ assertTrue(matches("e", "elephant", MATCHER));
+ assertTrue(matches("eL", "Elephant", MATCHER));
+
+ assertTrue(matches("电", "电子邮件", MATCHER));
+ assertTrue(matches("电子", "电子邮件", MATCHER));
+ assertTrue(matches("子", "电子邮件", MATCHER));
+ assertTrue(matches("邮件", "电子邮件", MATCHER));
+
+ assertFalse(matches("ba", "Bot", MATCHER));
+ assertFalse(matches("ba", "bot", MATCHER));
+ assertFalse(matches("phant", "elephant", MATCHER));
+ assertFalse(matches("elephants", "elephant", MATCHER));
+ }
+
+ @Test
+ public void testMatchesVN() {
+ assertTrue(matches("다", "다운로드", MATCHER));
+ assertTrue(matches("드", "드라이브", MATCHER));
+ assertTrue(matches("ㄷ", "다운로드 드라이브", MATCHER));
+ assertTrue(matches("ㄷ", "운로 드라이브", MATCHER));
+ assertTrue(matches("åbç", "abc", MATCHER));
+ assertTrue(matches("ål", "Alpha", MATCHER));
+
+ assertFalse(matches("ㄷㄷ", "다운로드 드라이브", MATCHER));
+ assertFalse(matches("ㄷ", "로드라이브", MATCHER));
+ assertFalse(matches("åç", "abc", MATCHER));
+ }
+}
diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
similarity index 66%
copy from src/com/android/launcher3/util/SafeCloseable.java
copy to tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
index ba8ee04..5fb3454 100644
--- a/src/com/android/launcher3/util/SafeCloseable.java
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.launcher3.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
/**
- * Extension of closeable which does not throw an exception
+ * A simple app widget showing a primary, secondary and neutral color.
*/
-public interface SafeCloseable extends AutoCloseable {
-
- @Override
- void close();
+public class AppWidgetDynamicColors extends AppWidgetProvider {
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 0edfbed..d4e8f1f 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -503,6 +503,7 @@
// Destroy Launcher activity.
executeOnLauncher(launcher -> {
if (launcher != null) {
+ onLauncherActivityClose(launcher);
launcher.finish();
}
});
@@ -511,7 +512,7 @@
}
protected boolean isInBackground(Launcher launcher) {
- return !launcher.hasBeenResumed();
+ return launcher == null || !launcher.hasBeenResumed();
}
protected boolean isInState(Supplier<LauncherState> state) {
@@ -524,7 +525,7 @@
return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
}
- private static void checkLauncherIntegrity(
+ private void checkLauncherIntegrity(
Launcher launcher, ContainerType expectedContainerType) {
if (launcher != null) {
final StateManager<LauncherState> stateManager = launcher.getStateManager();
@@ -535,10 +536,8 @@
stableState == stateManager.getState());
final boolean isResumed = launcher.hasBeenResumed();
- assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed,
- isResumed == launcher.isStarted());
- assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed,
- isResumed == launcher.isUserActive());
+ final boolean isStarted = launcher.isStarted();
+ checkLauncherState(launcher, expectedContainerType, isResumed, isStarted);
final int ordinal = stableState.ordinal;
@@ -561,8 +560,7 @@
break;
}
case OVERVIEW: {
- assertTrue(
- "Launcher is not resumed in state: " + expectedContainerType,
+ checkLauncherStateInOverview(launcher, expectedContainerType, isStarted,
isResumed);
assertTrue(TestProtocol.stateOrdinalToString(ordinal),
ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
@@ -587,4 +585,20 @@
expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
}
}
+
+ protected void checkLauncherState(Launcher launcher, ContainerType expectedContainerType,
+ boolean isResumed, boolean isStarted) {
+ assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed,
+ isResumed == isStarted);
+ assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed,
+ isResumed == launcher.isUserActive());
+ }
+
+ protected void checkLauncherStateInOverview(Launcher launcher,
+ ContainerType expectedContainerType, boolean isStarted, boolean isResumed) {
+ assertTrue("Launcher is not resumed in state: " + expectedContainerType,
+ isResumed);
+ }
+
+ protected void onLauncherActivityClose(Launcher launcher) { }
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4c9a8e7..6f47df0 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -334,20 +334,31 @@
// 1. Open all apps and wait for load complete.
// 2. Find the app and long press it to show shortcuts.
// 3. Press icon center until shortcuts appear
- final AllApps allApps = mLauncher.
- getWorkspace().
- switchToAllApps();
+ final AllApps allApps = mLauncher
+ .getWorkspace()
+ .switchToAllApps();
allApps.freeze();
try {
- final AppIconMenuItem menuItem = allApps.
- getAppIcon(APP_NAME).
- openMenu().
- getMenuItem(0);
- final String shortcutName = menuItem.getText();
- assertEquals("Wrong menu item", "Shortcut 3", shortcutName);
+ final AppIconMenu menu = allApps
+ .getAppIcon(APP_NAME)
+ .openMenu();
+ final AppIconMenuItem menuItem0 = menu.getMenuItem(0);
+ final AppIconMenuItem menuItem2 = menu.getMenuItem(2);
+
+ final AppIconMenuItem menuItem;
+
+ final String expectedShortcutName = "Shortcut 3";
+ if (menuItem0.getText().equals(expectedShortcutName)) {
+ menuItem = menuItem0;
+ } else {
+ final String shortcutName2 = menuItem2.getText();
+ assertEquals("Wrong menu item", expectedShortcutName, shortcutName2);
+ menuItem = menuItem2;
+ }
menuItem.dragToWorkspace(false, false);
- mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
+ mLauncher.getWorkspace().getWorkspaceAppIcon(expectedShortcutName)
+ .launch(getAppPackageName());
} finally {
allApps.unfreeze();
}
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src_disabled/WorkTabTest.java
similarity index 97%
rename from tests/src/com/android/launcher3/ui/WorkTabTest.java
rename to tests/src_disabled/WorkTabTest.java
index a6c7c03..bfacc74 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src_disabled/WorkTabTest.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.tapl.LauncherInstrumentation.LONG_WAIT_TIME_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -37,11 +36,13 @@
import com.android.launcher3.allapps.AllAppsPagedView;
import com.android.launcher3.allapps.WorkModeSwitch;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.WorkEduView;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -90,6 +91,7 @@
});
}
+ @Ignore("b/182844465")
@Test
public void workTabExists() {
mDevice.pressHome();
@@ -102,6 +104,7 @@
launcher -> launcher.getAppsView().isWorkTabVisible(), 60000);
}
+ @Ignore("b/182844465")
@Test
public void toggleWorks() {
mDevice.pressHome();
@@ -133,6 +136,7 @@
l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
}
+ @Ignore("b/182844465")
@Test
public void testWorkEduFlow() {
mDevice.pressHome();
@@ -147,7 +151,7 @@
return true;
}
return false;
- }, LONG_WAIT_TIME_MS);
+ }, LauncherInstrumentation.WAIT_TIME_MS);
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
WorkEduView workEduView = getEduView();
@@ -176,6 +180,7 @@
});
}
+ @Ignore("b/182844465")
@Test
public void testWorkEduIntermittent() {
mDevice.pressHome();
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index b6c17df..e32250e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -27,7 +27,6 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
-import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
import java.util.stream.Collectors;
@@ -108,26 +107,24 @@
"apps_list_view");
final UiObject2 searchBox = getSearchBox(allAppsContainer);
- int bottomGestureMargin = ResourceUtils.getNavbarSize(
- ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
- int deviceHeight = mLauncher.getDevice().getDisplayHeight();
- int displayBottom = deviceHeight - bottomGestureMargin;
+ int deviceHeight = mLauncher.getRealDisplaySize().y;
+ int bottomGestureStartOnScreen = mLauncher.getBottomGestureStartOnScreen();
final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
- displayBottom)) {
+ bottomGestureStartOnScreen)) {
scrollBackToBeginning();
int attempts = 0;
int scroll = getAllAppsScroll();
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
- displayBottom)) {
+ bottomGestureStartOnScreen)) {
mLauncher.scrollToLastVisibleRow(
allAppsContainer,
mLauncher.getObjectsInContainer(allAppsContainer, "icon")
.stream()
.filter(icon ->
- mLauncher.getVisibleBounds(icon).bottom
- <= displayBottom)
+ mLauncher.getVisibleBounds(icon).top
+ < bottomGestureStartOnScreen)
.collect(Collectors.toList()),
mLauncher.getVisibleBounds(searchBox).bottom
- mLauncher.getVisibleBounds(allAppsContainer).top);
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index d317783..4a666b1 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -122,7 +122,9 @@
endY = startY - swipeLength;
} else {
startX = getSwipeStartX();
- endX = startX - swipeLength;
+ // TODO(b/184059820) make horizontal swipe use swipe width not height, for the
+ // moment just double the swipe length.
+ endX = startX - swipeLength * 2;
startY = endY = mLauncher.getDevice().getDisplayHeight() / 2;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f279a82..475a4ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -52,6 +52,7 @@
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
@@ -155,8 +156,7 @@
private static final String OVERVIEW_RES_ID = "overview_panel";
private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
- public static final int WAIT_TIME_MS = 10000;
- public static final int LONG_WAIT_TIME_MS = 60000;
+ public static final int WAIT_TIME_MS = 60000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static final String ANDROID_PACKAGE = "android";
@@ -586,11 +586,7 @@
"but the current state is not " + containerType.name())) {
switch (containerType) {
case WORKSPACE: {
- if (mDevice.isNaturalOrientation()) {
- waitForLauncherObject(APPS_RES_ID);
- } else {
- waitUntilLauncherObjectGone(APPS_RES_ID);
- }
+ waitUntilLauncherObjectGone(APPS_RES_ID);
waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
return waitForLauncherObject(WORKSPACE_RES_ID);
@@ -656,7 +652,7 @@
try {
final AccessibilityEvent event =
mInstrumentation.getUiAutomation().executeAndWaitForEvent(
- command, eventFilter, LONG_WAIT_TIME_MS);
+ command, eventFilter, WAIT_TIME_MS);
assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
final Parcelable parcelableData = event.getParcelableData();
event.recycle();
@@ -872,6 +868,16 @@
return object;
}
+ @Nullable
+ UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
+ try {
+ return container.findObject(selector);
+ } catch (StaleObjectException e) {
+ fail("The container disappeared from screen");
+ return null;
+ }
+ }
+
@NonNull
List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
try {
@@ -1028,16 +1034,20 @@
expectedState);
}
- int getBottomGestureSize() {
+ private int getBottomGestureSize() {
return ResourceUtils.getNavbarSize(
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
}
int getBottomGestureMarginInContainer(UiObject2 container) {
- final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize();
+ final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen();
return getVisibleBounds(container).bottom - bottomGestureStartOnScreen;
}
+ int getBottomGestureStartOnScreen() {
+ return getRealDisplaySize().y - getBottomGestureSize();
+ }
+
void clickLauncherObject(UiObject2 object) {
expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_DOWN);
expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_UP);
@@ -1060,6 +1070,11 @@
final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
+ scrollDownByDistance(container, distance);
+ }
+
+ void scrollDownByDistance(UiObject2 container, int distance) {
+ final Rect containerRect = getVisibleBounds(container);
final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
scroll(
container,
@@ -1215,7 +1230,7 @@
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
assertTrue("injectInputEvent failed",
- mInstrumentation.getUiAutomation().injectInputEvent(event, true));
+ mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
event.recycle();
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 22f4d31..a3f9ade 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -17,8 +17,8 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
+import static com.android.launcher3.tapl.LauncherInstrumentation.log;
-import android.graphics.Point;
import android.graphics.Rect;
import androidx.test.uiautomator.By;
@@ -30,13 +30,13 @@
import com.android.launcher3.testing.TestProtocol;
import java.util.Collection;
-import java.util.List;
/**
* All widgets container.
*/
public final class Widgets extends LauncherInstrumentation.VisibleContainer {
private static final int FLING_STEPS = 10;
+ private static final int SCROLL_ATTEMPTS = 60;
Widgets(LauncherInstrumentation launcher) {
super(launcher);
@@ -50,7 +50,7 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to fling forward in widgets")) {
- LauncherInstrumentation.log("Widgets.flingForward enter");
+ log("Widgets.flingForward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
mLauncher.scroll(
widgetsContainer,
@@ -61,7 +61,7 @@
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
verifyActiveContainer();
}
- LauncherInstrumentation.log("Widgets.flingForward exit");
+ log("Widgets.flingForward exit");
}
}
@@ -72,7 +72,7 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to fling backwards in widgets")) {
- LauncherInstrumentation.log("Widgets.flingBackward enter");
+ log("Widgets.flingBackward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
mLauncher.scroll(
widgetsContainer,
@@ -82,7 +82,7 @@
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
verifyActiveContainer();
}
- LauncherInstrumentation.log("Widgets.flingBackward exit");
+ log("Widgets.flingBackward exit");
}
}
@@ -101,23 +101,27 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"getting widget " + labelText + " in widgets list")) {
+ final UiObject2 searchBar = findSearchBar();
+ final int searchBarHeight = searchBar.getVisibleBounds().height();
final UiObject2 fullWidgetsPicker = verifyActiveContainer();
mLauncher.assertTrue("Widgets container didn't become scrollable",
fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
- final Point displaySize = mLauncher.getRealDisplaySize();
final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
mLauncher.assertTrue("Can't locate widgets list for the test app: "
+ mLauncher.getLauncherPackageName(),
widgetsContainer != null);
final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+ final BySelector previewSelector = By.res(mLauncher.getLauncherPackageName(),
+ "widget_preview");
int i = 0;
for (; ; ) {
final Collection<UiObject2> tableRows = widgetsContainer.getChildren();
for (UiObject2 row : tableRows) {
final Collection<UiObject2> widgetCells = row.getChildren();
for (UiObject2 widget : widgetCells) {
- final UiObject2 label = widget.findObject(labelSelector);
+ final UiObject2 label = mLauncher.findObjectInContainer(widget,
+ labelSelector);
if (label == null) {
continue;
}
@@ -125,14 +129,15 @@
"View is not WidgetCell",
"com.android.launcher3.widget.WidgetCell",
widget.getClassName());
-
- return new Widget(mLauncher, widget);
+ UiObject2 preview = mLauncher.waitForObjectInContainer(widget,
+ previewSelector);
+ return new Widget(mLauncher, preview);
}
}
- mLauncher.assertTrue("Too many attempts", ++i <= 40);
+ mLauncher.assertTrue("Too many attempts", ++i <= SCROLL_ATTEMPTS);
final int scroll = getWidgetsScroll();
- mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, tableRows, 0);
+ mLauncher.scrollDownByDistance(fullWidgetsPicker, searchBarHeight);
final int newScroll = getWidgetsScroll();
mLauncher.assertTrue(
"Scrolled in a wrong direction in Widgets: from " + scroll + " to "
@@ -142,6 +147,18 @@
}
}
+ private UiObject2 findSearchBar() {
+ final BySelector searchBarContainerSelector = By.res(mLauncher.getLauncherPackageName(),
+ "search_and_recommendations_container");
+ final BySelector searchBarSelector = By.res(mLauncher.getLauncherPackageName(),
+ "widgets_search_bar");
+ final UiObject2 searchBarContainer = mLauncher.waitForLauncherObject(
+ searchBarContainerSelector);
+ UiObject2 searchBar = mLauncher.waitForObjectInContainer(searchBarContainer,
+ searchBarSelector);
+ return searchBar;
+ }
+
/** Finds the widgets list of this test app from the collapsed full widgets picker. */
private UiObject2 findTestAppWidgetsTableContainer() {
final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
@@ -152,38 +169,34 @@
"widgets_table");
boolean hasHeaderExpanded = false;
- for (int i = 0; i < 40; i++) {
+ for (int i = 0; i < SCROLL_ATTEMPTS; i++) {
UiObject2 fullWidgetsPicker = verifyActiveContainer();
- UiObject2 header = fullWidgetsPicker.findObject(headerSelector);
- mLauncher.assertTrue("Can't find a widget header", header != null);
+ UiObject2 header = mLauncher.waitForObjectInContainer(fullWidgetsPicker,
+ headerSelector);
+ int headerHeight = header.getVisibleBounds().height();
// Look for a header that has the test app name.
- UiObject2 headerTitle = fullWidgetsPicker.findObject(targetAppSelector);
+ UiObject2 headerTitle = mLauncher.findObjectInContainer(fullWidgetsPicker,
+ targetAppSelector);
if (headerTitle != null) {
// If we find the header and it has not been expanded, let's click it to see the
// widgets list.
if (!hasHeaderExpanded) {
+ log("Header has not been expanded. Click to expand.");
hasHeaderExpanded = true;
mLauncher.clickLauncherObject(headerTitle);
- // After clicking the header, the recyclerview has been updated. Let's refresh
- // the container UIObject2.
- fullWidgetsPicker = verifyActiveContainer();
- // Refresh headerTitle because the first instance is stale after
- // verifyActiveContainer call.
- headerTitle = fullWidgetsPicker.findObject(targetAppSelector);
}
// Look for a widgets list.
- UiObject2 widgetsContainer = fullWidgetsPicker.findObject(widgetsContainerSelector);
+ UiObject2 widgetsContainer = mLauncher.findObjectInContainer(fullWidgetsPicker,
+ widgetsContainerSelector);
if (widgetsContainer != null) {
+ log("Widgets container found.");
return widgetsContainer;
}
- mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, List.of(headerTitle), 0);
- } else {
- mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, fullWidgetsPicker.getChildren(),
- 0);
}
+ mLauncher.scrollDownByDistance(fullWidgetsPicker, headerHeight);
}
return null;