Merge "Waiting for the state of views to settle after orientation change" into sc-dev
diff --git a/Android.bp b/Android.bp
index a720658..cca48ce 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,6 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "packages_apps_Launcher3_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
android_library {
name: "launcher-aosp-tapl",
static_libs: [
@@ -24,7 +41,6 @@
],
srcs: [
"tests/tapl/**/*.java",
- "src/com/android/launcher3/util/SecureSettingsObserver.java",
"src/com/android/launcher3/ResourceUtils.java",
"src/com/android/launcher3/testing/TestProtocol.java",
],
diff --git a/Android.mk b/Android.mk
index 19ad328..304935b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -46,6 +46,9 @@
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 26
LOCAL_MODULE := Launcher3CommonDepsLib
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
LOCAL_PRIVILEGED_MODULE := true
LOCAL_MANIFEST_FILE := AndroidManifest-common.xml
@@ -137,6 +140,9 @@
LOCAL_MIN_SDK_VERSION := 26
endif
LOCAL_MODULE := Launcher3QuickStepLib
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
LOCAL_PRIVILEGED_MODULE := true
LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
@@ -145,7 +151,9 @@
$(call all-java-files-under, quickstep/src) \
$(call all-java-files-under, src_shortcuts_overrides)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/quickstep/res \
+ $(LOCAL_PATH)/quickstep/overview_ui_overrides/res
LOCAL_PROGUARD_ENABLED := disabled
@@ -174,7 +182,9 @@
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/quickstep/res \
+ $(LOCAL_PATH)/quickstep/overview_ui_overrides/res
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
@@ -213,7 +223,8 @@
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/quickstep/res \
$(LOCAL_PATH)/go/res \
- $(LOCAL_PATH)/go/quickstep/res
+ $(LOCAL_PATH)/go/quickstep/res \
+ $(LOCAL_PATH)/go/quickstep/overview_ui_overrides/res
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PROGUARD_ENABLED := full
diff --git a/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml b/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
new file mode 100644
index 0000000..b438da3
--- /dev/null
+++ b/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
@@ -0,0 +1,98 @@
+<?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.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">
+
+ <LinearLayout
+ android:id="@+id/action_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/action_listen"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_listen"
+ android:drawablePadding="1dp"
+ android:text="@string/action_listen"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+ <Button
+ android:id="@+id/action_translate"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_translate"
+ android:drawablePadding="1dp"
+ android:text="@string/action_translate"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+ <Button
+ android:id="@+id/action_search"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_search"
+ android:drawablePadding="1dp"
+ android:text="@string/action_search"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+ <Button
+ android:id="@+id/action_screenshot"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableTop="@drawable/ic_screenshot"
+ android:drawablePadding="1dp"
+ android:text="@string/action_screenshot"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/action_share"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableStart="@drawable/ic_share"
+ android:text="@string/action_share"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor"
+ android:visibility="gone" />
+
+ <Space
+ android:id="@+id/oav_three_button_space"
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1"
+ android:visibility="gone" />
+ </LinearLayout>
+
+</com.android.quickstep.views.GoOverviewActionsView>
\ No newline at end of file
diff --git a/go/quickstep/overview_ui_overrides/res/values/config.xml b/go/quickstep/overview_ui_overrides/res/values/config.xml
new file mode 100644
index 0000000..ec21a01
--- /dev/null
+++ b/go/quickstep/overview_ui_overrides/res/values/config.xml
@@ -0,0 +1,22 @@
+<?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/res/drawable/ic_listen.xml b/go/quickstep/res/drawable/ic_listen.xml
new file mode 100644
index 0000000..a8e6c93
--- /dev/null
+++ b/go/quickstep/res/drawable/ic_listen.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="28dp"
+ android:viewportWidth="28"
+ android:viewportHeight="28">
+ <path
+ android:pathData="M10.5,15.17c2.58,0 4.67,-2.09 4.67,-4.67s-2.09,-4.67 -4.67,-4.67c-2.58,0 -4.67,2.09 -4.67,4.67S7.92,15.17 10.5,15.17zM10.5,8.17c1.28,0 2.33,1.05 2.33,2.33s-1.05,2.33 -2.33,2.33c-1.28,0 -2.33,-1.05 -2.33,-2.33S9.22,8.17 10.5,8.17z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M10.5,17.5c-3.11,0 -9.33,1.56 -9.33,4.67v2.33h18.67v-2.33C19.83,19.06 13.62,17.5 10.5,17.5zM3.5,22.17c0.26,-0.84 3.86,-2.33 7,-2.33c3.15,0 6.77,1.5 7,2.33H3.5z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M25.67,10.5c0,0.36 -0.02,0.71 -0.05,1.05c-0.01,0.15 -0.03,0.29 -0.05,0.43c-0.02,0.18 -0.05,0.36 -0.08,0.54c-0.04,0.2 -0.07,0.39 -0.12,0.58c-0.01,0.06 -0.03,0.11 -0.04,0.17c-0.59,2.34 -1.81,4.01 -2.52,4.82c-0.09,0.1 -0.18,0.2 -0.28,0.3c-0.17,0.18 -0.27,0.27 -0.27,0.27l-1.65,-1.63c1.34,-1.33 2.27,-3.07 2.6,-5.01c0.01,-0.08 0.02,-0.16 0.04,-0.24c0.06,-0.42 0.1,-0.85 0.1,-1.29c0,-0.44 -0.04,-0.88 -0.1,-1.3c-0.01,-0.06 -0.02,-0.13 -0.03,-0.19c-0.32,-1.95 -1.25,-3.7 -2.6,-5.04l1.65,-1.63c0,0 0.11,0.1 0.27,0.27c0.09,0.1 0.19,0.2 0.28,0.3c0.71,0.82 1.93,2.48 2.52,4.82c0.01,0.06 0.03,0.11 0.04,0.17c0.04,0.19 0.08,0.38 0.12,0.58c0.03,0.18 0.06,0.36 0.08,0.54c0.02,0.14 0.04,0.28 0.05,0.43C25.65,9.79 25.67,10.14 25.67,10.5z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M20.61,8.4C20.85,9.06 21,9.76 21,10.5s-0.15,1.44 -0.39,2.1c-0.28,0.77 -0.71,1.46 -1.25,2.05l-1.66,-1.64c0.56,-0.63 0.91,-1.44 0.95,-2.34c0,-0.06 0.02,-0.11 0.02,-0.17s-0.01,-0.11 -0.02,-0.17c-0.04,-0.9 -0.39,-1.71 -0.95,-2.34l1.66,-1.64C19.9,6.94 20.32,7.63 20.61,8.4z"
+ android:fillColor="#FBBC04"/>
+</vector>
diff --git a/go/quickstep/res/drawable/ic_search.xml b/go/quickstep/res/drawable/ic_search.xml
new file mode 100644
index 0000000..4307330
--- /dev/null
+++ b/go/quickstep/res/drawable/ic_search.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="28dp"
+ android:viewportWidth="28"
+ android:viewportHeight="28">
+ <path
+ android:pathData="M24.5,22.75l-6.84,-6.84c1,-1.35 1.59,-3.02 1.59,-4.83h-2.33c0,3.22 -2.62,5.83 -5.83,5.83v2.33c1.81,0 3.47,-0.6 4.83,-1.59l6.84,6.84L24.5,22.75z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M11.08,2.92v2.33c3.22,0 5.83,2.62 5.83,5.83h2.33C19.25,6.57 15.59,2.92 11.08,2.92z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M5.25,11.08H2.92c0,4.51 3.66,8.17 8.17,8.17v-2.33C7.87,16.92 5.25,14.3 5.25,11.08z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M2.92,11.08h2.33c0,-3.22 2.62,-5.83 5.83,-5.83V2.92C6.57,2.92 2.92,6.57 2.92,11.08z"
+ android:fillColor="#FBBC04"/>
+</vector>
diff --git a/go/quickstep/res/drawable/ic_translate.xml b/go/quickstep/res/drawable/ic_translate.xml
new file mode 100644
index 0000000..1247807
--- /dev/null
+++ b/go/quickstep/res/drawable/ic_translate.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="28dp"
+ android:height="28dp"
+ android:viewportWidth="28"
+ android:viewportHeight="28">
+ <path
+ android:pathData="M12.28,15.19l-0.07,-0.05c-0.61,-0.49 -1.15,-1.05 -1.65,-1.63c-1.05,-1.22 -1.88,-2.63 -2.39,-4.17H5.83c0.54,2.17 1.58,4.16 3.01,5.85l-5.93,5.23l1.75,1.75l5.91,-5.26c0.05,0.04 0.1,0.09 0.15,0.13l3.42,2.79l1.02,-2.33L12.28,15.19z"
+ android:fillColor="#FBBC04"/>
+ <path
+ android:pathData="M21.58,11.67h-2.33l-5.25,14h2.33l1.31,-3.5h5.54l1.32,3.5h2.33L21.58,11.67zM18.53,19.83l1.89,-5.05l1.89,5.05H18.53z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M11.67,2.33l-2.34,0l0,2.34l-8.16,0l0,2.33l10.5,0l0,-2.33z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M11.67,4.67V7H14c-0.61,2.42 -1.79,4.65 -3.44,6.5c0.5,0.59 1.04,1.15 1.65,1.63l0.07,0.05c2.03,-2.32 3.44,-5.14 4.05,-8.19h3.5V4.67H11.67z"
+ android:fillColor="#34A853"/>
+</vector>
diff --git a/go/quickstep/res/values/config.xml b/go/quickstep/res/values/config.xml
index f376774..9dca137 100644
--- a/go/quickstep/res/values/config.xml
+++ b/go/quickstep/res/values/config.xml
@@ -14,5 +14,11 @@
limitations under the License.
-->
<resources>
+ <!-- The component to receive app sharing Intents -->
<string name="app_sharing_component" translatable="false"/>
+ <!-- The package to receive Listen, Translate, and Search Intents -->
+ <string name="niu_actions_package" translatable="false"/>
+
+ <!-- Feature Flags -->
+ <bool name="enable_niu_actions">false</bool>
</resources>
\ No newline at end of file
diff --git a/go/quickstep/res/values/strings.xml b/go/quickstep/res/values/strings.xml
index fdd8397..71e2f3a 100644
--- a/go/quickstep/res/values/strings.xml
+++ b/go/quickstep/res/values/strings.xml
@@ -3,4 +3,12 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Label for app share drop target. [CHAR_LIMIT=20] -->
<string name="app_share_drop_target_label">Share App</string>
+
+ <!-- ******* Overview ******* -->
+ <!-- Label for a button that lets the user listen to the content of the current app. [CHAR_LIMIT=40] -->
+ <string name="action_listen">Listen</string>
+ <!-- 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>
</resources>
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
new file mode 100644
index 0000000..b102a39
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -0,0 +1,147 @@
+/*
+ * 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;
+
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Matrix;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+import com.android.launcher3.R;
+import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Go-specific extension of the factory class that adds an overlay to TaskView
+ */
+public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
+ public static final String ACTION_LISTEN = "com.android.quickstep.ACTION_LISTEN";
+ 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";
+
+ // Empty constructor required for ResourceBasedOverride
+ public TaskOverlayFactoryGo(Context context) {}
+
+ /**
+ * Create a new overlay instance for the given View
+ */
+ public TaskOverlayGo createOverlay(TaskThumbnailView thumbnailView) {
+ return new TaskOverlayGo(thumbnailView);
+ }
+
+ /**
+ * Overlay on each task handling Overview Action Buttons.
+ * @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 TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
+ super(taskThumbnailView);
+ }
+
+ /**
+ * Called when the current task is interactive for the user
+ */
+ @Override
+ public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
+ boolean rotated) {
+ getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
+ mPackageName =
+ mApplicationContext.getResources().getString(R.string.niu_actions_package);
+
+ if (thumbnail == null || TextUtils.isEmpty(mPackageName)) {
+ return;
+ }
+
+ getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+ boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+ getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
+ }
+
+ private void sendNIUIntent(String actionType) {
+ Intent intent = createNIUIntent(actionType);
+ mImageApi.shareAsDataWithExplicitIntent(/* crop */ null, intent);
+ }
+
+ private Intent createNIUIntent(String actionType) {
+ return new Intent(actionType)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ .setPackage(mPackageName)
+ .putExtra(ELAPSED_NANOS, SystemClock.elapsedRealtimeNanos());
+ }
+
+ protected class OverlayUICallbacksGoImpl extends OverlayUICallbacksImpl
+ implements OverlayUICallbacksGo {
+ public OverlayUICallbacksGoImpl(boolean isAllowedByPolicy, Task task) {
+ super(isAllowedByPolicy, task);
+ }
+
+ @SuppressLint("NewApi")
+ public void onListen() {
+ if (mIsAllowedByPolicy) {
+ sendNIUIntent(ACTION_LISTEN);
+ } else {
+ showBlockedByPolicyMessage();
+ }
+ }
+
+ @SuppressLint("NewApi")
+ public void onTranslate() {
+ if (mIsAllowedByPolicy) {
+ sendNIUIntent(ACTION_TRANSLATE);
+ } else {
+ showBlockedByPolicyMessage();
+ }
+ }
+
+ @SuppressLint("NewApi")
+ public void onSearch() {
+ if (mIsAllowedByPolicy) {
+ sendNIUIntent(ACTION_SEARCH);
+ } else {
+ showBlockedByPolicyMessage();
+ }
+ }
+ }
+ }
+
+ /**
+ * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
+ * controller.
+ */
+ public interface OverlayUICallbacksGo extends OverlayUICallbacks {
+ /** User has requested to listen to the current content read aloud */
+ void onListen();
+
+ /** User has requested a translation of the current content */
+ void onTranslate();
+
+ /** User has requested a visual search of the current content */
+ void onSearch();
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java b/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java
new file mode 100644
index 0000000..9997d16
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java
@@ -0,0 +1,75 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.quickstep.TaskOverlayFactoryGo.OverlayUICallbacksGo;
+
+/**
+ * View for showing Go-specific action buttons in Overview
+ */
+public final class GoOverviewActionsView extends OverviewActionsView<OverlayUICallbacksGo> {
+ public GoOverviewActionsView(Context context) {
+ this(context, null);
+ }
+
+ public GoOverviewActionsView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GoOverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ if (getResources().getBoolean(R.bool.enable_niu_actions)) {
+ findViewById(R.id.action_listen).setOnClickListener(this);
+ findViewById(R.id.action_translate).setOnClickListener(this);
+ findViewById(R.id.action_search).setOnClickListener(this);
+ } else {
+ findViewById(R.id.action_listen).setVisibility(View.GONE);
+ findViewById(R.id.action_translate).setVisibility(View.GONE);
+ findViewById(R.id.action_search).setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ super.onClick(view);
+
+ if (mCallbacks == null) {
+ return;
+ }
+ int id = view.getId();
+ if (id == R.id.action_listen) {
+ mCallbacks.onListen();
+ } else if (id == R.id.action_translate) {
+ mCallbacks.onTranslate();
+ } else if (id == R.id.action_search) {
+ mCallbacks.onSearch();
+ }
+ }
+}
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 89b3831..a202095 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -25,7 +25,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.ArrayList;
import java.util.Collections;
@@ -43,17 +43,17 @@
public static final boolean GO_DISABLE_WIDGETS = true;
public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
- private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
+ private static final ArrayList<WidgetsListBaseEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
/**
- * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
- * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s
- * is not sorted. This list is sorted at the UI when using
- * {@link com.android.launcher3.widget.WidgetsDiffReporter}
+ * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row are
+ * sorted (based on label and user), but the overall list of {@link WidgetsListBaseEntry}s is
+ * not sorted. This list is sorted at the UI when using
+ * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter}
*
- * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList)
+ * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
*/
- public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) {
+ public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
return EMPTY_WIDGET_LIST;
}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index b4c6138..fe81b4c 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -29,6 +29,8 @@
Shortcut shortcut = 3;
Widget widget = 4;
FolderIcon folder_icon = 9;
+ Slice slice = 10;
+ SearchActionItem search_action_item = 11;
}
// When used for launch event, stores the global predictive rank
optional int32 rank = 5;
@@ -169,6 +171,17 @@
optional string label_info = 4;
}
+// Contains Slice details for logging.
+message Slice{
+ optional string uri = 1;
+}
+
+// Represents SearchAction with in launcher
+message SearchActionItem{
+ optional string package_name = 1;
+ optional string title = 2;
+}
+
//////////////////////////////////////////////
// Containers
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 9c14b85..97f4a21 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -39,9 +39,10 @@
<uses-permission android:name="android.permission.SET_ORIENTATION"/>
<uses-permission android:name="android.permission.READ_FRAME_BUFFER"/>
<uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY"/>
+ <uses-permission android:name="android.permission.MONITOR_INPUT"/>
<uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
<application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:fullBackupOnly="true"
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
similarity index 100%
rename from quickstep/res/layout/overview_actions_container.xml
rename to quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
diff --git a/quickstep/res/layout/search_result_thumbnail.xml b/quickstep/overview_ui_overrides/res/values/config.xml
similarity index 64%
copy from quickstep/res/layout/search_result_thumbnail.xml
copy to quickstep/overview_ui_overrides/res/values/config.xml
index 5062b76..0f09439 100644
--- a/quickstep/res/layout/search_result_thumbnail.xml
+++ b/quickstep/overview_ui_overrides/res/values/config.xml
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
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,7 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.search.SearchResultThumbnailView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="125dp"
- android:layout_height="125dp"/>
\ No newline at end of file
+<resources>
+ <string name="task_overlay_factory_class" translatable="false"/>
+</resources>
\ No newline at end of file
diff --git a/quickstep/protos_overrides/launcher_atom_extension.proto b/quickstep/protos_overrides/launcher_atom_extension.proto
index 2766acf..6253b41 100644
--- a/quickstep/protos_overrides/launcher_atom_extension.proto
+++ b/quickstep/protos_overrides/launcher_atom_extension.proto
@@ -31,4 +31,5 @@
// Represents on-device search result container.
message DeviceSearchResultContainer{
+ optional int32 query_length = 1;
}
diff --git a/quickstep/res/drawable/chip_scrim_gradient.xml b/quickstep/res/drawable/chip_scrim_gradient.xml
deleted file mode 100644
index 5a2dfb7..0000000
--- a/quickstep/res/drawable/chip_scrim_gradient.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <gradient
- android:angle="90"
- android:endColor="@android:color/transparent"
- android:startColor="@color/chip_scrim_start_color"
- android:type="linear" />
-</shape>
\ No newline at end of file
diff --git a/quickstep/res/layout/fallback_search_input.xml b/quickstep/res/layout/fallback_search_input.xml
deleted file mode 100644
index 0fb2924..0000000
--- a/quickstep/res/layout/fallback_search_input.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.search.FallbackSearchInputView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="48dp"
- android:layout_marginRight="48dp"
- android:background="@android:color/transparent"
- android:focusableInTouchMode="true"
- android:gravity="start|center_vertical"
- android:inputType="textNoSuggestions"
- android:imeOptions="actionSearch|flagNoExtractUi"
- android:maxLines="1"
- android:privateImeOptions="bc_search"
- android:scrollHorizontally="true"
- android:singleLine="true"
- android:textColor="?android:attr/textColorSecondary"
- android:textColorHint="?android:attr/textColorTertiary"
- android:textSize="16sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/search_edu_view.xml b/quickstep/res/layout/search_edu_view.xml
deleted file mode 100644
index d89f5c7..0000000
--- a/quickstep/res/layout/search_edu_view.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.search.DeviceSearchEdu
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_horizontal"
- android:orientation="vertical">
-
-
- <FrameLayout
- android:layout_height="wrap_content"
- android:id="@+id/search_box_wrapper"
- android:layout_width="match_parent">
-
- <include
- layout="@layout/fallback_search_input"
- android:id="@+id/mock_search_box" />
- </FrameLayout>
-
- <LinearLayout
- android:layout_height="wrap_content"
- android:id="@+id/edu_wrapper"
- android:padding="24dp"
- android:layout_marginTop="40dp"
- android:orientation="vertical"
- android:layout_width="match_parent">
-
- <TextView
- style="@style/TextHeadline"
- android:layout_width="match_parent"
- android:gravity="center"
- android:textSize="24sp"
- android:textColor="?android:attr/textColorPrimary"
- android:layout_height="wrap_content"
- android:text="@string/search_edu_primary" />
-
- <TextView
- style="@style/TextHeadline"
- android:layout_width="match_parent"
- android:gravity="center"
- android:textSize="18sp"
- android:layout_marginTop="30dp"
- android:textColor="?android:attr/textColorPrimary"
- android:layout_height="wrap_content"
- android:text="@string/search_edu_secondary" />
-
- <Button
- android:id="@+id/dismiss_edu"
- android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/dynamic_grid_edge_margin"
- android:background="?android:attr/selectableItemBackground"
- android:layout_height="wrap_content"
- android:textColor="?android:attr/textColorPrimary"
- android:gravity="center"
- android:layout_gravity="center"
- android:text="@string/search_edu_dismiss" />
-
- </LinearLayout>
-
-</com.android.launcher3.search.DeviceSearchEdu>
\ No newline at end of file
diff --git a/quickstep/res/layout/search_result_icon.xml b/quickstep/res/layout/search_result_icon.xml
deleted file mode 100644
index e1b6dfd..0000000
--- a/quickstep/res/layout/search_result_icon.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<com.android.launcher3.search.SearchResultIcon xmlns:launcher="http://schemas.android.com/apk/res-auto"
- style="@style/BaseIcon.AllApps"
- launcher:iconDisplay="all_apps"
- launcher:centerVertically="true" />
-
diff --git a/quickstep/res/layout/search_result_icon_row.xml b/quickstep/res/layout/search_result_icon_row.xml
deleted file mode 100644
index 084920a..0000000
--- a/quickstep/res/layout/search_result_icon_row.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.search.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="@dimen/dynamic_grid_edge_margin">
-
- <com.android.launcher3.search.SearchResultIcon
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- launcher:iconDisplay="hero_app" />
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:padding="@dimen/dynamic_grid_edge_margin"
- android:orientation="vertical"
- android:layout_gravity="center_vertical">
-
- <TextView
- android:id="@id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
- android:maxLines="1"
- android:textAlignment="viewStart"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="@dimen/search_hero_title_size" />
-
- <TextView
- android:layout_width="wrap_content"
- android:id="@+id/subtitle"
- android:maxLines="1"
- android:textColor="?android:attr/textColorTertiary"
- android:textSize="@dimen/search_hero_subtitle_size"
- android:layout_height="wrap_content" />
- </LinearLayout>
-
- <com.android.launcher3.search.SearchResultIcon
- android:id="@+id/shortcut_0"
- style="@style/BaseIcon"
- android:layout_width="@dimen/deep_shortcut_icon_size"
- android:layout_height="match_parent"
- android:layout_marginStart="4dp"
- android:gravity="center"
- launcher:iconDisplay="shortcut_popup"
- android:textSize="@dimen/search_hero_inline_button_size"
- launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
- launcher:layoutHorizontal="false" />
-
- <com.android.launcher3.search.SearchResultIcon
- android:id="@+id/shortcut_1"
- style="@style/BaseIcon"
- android:layout_width="@dimen/deep_shortcut_icon_size"
- android:layout_height="match_parent"
- android:layout_marginStart="4dp"
- android:gravity="center"
- launcher:iconDisplay="shortcut_popup"
- android:textSize="@dimen/search_hero_inline_button_size"
- launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
- launcher:layoutHorizontal="false" />
-
- <com.android.launcher3.search.SearchResultIcon
- android:id="@+id/shortcut_2"
- style="@style/BaseIcon"
- android:layout_width="@dimen/deep_shortcut_icon_size"
- android:layout_height="match_parent"
- android:layout_marginStart="4dp"
- android:gravity="center"
- launcher:iconDisplay="shortcut_popup"
- android:textSize="@dimen/search_hero_inline_button_size"
- launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
- launcher:layoutHorizontal="false" />
-</com.android.launcher3.search.SearchResultIconRow>
\ No newline at end of file
diff --git a/quickstep/res/layout/search_result_slice.xml b/quickstep/res/layout/search_result_slice.xml
deleted file mode 100644
index 4f884ff..0000000
--- a/quickstep/res/layout/search_result_slice.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.search.SearchResultIconSlice xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:padding="@dimen/dynamic_grid_edge_margin"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <com.android.launcher3.search.SearchResultIcon
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/icon"
- launcher:iconDisplay="hero_app" />
- </FrameLayout>
-
- <androidx.slice.widget.SliceView
- android:id="@+id/slice"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginStart="@dimen/dynamic_grid_cell_padding_x"
- android:layout_width="0dp" />
-
-</com.android.launcher3.search.SearchResultIconSlice>
-
diff --git a/quickstep/res/layout/search_result_small_icon_row.xml b/quickstep/res/layout/search_result_small_icon_row.xml
deleted file mode 100644
index 41856fe..0000000
--- a/quickstep/res/layout/search_result_small_icon_row.xml
+++ /dev/null
@@ -1,73 +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.
--->
-<com.android.launcher3.search.SearchResultSmallIconRow
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="@dimen/dynamic_grid_edge_margin">
-
- <com.android.launcher3.search.SearchResultIcon
- android:id="@+id/icon"
- style="@style/BaseIcon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
- android:drawableTint="?android:attr/textColorPrimary"
- android:padding="@dimen/dynamic_grid_edge_margin"
- launcher:iconDisplay="hero_app"
- launcher:iconSizeOverride="48dp"
- launcher:matchTextInsetWithQuery="true"
- launcher:layoutHorizontal="true" />
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:orientation="horizontal"
- android:padding="@dimen/dynamic_grid_edge_margin" >
-
- <TextView
- android:id="@id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
- android:maxLines="1"
- android:paddingEnd="4dp"
- android:textAlignment="viewStart"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="@dimen/search_hero_title_size" />
- <TextView
- android:id="@+id/delimeter"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
- android:maxLines="1"
- android:text="\u2022"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="@dimen/search_hero_subtitle_size" />
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="1"
- android:paddingStart="4dp"
- android:textColor="?android:attr/textColorTertiary"
- android:textSize="@dimen/search_hero_subtitle_size" />
- </LinearLayout>
-</com.android.launcher3.search.SearchResultSmallIconRow>
\ No newline at end of file
diff --git a/quickstep/res/layout/search_result_widget_live.xml b/quickstep/res/layout/search_result_widget_live.xml
deleted file mode 100644
index f2ac6cd..0000000
--- a/quickstep/res/layout/search_result_widget_live.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.launcher3.search.SearchResultWidget
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingVertical="@dimen/widget_section_vertical_padding"
- android:layout_marginBottom="@dimen/widget_section_vertical_padding"
- android:gravity="center"
- >
-
- <LinearLayout
- android:layout_width="match_parent"
- android:orientation="horizontal"
- android:layout_height="wrap_content">
-
- <com.android.launcher3.BubbleTextView
- android:id="@+id/widget_provider"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/widget_section_height"
- android:drawablePadding="@dimen/widget_section_horizontal_padding"
- android:focusable="true"
- android:gravity="start|center_vertical"
- android:paddingHorizontal="@dimen/widget_section_horizontal_padding"
- android:paddingVertical="@dimen/widget_section_horizontal_padding"
- android:singleLine="true"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
- android:textAlignment="viewStart"
- launcher:iconDisplay="widget_section"
- launcher:layoutHorizontal="true"
- launcher:iconSizeOverride="@dimen/widget_section_icon_size" />
-
- <TextView
- android:id="@+id/widget_label"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/widget_section_height"
- android:textSize="16sp" />
-
- </LinearLayout>
-
-</com.android.launcher3.search.SearchResultWidget>
\ No newline at end of file
diff --git a/quickstep/res/layout/search_result_widget_preview.xml b/quickstep/res/layout/search_result_widget_preview.xml
deleted file mode 100644
index 7af24a1..0000000
--- a/quickstep/res/layout/search_result_widget_preview.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.launcher3.search.SearchResultWidgetPreview xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:padding="@dimen/dynamic_grid_cell_padding_x"
- android:layout_height="wrap_content">
- <include layout="@layout/widget_cell" android:id="@+id/widget_cell"/>
-<!-- <include layout="@layout/widget_cell_content" />-->
-</com.android.launcher3.search.SearchResultWidgetPreview>
\ No newline at end of file
diff --git a/quickstep/res/layout/search_section_title.xml b/quickstep/res/layout/search_section_title.xml
deleted file mode 100644
index 5842e57..0000000
--- a/quickstep/res/layout/search_section_title.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.search.SearchSectionHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/section_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/TextHeadline"
- android:paddingStart="4dp"
- android:paddingBottom="2dp"
- android:paddingTop="12dp"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="18sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index f9bb2f2..0f9a6aa 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -17,8 +17,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
- android:elevation="4dp"
android:focusable="true">
<com.android.quickstep.views.TaskThumbnailView
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index 098b34f..3916ff9 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -24,20 +24,12 @@
android:orientation="vertical"
android:visibility="invisible">
- <com.android.quickstep.views.IconView
- android:id="@+id/task_icon"
- android:layout_width="@dimen/task_thumbnail_icon_size"
- android:layout_height="@dimen/task_thumbnail_icon_size"
- android:layout_gravity="top|center_horizontal"
- android:layout_marginBottom="@dimen/deep_shortcut_drawable_padding"
- android:focusable="false"
- android:importantForAccessibility="no" />
-
<TextView
android:id="@+id/task_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
+ android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:textSize="12sp"/>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 9ec303a..be66104 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -14,8 +14,6 @@
limitations under the License.
-->
<resources>
- <string name="task_overlay_factory_class" translatable="false"/>
-
<string name="overscroll_plugin_factory_class" translatable="false" />
<!-- Activities which block home gesture -->
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 0f40775..e46eb9e 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -19,6 +19,7 @@
<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>
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
@@ -28,7 +29,7 @@
<dimen name="overview_actions_bottom_margin_three_button">8dp</dimen>
<dimen name="overview_actions_horizontal_margin">16dp</dimen>
- <dimen name="recents_page_spacing">10dp</dimen>
+ <dimen name="recents_page_spacing">16dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
<!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
@@ -57,7 +58,7 @@
<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">96dp</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>
@@ -121,12 +122,13 @@
<dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
<!-- Taskbar -->
- <dimen name="taskbar_size">48dp</dimen>
- <dimen name="taskbar_icon_size">32dp</dimen>
+ <dimen name="taskbar_size">60dp</dimen>
+ <dimen name="taskbar_icon_size">44dp</dimen>
<dimen name="taskbar_icon_touch_size">48dp</dimen>
<dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
<!-- Note that this applies to both sides of all icons, so visible space is double this. -->
- <dimen name="taskbar_icon_spacing">14dp</dimen>
+ <dimen name="taskbar_icon_spacing">8dp</dimen>
<dimen name="taskbar_divider_thickness">1dp</dimen>
- <dimen name="taskbar_divider_height">24dp</dimen>
+ <dimen name="taskbar_divider_height">32dp</dimen>
+ <dimen name="taskbar_folder_margin">16dp</dimen>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 5a353f0..df089f6 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -89,6 +89,5 @@
<!-- Icon displayed on the taskbar -->
<style name="BaseIcon.Workspace.Taskbar" >
<item name="iconDisplay">taskbar</item>
- <item name="iconSizeOverride">@dimen/taskbar_icon_size</item>
</style>
</resources>
\ No newline at end of file
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 5cb55ec..eca27b5 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -43,8 +43,18 @@
@RunWith(RobolectricTestRunner.class)
public class OrientationTouchTransformerTest {
- private static final int SIZE_WIDTH = 1080;
- private static final int SIZE_HEIGHT = 2280;
+ static class ScreenSize {
+ int mHeight;
+ int mWidth;
+
+ ScreenSize(int height, int width) {
+ mHeight = height;
+ mWidth = width;
+ }
+ }
+
+ private static final ScreenSize NORMAL_SCREEN_SIZE = new ScreenSize(2280, 1080);
+ private static final ScreenSize LARGE_SCREEN_SIZE = new ScreenSize(3280, 1080);
private static final float DENSITY_DISPLAY_METRICS = 3.0f;
private OrientationTouchTransformer mTouchTransformer;
@@ -63,14 +73,16 @@
DisplayMetrics mockDisplayMetrics = new DisplayMetrics();
mockDisplayMetrics.density = DENSITY_DISPLAY_METRICS;
when(mResources.getDisplayMetrics()).thenReturn(mockDisplayMetrics);
- mInfo = createDisplayInfo(Surface.ROTATION_0);
+ mInfo = createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_0);
mTouchTransformer = new OrientationTouchTransformer(mResources, NO_BUTTON, () -> 0);
}
@Test
public void disabledMultipleRegions_shouldOverrideFirstRegion() {
- float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ float portraitRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
+ float landscapeRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
mTouchTransformer.createOrAddTouchRegion(mInfo);
tapAndAssertTrue(100, portraitRegionY,
@@ -83,7 +95,8 @@
event -> mTouchTransformer.touchInAssistantRegion(event));
// Override region
- mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ mTouchTransformer
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
tapAndAssertFalse(100, portraitRegionY,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
tapAndAssertTrue(100, landscapeRegionY,
@@ -107,10 +120,13 @@
@Test
public void enableMultipleRegions_shouldOverrideFirstRegion() {
- float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ float portraitRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
+ float landscapeRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
- mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ mTouchTransformer
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
tapAndAssertFalse(100, portraitRegionY,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
tapAndAssertTrue(100, landscapeRegionY,
@@ -136,11 +152,14 @@
@Test
public void enableMultipleRegions_assistantTriggersInMostRecent() {
- float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ float portraitRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
+ float landscapeRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
mTouchTransformer.enableMultipleRegions(true, mInfo);
- mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ mTouchTransformer
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
mTouchTransformer.createOrAddTouchRegion(mInfo);
tapAndAssertTrue(0, portraitRegionY,
event -> mTouchTransformer.touchInAssistantRegion(event));
@@ -150,12 +169,15 @@
@Test
public void enableMultipleRegions_assistantTriggersInCurrentOrientationAfterDisable() {
- float portraitRegionY = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
- float landscapeRegionY = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ float portraitRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
+ float landscapeRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
mTouchTransformer.enableMultipleRegions(true, mInfo);
mTouchTransformer.createOrAddTouchRegion(mInfo);
- mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ mTouchTransformer
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
mTouchTransformer.enableMultipleRegions(false, mInfo);
tapAndAssertTrue(0, portraitRegionY,
event -> mTouchTransformer.touchInAssistantRegion(event));
@@ -164,6 +186,26 @@
}
@Test
+ public void assistantTriggersInCurrentScreenAfterScreenSizeChange() {
+ float smallerScreenPortraitRegionY =
+ generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
+ float largerScreenPortraitRegionY =
+ generateTouchRegionHeight(LARGE_SCREEN_SIZE, Surface.ROTATION_0) + 1;
+
+ mTouchTransformer.enableMultipleRegions(false,
+ createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_0));
+ tapAndAssertTrue(0, smallerScreenPortraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+
+ mTouchTransformer
+ .enableMultipleRegions(false, createDisplayInfo(LARGE_SCREEN_SIZE, Surface.ROTATION_0));
+ tapAndAssertTrue(0, largerScreenPortraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ tapAndAssertFalse(0, smallerScreenPortraitRegionY,
+ event -> mTouchTransformer.touchInAssistantRegion(event));
+ }
+
+ @Test
public void applyTransform_taskNotFrozen_notInRegion() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
tapAndAssertFalse(100, 100,
@@ -182,7 +224,7 @@
public void applyTransform_taskFrozen_noRotate_inRegion() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
mTouchTransformer.enableMultipleRegions(true, mInfo);
- float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
tapAndAssertTrue(100, y,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@@ -190,15 +232,16 @@
@Test
public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
- float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+ float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
tapAndAssertTrue(100, y,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@Test
public void applyTransform_taskNotFrozen_90Rotate_inRegion() {
- mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
- float y = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ mTouchTransformer
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
tapAndAssertTrue(100, y,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@@ -210,9 +253,10 @@
public void applyTransform_taskNotFrozen_90Rotate_inTwoRegions() {
mTouchTransformer.createOrAddTouchRegion(mInfo);
mTouchTransformer.enableMultipleRegions(true, mInfo);
- mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+ mTouchTransformer
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
// Landscape point
- float y1 = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+ float y1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1);
MotionEvent inRegion1_up = generateMotionEvent(MotionEvent.ACTION_UP, 10, y1);
// Portrait point in landscape orientation axis
@@ -231,18 +275,18 @@
assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
}
- private DisplayController.Info createDisplayInfo(int rotation) {
- Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT);
+ private DisplayController.Info createDisplayInfo(ScreenSize screenSize, int rotation) {
+ Point p = new Point(screenSize.mWidth, screenSize.mHeight);
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
- p = new Point(SIZE_HEIGHT, SIZE_WIDTH);
+ p = new Point(screenSize.mHeight, screenSize.mWidth);
}
return new DisplayController.Info(0, rotation, 0, p, p, p, null);
}
- private float generateTouchRegionHeight(int rotation) {
- float height = SIZE_HEIGHT;
+ private float generateTouchRegionHeight(ScreenSize screenSize, int rotation) {
+ float height = screenSize.mHeight;
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
- height = SIZE_WIDTH;
+ height = screenSize.mWidth;
}
return height - ResourceUtils.DEFAULT_NAVBAR_VALUE * DENSITY_DISPLAY_METRICS;
}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index edcd0a2..5b30143 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -30,7 +30,6 @@
import android.content.IntentSender;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.Nullable;
@@ -43,7 +42,7 @@
import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.taskbar.TaskbarContainerView;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarController;
import com.android.launcher3.taskbar.TaskbarStateHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
@@ -207,6 +206,7 @@
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
addTaskbarIfNecessary();
+ addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
}
@Override
@@ -222,10 +222,10 @@
mTaskbarController.cleanup();
mTaskbarController = null;
}
- if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) {
- TaskbarContainerView taskbarContainer = (TaskbarContainerView) LayoutInflater.from(this)
- .inflate(R.layout.taskbar, null, false);
- mTaskbarController = new TaskbarController(this, taskbarContainer);
+ if (mDeviceProfile.isTaskbarPresent) {
+ TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
+ mTaskbarController = new TaskbarController(this,
+ taskbarActivityContext.getTaskbarContainerView());
mTaskbarController.init();
}
}
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 034d51f..588d676 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -56,17 +56,22 @@
return mHandler;
}
- // Called only in R+ platform
+ // Called only in S+ platform
@BinderThread
- public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) {
+ public void onAnimationStart(
+ int transit,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ Runnable runnable) {
Runnable r = () -> {
finishExistingAnimation();
mAnimationResult = new AnimationResult(() -> {
UI_HELPER_EXECUTOR.execute(runnable);
mAnimationResult = null;
});
- onCreateAnimation(appTargets, wallpaperTargets, mAnimationResult);
+ onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,
+ mAnimationResult);
};
if (mStartAtFrontOfQueue) {
postAtFrontOfQueueAsynchronously(mHandler, r);
@@ -75,6 +80,14 @@
}
}
+ // Called only in R platform
+ @BinderThread
+ public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) {
+ onAnimationStart(0 /* transit */, appTargets, wallpaperTargets,
+ new RemoteAnimationTargetCompat[0], runnable);
+ }
+
// Called only in Q platform
@BinderThread
@Deprecated
@@ -88,8 +101,11 @@
*/
@UiThread
public abstract void onCreateAnimation(
+ int transit,
RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result);
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ AnimationResult result);
@UiThread
private void finishExistingAnimation() {
diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
new file mode 100644
index 0000000..96559cb
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+
+import java.util.List;
+
+public class QuickstepAccessibilityDelegate extends LauncherAccessibilityDelegate {
+
+ public QuickstepAccessibilityDelegate(QuickstepLauncher launcher) {
+ super(launcher);
+ mActions.put(PIN_PREDICTION, new LauncherAction(
+ PIN_PREDICTION, R.string.pin_prediction, KeyEvent.KEYCODE_P));
+ }
+
+ @Override
+ protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
+ if (host instanceof PredictedAppIcon && !((PredictedAppIcon) host).isPinned()) {
+ out.add(new LauncherAction(PIN_PREDICTION, R.string.pin_prediction,
+ KeyEvent.KEYCODE_P));
+ }
+ super.getSupportedActions(host, item, out);
+ }
+
+ @Override
+ protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
+ QuickstepLauncher launcher = (QuickstepLauncher) mLauncher;
+ if (action == PIN_PREDICTION) {
+ if (launcher.getHotseatPredictionController() == null) {
+ return false;
+ }
+ launcher.getHotseatPredictionController().pinPrediction(item);
+ return true;
+ }
+ return super.performAction(host, item, action, fromKeyboard);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 876cabc..c4b6961 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -866,8 +866,10 @@
}
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
LauncherAnimationRunner.AnimationResult result) {
if (mLauncher.isDestroyed()) {
AnimatorSet anim = new AnimatorSet();
@@ -880,7 +882,8 @@
// If launcher is not resumed, wait until new async-frame after resume
mLauncher.addOnResumeCallback(() ->
postAsyncCallback(mHandler, () ->
- onCreateAnimation(appTargets, wallpaperTargets, result)));
+ onCreateAnimation(transit, appTargets, wallpaperTargets,
+ nonAppTargets, result)));
return;
}
@@ -964,8 +967,10 @@
}
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
LauncherAnimationRunner.AnimationResult result) {
AnimatorSet anim = new AnimatorSet();
diff --git a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
index da2aee4..03cc28e 100644
--- a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
+++ b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
@@ -26,7 +26,9 @@
*/
public interface WrappedAnimationRunnerImpl {
Handler getHandler();
- void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+ void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
LauncherAnimationRunner.AnimationResult result);
}
diff --git a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
index 1753b62..1e1631b 100644
--- a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
@@ -46,11 +46,15 @@
}
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ AnimationResult result) {
R animationRunnerImpl = mImpl.get();
if (animationRunnerImpl != null) {
- animationRunnerImpl.onCreateAnimation(appTargets, wallpaperTargets, result);
+ animationRunnerImpl.onCreateAnimation(transit, appTargets, wallpaperTargets,
+ nonAppTargets, result);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index b891120..66b1a86 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -42,7 +42,6 @@
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.Themes;
@@ -183,11 +182,6 @@
}
private void updateViewVisibility() {
- // hide divider since we have item decoration for prediction row
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- setVisibility(GONE);
- return;
- }
setVisibility(mDividerType == DividerType.NONE
? GONE
: (mIsScrolledOut ? INVISIBLE : VISIBLE));
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index f53a5ef..b570d55 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -108,7 +108,8 @@
AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
- @Nullable private List<ItemInfo> mPendingPredictedItems;
+ @Nullable
+ private List<ItemInfo> mPendingPredictedItems;
public PredictionRowView(@NonNull Context context) {
this(context, null);
@@ -130,7 +131,7 @@
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mDecorationHandler = new AllAppsSectionDecorator.SectionDecorationHandler(getContext(),
- false);
+ false, 0, true, true);
}
updateVisibility();
@@ -164,7 +165,7 @@
for (int i = 0; i < childrenCount; i++) {
mDecorationHandler.extendBounds(getChildAt(i));
}
- mDecorationHandler.onDraw(canvas);
+ mDecorationHandler.onGroupDraw(canvas);
mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
mLauncher.getAppsView().getActiveRecyclerView().invalidateItemDecorations();
}
@@ -181,7 +182,7 @@
@Override
public boolean shouldDraw() {
- return getVisibility() == VISIBLE;
+ return getVisibility() != GONE;
}
@Override
@@ -189,6 +190,11 @@
return mPredictionsEnabled;
}
+ @Override
+ public boolean isVisible() {
+ return getVisibility() == VISIBLE;
+ }
+
/**
* Returns the predicted apps.
*/
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
index c968de9..3a1a2f7 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
@@ -29,7 +29,6 @@
*/
public class HotseatEduActivity extends Activity {
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -49,7 +48,7 @@
@Override
public boolean init(BaseActivity activity, boolean alreadyOnHome) {
QuickstepLauncher launcher = (QuickstepLauncher) activity;
- if (launcher != null && launcher.getHotseatPredictionController() != null) {
+ if (launcher != null) {
launcher.getHotseatPredictionController().showEdu();
}
return false;
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
deleted file mode 100644
index 20e1edc..0000000
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
+++ /dev/null
@@ -1,128 +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.hybridhotseat;
-
-import android.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.MainThreadInitializedObject;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.PrintWriter;
-import java.text.DateFormat;
-import java.util.Calendar;
-import java.util.Date;
-
-/**
- * Helper class to allow hot seat file logging
- */
-public class HotseatFileLog {
-
- public static final int LOG_DAYS = 10;
- private static final String FILE_NAME_PREFIX = "hotseat-log-";
- private static final DateFormat DATE_FORMAT =
- DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
- public static final MainThreadInitializedObject<HotseatFileLog> INSTANCE =
- new MainThreadInitializedObject<>(HotseatFileLog::new);
-
-
- private final Handler mHandler = new Handler(
- Executors.createAndStartNewLooper("hotseat-logger"));
- private final File mLogsDir;
- private PrintWriter mCurrentWriter;
- private String mFileName;
-
- private HotseatFileLog(Context context) {
- mLogsDir = context.getFilesDir();
- }
-
- /**
- * Prints log values to disk
- */
- public void log(String tag, String msg) {
- String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
-
- mHandler.post(() -> {
- synchronized (this) {
- PrintWriter writer = getWriter();
- if (writer != null) {
- writer.println(out);
- }
- }
- });
- }
-
- private PrintWriter getWriter() {
- Calendar cal = Calendar.getInstance();
- String fName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) % 10);
- if (fName.equals(mFileName)) return mCurrentWriter;
-
- boolean append = false;
- File logFile = new File(mLogsDir, fName);
- if (logFile.exists()) {
- Calendar modifiedTime = Calendar.getInstance();
- modifiedTime.setTimeInMillis(logFile.lastModified());
-
- // If the file was modified more that 36 hours ago, purge the file.
- // We use instead of 24 to account for day-365 followed by day-1
- modifiedTime.add(Calendar.HOUR, 36);
- append = cal.before(modifiedTime);
- }
-
-
- if (mCurrentWriter != null) {
- mCurrentWriter.close();
- }
- try {
- mCurrentWriter = new PrintWriter(new FileWriter(logFile, append));
- mFileName = fName;
- } catch (Exception ex) {
- Log.e("HotseatLogs", "Error writing logs to file", ex);
- closeWriter();
- }
- return mCurrentWriter;
- }
-
-
- private synchronized void closeWriter() {
- mFileName = null;
- if (mCurrentWriter != null) {
- mCurrentWriter.close();
- }
- mCurrentWriter = null;
- }
-
-
- /**
- * Returns a list of all log files
- */
- public synchronized File[] getLogFiles() {
- File[] files = new File[LOG_DAYS + FileLog.LOG_DAYS];
- //include file log files here
- System.arraycopy(FileLog.getLogFiles(), 0, files, 0, FileLog.LOG_DAYS);
-
- closeWriter();
- for (int i = 0; i < LOG_DAYS; i++) {
- files[FileLog.LOG_DAYS + i] = new File(mLogsDir, FILE_NAME_PREFIX + i);
- }
- return files;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 88cfacb..0156e8f 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -26,7 +26,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.ComponentName;
-import android.os.Process;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewGroup;
@@ -40,7 +39,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherSettings;
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.dragndrop.DragController;
@@ -76,11 +74,13 @@
private static final int FLAG_UPDATE_PAUSED = 1 << 0;
private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1;
private static final int FLAG_FILL_IN_PROGRESS = 1 << 2;
+ private static final int FLAG_REMOVING_PREDICTED_ICON = 1 << 3;
private int mHotSeatItemsCount;
private QuickstepLauncher mLauncher;
private final Hotseat mHotseat;
+ private final Runnable mUpdateFillIfNotLoading = this::updateFillIfNotLoading;
private List<ItemInfo> mPredictedItems = Collections.emptyList();
@@ -134,7 +134,8 @@
private void onHotseatHierarchyChanged() {
if (mPauseFlags == 0 && !mLauncher.isWorkspaceLoading()) {
// Post update after a single frame to avoid layout within layout
- MAIN_EXECUTOR.getHandler().post(this::updateFillIfNotLoading);
+ MAIN_EXECUTOR.getHandler().removeCallbacks(mUpdateFillIfNotLoading);
+ MAIN_EXECUTOR.getHandler().post(mUpdateFillIfNotLoading);
}
}
@@ -376,7 +377,7 @@
continue;
}
if (dragObject.dragSource == this && icon.equals(dragObject.originalView)) {
- mHotseat.removeView(icon);
+ removeIconWithoutNotify(icon);
continue;
}
int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
@@ -388,7 +389,7 @@
@Override
public void onAnimationSuccess(Animator animator) {
if (icon.getParent() != null) {
- mHotseat.removeView(icon);
+ removeIconWithoutNotify(icon);
}
}
});
@@ -397,6 +398,17 @@
mIconRemoveAnimators.start();
}
+ /**
+ * Removes icon while suppressing any extra tasks performed on view-hierarchy changes.
+ * This avoids recursive/redundant updates as the control updates the UI anyway after
+ * it's animation.
+ */
+ private void removeIconWithoutNotify(PredictedAppIcon icon) {
+ mPauseFlags |= FLAG_REMOVING_PREDICTED_ICON;
+ mHotseat.removeView(icon);
+ mPauseFlags &= ~FLAG_REMOVING_PREDICTED_ICON;
+ }
+
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
removePredictedApps(mOutlineDrawings, dragObject);
@@ -446,16 +458,6 @@
* Logs rank info based on current list of predicted items
*/
public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
- if (Utilities.IS_DEBUG_DEVICE) {
- final String pkg = itemInfo.getTargetComponent() != null
- ? itemInfo.getTargetComponent().getPackageName() : "unknown";
- HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
- "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
- && !Process.myUserHandle().equals(itemInfo.user))
- + ",launchLocation:" + itemInfo.container);
- }
-
-
ComponentName targetCN = itemInfo.getTargetComponent();
if (targetCN == null) {
return;
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index c3f5c00..2ea8bd2 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -84,7 +84,7 @@
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
- private boolean mActive = false;
+ protected boolean mActive = false;
public QuickstepModelDelegate(Context context) {
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
@@ -148,10 +148,13 @@
? (FolderInfo) itemsIdMap.get(info.container) : null;
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
}
+ additionalSnapshotEvents(instanceId);
prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
}
}
+ protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
+
@Override
public void validateData() {
super.validateData();
@@ -200,7 +203,6 @@
.setPredictedTargetCount(mIDP.numHotseatIcons)
.setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
.build()));
-
}
private void registerPredictor(PredictorState state, AppPredictor predictor) {
@@ -236,14 +238,14 @@
static class PredictorState {
public final FixedContainerItems items;
- public final PersistedItemArray storage;
+ public final PersistedItemArray<ItemInfo> storage;
public AppPredictor predictor;
private List<AppTarget> mLastTargets;
PredictorState(int container, String storageName) {
items = new FixedContainerItems(container);
- storage = new PersistedItemArray(storageName);
+ storage = new PersistedItemArray<>(storageName);
mLastTargets = Collections.emptyList();
}
@@ -255,7 +257,7 @@
}
/**
- * Sets the new targets and returns true if it was different than before.
+ * Sets the new targets and returns true if it was the same as before.
*/
boolean setTargets(List<AppTarget> newTargets) {
List<AppTarget> oldTargets = mLastTargets;
@@ -289,7 +291,7 @@
return true;
}
- private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory {
+ private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory<ItemInfo> {
private final LauncherAppState mAppState;
private final UserManagerState mUMS;
diff --git a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
index e302b4f..4d7cc85 100644
--- a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
+++ b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
@@ -17,6 +17,7 @@
package com.android.launcher3.proxy;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
@@ -48,19 +49,20 @@
return;
}
- if (mParams.intent != null) {
- startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
- return;
- } else if (mParams.intentSender != null) {
- try {
+ try {
+ if (mParams.intent != null) {
+ startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
+ return;
+ } else if (mParams.intentSender != null) {
startIntentSenderForResult(mParams.intentSender, mParams.requestCode,
mParams.fillInIntent, mParams.flagsMask, mParams.flagsValues,
mParams.extraFlags,
mParams.options);
return;
- } catch (SendIntentException e) {
- mParams.deliverResult(this, RESULT_CANCELED, null);
}
+ } catch (NullPointerException | ActivityNotFoundException | SecurityException
+ | SendIntentException e) {
+ mParams.deliverResult(this, RESULT_CANCELED, null);
}
finishAndRemoveTask();
}
diff --git a/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java b/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java
deleted file mode 100644
index 4a656c1..0000000
--- a/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java
+++ /dev/null
@@ -1,155 +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.search;
-
-import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
-import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_ICON;
-
-import android.app.search.SearchTarget;
-import android.util.Log;
-import android.util.SparseIntArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.app.search.LayoutType;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
-import com.android.launcher3.config.FeatureFlags;
-
-/**
- * Provides views for on-device search results
- */
-public class DeviceSearchAdapterProvider extends SearchAdapterProvider {
-
- public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
- public static final int VIEW_TYPE_SEARCH_SLICE = 1 << 7;
- public static final int VIEW_TYPE_SEARCH_ICON = (1 << 8) | VIEW_TYPE_ICON;
- public static final int VIEW_TYPE_SEARCH_ICON_ROW = (1 << 9);
- public static final int VIEW_TYPE_SEARCH_SMALL_ICON_ROW = (1 << 10);
- public static final int VIEW_TYPE_SEARCH_THUMBNAIL = 1 << 12;
- public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15;
- public static final int VIEW_TYPE_SEARCH_WIDGET_PREVIEW = 1 << 16;
-
- private static final String TAG = "SearchServiceAdapter";
-
- private final AllAppsContainerView mAppsView;
- private final SparseIntArray mViewTypeToLayoutMap = new SparseIntArray();
-
- public DeviceSearchAdapterProvider(Launcher launcher, AllAppsContainerView appsView) {
- super(launcher);
- mAppsView = appsView;
-
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_CORPUS_TITLE, R.layout.search_section_title);
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ICON, R.layout.search_result_icon);
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ICON_ROW, R.layout.search_result_icon_row);
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_SMALL_ICON_ROW, R.layout.search_result_small_icon_row);
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_SLICE, R.layout.search_result_slice);
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_THUMBNAIL, R.layout.search_result_thumbnail);
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_WIDGET_LIVE, R.layout.search_result_widget_live);
- mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_WIDGET_PREVIEW,
- R.layout.search_result_widget_preview);
- mViewTypeToLayoutMap.put(VIEW_TYPE_ALL_APPS_DIVIDER, R.layout.all_apps_divider);
- }
-
- @Override
- public void onBindView(AllAppsGridAdapter.ViewHolder holder, int position) {
- SearchAdapterItem item = (SearchAdapterItem) mAppsView.getApps().getAdapterItems().get(
- position);
- SearchTargetHandler
- payloadResultView =
- (SearchTargetHandler) holder.itemView;
- payloadResultView.apply(item.getSearchTarget(), item.getInlineItems());
- }
-
- @Override
- public boolean isSearchView(int viewType) {
- return mViewTypeToLayoutMap.get(viewType, -1) != -1;
- }
-
- @Override
- public AllAppsGridAdapter.ViewHolder onCreateViewHolder(LayoutInflater inflater,
- ViewGroup parent, int viewType) {
- return new AllAppsGridAdapter.ViewHolder(inflater.inflate(
- mViewTypeToLayoutMap.get(viewType), parent, false));
- }
-
- @Override
- public int getGridSpanSize(int viewType, int appsPerRow) {
- if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL
- || viewType == VIEW_TYPE_SEARCH_WIDGET_PREVIEW) {
- return appsPerRow;
- }
- return super.getGridSpanSize(viewType, appsPerRow);
- }
-
-
- @Override
- public boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem adapterItem, View view) {
- if (view instanceof SearchTargetHandler) {
- return ((SearchTargetHandler) view).quickSelect();
- }
- return false;
- }
-
- /**
- * Determines what view type should be used to present search target.
- * Returns -1 if viewType is not found or if required field is not present
- * to render the viewType.
- */
- public int getViewTypeForSearchTarget(SearchTarget t) {
- switch (t.getLayoutType()) {
- case LayoutType.TEXT_HEADER:
- return VIEW_TYPE_SEARCH_CORPUS_TITLE;
- case LayoutType.ICON_SINGLE_VERTICAL_TEXT:
- return VIEW_TYPE_SEARCH_ICON;
- case LayoutType.ICON_SLICE:
- if (FeatureFlags.DISABLE_SLICE_IN_ALLAPPS.get()) {
- return -1;
- }
- if (t.getSliceUri() != null) {
- return VIEW_TYPE_SEARCH_SLICE;
- }
- Log.w(TAG, "LayoutType.ICON_SLICE target doesn't contain sliceUri.");
- break;
- case LayoutType.ICON_DOUBLE_HORIZONTAL_TEXT:
- case LayoutType.ICON_SINGLE_HORIZONTAL_TEXT:
- case LayoutType.ICON_DOUBLE_HORIZONTAL_TEXT_BUTTON:
- case LayoutType.ICON_HORIZONTAL_TEXT:
- return VIEW_TYPE_SEARCH_ICON_ROW;
- case LayoutType.SMALL_ICON_HORIZONTAL_TEXT:
- return VIEW_TYPE_SEARCH_SMALL_ICON_ROW;
- case LayoutType.THUMBNAIL:
- if (t.getSearchAction() != null) {
- return VIEW_TYPE_SEARCH_THUMBNAIL;
- }
- Log.w(TAG, "LayoutType.THUMBNAIL target doesn't contain searchAction.");
- break;
- case LayoutType.WIDGET_PREVIEW:
- return VIEW_TYPE_SEARCH_WIDGET_PREVIEW;
- case LayoutType.WIDGET_LIVE:
- return VIEW_TYPE_SEARCH_WIDGET_LIVE;
- case LayoutType.DIVIDER:
- return VIEW_TYPE_ALL_APPS_DIVIDER;
- }
-
- return -1;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/DeviceSearchEdu.java b/quickstep/src/com/android/launcher3/search/DeviceSearchEdu.java
deleted file mode 100644
index 425e557..0000000
--- a/quickstep/src/com/android/launcher3/search/DeviceSearchEdu.java
+++ /dev/null
@@ -1,221 +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.search;
-
-import static com.android.launcher3.util.OnboardingPrefs.SEARCH_EDU_SEEN;
-
-import android.animation.PropertyValuesHolder;
-import android.content.Context;
-import android.graphics.Rect;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import androidx.core.graphics.ColorUtils;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.AbstractSlideInView;
-
-/**
- * Feature education for on-device Search. Shown the first time user opens AllApps Search
- */
-public class DeviceSearchEdu extends AbstractSlideInView implements
- StateManager.StateListener<LauncherState>, TextWatcher, Insettable,
- TextView.OnEditorActionListener {
-
- private static final long ANIMATION_DURATION = 350;
- private static final int ANIMATION_CONTENT_TRANSLATION = 200;
-
- private EditText mEduInput;
-
- private View mInputWrapper;
- private EditText mSearchInput;
-
- private boolean mSwitchFocusOnDismiss;
-
-
- public DeviceSearchEdu(Context context) {
- this(context, null, 0);
- }
-
- public DeviceSearchEdu(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DeviceSearchEdu(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
-
- private void close(boolean animate, boolean markAsSeen) {
- handleClose(animate);
- if (markAsSeen) {
- mLauncher.getOnboardingPrefs().markChecked(SEARCH_EDU_SEEN);
- }
- }
-
- @Override
- protected void handleClose(boolean animate) {
- handleClose(animate, ANIMATION_DURATION);
- mLauncher.getStateManager().removeStateListener(this);
- }
-
- @Override
- protected boolean isOfType(int type) {
- return false;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSearchInput = mLauncher.getAppsView().getSearchUiManager().getEditText();
- mInputWrapper = findViewById(R.id.search_box_wrapper);
- mContent = findViewById(R.id.edu_wrapper);
-
- mEduInput = findViewById(R.id.mock_search_box);
- mEduInput.setHint(R.string.all_apps_on_device_search_bar_hint);
- mEduInput.addTextChangedListener(this);
- if (mSearchInput != null) {
- mEduInput.getLayoutParams().height = mSearchInput.getHeight();
- mEduInput.setOnEditorActionListener(this);
- } else {
- mEduInput.setVisibility(INVISIBLE);
- }
-
- findViewById(R.id.dismiss_edu).setOnClickListener((view) -> {
- mSwitchFocusOnDismiss = true;
- close(true, true);
- });
- }
-
- private void showInternal() {
- mLauncher.getStateManager().addStateListener(this);
- AbstractFloatingView.closeAllOpenViews(mLauncher);
- attachToContainer();
- if (mSearchInput != null) {
- Rect r = mLauncher.getViewBounds(mSearchInput);
- mEduInput.requestFocus();
- InputMethodManager imm = mLauncher.getSystemService(InputMethodManager.class);
- imm.showSoftInput(mEduInput, InputMethodManager.SHOW_IMPLICIT);
- ((LayoutParams) mInputWrapper.getLayoutParams()).setMargins(0, r.top, 0, 0);
- }
- animateOpen();
- }
-
- @Override
- protected int getScrimColor(Context context) {
- return ColorUtils.setAlphaComponent(Themes.getAttrColor(context, R.attr.allAppsScrimColor),
- 230);
- }
-
- protected void setTranslationShift(float translationShift) {
- mTranslationShift = translationShift;
- mContent.setAlpha(getBoxedProgress(1 - mTranslationShift, .25f, 1));
- mContent.setTranslationY(ANIMATION_CONTENT_TRANSLATION * translationShift);
- if (mColorScrim != null) {
- mColorScrim.setAlpha(getBoxedProgress(1 - mTranslationShift, 0, .75f));
- }
- }
-
- /**
- * Given input [0-1], returns progress within bounds [min,max] allowing for staged animations
- */
- private float getBoxedProgress(float input, float min, float max) {
- if (input < min) return 0;
- if (input > max) return 1;
- return (input - min) / (max - min);
- }
-
- private void animateOpen() {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
- return;
- }
- mIsOpen = true;
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.setDuration(ANIMATION_DURATION);
- mOpenCloseAnimator.start();
- }
-
- /**
- * Show On-device search education view.
- */
- public static void show(Launcher launcher) {
- LayoutInflater layoutInflater = LayoutInflater.from(launcher);
- ((DeviceSearchEdu) layoutInflater.inflate(
- R.layout.search_edu_view, launcher.getDragLayer(),
- false)).showInternal();
- }
-
- @Override
- public void onStateTransitionStart(LauncherState toState) {
- close(true, false);
- }
-
- @Override
- protected void onCloseComplete() {
- super.onCloseComplete();
- if (mSearchInput != null && mSwitchFocusOnDismiss) {
- mSearchInput.requestFocus();
- mSearchInput.setSelection(mSearchInput.getText().length());
- }
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- //Does nothing
- }
-
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- //Does nothing
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
- if (mSearchInput != null) {
- mSearchInput.setText(charSequence.toString());
- mSwitchFocusOnDismiss = true;
- close(true, true);
- }
- }
-
- @Override
- public void setInsets(Rect insets) {
-
- }
-
- @Override
- public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
- mSearchInput.onEditorAction(i);
- close(true, true);
- return true;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/FallbackSearchInputView.java b/quickstep/src/com/android/launcher3/search/FallbackSearchInputView.java
deleted file mode 100644
index 7b10622..0000000
--- a/quickstep/src/com/android/launcher3/search/FallbackSearchInputView.java
+++ /dev/null
@@ -1,140 +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.search;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.Launcher;
-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.FloatingHeaderView;
-import com.android.launcher3.allapps.search.AllAppsSearchBarController;
-import com.android.launcher3.allapps.search.SearchAlgorithm;
-
-import java.util.ArrayList;
-
-/**
- * A search view shown in all apps for on device search
- */
-public class FallbackSearchInputView extends ExtendedEditText
- implements AllAppsSearchBarController.Callbacks, AllAppsStore.OnUpdateListener {
-
- private final AllAppsSearchBarController mSearchBarController;
-
- private AlphabeticalAppsList mApps;
- private Runnable mOnResultsChanged;
- private AllAppsContainerView mAppsView;
-
- public FallbackSearchInputView(Context context) {
- this(context, null);
- }
-
- public FallbackSearchInputView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public FallbackSearchInputView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mSearchBarController = new AllAppsSearchBarController();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Launcher.getLauncher(getContext()).getAppsView().getAppsStore().addUpdateListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Launcher.getLauncher(getContext()).getAppsView().getAppsStore().removeUpdateListener(this);
- }
-
- /**
- * Initializes SearchInput
- */
- public void initialize(AllAppsContainerView appsView, SearchAlgorithm algo, Runnable changed) {
- mOnResultsChanged = changed;
- mApps = appsView.getApps();
- mAppsView = appsView;
- mSearchBarController.initialize(algo, this, Launcher.getLauncher(getContext()), this);
- }
-
- @Override
- public void onSearchResult(String query, ArrayList<AdapterItem> items) {
- if (mApps != null && getParent() != null) {
- mApps.setSearchResults(items);
- notifyResultChanged();
- collapseAppsViewHeader(true);
- mAppsView.setLastSearchQuery(query);
- }
- }
-
- @Override
- public void onAppendSearchResult(String query, ArrayList<AdapterItem> items) {
- if (mApps != null && getParent() != null) {
- mApps.appendSearchResults(items);
- notifyResultChanged();
- }
- }
-
- @Override
- public void clearSearchResult() {
- if (getParent() != null && mApps != null) {
- mApps.setSearchResults(null);
- notifyResultChanged();
- collapseAppsViewHeader(false);
- mAppsView.onClearSearchResult();
- }
- }
-
- @Override
- public void onAppsUpdated() {
- mSearchBarController.refreshSearchResult();
- }
-
- private void collapseAppsViewHeader(boolean collapse) {
- FloatingHeaderView header = mAppsView.getFloatingHeaderView();
- if (header != null) {
- header.setCollapsed(collapse);
- }
- }
-
- private void notifyResultChanged() {
- if (mOnResultsChanged != null) {
- mOnResultsChanged.run();
- }
- mAppsView.onSearchResultsChanged();
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- // TODO: Consider animating the state transition here
- if (focused) {
- // Getting focus will open the keyboard. Go to the all-apps state, so that the input
- // box is at the top and there is enough space below to show search results.
- Launcher.getLauncher(getContext()).getStateManager().goToState(ALL_APPS, false);
- }
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java b/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java
deleted file mode 100644
index 8983c4f..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java
+++ /dev/null
@@ -1,66 +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.search;
-
-import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ICON;
-import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ICON_ROW;
-import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_SMALL_ICON_ROW;
-import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_SLICE;
-import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_THUMBNAIL;
-import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_WIDGET_LIVE;
-import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_WIDGET_PREVIEW;
-
-import android.app.search.SearchTarget;
-
-import com.android.launcher3.allapps.AllAppsGridAdapter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Extension of AdapterItem that contains an extra payload specific to item
- */
-public class SearchAdapterItem extends AllAppsGridAdapter.AdapterItem {
- private SearchTarget mSearchTarget;
- private List<SearchTarget> mInlineItems = new ArrayList<>();
-
-
- private static final int AVAILABLE_FOR_ACCESSIBILITY = VIEW_TYPE_SEARCH_SLICE
- | VIEW_TYPE_SEARCH_THUMBNAIL
- | VIEW_TYPE_SEARCH_ICON_ROW
- | VIEW_TYPE_SEARCH_ICON
- | VIEW_TYPE_SEARCH_SMALL_ICON_ROW
- | VIEW_TYPE_SEARCH_WIDGET_PREVIEW
- | VIEW_TYPE_SEARCH_WIDGET_LIVE;
-
- public SearchAdapterItem(SearchTarget searchTarget, int type) {
- mSearchTarget = searchTarget;
- viewType = type;
- }
-
- public SearchTarget getSearchTarget() {
- return mSearchTarget;
- }
-
- public List<SearchTarget> getInlineItems() {
- return mInlineItems;
- }
- @Override
- protected boolean isCountedForAccessibility() {
- return (AVAILABLE_FOR_ACCESSIBILITY & viewType) == viewType;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
deleted file mode 100644
index f7d5f45..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
+++ /dev/null
@@ -1,329 +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.search;
-
-import static com.android.launcher3.model.data.SearchActionItemInfo.FLAG_BADGE_WITH_PACKAGE;
-import static com.android.launcher3.model.data.SearchActionItemInfo.FLAG_PRIMARY_ICON_FROM_TITLE;
-import static com.android.launcher3.search.SearchTargetUtil.BUNDLE_EXTRA_PRIMARY_ICON_FROM_TITLE;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.app.search.SearchAction;
-import android.app.search.SearchTarget;
-import android.app.search.SearchTargetEvent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.app.search.ResultType;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
-import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer;
-import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.model.data.SearchActionItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.Themes;
-
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * A {@link BubbleTextView} representing a single cell result in AllApps
- */
-public class SearchResultIcon extends BubbleTextView implements
- SearchTargetHandler, View.OnClickListener,
- View.OnLongClickListener {
-
- //Play store thumbnail process workaround
- private final Rect mTempRect = new Rect();
- private final Paint mIconPaint = new Paint();
- private static final int BITMAP_CROP_MASK_COLOR = 0xff424242;
-
- private final Launcher mLauncher;
-
- private String mTargetId;
- private Consumer<ItemInfoWithIcon> mOnItemInfoChanged;
-
-
- public SearchResultIcon(Context context) {
- this(context, null, 0);
- }
-
- public SearchResultIcon(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchResultIcon(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mLauncher = Launcher.getLauncher(getContext());
- }
-
- private boolean mLongPressSupported;
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setLongPressTimeoutFactor(1f);
- setOnFocusChangeListener(mLauncher.getFocusHandler());
- setOnClickListener(this);
- setOnLongClickListener(this);
- setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- mLauncher.getDeviceProfile().allAppsCellHeightPx));
- }
-
- /**
- * Applies {@link SearchTarget} to view. registers a consumer after a corresponding
- * {@link ItemInfoWithIcon} is created
- */
- public void apply(SearchTarget searchTarget, List<SearchTarget> inlineItems,
- Consumer<ItemInfoWithIcon> cb) {
- mOnItemInfoChanged = cb;
- apply(searchTarget, inlineItems);
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- mTargetId = parentTarget.getId();
- if (parentTarget.getShortcutInfo() != null) {
- prepareUsingShortcutInfo(parentTarget.getShortcutInfo());
- mLongPressSupported = true;
- } else if (parentTarget.getSearchAction() != null) {
- prepareUsingSearchAction(parentTarget);
- mLongPressSupported = false;
- } else {
- String className = parentTarget.getExtras().getString(SearchTargetUtil.EXTRA_CLASS);
- prepareUsingApp(new ComponentName(parentTarget.getPackageName(), className),
- parentTarget.getUserHandle());
- mLongPressSupported = true;
- }
- }
-
- private void prepareUsingSearchAction(SearchTarget searchTarget) {
- SearchAction searchAction = searchTarget.getSearchAction();
- Bundle extras = searchAction.getExtras();
-
- SearchActionItemInfo itemInfo = new SearchActionItemInfo(searchAction.getIcon(),
- searchTarget.getPackageName(), searchTarget.getUserHandle(),
- searchAction.getTitle()) {
- // Workaround to log ItemInfo with DeviceSearchResultContainer without
- // updating ItemInfo.container field.
- @Override
- public ContainerInfo getContainerInfo() {
- return buildDeviceSearchResultContainer();
- }
- };
- itemInfo.setIntent(searchAction.getIntent());
- itemInfo.setPendingIntent(searchAction.getPendingIntent());
-
- //TODO: remove this after flags are introduced in SearchAction. Settings results require
- // startActivityForResult
- boolean isSettingsResult = searchTarget.getResultType() == ResultType.SETTING;
- if ((extras != null && extras.getBoolean(
- SearchTargetUtil.BUNDLE_EXTRA_SHOULD_START_FOR_RESULT))
- || isSettingsResult) {
- itemInfo.setFlags(SearchActionItemInfo.FLAG_SHOULD_START_FOR_RESULT);
- } else if (extras != null && extras.getBoolean(
- SearchTargetUtil.BUNDLE_EXTRA_SHOULD_START)) {
- itemInfo.setFlags(SearchActionItemInfo.FLAG_SHOULD_START);
- }
- if (extras != null && extras.getBoolean(
- SearchTargetUtil.BUNDLE_EXTRA_BADGE_WITH_PACKAGE)) {
- itemInfo.setFlags(FLAG_BADGE_WITH_PACKAGE);
- }
- if (extras != null && extras.getBoolean(BUNDLE_EXTRA_PRIMARY_ICON_FROM_TITLE)) {
- itemInfo.setFlags(FLAG_PRIMARY_ICON_FROM_TITLE);
- }
-
- notifyItemInfoChanged(itemInfo);
- LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
- MODEL_EXECUTOR.post(() -> {
- try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
- Icon icon = searchTarget.getSearchAction().getIcon();
- BitmapInfo pkgBitmap = getPackageBitmap(appState, searchTarget);
- if (itemInfo.hasFlags(FLAG_PRIMARY_ICON_FROM_TITLE)) {
- // create a bitmap with first char if FLAG_PRIMARY_ICON_FROM_TITLE is set
- itemInfo.bitmap = li.createIconBitmap(String.valueOf(itemInfo.title.charAt(0)),
- pkgBitmap.color);
- } else if (icon == null) {
- // Use default icon from package name
- itemInfo.bitmap = pkgBitmap;
- } else {
- boolean isPlayResult = searchTarget.getResultType() == ResultType.PLAY;
- if (isPlayResult) {
- Bitmap b = getPlayResultBitmap(searchAction.getIcon());
- itemInfo.bitmap = b == null
- ? BitmapInfo.LOW_RES_INFO : BitmapInfo.fromBitmap(b);
- } else {
- itemInfo.bitmap = li.createBadgedIconBitmap(icon.loadDrawable(getContext()),
- itemInfo.user, false);
- }
- }
-
- // badge with package name
- if (itemInfo.hasFlags(FLAG_BADGE_WITH_PACKAGE) && itemInfo.bitmap != pkgBitmap) {
- itemInfo.bitmap = li.badgeBitmap(itemInfo.bitmap.icon, pkgBitmap);
- }
- }
- MAIN_EXECUTOR.post(() -> applyFromSearchActionItemInfo(itemInfo));
- });
- }
-
- private static BitmapInfo getPackageBitmap(LauncherAppState appState, SearchTarget target) {
- PackageItemInfo pkgInfo = new PackageItemInfo(target.getPackageName());
- pkgInfo.user = target.getUserHandle();
- appState.getIconCache().getTitleAndIconForApp(pkgInfo, false);
- return pkgInfo.bitmap;
- }
-
- private Bitmap getPlayResultBitmap(Icon icon) {
- try {
- int iconSize = getIconSize();
- URL url = new URL(icon.getUri().toString());
- URLConnection con = url.openConnection();
- con.addRequestProperty("Cache-Control", "max-age: 0");
- con.setUseCaches(true);
- Bitmap bitmap = BitmapFactory.decodeStream(con.getInputStream());
- return getRoundedBitmap(Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false));
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
-
- private Bitmap getRoundedBitmap(Bitmap bitmap) {
- final int iconSize = bitmap.getWidth();
- final float radius = Themes.getDialogCornerRadius(getContext());
-
- return BitmapRenderer.createHardwareBitmap(iconSize, iconSize, (canvas) -> {
- mTempRect.set(0, 0, iconSize, iconSize);
- final RectF rectF = new RectF(mTempRect);
-
- mIconPaint.setAntiAlias(true);
- mIconPaint.reset();
- canvas.drawARGB(0, 0, 0, 0);
- mIconPaint.setColor(BITMAP_CROP_MASK_COLOR);
- canvas.drawRoundRect(rectF, radius, radius, mIconPaint);
-
- mIconPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- canvas.drawBitmap(bitmap, mTempRect, mTempRect, mIconPaint);
- });
- }
-
- private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) {
- AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
- AppInfo appInfo = new AppInfo(
- appsStore.getApp(new ComponentKey(componentName, userHandle))) {
- // Workaround to log ItemInfo with DeviceSearchResultContainer without
- // updating ItemInfo.container field.
- @Override
- public ContainerInfo getContainerInfo() {
- return buildDeviceSearchResultContainer();
- }
- };
-
- if (appInfo == null) {
- setVisibility(GONE);
- return;
- }
- applyFromApplicationInfo(appInfo);
- notifyItemInfoChanged(appInfo);
- }
-
- private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
- WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()) {
- // Workaround to log ItemInfo with DeviceSearchResultContainer without
- // updating ItemInfo.container field.
- @Override
- public ContainerInfo getContainerInfo() {
- return buildDeviceSearchResultContainer();
- }
- };
- notifyItemInfoChanged(workspaceItemInfo);
- LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
- MODEL_EXECUTOR.execute(() -> {
- launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
- MAIN_EXECUTOR.post(() -> applyFromWorkspaceItem(workspaceItemInfo));
- });
- }
-
- @Override
- public boolean quickSelect() {
- this.performClick();
- notifyEvent(mLauncher, mTargetId, SearchTargetEvent.ACTION_LAUNCH_KEYBOARD_FOCUS);
- return true;
- }
-
- @Override
- public void onClick(View view) {
- ItemClickHandler.INSTANCE.onClick(view);
- notifyEvent(mLauncher, mTargetId, SearchTargetEvent.ACTION_LAUNCH_TOUCH);
- }
-
- @Override
- public boolean onLongClick(View view) {
- if (!mLongPressSupported) {
- return false;
- }
- notifyEvent(mLauncher, mTargetId, SearchTargetEvent.ACTION_LONGPRESS);
- return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(this);
- }
-
-
- private void notifyItemInfoChanged(ItemInfoWithIcon itemInfoWithIcon) {
- if (mOnItemInfoChanged != null) {
- mOnItemInfoChanged.accept(itemInfoWithIcon);
- mOnItemInfoChanged = null;
- }
- }
-
- private static ContainerInfo buildDeviceSearchResultContainer() {
- return ContainerInfo.newBuilder().setExtendedContainers(
- ExtendedContainers
- .newBuilder()
- .setDeviceSearchResultContainer(
- DeviceSearchResultContainer
- .newBuilder()))
- .build();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java b/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java
deleted file mode 100644
index 12a1a1c..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java
+++ /dev/null
@@ -1,158 +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.search;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.app.search.SearchTarget;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.PackageItemInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A full width representation of {@link SearchResultIcon} with a secondary label and inline
- * SearchTargets
- */
-public class SearchResultIconRow extends LinearLayout implements SearchTargetHandler {
-
- public static final int MAX_INLINE_ITEMS = 3;
-
- protected final Launcher mLauncher;
- protected final SearchResultIcon[] mInlineIcons = new SearchResultIcon[MAX_INLINE_ITEMS];
- private SearchResultIcon mResultIcon;
-
- private final LauncherAppState mLauncherAppState;
- private TextView mTitleView;
- private TextView mSubTitleView;
-
- private PackageItemInfo mProviderInfo;
-
- public SearchResultIconRow(Context context) {
- this(context, null, 0);
- }
-
- public SearchResultIconRow(Context context,
- @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchResultIconRow(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(getContext());
- mLauncherAppState = LauncherAppState.getInstance(getContext());
- }
-
- protected int getIconSize() {
- return mLauncher.getDeviceProfile().allAppsIconSizePx;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- int iconSize = getIconSize();
-
- mResultIcon = findViewById(R.id.icon);
-
- mTitleView = findViewById(R.id.title);
- mSubTitleView = findViewById(R.id.subtitle);
- mSubTitleView.setVisibility(GONE);
-
- mResultIcon.getLayoutParams().height = iconSize;
- mResultIcon.getLayoutParams().width = iconSize;
- mResultIcon.setTextVisibility(false);
-
- mInlineIcons[0] = findViewById(R.id.shortcut_0);
- mInlineIcons[1] = findViewById(R.id.shortcut_1);
- mInlineIcons[2] = findViewById(R.id.shortcut_2);
- for (SearchResultIcon inlineIcon : mInlineIcons) {
- inlineIcon.getLayoutParams().width = getIconSize();
- }
- setOnClickListener(mResultIcon);
- setOnLongClickListener(mResultIcon);
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- mResultIcon.apply(parentTarget, children, this::onItemInfoCreated);
-
- showSubtitleIfNeeded(null);
-
- if (parentTarget.getShortcutInfo() != null) {
- updateWithShortcutInfo(parentTarget.getShortcutInfo());
- } else if (parentTarget.getSearchAction() != null) {
- showSubtitleIfNeeded(parentTarget.getSearchAction().getSubtitle());
- }
- showInlineItems(children);
- }
-
- @Override
- public boolean quickSelect() {
- this.performClick();
- return true;
- }
-
- private void updateWithShortcutInfo(ShortcutInfo shortcutInfo) {
- PackageItemInfo packageItemInfo = new PackageItemInfo(shortcutInfo.getPackage());
- if (packageItemInfo.equals(mProviderInfo)) return;
- MODEL_EXECUTOR.post(() -> {
- mLauncherAppState.getIconCache().getTitleAndIconForApp(packageItemInfo, true);
- MAIN_EXECUTOR.post(() -> {
- showSubtitleIfNeeded(packageItemInfo.title);
- mProviderInfo = packageItemInfo;
- });
- });
- }
-
- protected void showSubtitleIfNeeded(CharSequence subTitle) {
- if (!TextUtils.isEmpty(subTitle)) {
- mSubTitleView.setText(subTitle);
- mSubTitleView.setVisibility(VISIBLE);
- } else {
- mSubTitleView.setVisibility(GONE);
- }
- }
-
- protected void showInlineItems(List<SearchTarget> children) {
- for (int i = 0; i < MAX_INLINE_ITEMS; i++) {
- if (i < children.size()) {
- mInlineIcons[i].apply(children.get(i), new ArrayList<>());
- mInlineIcons[i].setVisibility(VISIBLE);
- } else {
- mInlineIcons[i].setVisibility(GONE);
- }
- }
- }
-
- protected void onItemInfoCreated(ItemInfoWithIcon info) {
- setTag(info);
- mTitleView.setText(info.title);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java b/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java
deleted file mode 100644
index 4bf3432..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java
+++ /dev/null
@@ -1,122 +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.search;
-
-import android.app.search.SearchTarget;
-import android.app.search.SearchTargetEvent;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.slice.SliceItem;
-import androidx.slice.widget.EventInfo;
-import androidx.slice.widget.SliceView;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.util.SafeCloseable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A slice view wrapper with settings app icon at start
- */
-public class SearchResultIconSlice extends LinearLayout implements SearchTargetHandler,
- SliceView.OnSliceActionListener {
-
- private final Launcher mLauncher;
-
- private SliceView mSliceView;
- private SearchResultIcon mIcon;
- private SafeCloseable mSliceSession;
- private String mTargetId;
-
- public SearchResultIconSlice(Context context) {
- this(context, null, 0);
- }
-
- public SearchResultIconSlice(Context context,
- @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchResultIconSlice(Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(getContext());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSliceView = findViewById(R.id.slice);
- mIcon = findViewById(R.id.icon);
- mIcon.setTextVisibility(false);
- int iconSize = mLauncher.getDeviceProfile().iconSizePx;
- mIcon.getLayoutParams().height = iconSize;
- mIcon.getLayoutParams().width = iconSize;
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- mTargetId = parentTarget.getId();
- reset();
- updateIcon(parentTarget, children);
- mSliceSession = mLauncher.getLiveSearchManager()
- .addObserver(parentTarget.getSliceUri(), mSliceView);
- }
-
- private void updateIcon(SearchTarget parentTarget, List<SearchTarget> children) {
- if (children.size() == 1) {
- mIcon.apply(children.get(0), new ArrayList<>());
- } else {
- PackageItemInfo pkgItem = new PackageItemInfo(parentTarget.getPackageName());
- pkgItem.user = parentTarget.getUserHandle();
- if (!pkgItem.equals(mIcon.getTag())) {
- // The icon will load and apply high res icon automatically
- mIcon.applyFromItemInfoWithIcon(pkgItem);
- }
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mSliceView.setOnSliceActionListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- reset();
- }
-
- private void reset() {
- mSliceView.setOnSliceActionListener(null);
- if (mSliceSession != null) {
- mSliceSession.close();
- }
- }
-
- @Override
- public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) {
- notifyEvent(mLauncher, mTargetId, SearchTargetEvent.ACTION_TAP);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultSmallIconRow.java b/quickstep/src/com/android/launcher3/search/SearchResultSmallIconRow.java
deleted file mode 100644
index ca8aa81..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchResultSmallIconRow.java
+++ /dev/null
@@ -1,142 +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.search;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.app.search.SearchTarget;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.PackageItemInfo;
-
-import java.util.List;
-
-/**
- * A full width representation of {@link SearchResultIcon} with a secondary label and inline
- * SearchTargets
- */
-public class SearchResultSmallIconRow extends LinearLayout implements SearchTargetHandler {
-
- protected final Launcher mLauncher;
- private final LauncherAppState mLauncherAppState;
- protected SearchResultIcon mResultIcon;
-
- private TextView mTitleView;
- private TextView mDelimeterView;
- private TextView mSubTitleView;
-
- private PackageItemInfo mProviderInfo;
-
- public SearchResultSmallIconRow(Context context) {
- this(context, null, 0);
- }
-
- public SearchResultSmallIconRow(Context context,
- @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchResultSmallIconRow(Context context,
- @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(getContext());
- mLauncherAppState = LauncherAppState.getInstance(getContext());
- }
-
- protected int getIconSize() {
- return mLauncher.getDeviceProfile().allAppsIconSizePx;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- int iconSize = getIconSize();
-
- mResultIcon = findViewById(R.id.icon);
-
- mTitleView = findViewById(R.id.title);
- mDelimeterView = findViewById(R.id.delimeter);
- mDelimeterView.setVisibility(GONE);
- mSubTitleView = findViewById(R.id.subtitle);
- mSubTitleView.setVisibility(GONE);
-
- mResultIcon.getLayoutParams().height = iconSize;
- mResultIcon.getLayoutParams().width = iconSize;
- mResultIcon.setTextVisibility(false);
-
- setOnClickListener(mResultIcon);
- setOnLongClickListener(mResultIcon);
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- mResultIcon.apply(parentTarget, children, this::onItemInfoCreated);
-
- showSubtitleIfNeeded(null);
-
- if (parentTarget.getShortcutInfo() != null) {
- updateWithShortcutInfo(parentTarget.getShortcutInfo());
- } else if (parentTarget.getSearchAction() != null) {
- showSubtitleIfNeeded(parentTarget.getSearchAction().getSubtitle());
- }
- }
-
- @Override
- public boolean quickSelect() {
- this.performClick();
- return true;
- }
-
- private void updateWithShortcutInfo(ShortcutInfo shortcutInfo) {
- PackageItemInfo packageItemInfo = new PackageItemInfo(shortcutInfo.getPackage());
- if (packageItemInfo.equals(mProviderInfo)) return;
- MODEL_EXECUTOR.post(() -> {
- mLauncherAppState.getIconCache().getTitleAndIconForApp(packageItemInfo, true);
- MAIN_EXECUTOR.post(() -> {
- showSubtitleIfNeeded(packageItemInfo.title);
- mProviderInfo = packageItemInfo;
- });
- });
- }
-
- protected void showSubtitleIfNeeded(CharSequence subTitle) {
- if (!TextUtils.isEmpty(subTitle)) {
- mSubTitleView.setText(subTitle);
- mSubTitleView.setVisibility(VISIBLE);
- mDelimeterView.setVisibility(VISIBLE);
-
- } else {
- mSubTitleView.setVisibility(GONE);
- }
- }
-
- protected void onItemInfoCreated(ItemInfoWithIcon info) {
- setTag(info);
- mTitleView.setText(info.title);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultThumbnailView.java b/quickstep/src/com/android/launcher3/search/SearchResultThumbnailView.java
deleted file mode 100644
index 8803c98..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchResultThumbnailView.java
+++ /dev/null
@@ -1,102 +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.search;
-
-
-import android.app.search.SearchTarget;
-import android.app.search.SearchTargetEvent;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.model.data.SearchActionItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.util.Themes;
-
-import java.util.List;
-
-/**
- * A view representing a high confidence app search result that includes shortcuts
- */
-public class SearchResultThumbnailView extends androidx.appcompat.widget.AppCompatImageView
- implements SearchTargetHandler, View.OnClickListener {
-
- private SearchTarget mSearchTarget;
-
- public SearchResultThumbnailView(Context context) {
- super(context);
- }
-
- public SearchResultThumbnailView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SearchResultThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setOnFocusChangeListener(Launcher.getLauncher(getContext()).getFocusHandler());
- setOnClickListener(this);
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- mSearchTarget = parentTarget;
- Bitmap bitmap;
-
- SearchActionItemInfo itemInfo = new SearchActionItemInfo(
- parentTarget.getSearchAction().getIcon(),
- parentTarget.getPackageName(),
- parentTarget.getUserHandle(),
- parentTarget.getSearchAction().getTitle());
- itemInfo.setIntent(parentTarget.getSearchAction().getIntent());
- itemInfo.setPendingIntent(parentTarget.getSearchAction().getPendingIntent());
-
- bitmap = ((BitmapDrawable) itemInfo.getIcon()
- .loadDrawable(getContext())).getBitmap();
- // crop
- if (bitmap.getWidth() < bitmap.getHeight()) {
- bitmap = Bitmap.createBitmap(bitmap, 0,
- bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
- bitmap.getWidth(), bitmap.getWidth());
- } else {
- bitmap = Bitmap.createBitmap(bitmap, bitmap.getWidth() / 2 - bitmap.getHeight() / 2,
- 0,
- bitmap.getHeight(), bitmap.getHeight());
- }
- setTag(itemInfo);
-
- RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(null, bitmap);
- drawable.setCornerRadius(Themes.getDialogCornerRadius(getContext()));
- setImageDrawable(drawable);
- }
-
- @Override
- public void onClick(View view) {
- ItemClickHandler.onClickSearchAction(Launcher.getLauncher(getContext()),
- (SearchActionItemInfo) view.getTag());
- notifyEvent(getContext(), mSearchTarget.getId(), SearchTargetEvent.ACTION_LAUNCH_TOUCH);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultWidget.java b/quickstep/src/com/android/launcher3/search/SearchResultWidget.java
deleted file mode 100644
index e22f6ab..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchResultWidget.java
+++ /dev/null
@@ -1,228 +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.search;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.app.search.SearchTarget;
-import android.app.search.SearchTargetEvent;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.AppWidgetResizeFrame;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.CheckLongPressHelper;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.search.SearchWidgetInfoContainer;
-import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.icons.cache.HandlerRunnable;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
-
-import java.util.List;
-
-/**
- * displays live version of a widget upon receiving {@link AppWidgetProviderInfo} from Search
- * provider
- */
-public class SearchResultWidget extends LinearLayout implements SearchTargetHandler, DraggableView,
- View.OnLongClickListener {
-
-
- private final Rect mWidgetOffset = new Rect();
-
- private final Launcher mLauncher;
- private final CheckLongPressHelper mLongPressHelper;
- private final GestureDetector mClickDetector;
- private final AppWidgetHostView mHostView;
- private final float mScaleToFit;
-
- private SearchWidgetInfoContainer mInfoContainer;
- private HandlerRunnable mLabelRequest;
- private BubbleTextView mWidgetProvider;
- private TextView mWidgetLabel;
-
- public SearchResultWidget(@NonNull Context context) {
- this(context, null, 0);
- }
-
- public SearchResultWidget(@NonNull Context context,
- @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchResultWidget(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
- mHostView = new AppWidgetHostView(context);
- DeviceProfile grid = mLauncher.getDeviceProfile();
- mScaleToFit = Math.min(grid.appWidgetScale.x, grid.appWidgetScale.y);
-
- // detect tap event on widget container for search target event reporting
- mClickDetector = new GestureDetector(context,
- new ClickListener(
- () -> reportEvent(SearchTargetEvent.ACTION_LAUNCH_TOUCH)));
- mLongPressHelper = new CheckLongPressHelper(this);
- mLongPressHelper.setLongPressTimeoutFactor(1);
- setOnLongClickListener(this);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mWidgetProvider = findViewById(R.id.widget_provider);
- mWidgetLabel = findViewById(R.id.widget_label);
- addView(mHostView);
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- AppWidgetProviderInfo providerInfo = parentTarget.getAppWidgetProviderInfo();
- removeListener();
-
- showWidgetInfo(providerInfo);
- mInfoContainer = mLauncher.getLiveSearchManager().getPlaceHolderWidget(providerInfo);
- if (mInfoContainer == null) {
- setVisibility(GONE);
- return;
- }
- setVisibility(VISIBLE);
- mInfoContainer.attachWidget(mHostView);
- PendingAddWidgetInfo info = (PendingAddWidgetInfo) mHostView.getTag();
- int[] size = mLauncher.getWorkspace().estimateItemSize(info);
- mHostView.getLayoutParams().width = size[0];
- mHostView.getLayoutParams().height = size[1];
- AppWidgetResizeFrame.updateWidgetSizeRanges(mHostView, mLauncher, info.spanX,
- info.spanY);
- mHostView.requestLayout();
- setTag(info);
- }
-
- private void showWidgetInfo(AppWidgetProviderInfo providerInfo) {
- PackageItemInfo pinfo = new PackageItemInfo(providerInfo.provider.getPackageName());
- pinfo.user = providerInfo.getProfile();
- mWidgetProvider.applyFromItemInfoWithIcon(pinfo);
-
- mLabelRequest = new HandlerRunnable<>(
- MODEL_EXECUTOR.getHandler(),
- () -> LauncherAppState.getInstance(mLauncher).getIconCache()
- .getTitleNoCache(LauncherAppWidgetProviderInfo
- .fromProviderInfo(mLauncher, providerInfo)),
- MAIN_EXECUTOR,
- mWidgetLabel::setText);
- Utilities.postAsyncCallback(MODEL_EXECUTOR.getHandler(), mLabelRequest);
- }
-
- /**
- * Stops hostView from getting updates on a widget provider
- */
- public void removeListener() {
- if (mInfoContainer != null) {
- mInfoContainer.detachWidget(mHostView);
- mInfoContainer = null;
- }
- if (mLabelRequest != null) {
- mLabelRequest.cancel();
- mLabelRequest = null;
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- removeListener();
- }
-
- private void reportEvent(int eventType) {
- SearchSessionTracker.INSTANCE.get(getContext()).notifyEvent(
- new SearchTargetEvent.Builder("search target id", eventType).build());
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- mLongPressHelper.onTouchEvent(ev);
- mClickDetector.onTouchEvent(ev);
- return mLongPressHelper.hasPerformedLongPress();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- mLongPressHelper.onTouchEvent(ev);
- return true;
- }
-
- @Override
- public void cancelLongPress() {
- super.cancelLongPress();
- mLongPressHelper.cancelLongPress();
- }
-
- @Override
- public int getViewType() {
- return DraggableView.DRAGGABLE_WIDGET;
- }
-
- @Override
- public void getSourceVisualDragBounds(Rect bounds) {
- mHostView.getHitRect(mWidgetOffset);
- int width = (int) (mHostView.getMeasuredWidth() * mScaleToFit);
- int height = (int) (mHostView.getMeasuredHeight() * mScaleToFit);
- bounds.set(mWidgetOffset.left,
- mWidgetOffset.top,
- width + mWidgetOffset.left,
- height + mWidgetOffset.top);
- }
-
- @Override
- public boolean onLongClick(View view) {
- ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
- return false;
- }
-
-
- static class ClickListener extends GestureDetector.SimpleOnGestureListener {
- private final Runnable mCb;
-
- ClickListener(Runnable cb) {
- mCb = cb;
- }
-
- @Override
- public boolean onSingleTapConfirmed(MotionEvent e) {
- mCb.run();
- return super.onSingleTapConfirmed(e);
- }
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultWidgetPreview.java b/quickstep/src/com/android/launcher3/search/SearchResultWidgetPreview.java
deleted file mode 100644
index 5bf1908..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchResultWidgetPreview.java
+++ /dev/null
@@ -1,130 +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.search;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.app.search.SearchTarget;
-import android.app.search.SearchTargetEvent;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-import android.graphics.Point;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.widget.BaseWidgetSheet;
-import com.android.launcher3.widget.PendingItemDragHelper;
-import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetImageView;
-
-import java.util.List;
-
-/**
- * displays preview of a widget upon receiving {@link AppWidgetProviderInfo} from Search provider
- */
-public class SearchResultWidgetPreview extends LinearLayout implements SearchTargetHandler,
- View.OnClickListener, View.OnLongClickListener {
-
- private final Launcher mLauncher;
- private final LauncherAppState mAppState;
- private WidgetCell mWidgetCell;
- private Toast mWidgetToast;
-
- private String mTargetId;
- public SearchResultWidgetPreview(Context context) {
- this(context, null, 0);
- }
-
- public SearchResultWidgetPreview(Context context,
- @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchResultWidgetPreview(Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
- mAppState = LauncherAppState.getInstance(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mWidgetCell = findViewById(R.id.widget_cell);
- mWidgetCell.setOnLongClickListener(this);
- mWidgetCell.setOnClickListener(this);
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- mTargetId = parentTarget.getId();
- AppWidgetProviderInfo providerInfo = parentTarget.getAppWidgetProviderInfo();
- LauncherAppWidgetProviderInfo pInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
- getContext(), providerInfo);
- MODEL_EXECUTOR.post(() -> {
- WidgetItem widgetItem = new WidgetItem(pInfo, mLauncher.getDeviceProfile().inv,
- mAppState.getIconCache());
- MAIN_EXECUTOR.post(() -> {
- mWidgetCell.applyFromCellItem(widgetItem, mAppState.getWidgetCache());
- mWidgetCell.ensurePreview();
- });
- });
- }
-
- @Override
- public boolean onLongClick(View view) {
- view.cancelLongPress();
- if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
- if (mWidgetCell.getTag() == null) return false;
-
- WidgetImageView imageView = mWidgetCell.getWidgetView();
- if (imageView.getBitmap() == null) {
- return false;
- }
-
- int[] loc = new int[2];
- mLauncher.getDragLayer().getLocationInDragLayer(imageView, loc);
-
- new PendingItemDragHelper(mWidgetCell).startDrag(
- imageView.getBitmapBounds(), imageView.getBitmap().getWidth(), imageView.getWidth(),
- new Point(loc[0], loc[1]), mLauncher.getAppsView(), new DragOptions());
- reportEvent(SearchTargetEvent.ACTION_LONGPRESS);
- return true;
- }
-
- @Override
- public void onClick(View view) {
- mWidgetToast = BaseWidgetSheet.showWidgetToast(getContext(), mWidgetToast);
- reportEvent(SearchTargetEvent.ACTION_LAUNCH_TOUCH);
- }
-
- private void reportEvent(int eventType) {
- SearchSessionTracker.INSTANCE.get(getContext()).notifyEvent(
- new SearchTargetEvent.Builder(mTargetId, eventType).build());
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchSectionHeaderView.java b/quickstep/src/com/android/launcher3/search/SearchSectionHeaderView.java
deleted file mode 100644
index 9276841..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchSectionHeaderView.java
+++ /dev/null
@@ -1,50 +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.search;
-
-import android.app.search.SearchTarget;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import java.util.List;
-
-/**
- * Header text view that shows a title for a given section in All apps search
- */
-public class SearchSectionHeaderView extends TextView implements SearchTargetHandler {
-
- public SearchSectionHeaderView(Context context) {
- super(context);
- }
-
- public SearchSectionHeaderView(Context context,
- @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) {
- super(context, attrs, styleAttr);
- }
-
- @Override
- public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
- setText(parentTarget.getSearchAction().getTitle());
- setVisibility(VISIBLE);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchServicePipeline.java b/quickstep/src/com/android/launcher3/search/SearchServicePipeline.java
deleted file mode 100644
index fac6ba7..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchServicePipeline.java
+++ /dev/null
@@ -1,153 +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.search;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.app.search.Query;
-import android.app.search.SearchContext;
-import android.app.search.SearchSession;
-import android.app.search.SearchTarget;
-import android.app.search.SearchUiManager;
-import android.content.Context;
-import android.os.CancellationSignal;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.app.search.ResultType;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
-import com.android.launcher3.allapps.AllAppsSectionDecorator;
-import com.android.launcher3.allapps.search.SearchPipeline;
-import com.android.launcher3.allapps.search.SearchSectionInfo;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Search pipeline utilizing {@link android.app.search.SearchUiManager}
- */
-public class SearchServicePipeline implements SearchPipeline {
- private static final int SUPPORTED_RESULT_TYPES =
- ResultType.APPLICATION | ResultType.SHORTCUT | ResultType.PLAY | ResultType.PEOPLE
- | ResultType.SETTING;
-
- private static final boolean DEBUG = true;
- private static final int REQUEST_TIMEOUT = 200;
- private static final String TAG = "SearchServicePipeline";
-
- private final Context mContext;
- private final SearchSession mSession;
- private final DeviceSearchAdapterProvider mAdapterProvider;
-
- private boolean mCanceled = false;
-
-
- public SearchServicePipeline(Context context, DeviceSearchAdapterProvider adapterProvider) {
- mContext = context;
- mAdapterProvider = adapterProvider;
- SearchUiManager manager = context.getSystemService(SearchUiManager.class);
- mSession = manager.createSearchSession(
- new SearchContext(SUPPORTED_RESULT_TYPES, REQUEST_TIMEOUT, null));
- SearchSessionTracker.getInstance(context).setSearchSession(mSession);
- }
-
- @WorkerThread
- @Override
- public void query(String input, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
- CancellationSignal cancellationSignal) {
- mCanceled = false;
- Query query = new Query(input, System.currentTimeMillis(), null);
- mSession.query(query, UI_HELPER_EXECUTOR, targets -> {
- if (!mCanceled) {
- if (DEBUG) {
- printSearchTargets(input, targets);
- }
- SearchSessionTracker.getInstance(mContext).setQuery(query);
- callback.accept(this.onResult(targets));
- }
- Log.w(TAG, "Ignoring results due to cancel signal");
- });
- }
-
- /**
- * Given A list of search Targets, pairs a group of search targets to a AdapterItem that can
- * be inflated in AllAppsRecyclerView
- */
- private ArrayList<AllAppsGridAdapter.AdapterItem> onResult(List<SearchTarget> searchTargets) {
- HashMap<String, SearchAdapterItem> adapterMap = new LinkedHashMap<>();
- List<SearchTarget> unmappedChildren = new ArrayList<>();
- SearchSectionInfo section = new SearchSectionInfo();
- section.setDecorationHandler(
- new AllAppsSectionDecorator.SectionDecorationHandler(mContext, true));
- for (SearchTarget target : searchTargets) {
- if (!TextUtils.isEmpty(target.getParentId())) {
- if (!addChildToParent(target, adapterMap)) {
- unmappedChildren.add(target);
- }
- continue;
- }
- int viewType = mAdapterProvider.getViewTypeForSearchTarget(target);
- if (viewType != -1) {
- SearchAdapterItem adapterItem = new SearchAdapterItem(target, viewType);
- adapterItem.searchSectionInfo = section;
- adapterMap.put(target.getId(), adapterItem);
- }
- }
- for (SearchTarget s : unmappedChildren) {
- if (!addChildToParent(s, adapterMap)) {
- Log.w(TAG,
- "Unable to pair child " + s.getId() + " to parent " + s.getParentId());
- }
- }
- return new ArrayList<>(adapterMap.values());
- }
-
- private void printSearchTargets(String query, List<SearchTarget> results) {
- Log.d(TAG, " query=" + query + " size=" + results.size());
- for (SearchTarget s : results) {
- Log.d(TAG, "layoutType=" + s.getLayoutType() + " resultType=" + s.getResultType());
- }
- }
-
- /**
- * Adds a child SearchTarget to a collection of searchTarget children with a shared parentId.
- * Returns false if no parent searchTarget with id=$parentId does not exists.
- */
- private boolean addChildToParent(SearchTarget target, HashMap<String, SearchAdapterItem> map) {
- if (!map.containsKey(target.getParentId())) return false;
- map.get(target.getParentId()).getInlineItems().add(target);
- return true;
- }
-
- /**
- * Unregister callbacks and destroy search session
- */
- public void destroy() {
- mSession.destroy();
- }
-
- /**
- * Cancels current ongoing search request.
- */
- public void cancel() {
- mCanceled = true;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchSessionTracker.java b/quickstep/src/com/android/launcher3/search/SearchSessionTracker.java
deleted file mode 100644
index 3079965..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchSessionTracker.java
+++ /dev/null
@@ -1,76 +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.search;
-
-import android.app.search.Query;
-import android.app.search.SearchSession;
-import android.app.search.SearchTarget;
-import android.app.search.SearchTargetEvent;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-/**
- * A singleton class to track and report search events back to SearchSession
- */
-public class SearchSessionTracker {
-
- private static final String TAG = "SearchSessionTracker";
-
- @Nullable
- private SearchSession mSession;
- private Query mQuery;
-
- public static final MainThreadInitializedObject<SearchSessionTracker> INSTANCE =
- new MainThreadInitializedObject<>(SearchSessionTracker::new);
-
- private SearchSessionTracker(Context context) {
- }
-
- /**
- * Returns instance of SearchSessionTracker
- */
- public static SearchSessionTracker getInstance(Context context) {
- return SearchSessionTracker.INSTANCE.get(context);
- }
-
- @WorkerThread
- public void setSearchSession(SearchSession session) {
- mSession = session;
- }
-
- @WorkerThread
- public void setQuery(Query query) {
- mQuery = query;
- }
-
- /**
- * Send the user event handling back to the {@link SearchSession} object.
- */
- public void notifyEvent(SearchTargetEvent event) {
- if (mSession == null) {
- Log.d(TAG, "Dropping event " + event.getTargetId());
- }
- Log.d(TAG, "notifyEvent:" + mQuery.getInput() + ":" + event.getTargetId());
- UI_HELPER_EXECUTOR.post(() -> mSession.notifyEvent(mQuery, event));
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchTargetHandler.java b/quickstep/src/com/android/launcher3/search/SearchTargetHandler.java
deleted file mode 100644
index acf6f8a..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchTargetHandler.java
+++ /dev/null
@@ -1,46 +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.search;
-
-import android.app.search.SearchTarget;
-import android.app.search.SearchTargetEvent;
-import android.content.Context;
-
-import java.util.List;
-
-/**
- * An interface for supporting dynamic search results
- */
-public interface SearchTargetHandler {
-
- /**
- * Update view using values from {@link SearchTarget}
- */
- default void apply(SearchTarget parentTarget, List<SearchTarget> children) { }
-
- /**
- * Handle IME quick select event. returns whether event was handled.
- */
- default boolean quickSelect() {
- return false;
- }
-
- default void notifyEvent(Context context, String id, int eventType) {
- SearchTargetEvent.Builder builder = new SearchTargetEvent.Builder(id, eventType);
- SearchSessionTracker.getInstance(context).notifyEvent(builder.build());
- }
-}
diff --git a/quickstep/src/com/android/launcher3/search/SearchTargetUtil.java b/quickstep/src/com/android/launcher3/search/SearchTargetUtil.java
deleted file mode 100644
index ede3b9d..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchTargetUtil.java
+++ /dev/null
@@ -1,198 +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.search;
-
-import static com.android.app.search.LayoutType.DIVIDER;
-import static com.android.app.search.LayoutType.ICON_HORIZONTAL_TEXT;
-import static com.android.app.search.LayoutType.SMALL_ICON_HORIZONTAL_TEXT;
-import static com.android.app.search.LayoutType.THUMBNAIL;
-import static com.android.app.search.ResultType.ACTION;
-import static com.android.app.search.ResultType.SCREENSHOT;
-import static com.android.app.search.ResultType.SUGGEST;
-
-import android.app.PendingIntent;
-import android.app.search.SearchAction;
-import android.app.search.SearchTarget;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-
-import com.android.app.search.ResultType;
-
-public class SearchTargetUtil {
-
- public static final String BUNDLE_EXTRA_SHOULD_START = "should_start";
- public static final String BUNDLE_EXTRA_SHOULD_START_FOR_RESULT = "should_start_for_result";
- public static final String BUNDLE_EXTRA_BADGE_WITH_PACKAGE = "badge_with_package";
- public static final String BUNDLE_EXTRA_PRIMARY_ICON_FROM_TITLE = "primary_icon_from_title";
-
- public static final String EXTRA_CLASS = "class";
-
- private static final String TITLE = " title: weather ";
- private static final String SUBTITLE = " subtitle: 68 degrees ";
- private static final String PACKAGE2 = "com.google.android.gm";
- private static final UserHandle USERHANDLE = Process.myUserHandle();
-
-
- /**
- * Generate SearchTargetUtil for ICON_HORIZONTAL_TEXT layout type.
- *
- * targets.add(SearchTargetUtil.generateIconDoubleHorizontalText_SearchAction(
- * mContext, "red", Color.RED));
- * targets.add(SearchTargetUtil.generateIconDoubleHorizontalText_SearchAction(
- * mContext, "yellow", Color.YELLOW));
- */
- public static SearchTarget generateIcoHorizontalText_usingSearchAction(
- Context context, String id, int color) {
- SearchTarget.Builder builder =
- new SearchTarget.Builder(ACTION, ICON_HORIZONTAL_TEXT, id)
- .setPackageName(PACKAGE2) /* required */
- .setUserHandle(USERHANDLE); /* required */
-
- Intent intent = new Intent("com.google.android.googlequicksearchbox.GENERIC_QUERY");
- intent.putExtra("query", "weather");
- intent.putExtra("full_screen", false);
- PendingIntent pendingIntent =
- PendingIntent.getActivity(
- context, 1, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- canvas.drawColor(color);
- Icon icon = Icon.createWithAdaptiveBitmap(bitmap);
-
- Bundle b = new Bundle();
- b.putBoolean(BUNDLE_EXTRA_SHOULD_START_FOR_RESULT, true);
- b.putBoolean(BUNDLE_EXTRA_BADGE_WITH_PACKAGE, true);
- b.putBoolean(BUNDLE_EXTRA_PRIMARY_ICON_FROM_TITLE, true);
-
- builder.setSearchAction(new SearchAction.Builder(id, id + TITLE)
- .setSubtitle(id + SUBTITLE)
- .setPendingIntent(pendingIntent)
- .setIcon(icon)
- .setExtras(b)
- .build());
- return builder.build();
- }
-
- /**
- * Inside SearchServicePipeline, add following samples to test the search target.
- *
- * targets.add(SearchTargetUtil.generateThumbnail_SearchAction("blue", Color.BLUE));
- * targets.add(SearchTargetUtil.generateThumbnail_SearchAction("red", Color.RED));
- * targets.add(SearchTargetUtil.generateThumbnail_SearchAction("green", Color.GREEN));
- */
- public static SearchTarget generateThumbnail_usingSearchAction(String id, int color) {
- SearchTarget.Builder builder =
- new SearchTarget.Builder(SCREENSHOT, THUMBNAIL, id)
- .setPackageName(PACKAGE2) /* required */
- .setUserHandle(USERHANDLE); /* required */
-
-
- Intent intent = new Intent(Intent.ACTION_VIEW)
- .setData(Uri.parse("uri blah blah"))
- .setType("image/*")
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- Bitmap bitmap = Bitmap.createBitmap(1000, 500, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- canvas.drawColor(color);
- Icon icon = Icon.createWithBitmap(bitmap);
-
- builder.setSearchAction(new SearchAction.Builder(id, TITLE)
- .setSubtitle(SUBTITLE)
- .setIcon(icon)
- .setIntent(intent)
- .build());
- return builder.build();
-
- }
-
- /**
- * Generate SearchTargetUtil for SMALL_ICON_HORIZONTAL_TEXT layout type.
- *
- * targets.add(SearchTargetUtil.generateIconHorizontalText_SearchAction(
- * mContext, "red", Color.RED));
- * targets.add(SearchTargetUtil.generateIconHorizontalText_SearchAction(
- * mContext, "yellow", Color.YELLOW));
- */
- public static SearchTarget generateSmallIconHorizontalText_usingSearchAction(
- Context context, String id, int color) {
- String title = "Ask the assistant";
- String fallbackQuery = "sourdough bread";
- SearchTarget.Builder builder =
- new SearchTarget.Builder(SUGGEST, SMALL_ICON_HORIZONTAL_TEXT, id)
- .setPackageName(PACKAGE2) /* required */
- .setUserHandle(USERHANDLE); /* required */
-
- Intent intent3 = new Intent("com.google.android.googlequicksearchbox.GENERIC_QUERY");
- intent3.putExtra("query", fallbackQuery);
- intent3.putExtra("full_screen", false);
- PendingIntent pendingIntent3 =
- PendingIntent.getActivity(
- context, 1, intent3,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- canvas.drawColor(color);
- Icon icon = Icon.createWithAdaptiveBitmap(bitmap);
-
- Bundle extra = new Bundle();
- extra.putBoolean(BUNDLE_EXTRA_SHOULD_START_FOR_RESULT, true);
-
- SearchAction searchAction = new SearchAction.Builder(id, title)
- .setSubtitle(fallbackQuery)
- .setPendingIntent(pendingIntent3)
- .setIcon(icon)
- .setExtras(extra)
- .build();
- return builder.setSearchAction(searchAction).build();
- }
-
- public static SearchTarget generateDivider() {
- SearchTarget.Builder builder =
- new SearchTarget.Builder(SUGGEST, DIVIDER, "divider")
- .setPackageName("") /* required but not used*/
- .setUserHandle(USERHANDLE); /* required */
- return builder.build();
- }
-
-
- /**
- * Generate SearchTargetUtil for ICON_DOUBLE_HORIZONTAL_TEXT layout type.
- */
- public static SearchTarget generateIconDoubleHorizontalText_ShortcutInfo(Context context) {
- String id = "23456";
- SearchTarget.Builder builder =
- new SearchTarget.Builder(ResultType.SHORTCUT, SMALL_ICON_HORIZONTAL_TEXT, id)
- .setPackageName("com.google.android.gm") /* required */
- .setUserHandle(UserHandle.CURRENT); /* required */
-
- builder.setShortcutInfo(new ShortcutInfo.Builder(context, id).build());
- return builder.build();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
new file mode 100644
index 0000000..06372fe
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -0,0 +1,78 @@
+/*
+ * 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.ContextWrapper;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
+ * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
+ * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
+ */
+public class TaskbarActivityContext extends ContextWrapper implements ActivityContext {
+
+ private final DeviceProfile mDeviceProfile;
+ private final LayoutInflater mLayoutInflater;
+ private final TaskbarContainerView mTaskbarContainerView;
+
+ public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
+ super(launcher);
+ mDeviceProfile = launcher.getDeviceProfile().copy(this);
+ float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+ float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+ mDeviceProfile.updateIconSize(iconScale, getResources());
+
+ mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+
+ mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater
+ .inflate(R.layout.taskbar, null, false);
+ }
+
+ public TaskbarContainerView getTaskbarContainerView() {
+ 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().
+ */
+ public LayoutInflater getLayoutInflater() {
+ return mLayoutInflater;
+ }
+
+ @Override
+ public BaseDragLayer<TaskbarActivityContext> getDragLayer() {
+ return mTaskbarContainerView;
+ }
+
+ @Override
+ public DeviceProfile getDeviceProfile() {
+ return mDeviceProfile;
+ }
+
+ @Override
+ public Rect getFolderBoundingBox() {
+ return mTaskbarContainerView.getFolderBoundingBox();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 0093e66..ddd0d15 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -15,17 +15,38 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
+
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.view.View;
import androidx.annotation.NonNull;
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;
+
/**
* Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
*/
-public class TaskbarContainerView extends FrameLayout {
+public class TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext> {
+
+ private final int[] mTempLoc = new int[2];
+ private final int mFolderMargin;
+
+ // Initialized in TaskbarController constructor.
+ private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks;
+
+ // Initialized in init.
+ private TaskbarView mTaskbarView;
+ private ViewTreeObserverWrapper.OnComputeInsetsListener mTaskbarInsetsComputer;
+
public TaskbarContainerView(@NonNull Context context) {
this(context, null);
}
@@ -41,6 +62,91 @@
public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
+ super(context, attrs, 1 /* alphaChannelCount */);
+ mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+ }
+
+ protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) {
+ mControllerCallbacks = callbacks;
+ }
+
+ protected void init(TaskbarView taskbarView) {
+ mTaskbarView = taskbarView;
+ mTaskbarInsetsComputer = createTaskbarInsetsComputer();
+ recreateControllers();
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[0];
+ }
+
+ private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
+ return insetsInfo -> {
+ if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+ // We're invisible, 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
+ // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
+ // to show a floating view like Folder. Thus, we set the contentInsets to be where
+ // mTaskbarView is, since its position never changes and insets rather than overlays.
+ int[] loc = mTempLoc;
+ mTaskbarView.getLocationInWindow(loc);
+ insetsInfo.contentInsets.left = loc[0];
+ insetsInfo.contentInsets.top = loc[1];
+ insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth());
+ insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight());
+ };
+ }
+
+ protected void cleanup() {
+ ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
+ mTaskbarInsetsComputer);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ cleanup();
+ }
+
+ @Override
+ protected boolean canFindActiveController() {
+ // Unlike super class, we want to be able to find controllers when touches occur in the
+ // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
+ return true;
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ mControllerCallbacks.onViewRemoved();
+ }
+
+ /**
+ * @return Bounds (in our coordinates) where an opened Folder can display.
+ */
+ protected Rect getFolderBoundingBox() {
+ Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
+ boundingBox.inset(mFolderMargin, mFolderMargin);
+ return boundingBox;
+ }
+
+ protected TaskbarActivityContext getTaskbarActivityContext() {
+ return mActivity;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 6a74aac..5dddaf3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -34,12 +34,15 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepAppTransitionManagerImpl;
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;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.ItemClickHandler;
@@ -82,11 +85,11 @@
TaskbarContainerView taskbarContainerView) {
mLauncher = launcher;
mTaskbarContainerView = taskbarContainerView;
+ mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
- mTaskbarView.setCallbacks(createTaskbarViewCallbacks());
+ mTaskbarView.construct(createTaskbarViewCallbacks());
mWindowManager = mLauncher.getWindowManager();
- mTaskbarSize = new Point(MATCH_PARENT,
- mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
+ mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
createTaskbarVisibilityControllerCallbacks());
@@ -107,7 +110,18 @@
@Override
public void updateTaskbarVisibilityAlpha(float alpha) {
mTaskbarContainerView.setAlpha(alpha);
- AlphaUpdateListener.updateVisibility(mTaskbarContainerView);
+ }
+ };
+ }
+
+ private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() {
+ return new TaskbarContainerViewCallbacks() {
+ @Override
+ public void onViewRemoved() {
+ if (mTaskbarContainerView.getChildCount() == 1) {
+ // Only TaskbarView remains.
+ setTaskbarWindowFullscreen(false);
+ }
}
};
}
@@ -122,9 +136,29 @@
Task task = (Task) tag;
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
ActivityOptions.makeBasic());
+ } else if (tag instanceof FolderInfo) {
+ FolderIcon folderIcon = (FolderIcon) view;
+ Folder folder = folderIcon.getFolder();
+
+ setTaskbarWindowFullscreen(true);
+
+ mTaskbarContainerView.post(() -> {
+ folder.animateOpen();
+
+ folder.iterateOverItems((itemInfo, itemView) -> {
+ itemView.setOnClickListener(getItemOnClickListener());
+ itemView.setOnLongClickListener(getItemOnLongClickListener());
+ // To play haptic when dragging, like other Taskbar items do.
+ itemView.setHapticFeedbackEnabled(true);
+ return false;
+ });
+ });
} else {
ItemClickHandler.INSTANCE.onClick(view);
}
+
+ AbstractFloatingView.closeAllOpenViews(
+ mTaskbarContainerView.getTaskbarActivityContext());
};
}
@@ -167,6 +201,7 @@
public void init() {
mTaskbarView.init(mHotseatController.getNumHotseatIcons(),
mRecentsController.getNumRecentIcons());
+ mTaskbarContainerView.init(mTaskbarView);
addToWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
mTaskbarVisibilityController.init();
@@ -188,6 +223,7 @@
*/
public void cleanup() {
mTaskbarView.cleanup();
+ mTaskbarContainerView.cleanup();
removeFromWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(null);
mTaskbarVisibilityController.cleanup();
@@ -196,14 +232,10 @@
}
private void removeFromWindowManager() {
- if (mTaskbarContainerView.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(mTaskbarContainerView);
- }
+ mWindowManager.removeViewImmediate(mTaskbarContainerView);
}
private void addToWindowManager() {
- removeFromWindowManager();
-
final int gravity = Gravity.BOTTOM;
mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -218,6 +250,7 @@
mWindowLayoutParams.setFitInsetsTypes(0);
mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.setSystemApplicationOverlay(true);
WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
wmWrapper.setProvidesInsetsTypes(
@@ -340,7 +373,22 @@
* @return Whether the given View is in the same window as Taskbar.
*/
public boolean isViewInTaskbar(View v) {
- return mTaskbarContainerView.getWindowId().equals(v.getWindowId());
+ return mTaskbarContainerView.isAttachedToWindow()
+ && mTaskbarContainerView.getWindowId().equals(v.getWindowId());
+ }
+
+ /**
+ * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
+ */
+ private void setTaskbarWindowFullscreen(boolean fullscreen) {
+ if (fullscreen) {
+ mWindowLayoutParams.width = MATCH_PARENT;
+ mWindowLayoutParams.height = MATCH_PARENT;
+ } else {
+ mWindowLayoutParams.width = mTaskbarSize.x;
+ mWindowLayoutParams.height = mTaskbarSize.y;
+ }
+ mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams);
}
/**
@@ -360,6 +408,13 @@
}
/**
+ * Contains methods that TaskbarContainerView can call to interface with TaskbarController.
+ */
+ protected interface TaskbarContainerViewCallbacks {
+ void onViewRemoved();
+ }
+
+ /**
* Contains methods that TaskbarView can call to interface with TaskbarController.
*/
protected interface TaskbarViewCallbacks {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
index 4dc051a..082343e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -57,6 +57,7 @@
protected void init() {
mLauncher.getDragController().addDragListener(mDragListener);
+ onHotseatUpdated();
}
protected void cleanup() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java
index 9d4e000..4256d2b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java
@@ -50,6 +50,8 @@
// The current background requests to load the task icons
private CancellableTask[] mIconLoadRequests = new CancellableTask[mNumRecentIcons];
+ private boolean mIsAlive;
+
public TaskbarRecentsController(BaseQuickstepLauncher launcher,
TaskbarController.TaskbarRecentsControllerCallbacks taskbarCallbacks) {
mLauncher = launcher;
@@ -58,11 +60,13 @@
}
protected void init() {
+ mIsAlive = true;
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackChangeListener);
reloadRecentTasksIfNeeded();
}
protected void cleanup() {
+ mIsAlive = false;
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mTaskStackChangeListener);
cancelAllPendingIconLoadTasks();
@@ -84,7 +88,9 @@
}
private void onRecentTasksChanged(ArrayList<Task> tasks) {
- mTaskbarCallbacks.updateRecentItems(tasks);
+ if (mIsAlive) {
+ mTaskbarCallbacks.updateRecentItems(tasks);
+ }
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index d8f3bb5..7a13b89 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -17,12 +17,12 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.DragEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -32,16 +32,20 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
import com.android.systemui.shared.recents.model.Task;
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
-public class TaskbarView extends LinearLayout {
+public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent {
private final ColorDrawable mBackgroundDrawable;
private final int mItemMarginLeftRight;
@@ -51,6 +55,9 @@
private final RectF mDelegateSlopBounds = new RectF();
private final int[] mTempOutLocation = new int[2];
+ // Initialized in TaskbarController constructor.
+ private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+
// Initialized in init().
private int mHotseatStartIndex;
private int mHotseatEndIndex;
@@ -58,13 +65,13 @@
private int mRecentsStartIndex;
private int mRecentsEndIndex;
- private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
-
// Delegate touches to the closest view if within mIconTouchSize.
private boolean mDelegateTargeted;
private View mDelegateView;
private boolean mIsDraggingItem;
+ // Only non-null when the corresponding Folder is open.
+ private @Nullable FolderIcon mLeaveBehindFolderIcon;
public TaskbarView(@NonNull Context context) {
this(context, null);
@@ -90,7 +97,7 @@
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
- protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
+ protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
mControllerCallbacks = taskbarViewCallbacks;
}
@@ -130,17 +137,37 @@
// Replace any Hotseat views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
+ boolean isFolder = false;
+ boolean needsReinflate = false;
if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
+ } else if (hotseatItemInfo instanceof FolderInfo) {
+ expectedLayoutResId = R.layout.folder_icon;
+ isFolder = true;
+ // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
+ // if the info changes we need to reinflate. This should only happen if a new folder
+ // is dragged to the position that another folder previously existed.
+ needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
} else {
expectedLayoutResId = R.layout.taskbar_app_icon;
}
- if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) {
+ if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
+ || needsReinflate) {
removeView(hotseatView);
- BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId);
- LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
+ TaskbarActivityContext activityContext =
+ ActivityContext.lookupContext(getContext());
+ if (isFolder) {
+ FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
+ FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
+ activityContext, this, folderInfo);
+ folderIcon.setTextVisible(false);
+ hotseatView = folderIcon;
+ } else {
+ hotseatView = inflate(expectedLayoutResId);
+ }
+ int iconSize = activityContext.getDeviceProfile().iconSizePx;
+ LayoutParams lp = new LayoutParams(iconSize, iconSize);
lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
- hotseatView = btv;
addView(hotseatView, hotseatIndex, lp);
}
@@ -153,6 +180,11 @@
hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
hotseatView.setOnLongClickListener(
mControllerCallbacks.getItemOnLongClickListener());
+ } else if (isFolder) {
+ hotseatView.setVisibility(VISIBLE);
+ hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+ hotseatView.setOnLongClickListener(
+ mControllerCallbacks.getItemOnLongClickListener());
} else {
hotseatView.setVisibility(GONE);
hotseatView.setOnClickListener(null);
@@ -345,6 +377,7 @@
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
mIsDraggingItem = true;
+ AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext()));
return true;
case DragEvent.ACTION_DRAG_ENDED:
mIsDraggingItem = false;
@@ -357,7 +390,35 @@
return mIsDraggingItem;
}
+ // FolderIconParent implemented methods.
+
+ @Override
+ public void drawFolderLeaveBehindForIcon(FolderIcon child) {
+ mLeaveBehindFolderIcon = child;
+ invalidate();
+ }
+
+ @Override
+ public void clearFolderLeaveBehind(FolderIcon child) {
+ mLeaveBehindFolderIcon = null;
+ invalidate();
+ }
+
+ // End FolderIconParent implemented methods.
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mLeaveBehindFolderIcon != null) {
+ canvas.save();
+ canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop());
+ mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas);
+ canvas.restore();
+ }
+ }
+
private View inflate(@LayoutRes int layoutResId) {
- return LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
+ TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
+ return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
index 4cf55d8..6d20d97 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
@@ -59,6 +59,9 @@
boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
& QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
+
+ onTaskbarBackgroundAlphaChanged();
+ updateVisibilityAlpha();
}
protected void cleanup() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 22c4a7e..98551fb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION;
import static com.android.launcher3.graphics.IconShape.getShape;
import android.content.Context;
@@ -29,7 +28,6 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.graphics.ColorUtils;
@@ -37,9 +35,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
@@ -53,8 +49,7 @@
/**
* A BubbleTextView with a ring around it's drawable
*/
-public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
- LauncherAccessibilityDelegate.AccessibilityActionHandler {
+public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.095f;
@@ -148,29 +143,6 @@
}
@Override
- public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
- if (!mIsPinned) {
- accessibilityNodeInfo.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
- getContext().getText(R.string.pin_prediction)));
- }
- }
-
- @Override
- public boolean performAccessibilityAction(int action, ItemInfo info) {
- QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext()));
- if (action == PIN_PREDICTION) {
- if (launcher == null || launcher.getHotseatPredictionController() == null) {
- return false;
- }
- HotseatPredictionController controller = launcher.getHotseatPredictionController();
- controller.pinPrediction(info);
- return true;
- }
- return false;
- }
-
- @Override
public void getIconBounds(Rect outBounds) {
super.getIconBounds(outBounds);
if (!mIsPinned && !mIsDrawingDot) {
@@ -179,6 +151,10 @@
}
}
+ public boolean isPinned() {
+ return mIsPinned;
+ }
+
private int getOutlineOffsetX() {
return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9a1e707..55dde45 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -21,12 +21,11 @@
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
-import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
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.OVERVIEW_STATE_ORDINAL;
@@ -44,12 +43,11 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepAccessibilityDelegate;
import com.android.launcher3.Workspace;
-import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.appprediction.PredictionRowView;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
@@ -57,7 +55,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.search.DeviceSearchAdapterProvider;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
@@ -86,7 +83,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
import java.util.stream.Stream;
public class QuickstepLauncher extends BaseQuickstepLauncher {
@@ -104,20 +100,16 @@
@Override
protected void setupViews() {
super.setupViews();
- if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
- mHotseatPredictionController = new HotseatPredictionController(this);
- }
+ mHotseatPredictionController = new HotseatPredictionController(this);
}
@Override
protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
- // If the app launch is from DeviceSearchResultContainer then add the InstanceId from
- // LiveSearchManager to recreate the AllApps search session on the server side.
- Optional<InstanceId> logInstanceId = this.getLiveSearchManager().getLogInstanceId();
- if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS
- && info.getContainerInfo().getExtendedContainers().getContainerCase()
- == DEVICE_SEARCH_RESULT_CONTAINER && logInstanceId.isPresent()) {
- instanceId = logInstanceId.get();
+ // If the app launch is from any of the surfaces in AllApps then add the InstanceId from
+ // LiveSearchManager to recreate the AllApps session on the server side.
+ if (mAllAppsSessionLogId != null && ALL_APPS.equals(
+ getStateManager().getCurrentStableState())) {
+ instanceId = mAllAppsSessionLogId;
}
StatsLogger logger = getStatsLogManager()
@@ -141,9 +133,12 @@
}
logger.log(LAUNCHER_APP_LAUNCH_TAP);
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
- }
+ mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
+ }
+
+ @Override
+ protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
+ return new QuickstepAccessibilityDelegate(this);
}
/**
@@ -166,10 +161,8 @@
@Override
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
- if (mHotseatPredictionController != null) {
- // Only pause is taskbar controller is not present
- mHotseatPredictionController.setPauseUIUpdate(getTaskbarController() == null);
- }
+ // Only pause is taskbar controller is not present
+ mHotseatPredictionController.setPauseUIUpdate(getTaskbarController() == null);
return super.startActivitySafely(v, intent, item);
}
@@ -181,7 +174,7 @@
onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
}
- if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
+ if (((changeBits & ACTIVITY_STATE_STARTED) != 0
|| (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
mHotseatPredictionController.setPauseUIUpdate(false);
}
@@ -195,12 +188,8 @@
@Override
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
- if (mHotseatPredictionController != null) {
- return Stream.concat(super.getSupportedShortcuts(),
- Stream.of(mHotseatPredictionController));
- } else {
- return super.getSupportedShortcuts();
- }
+ return Stream.concat(
+ super.getSupportedShortcuts(), Stream.of(mHotseatPredictionController));
}
/**
@@ -227,8 +216,7 @@
mAllAppsPredictions = item;
getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
.setPredictedApps(item.items);
- } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION
- && mHotseatPredictionController != null) {
+ } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
mHotseatPredictionController.setPredictedItems(item);
}
}
@@ -246,10 +234,7 @@
public void onDestroy() {
super.onDestroy();
getAppsView().getSearchUiManager().destroy();
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.destroy();
- mHotseatPredictionController = null;
- }
+ mHotseatPredictionController.destroy();
}
@Override
@@ -293,11 +278,6 @@
}
@Override
- public SearchAdapterProvider createSearchAdapterProvider(AllAppsContainerView appsView) {
- return new DeviceSearchAdapterProvider(this, appsView);
- }
-
- @Override
public TouchController[] createTouchControllers() {
Mode mode = SysUINavigationMode.getMode(this);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 73f4ff2..c60e257 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -49,6 +49,7 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
/**
* Touch controller for handling various state transitions in portrait UI.
@@ -319,4 +320,44 @@
return baseInterpolator.getInterpolation(v);
}
}
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ InteractionJankMonitorWrapper.begin(
+ mLauncher.getRootView(), InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ InteractionJankMonitorWrapper.cancel(
+ InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ break;
+ }
+ return super.onControllerInterceptTouchEvent(ev);
+
+ }
+
+ @Override
+ protected void onReinitToState(LauncherState newToState) {
+ super.onReinitToState(newToState);
+ if (newToState != ALL_APPS) {
+ InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ }
+ }
+
+ @Override
+ protected void onReachedFinalState(LauncherState toState) {
+ super.onReinitToState(toState);
+ if (toState == ALL_APPS) {
+ InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ }
+ }
+
+ @Override
+ protected void clearState() {
+ super.clearState();
+ InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 7675a79..c2e5cda 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -32,6 +32,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
@@ -150,9 +151,11 @@
mTaskBeingDragged = view;
int upDirection = mRecentsView.getPagedOrientationHandler()
.getUpDirection(mIsRtl);
- if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
+ if (!SysUINavigationMode.getMode(mActivity).hasGestures || (
+ mActivity.getDeviceProfile().isTablet
+ && FeatureFlags.ENABLE_OVERVIEW_GRID.get())) {
// Don't allow swipe down to open if we don't support swipe up
- // to enter overview.
+ // to enter overview, or when grid layout is enabled.
directionsToDetectScroll = upDirection;
mAllowGoingUp = true;
mAllowGoingDown = false;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 36b51cd..feeee50 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1071,9 +1071,7 @@
&& runningTaskTarget != null
&& runningTaskTarget.pictureInPictureParams != null
&& TaskInfoCompat.isAutoEnterPipEnabled(
- runningTaskTarget.pictureInPictureParams)
- && TaskInfoCompat.getPipSourceRectHint(
- runningTaskTarget.pictureInPictureParams) != null;
+ runningTaskTarget.pictureInPictureParams);
if (mIsSwipingPipToHome) {
mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
homeAnimFactory, runningTaskTarget, start);
@@ -1176,8 +1174,10 @@
swipePipToHomeAnimator.setFromRotation(mTaskViewSimulator, windowRotation);
}
swipePipToHomeAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean mHasAnimationEnded;
@Override
public void onAnimationStart(Animator animation) {
+ if (mHasAnimationEnded) return;
// Ensure Launcher ends in NORMAL state, we intentionally skip other callbacks
// since they are not relevant in this swipe-pip-to-home case.
homeAnimFactory.createActivityAnimationToHome().dispatchOnStart();
@@ -1185,6 +1185,8 @@
@Override
public void onAnimationEnd(Animator animation) {
+ if (mHasAnimationEnded) return;
+ mHasAnimationEnded = true;
if (mRecentsAnimationController == null) {
// If the recents animation is interrupted, we still end the running
// animation (not canceled) so this is still called. In that case, we can
diff --git a/quickstep/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java
index cb4d53a..8cb64c2 100644
--- a/quickstep/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/src/com/android/quickstep/ImageActionsApi.java
@@ -64,6 +64,20 @@
*/
@UiThread
public void shareWithExplicitIntent(@Nullable Rect crop, Intent intent) {
+ addImageAndSendIntent(crop, intent, false);
+ }
+
+ /**
+ * Share the image this api was constructed with using the provided intent. The implementation
+ * should set the intent's data field to the URI pointing to the image.
+ */
+ @UiThread
+ public void shareAsDataWithExplicitIntent(@Nullable Rect crop, Intent intent) {
+ addImageAndSendIntent(crop, intent, true);
+ }
+
+ @UiThread
+ private void addImageAndSendIntent(@Nullable Rect crop, Intent intent, boolean setData) {
if (mBitmapSupplier.get() == null) {
Log.e(TAG, "No snapshot available, not starting share.");
return;
@@ -71,12 +85,14 @@
UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
- intentForUri
- .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
- .putExtra(EXTRA_STREAM, uri);
+ intentForUri.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+ if (setData) {
+ intentForUri.setData(uri);
+ } else {
+ intentForUri.putExtra(EXTRA_STREAM, uri);
+ }
return new Intent[]{intentForUri};
}, TAG));
-
}
/**
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index b258a10..6a32dc4 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -30,7 +30,6 @@
import android.graphics.Point;
import android.graphics.RectF;
import android.util.Log;
-import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.Surface;
@@ -40,6 +39,9 @@
import com.android.launcher3.util.DisplayController.Info;
import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
/**
* Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
@@ -51,6 +53,37 @@
*/
class OrientationTouchTransformer {
+ class CurrentDisplay {
+ public Point size;
+ public int rotation;
+
+ CurrentDisplay() {
+ this.size = new Point(0, 0);
+ this.rotation = 0;
+ }
+
+ CurrentDisplay(Point size, int rotation) {
+ this.size = size;
+ this.rotation = rotation;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CurrentDisplay display = (CurrentDisplay) o;
+ if (rotation != display.rotation) return false;
+
+ return Objects.equals(size, display.size);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(size, rotation);
+ }
+ };
+
private static final String TAG = "OrientationTouchTransformer";
private static final boolean DEBUG = false;
private static final int MAX_ORIENTATIONS = 4;
@@ -60,11 +93,12 @@
private final Matrix mTmpMatrix = new Matrix();
private final float[] mTmpPoint = new float[2];
- private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
+ private 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 int mCurrentDisplayRotation;
+ private CurrentDisplay mCurrentDisplay = new CurrentDisplay();
private int mNavBarGesturalHeight;
private int mNavBarLargerGesturalHeight;
private boolean mEnableMultipleRegions;
@@ -147,21 +181,22 @@
* @see #enableMultipleRegions(boolean, Info)
*/
void createOrAddTouchRegion(Info info) {
- mCurrentDisplayRotation = info.rotation;
+ mCurrentDisplay = new CurrentDisplay(info.realSize, info.rotation);
+
if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
- && mCurrentDisplayRotation == mQuickStepStartingRotation) {
+ && mCurrentDisplay.rotation == mQuickStepStartingRotation) {
// User already was swiping and the current screen is same rotation as the starting one
// Remove active nav bars in other rotations except for the one we started out in
resetSwipeRegions(info);
return;
}
- OrientationRectF region = mSwipeTouchRegions.get(mCurrentDisplayRotation);
+ OrientationRectF region = mSwipeTouchRegions.get(mCurrentDisplay);
if (region != null) {
return;
}
if (mEnableMultipleRegions) {
- mSwipeTouchRegions.put(mCurrentDisplayRotation, createRegionForDisplay(info));
+ mSwipeTouchRegions.put(mCurrentDisplay, createRegionForDisplay(info));
} else {
resetSwipeRegions(info);
}
@@ -208,31 +243,31 @@
*/
private void resetSwipeRegions(Info region) {
if (DEBUG) {
- Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplayRotation);
+ Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplay.rotation);
}
- mCurrentDisplayRotation = region.rotation;
- OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplayRotation);
+ mCurrentDisplay = new CurrentDisplay(region.realSize, region.rotation);
+ OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay);
if (regionToKeep == null) {
regionToKeep = createRegionForDisplay(region);
}
mSwipeTouchRegions.clear();
- mSwipeTouchRegions.put(mCurrentDisplayRotation, regionToKeep);
+ mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep);
updateAssistantRegions(regionToKeep);
}
private void resetSwipeRegions() {
- OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplayRotation);
+ OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentDisplay);
mSwipeTouchRegions.clear();
if (regionToKeep != null) {
- mSwipeTouchRegions.put(mCurrentDisplayRotation, regionToKeep);
+ mSwipeTouchRegions.put(mCurrentDisplay, regionToKeep);
updateAssistantRegions(regionToKeep);
}
}
private OrientationRectF createRegionForDisplay(Info display) {
if (DEBUG) {
- Log.d(TAG, "creating rotation region for: " + mCurrentDisplayRotation);
+ Log.d(TAG, "creating rotation region for: " + mCurrentDisplay.rotation);
}
Point size = display.realSize;
@@ -341,7 +376,9 @@
}
for (int i = 0; i < MAX_ORIENTATIONS; i++) {
- OrientationRectF rect = mSwipeTouchRegions.get(i);
+ CurrentDisplay display = new CurrentDisplay(mCurrentDisplay.size, i);
+ OrientationRectF rect = mSwipeTouchRegions.get(display);
+
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "transform:DOWN, rect=" + rect);
}
@@ -355,7 +392,7 @@
mLastRectTouched = rect;
mActiveTouchRotation = rect.mRotation;
if (mEnableMultipleRegions
- && mCurrentDisplayRotation == mActiveTouchRotation) {
+ && mCurrentDisplay.rotation == mActiveTouchRotation) {
// TODO(b/154580671) might make this block unnecessary
// Start a touch session for the default nav region for the display
mQuickStepStartingRotation = mLastRectTouched.mRotation;
@@ -378,8 +415,8 @@
pw.println(" lastTouchedRegion=" + mLastRectTouched);
pw.println(" multipleRegionsEnabled=" + mEnableMultipleRegions);
StringBuilder regions = new StringBuilder(" currentTouchableRotations=");
- for(int i = 0; i < mSwipeTouchRegions.size(); i++) {
- OrientationRectF rectF = mSwipeTouchRegions.get(mSwipeTouchRegions.keyAt(i));
+ for (CurrentDisplay key: mSwipeTouchRegions.keySet()) {
+ OrientationRectF rectF = mSwipeTouchRegions.get(key);
regions.append(rectF).append(" ");
}
pw.println(regions.toString());
@@ -417,12 +454,12 @@
boolean applyTransform(MotionEvent event, boolean forceTransform) {
mTmpMatrix.reset();
- postDisplayRotation(deltaRotation(mCurrentDisplayRotation, mRotation),
+ postDisplayRotation(deltaRotation(mCurrentDisplay.rotation, mRotation),
mHeight, mWidth, mTmpMatrix);
if (forceTransform) {
if (DEBUG) {
Log.d(TAG, "Transforming rotation due to forceTransform, "
- + "mCurrentRotation: " + mCurrentDisplayRotation
+ + "mCurrentRotation: " + mCurrentDisplay.rotation
+ "mRotation: " + mRotation);
}
event.transform(mTmpMatrix);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 70b4f20..1c5dc4c 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -191,6 +191,7 @@
} else {
task = new Task(taskKey);
}
+ task.setLastSnapshotData(rawTask);
allTasks.add(task);
}
@@ -200,9 +201,7 @@
private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
ArrayList<Task> newTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
- Task t = tasks.get(i);
- newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable,
- t.isLocked, t.taskDescription, t.topActivity));
+ newTasks.add(new Task(tasks.get(i)));
}
return newTasks;
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 7beeae2..8aa0842 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -184,8 +184,11 @@
}
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ AnimationResult result) {
AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
wallpaperTargets);
anim.addListener(resetStateListener());
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 4301377..f99b7e6 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,14 +17,16 @@
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.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_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_ASSIST_GESTURE_CONSTRAINED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
@@ -44,6 +46,7 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserManager;
@@ -57,11 +60,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.SecureSettingsObserver;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
@@ -177,33 +180,33 @@
}
}
+ SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
if (mIsOneHandedModeSupported) {
- SecureSettingsObserver oneHandedEnabledObserver =
- SecureSettingsObserver.newOneHandedSettingsObserver(
- mContext, enabled -> mIsOneHandedModeEnabled = enabled);
- oneHandedEnabledObserver.register();
- oneHandedEnabledObserver.dispatchOnChange();
- runOnDestroy(oneHandedEnabledObserver::unregister);
+ Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
+ SettingsCache.OnChangeListener onChangeListener =
+ enabled -> mIsOneHandedModeEnabled = enabled;
+ settingsCache.register(oneHandedUri, onChangeListener);
+ settingsCache.dispatchOnChange(oneHandedUri);
+ runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
} else {
mIsOneHandedModeEnabled = false;
}
- SecureSettingsObserver swipeBottomEnabledObserver =
- SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
- mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
- swipeBottomEnabledObserver.register();
- swipeBottomEnabledObserver.dispatchOnChange();
- runOnDestroy(swipeBottomEnabledObserver::unregister);
- SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
- context.getContentResolver(),
- e -> mIsUserSetupComplete = e,
- Settings.Secure.USER_SETUP_COMPLETE,
- 0);
- mIsUserSetupComplete = userSetupObserver.getValue();
+ Uri swipeBottomNotificationUri =
+ Settings.Secure.getUriFor(ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
+ SettingsCache.OnChangeListener onChangeListener =
+ enabled -> mIsSwipeToNotificationEnabled = enabled;
+ settingsCache.register(swipeBottomNotificationUri, onChangeListener);
+ settingsCache.dispatchOnChange(swipeBottomNotificationUri);
+ runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
+
+ Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
+ mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0);
if (!mIsUserSetupComplete) {
- userSetupObserver.register();
- runOnDestroy(userSetupObserver::unregister);
+ SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
+ settingsCache.register(setupCompleteUri, userSetupChangeListener);
+ runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 85b21e0..64d05e1 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -481,6 +481,17 @@
}
@Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.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) {
try {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 93ebd5a..0d2c42e 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -122,12 +122,11 @@
*/
public static class TaskOverlay<T extends OverviewActionsView> {
- private final Context mApplicationContext;
+ protected final Context mApplicationContext;
protected final TaskThumbnailView mThumbnailView;
private T mActionsView;
- private ImageActionsApi mImageApi;
- private boolean mIsAllowedByPolicy;
+ protected ImageActionsApi mImageApi;
protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
@@ -153,24 +152,8 @@
if (thumbnail != null) {
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
- final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
-
- getActionsView().setCallbacks(new OverlayUICallbacks() {
- @Override
- public void onShare() {
- if (isAllowedByPolicy) {
- endLiveTileMode(() -> mImageApi.startShareActivity(null));
- } else {
- showBlockedByPolicyMessage();
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onScreenshot() {
- endLiveTileMode(() -> saveScreenshot(task));
- }
- });
+ boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+ getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
}
}
@@ -193,7 +176,7 @@
* Called to save screenshot of the task thumbnail.
*/
@SuppressLint("NewApi")
- private void saveScreenshot(Task task) {
+ protected void saveScreenshot(Task task) {
if (mThumbnailView.isRealSnapshot()) {
mImageApi.saveScreenshot(mThumbnailView.getThumbnail(),
getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
@@ -222,6 +205,12 @@
}
/**
+ * Sets full screen progress to the task overlay.
+ */
+ public void setFullscreenProgress(float progress) {
+ }
+
+ /**
* Gets the system shortcut for the screenshot that will be added to the task menu.
*/
public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity,
@@ -251,7 +240,7 @@
return mThumbnailView.getScaledInsets();
}
- private void showBlockedByPolicyMessage() {
+ protected void showBlockedByPolicyMessage() {
Toast.makeText(
mThumbnailView.getContext(),
R.string.blocked_by_policy,
@@ -273,6 +262,29 @@
dismissTaskMenuView(mActivity);
}
}
+
+ protected class OverlayUICallbacksImpl implements OverlayUICallbacks {
+ protected final boolean mIsAllowedByPolicy;
+ protected final Task mTask;
+
+ public OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task) {
+ mIsAllowedByPolicy = isAllowedByPolicy;
+ mTask = task;
+ }
+
+ public void onShare() {
+ if (mIsAllowedByPolicy) {
+ endLiveTileMode(() -> mImageApi.startShareActivity(null));
+ } else {
+ showBlockedByPolicyMessage();
+ }
+ }
+
+ @SuppressLint("NewApi")
+ public void onScreenshot() {
+ endLiveTileMode(() -> saveScreenshot(mTask));
+ }
+ }
}
/**
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 9da306e..25c0928 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -49,6 +49,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
@@ -174,11 +175,13 @@
final RecentsView recentsView = v.getRecentsView();
int taskIndex = recentsView.indexOfChild(v);
- boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage();
- int startScroll = recentsView.getScrollOffset(taskIndex);
-
Context context = v.getContext();
DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+ boolean parallaxCenterAndAdjacentTask =
+ taskIndex != recentsView.getCurrentPage() && !(dp.isTablet
+ && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
+ int startScroll = recentsView.getScrollOffset(taskIndex);
+
TaskViewSimulator topMostSimulator = null;
if (tsv == null && targets.apps.length > 0) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 8ebea33..e4c8b6f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,7 +24,6 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -38,18 +37,23 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
import android.view.Choreographer;
+import android.view.Display;
import android.view.InputEvent;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.BinderThread;
@@ -92,6 +96,7 @@
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -122,6 +127,9 @@
*/
private static final int SYSTEM_ACTION_ID_ALL_APPS = 14;
+ public static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
+ SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
+
private int mBackGestureNotificationCounter = -1;
@Nullable
private OverscrollPlugin mOverscrollPlugin;
@@ -249,6 +257,8 @@
private InputMonitorCompat mInputMonitorCompat;
private InputEventReceiver mInputEventReceiver;
+ private DisplayManager mDisplayManager;
+
@Override
public void onCreate() {
super.onCreate();
@@ -262,6 +272,7 @@
mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
ProtoTracer.INSTANCE.get(this).add(this);
+ mDisplayManager = getSystemService(DisplayManager.class);
sConnected = true;
}
@@ -283,9 +294,7 @@
return;
}
- Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
- mDeviceState.getDisplayId());
- mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
+ mInputMonitorCompat = new InputMonitorCompat("swipe-up", mDeviceState.getDisplayId());
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
@@ -429,6 +438,15 @@
return;
}
MotionEvent event = (MotionEvent) ev;
+ if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
+ final Display display = mDisplayManager.getDisplay(mDeviceState.getDisplayId());
+ int rotation = display.getRotation();
+ Point sz = new Point();
+ display.getRealSize(sz);
+ if (rotation != Surface.ROTATION_0) {
+ event.transform(InputChannelCompat.createRotationMatrix(rotation, sz.x, sz.y));
+ }
+ }
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index f2f9fb7..5baf518 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -38,10 +38,12 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.graphics.PointF;
+import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -64,6 +66,7 @@
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;
import com.android.quickstep.util.MotionPauseDetector;
@@ -113,6 +116,8 @@
private final PointF mLastPos = new PointF();
private int mActivePointerId = INVALID_POINTER_ID;
+ private int mLastRotation = -1;
+
// Distance after which we start dragging the window.
private final float mTouchSlop;
@@ -130,6 +135,8 @@
// Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
private float mStartDisplacement;
+ private final DisplayManager mDisplayManager;
+
private Handler mMainThreadHandler;
private Runnable mCancelRecentsAnimationRunnable = () -> {
ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
@@ -172,6 +179,7 @@
mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
+ mDisplayManager = getSystemService(DisplayManager.class);
}
@Override
@@ -197,6 +205,17 @@
return;
}
+ if (TouchInteractionService.ENABLE_PER_WINDOW_INPUT_ROTATION) {
+ final Display display = mDisplayManager.getDisplay(mDeviceState.getDisplayId());
+ final int rotation = display.getRotation();
+ if (rotation != mLastRotation) {
+ // If rotation changes, reset tracking to avoid degenerate velocities.
+ mLastPos.set(ev.getX(), ev.getY());
+ mVelocityTracker.clear();
+ mLastRotation = rotation;
+ }
+ }
+
// Proxy events to recents view
if (mPassedWindowMoveSlop && mInteractionHandler != null
&& !mRecentsViewDispatcher.hasConsumer()) {
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 3157865..f336bf5 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -28,7 +28,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED;
import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import android.content.Context;
import android.content.SharedPreferences;
@@ -43,7 +43,7 @@
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
@@ -77,11 +77,10 @@
getPrefs(context).registerOnSharedPreferenceChangeListener(this);
getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
- SecureSettingsObserver dotsObserver =
- newNotificationSettingsObserver(context, this::onNotificationDotsChanged);
- mNotificationDotsEnabled = dotsObserver.getValue();
- dispatchUserEvent();
-
+ SettingsCache mSettingsCache = SettingsCache.INSTANCE.get(context);
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ this::onNotificationDotsChanged);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
}
private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index d22496d..a762cb7 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,9 +16,13 @@
package com.android.quickstep.logging;
+import static androidx.core.util.Preconditions.checkNotNull;
+import static androidx.core.util.Preconditions.checkState;
+
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
+import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
@@ -28,7 +32,9 @@
import android.content.Context;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
+import androidx.slice.SliceItem;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
@@ -38,6 +44,8 @@
import com.android.launcher3.logger.LauncherAtom.FolderIcon;
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
+import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer;
+import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.AllAppsList;
@@ -85,7 +93,7 @@
}
@Override
- public StatsLogger logger() {
+ protected StatsLogger createLogger() {
return new StatsCompatLogger();
}
@@ -137,6 +145,7 @@
private Optional<FromState> mFromState = Optional.empty();
private Optional<ToState> mToState = Optional.empty();
private Optional<String> mEditText = Optional.empty();
+ private SliceItem mSliceItem;
@Override
public StatsLogger withItemInfo(ItemInfo itemInfo) {
@@ -174,10 +183,8 @@
@Override
public StatsLogger withContainerInfo(ContainerInfo containerInfo) {
- if (mItemInfo != DEFAULT_ITEM_INFO) {
- throw new IllegalArgumentException(
+ checkState(mItemInfo == DEFAULT_ITEM_INFO,
"ItemInfo and ContainerInfo are mutual exclusive; cannot log both.");
- }
this.mContainerInfo = Optional.of(containerInfo);
return this;
}
@@ -201,12 +208,34 @@
}
@Override
+ public StatsLogger withSliceItem(@NonNull SliceItem sliceItem) {
+ this.mSliceItem = checkNotNull(sliceItem, "expected valid sliceItem but received null");
+ checkState(mItemInfo == DEFAULT_ITEM_INFO,
+ "ItemInfo and SliceItem are mutual exclusive; cannot log both.");
+ return this;
+ }
+
+ @Override
public void log(EventEnum event) {
if (!Utilities.ATLEAST_R) {
return;
}
LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+
+ if (mSliceItem != null) {
+ Executors.MODEL_EXECUTOR.execute(
+ () -> {
+ LauncherAtom.ItemInfo.Builder itemInfoBuilder =
+ LauncherAtom.ItemInfo.newBuilder().setSlice(
+ LauncherAtom.Slice.newBuilder().setUri(
+ mSliceItem.getSlice().getUri().toString()));
+ mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
+ write(event, applyOverwrites(itemInfoBuilder.build()));
+ });
+ return;
+ }
+
if (mItemInfo.container < 0 || appState == null) {
// Write log on the model thread so that logs do not go out of order
// (for eg: drop comes after drag)
@@ -301,6 +330,14 @@
return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
case SEARCH_RESULT_CONTAINER:
return info.getContainerInfo().getSearchResultContainer().getQueryLength();
+ case EXTENDED_CONTAINERS:
+ ExtendedContainers extendedCont = info.getContainerInfo().getExtendedContainers();
+ if (extendedCont.getContainerCase() == DEVICE_SEARCH_RESULT_CONTAINER) {
+ DeviceSearchResultContainer deviceSearchResultCont = extendedCont
+ .getDeviceSearchResultContainer();
+ return deviceSearchResultCont.hasQueryLength() ? deviceSearchResultCont
+ .getQueryLength() : -1;
+ }
default:
return info.getFolderIcon().getCardinality();
}
@@ -316,6 +353,8 @@
return info.getWidget().getPackageName();
case TASK:
return info.getTask().getPackageName();
+ case SEARCH_ACTION_ITEM:
+ return info.getSearchActionItem().getPackageName();
default:
return null;
}
@@ -331,6 +370,10 @@
return info.getWidget().getComponentName();
case TASK:
return info.getTask().getComponentName();
+ case SEARCH_ACTION_ITEM:
+ return info.getSearchActionItem().getTitle();
+ case SLICE:
+ return info.getSlice().getUri();
default:
return null;
}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 8cde5f2..8151d41 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,8 +15,6 @@
*/
package com.android.quickstep.util;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SYSTEM_VELOCITY_PROVIDER;
-
import android.content.Context;
import android.content.res.Resources;
import android.view.MotionEvent;
@@ -51,7 +49,7 @@
private final Alarm mForcePauseTimeout;
private final boolean mMakePauseHarderToTrigger;
private final Context mContext;
- private final VelocityProvider mVelocityProvider;
+ private final SystemVelocityProvider mVelocityProvider;
private Float mPreviousVelocity = null;
@@ -88,8 +86,7 @@
mForcePauseTimeout = new Alarm();
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
mMakePauseHarderToTrigger = makePauseHarderToTrigger;
- mVelocityProvider = ENABLE_SYSTEM_VELOCITY_PROVIDER.get()
- ? new SystemVelocityProvider(axis) : new LinearVelocityProvider(axis);
+ mVelocityProvider = new SystemVelocityProvider(axis);
}
/**
@@ -124,8 +121,8 @@
mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
? HARDER_TRIGGER_TIMEOUT
: FORCE_PAUSE_TIMEOUT);
- Float newVelocity = mVelocityProvider.addMotionEvent(ev, pointerIndex);
- if (newVelocity != null && mPreviousVelocity != null) {
+ float newVelocity = mVelocityProvider.addMotionEvent(ev, pointerIndex);
+ if (mPreviousVelocity != null) {
checkMotionPaused(newVelocity, mPreviousVelocity, ev.getEventTime());
}
mPreviousVelocity = newVelocity;
@@ -210,58 +207,7 @@
default void onMotionPauseChanged(boolean isPaused) { }
}
- /**
- * Interface to abstract out velocity calculations
- */
- protected interface VelocityProvider {
-
- /**
- * Adds a new motion events, and returns the velocity at this point, or null if
- * the velocity is not available
- */
- Float addMotionEvent(MotionEvent ev, int pointer);
-
- /**
- * Clears all stored motion event records
- */
- void clear();
- }
-
- private static class LinearVelocityProvider implements VelocityProvider {
-
- private Long mPreviousTime = null;
- private Float mPreviousPosition = null;
-
- private final int mAxis;
-
- LinearVelocityProvider(int axis) {
- mAxis = axis;
- }
-
- @Override
- public Float addMotionEvent(MotionEvent ev, int pointer) {
- long time = ev.getEventTime();
- float position = ev.getAxisValue(mAxis, pointer);
- Float velocity = null;
-
- if (mPreviousTime != null && mPreviousPosition != null) {
- long changeInTime = Math.max(1, time - mPreviousTime);
- float changeInPosition = position - mPreviousPosition;
- velocity = changeInPosition / changeInTime;
- }
- mPreviousTime = time;
- mPreviousPosition = position;
- return velocity;
- }
-
- @Override
- public void clear() {
- mPreviousTime = null;
- mPreviousPosition = null;
- }
- }
-
- private static class SystemVelocityProvider implements VelocityProvider {
+ private static class SystemVelocityProvider {
private final VelocityTracker mVelocityTracker;
private final int mAxis;
@@ -271,8 +217,11 @@
mAxis = axis;
}
- @Override
- public Float addMotionEvent(MotionEvent ev, int pointer) {
+ /**
+ * Adds a new motion events, and returns the velocity at this point, or null if
+ * the velocity is not available
+ */
+ public float addMotionEvent(MotionEvent ev, int pointer) {
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1); // px / ms
return mAxis == MotionEvent.AXIS_X
@@ -280,7 +229,9 @@
: mVelocityTracker.getYVelocity(pointer);
}
- @Override
+ /**
+ * Clears all stored motion event records
+ */
public void clear() {
mVelocityTracker.clear();
}
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index cb81d36..1544f00 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -27,10 +27,8 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
-import com.android.launcher3.allapps.AllAppsInsetTransitionController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
-import com.android.launcher3.search.DeviceSearchEdu;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -43,7 +41,6 @@
*/
public class QuickstepOnboardingPrefs extends OnboardingPrefs<QuickstepLauncher> {
-
public QuickstepOnboardingPrefs(QuickstepLauncher launcher, SharedPreferences sharedPrefs) {
super(launcher, sharedPrefs);
@@ -66,8 +63,7 @@
});
}
- if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() && !hasReachedMaxCount(
- HOTSEAT_DISCOVERY_TIP_COUNT)) {
+ if (!hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
stateManager.addStateListener(new StateListener<LauncherState>() {
boolean mFromAllApps = false;
@@ -134,22 +130,5 @@
}
});
}
-
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !getBoolean(SEARCH_EDU_SEEN)) {
- stateManager.addStateListener(new StateListener<LauncherState>() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
- if (toState == ALL_APPS) {
- AllAppsInsetTransitionController insetTransitionController =
- mLauncher.getAllAppsController().getInsetController();
- insetTransitionController.setSearchEduRunnable(() -> {
- DeviceSearchEdu.show(launcher);
- insetTransitionController.setSearchEduRunnable(null);
- });
- stateManager.removeStateListener(this);
- }
- }
- });
- }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index a3ee912..215f05a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -23,22 +23,18 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static com.android.launcher3.Utilities.newContentObserver;
+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 java.lang.annotation.RetentionPolicy.SOURCE;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Handler;
-import android.provider.Settings;
import android.util.Log;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
@@ -51,6 +47,7 @@
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.WindowBounds;
@@ -72,9 +69,6 @@
private static final String TAG = "RecentsOrientedState";
private static final boolean DEBUG = false;
- private ContentObserver mSystemAutoRotateObserver =
- newContentObserver(new Handler(), t -> updateAutoRotateSetting());
-
@Retention(SOURCE)
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
public @interface SurfaceRotation {}
@@ -118,9 +112,11 @@
| FLAG_SWIPE_UP_NOT_RUNNING;
private final Context mContext;
- private final ContentResolver mContentResolver;
private final SharedPreferences mSharedPrefs;
private final OrientationEventListener mOrientationListener;
+ private final SettingsCache mSettingsCache;
+ private final SettingsCache.OnChangeListener mRotationChangeListener =
+ isEnabled -> updateAutoRotateSetting();
private final Matrix mTmpMatrix = new Matrix();
@@ -138,7 +134,6 @@
public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
IntConsumer rotationChangeListener) {
mContext = context;
- mContentResolver = context.getContentResolver();
mSharedPrefs = Utilities.getPrefs(context);
mOrientationListener = new OrientationEventListener(context) {
@Override
@@ -162,6 +157,7 @@
mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
}
mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
+ mSettingsCache = SettingsCache.INSTANCE.get(mContext);
initFlags();
}
@@ -271,8 +267,8 @@
}
private void updateAutoRotateSetting() {
- setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver,
- Settings.System.ACCELEROMETER_ROTATION, 1) == 1);
+ setFlag(FLAG_SYSTEM_ROTATION_ALLOWED,
+ mSettingsCache.getValue(ROTATION_SETTING_URI, 1));
}
private void updateHomeRotationSetting() {
@@ -295,9 +291,7 @@
public void initListeners() {
if (isMultipleOrientationSupportedByDevice()) {
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
- mContentResolver.registerContentObserver(
- Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
- false, mSystemAutoRotateObserver);
+ mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
}
initFlags();
}
@@ -308,7 +302,7 @@
public void destroyListeners() {
if (isMultipleOrientationSupportedByDevice()) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
- mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
+ mSettingsCache.unregister(ROTATION_SETTING_URI, mRotationChangeListener);
}
setRotationWatcherEnabled(false);
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 19c6588..3adb459 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -43,8 +43,11 @@
}
@Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonApps,
+ AnimationResult result) {
result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
}
};
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 5bae3c7..932ff27 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -139,7 +139,7 @@
addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
- mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f)
+ mAnimators.play(launcher.getDragLayer().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
.setDuration(ALPHA_DURATION_MS));
mAnimators.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 378f25b..0ce5072 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -31,6 +31,7 @@
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
@@ -60,7 +61,7 @@
/** for calculating the transform in {@link #onAnimationUpdate(ValueAnimator)} */
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
- private final Rect mSourceHintRectInsets = new Rect();
+ private final Rect mSourceHintRectInsets;
private final Rect mSourceInsets = new Rect();
/** for rotation via {@link #setFromRotation(TaskViewSimulator, int)} */
@@ -89,7 +90,7 @@
public SwipePipToHomeAnimator(int taskId,
@NonNull ComponentName componentName,
@NonNull SurfaceControl leash,
- @NonNull Rect sourceRectHint,
+ @Nullable Rect sourceRectHint,
@NonNull Rect appBounds,
@NonNull Rect startBounds,
@NonNull Rect destinationBounds,
@@ -104,10 +105,14 @@
mDestinationBoundsAnimation.set(mDestinationBounds);
mSurfaceTransactionHelper = new PipSurfaceTransactionHelper();
- mSourceHintRectInsets.set(sourceRectHint.left - appBounds.left,
- sourceRectHint.top - appBounds.top,
- appBounds.right - sourceRectHint.right,
- appBounds.bottom - sourceRectHint.bottom);
+ if (sourceRectHint == null) {
+ mSourceHintRectInsets = null;
+ } else {
+ mSourceHintRectInsets = new Rect(sourceRectHint.left - appBounds.left,
+ sourceRectHint.top - appBounds.top,
+ appBounds.right - sourceRectHint.right,
+ appBounds.bottom - sourceRectHint.bottom);
+ }
addListener(new AnimationSuccessListener() {
@Override
@@ -168,34 +173,44 @@
final float fraction = animator.getAnimatedFraction();
final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds,
mDestinationBoundsAnimation);
- final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
- mSourceHintRectInsets);
final SurfaceControl.Transaction tx =
PipSurfaceTransactionHelper.newSurfaceControlTransaction();
- if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
- final float degree, positionX, positionY;
- if (mFromRotation == Surface.ROTATION_90) {
- degree = -90 * fraction;
- positionX = fraction * (mDestinationBoundsTransformed.left - mAppBounds.left)
- + mAppBounds.left;
- positionY = fraction * (mDestinationBoundsTransformed.bottom - mAppBounds.top)
- + mAppBounds.top;
- } else {
- degree = 90 * fraction;
- positionX = fraction * (mDestinationBoundsTransformed.right - mAppBounds.left)
- + mAppBounds.left;
- positionY = fraction * (mDestinationBoundsTransformed.top - mAppBounds.top)
- + mAppBounds.top;
- }
- mSurfaceTransactionHelper.scaleAndRotate(tx, mLeash, mAppBounds, bounds, insets,
- degree, positionX, positionY);
+ if (mSourceHintRectInsets == null) {
+ // no source rect hint been set, directly scale the window down
+ onAnimationScale(fraction, tx, bounds);
} else {
- mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mAppBounds, bounds, insets);
+ // scale and crop according to the source rect hint
+ onAnimationScaleAndCrop(fraction, tx, bounds);
}
mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
tx.apply();
}
+ /** scale the window directly with no source rect hint being set */
+ private void 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,
+ rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
+ } else {
+ mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds);
+ }
+ }
+
+ /** scale and crop the window with source rect hint */
+ private void 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,
+ rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
+ } else {
+ mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mAppBounds, bounds, insets);
+ }
+ }
+
public int getTaskId() {
return mTaskId;
}
@@ -217,4 +232,34 @@
tx.apply();
mHasAnimationEnded = true;
}
+
+ private RotatedPosition getRotatedPosition(float fraction) {
+ final float degree, positionX, positionY;
+ if (mFromRotation == Surface.ROTATION_90) {
+ degree = -90 * fraction;
+ positionX = fraction * (mDestinationBoundsTransformed.left - mAppBounds.left)
+ + mAppBounds.left;
+ positionY = fraction * (mDestinationBoundsTransformed.bottom - mAppBounds.top)
+ + mAppBounds.top;
+ } else {
+ degree = 90 * fraction;
+ positionX = fraction * (mDestinationBoundsTransformed.right - mAppBounds.left)
+ + mAppBounds.left;
+ positionY = fraction * (mDestinationBoundsTransformed.top - mAppBounds.top)
+ + mAppBounds.top;
+ }
+ return new RotatedPosition(degree, positionX, positionY);
+ }
+
+ private static class RotatedPosition {
+ private final float degree;
+ private final float positionX;
+ private final float positionY;
+
+ private RotatedPosition(float degree, float positionX, float positionY) {
+ this.degree = degree;
+ this.positionX = positionX;
+ this.positionY = positionY;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 65bcf26..edce194 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -41,7 +41,6 @@
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;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -92,7 +91,6 @@
// TaskView properties
private final FullscreenDrawParams mCurrentFullscreenParams;
- private float mCurveScale = 1;
public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
@@ -276,8 +274,7 @@
int start = mOrientationState.getOrientationHandler()
.getPrimaryValue(mTaskRect.left, mTaskRect.top);
mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
- mScrollState.updateInterpolation(start);
- mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation);
+ mScrollState.updateInterpolation(mDp, start);
}
float progress = Utilities.boundToRange(fullScreenProgress.value, 0, 1);
@@ -294,8 +291,7 @@
mMatrix.postTranslate(insets.left, insets.top);
mMatrix.postScale(scale, scale);
- // Apply TaskView matrix: scale, translate, scroll
- mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
+ // Apply TaskView matrix: translate, scroll
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
taskPrimaryTranslation.value);
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 6630aed..25ae055 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -71,6 +71,7 @@
private ViewOutlineProvider mOldBannerOutlineProvider;
private float mBannerOffsetPercentage;
private float mBannerAlpha = 1f;
+ private float mVerticalOffset = 0f;
public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) {
mActivity = activity;
@@ -275,16 +276,17 @@
@Override
public void getOutline(View view, Outline outline) {
mOldBannerOutlineProvider.getOutline(view, outline);
- outline.offset(0, -Math.round(view.getTranslationY()));
+ outline.offset(0, Math.round(-view.getTranslationY() + mVerticalOffset));
}
});
mBanner.setClipToOutline(true);
}
- void updateBannerOffset(float offsetPercentage) {
+ void updateBannerOffset(float offsetPercentage, float verticalOffset) {
if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) {
+ mVerticalOffset = verticalOffset;
mBannerOffsetPercentage = offsetPercentage;
- mBanner.setTranslationY(offsetPercentage * mBanner.getHeight());
+ mBanner.setTranslationY(offsetPercentage * mBanner.getHeight() + mVerticalOffset);
mBanner.invalidateOutline();
}
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 8fb7e03..1241982 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -189,6 +189,14 @@
/** Updates vertical margins for different navigation mode or configuration changes. */
public void updateVerticalMargin(Mode mode) {
+ LayoutParams actionParams = (LayoutParams) findViewById(
+ R.id.action_buttons).getLayoutParams();
+ actionParams.setMargins(
+ actionParams.leftMargin, actionParams.topMargin, actionParams.rightMargin,
+ getBottomVerticalMargin(mode));
+ }
+
+ protected int getBottomVerticalMargin(Mode mode) {
int bottomMargin;
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -201,8 +209,6 @@
.getDimensionPixelSize(R.dimen.overview_actions_bottom_margin_gesture);
}
bottomMargin += mInsets.bottom;
- LayoutParams params = (LayoutParams) getLayoutParams();
- params.setMargins(
- params.leftMargin, params.topMargin, params.rightMargin, bottomMargin);
+ return bottomMargin;
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d9d0a93..248fa46 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -537,7 +537,7 @@
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
- if (visibility == GONE && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (visibility != VISIBLE && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
finishRecentsAnimation(true /* toRecents */, null);
}
updateTaskStackListenerState();
@@ -646,9 +646,9 @@
public TaskView getTaskView(int taskId) {
for (int i = 0; i < getTaskViewCount(); i++) {
- TaskView tv = getTaskViewAt(i);
- if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) {
- return tv;
+ TaskView taskView = getTaskViewAt(i);
+ if (taskView.hasTaskId(taskId)) {
+ return taskView;
}
}
return null;
@@ -808,6 +808,7 @@
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
taskView.bind(task, mOrientationState);
+ taskView.updateTaskSize(!taskView.hasTaskId(mRunningTaskId));
}
if (mNextPage == INVALID_PAGE) {
@@ -898,9 +899,14 @@
public void setFullscreenProgress(float fullscreenProgress) {
mFullscreenProgress = fullscreenProgress;
int taskCount = getTaskViewCount();
+ float accumulatedTranslationX = 0;
for (int i = 0; i < taskCount; i++) {
- getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
+ TaskView taskView = getTaskViewAt(i);
+ taskView.setFullscreenProgress(mFullscreenProgress);
+ taskView.setAccumulatedTranslationX(accumulatedTranslationX);
+ accumulatedTranslationX += taskView.getFullscreenTranslationX();
}
+
// Fade out the actions view quickly (0.1 range)
mActionsView.getFullscreenAlpha().setValue(
mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
@@ -934,6 +940,12 @@
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
+ // Force TaskView to update size from thumbnail
+ final int taskCount = getTaskViewCount();
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ taskView.updateTaskSize(!taskView.hasTaskId(mRunningTaskId));
+ }
}
public void getTaskSize(Rect outRect) {
@@ -942,6 +954,11 @@
mLastComputedTaskSize.set(outRect);
}
+ /** Gets the last computed task size */
+ public Rect getLastComputedTaskSize() {
+ return mLastComputedTaskSize;
+ }
+
/** Gets the task size for modal state. */
public void getModalTaskSize(Rect outRect) {
mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
@@ -987,7 +1004,7 @@
final int pageCount = getPageCount();
for (int i = 0; i < pageCount; i++) {
View page = getPageAt(i);
- mScrollState.updateInterpolation(
+ mScrollState.updateInterpolation(mActivity.getDeviceProfile(),
mOrientationHandler.getChildStartWithTranslation(page));
((PageCallbacks) page).onPageScroll(mScrollState);
}
@@ -1237,6 +1254,7 @@
// gesture and the task list is loaded and applied
mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
taskView.bind(mTmpRunningTask, mOrientationState);
+ taskView.updateTaskSize(false);
// Measure and layout immediately so that the scroll values is updated instantly
// as the user might be quick-switching
@@ -1422,13 +1440,13 @@
/**
* Updates linearInterpolation for the provided child position
*/
- public void updateInterpolation(float childStart) {
+ 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.EDGE_SCALE_DOWN_FACTOR);
+ + halfPageSize * (1 - TaskView.getEdgeScaleDownFactor(deviceProfile));
linearInterpolation = Math.min(1,
Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
}
@@ -1444,12 +1462,13 @@
}
}
- private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) {
+ private void addDismissedTaskAnimations(TaskView taskView, long duration,
+ PendingAnimation anim) {
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// 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<View> secondaryViewTranslate =
- mOrientationHandler.getSecondaryViewTranslate();
+ FloatProperty<TaskView> secondaryViewTranslate =
+ taskView.getDismissTaskTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
@@ -1515,7 +1534,7 @@
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
FloatProperty translationProperty = child instanceof TaskView
- ? ((TaskView) child).getPrimaryFillDismissGapTranslationProperty()
+ ? ((TaskView) child).getFillDismissGapTranslationProperty()
: mOrientationHandler.getPrimaryViewTranslate();
ResourceProvider rp = DynamicResource.provider(mActivity);
@@ -1729,7 +1748,7 @@
if (alpha > 0) {
setVisibility(VISIBLE);
} else if (!mFreezeViewVisibility) {
- setVisibility(GONE);
+ setVisibility(INVISIBLE);
}
}
@@ -1741,7 +1760,7 @@
if (mFreezeViewVisibility != freezeViewVisibility) {
mFreezeViewVisibility = freezeViewVisibility;
if (!mFreezeViewVisibility) {
- setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
+ setVisibility(mContentAlpha > 0 ? VISIBLE : INVISIBLE);
}
}
}
@@ -1968,10 +1987,6 @@
centerToOffscreenProgress = Utilities.mapRange(centerToOffscreenProgress,
distanceFromMidpoint / distanceToOffscreen, 1);
}
- // Find the task's scale based on its offscreen progress, then see how far it still needs to
- // move to be completely offscreen.
- Utilities.scaleRectFAboutCenter(taskPosition,
- TaskView.getCurveScaleForInterpolation(centerToOffscreenProgress));
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
@@ -1990,7 +2005,7 @@
mTaskViewsSecondaryTranslation = translation;
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView task = getTaskViewAt(i);
- mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY());
+ task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY());
}
mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
}
@@ -2097,7 +2112,7 @@
anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
} else {
// We are launching an adjacent task, so parallax the center and other adjacent task.
- float displacementX = tv.getWidth() * (toScale - tv.getCurveScale());
+ float displacementX = tv.getWidth() * (toScale - 1f);
float primaryTranslation = mIsRtl ? -displacementX : displacementX;
anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
@@ -2359,6 +2374,52 @@
}
@Override
+ protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
+ ComputePageScrollsLogic scrollLogic) {
+ boolean pageScrollChanged = super.getPageScrolls(outPageScrolls, layoutChildren,
+ scrollLogic);
+
+ final int taskCount = getTaskViewCount();
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (childCount < mTaskViewStartIndex) {
+ continue;
+ }
+
+ final TaskView taskView = getTaskViewAt(
+ Utilities.boundToRange(i, mTaskViewStartIndex, taskCount - 1));
+ float scrollDiff =
+ taskView.getFullscreenTranslationX() + taskView.getAccumulatedTranslationX();
+ if (scrollDiff != 0) {
+ outPageScrolls[i] += scrollDiff;
+ pageScrollChanged = true;
+ }
+ }
+ return pageScrollChanged;
+ }
+
+ @Override
+ protected int getChildOffset(int index) {
+ if (index < mTaskViewStartIndex) {
+ return super.getChildOffset(index);
+ }
+
+ final TaskView taskView = getTaskViewAt(
+ Utilities.boundToRange(index, mTaskViewStartIndex, getTaskViewCount() - 1));
+ return super.getChildOffset(index) + (int) taskView.getFullscreenTranslationX()
+ + (int) taskView.getAccumulatedTranslationX();
+ }
+
+ @Override
+ protected int getChildVisibleSize(int index) {
+ final TaskView taskView = getTaskViewAtByAbsoluteIndex(index);
+ if (taskView == null) {
+ return super.getChildVisibleSize(index);
+ }
+ return super.getChildVisibleSize(index) - (int) taskView.getFullscreenTranslationX();
+ }
+
+ @Override
protected int computeMaxScroll() {
if (getTaskViewCount() > 0) {
if (mDisallowScrollToClearAll) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 4aff7e3..e21bf76 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -24,7 +24,6 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -35,7 +34,6 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
@@ -46,7 +44,6 @@
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.views.IconView.OnScaleUpdateListener;
/**
* Contains options for a recent task when long-pressing its icon.
@@ -55,42 +52,15 @@
private static final Rect sTempRect = new Rect();
- private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() {
- @Override
- public void onScaleUpdate(float scale) {
- final Drawable drawable = mTaskIcon.getDrawable();
- if (drawable instanceof FastBitmapDrawable) {
- if (scale != ((FastBitmapDrawable) drawable).getScale()) {
- mMenuIconDrawable.setScale(scale);
- }
- }
- }
- };
-
- private final OnScaleUpdateListener mMenuIconScaleListener = new OnScaleUpdateListener() {
- @Override
- public void onScaleUpdate(float scale) {
- final Drawable taskViewDrawable = mTaskView.getIconView().getDrawable();
- if (taskViewDrawable instanceof FastBitmapDrawable) {
- final float currentScale = ((FastBitmapDrawable) taskViewDrawable).getScale();
- if (currentScale != scale) {
- ((FastBitmapDrawable) taskViewDrawable).setScale(scale);
- }
- }
- }
- };
-
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 IconView mTaskIcon;
private AnimatorSet mOpenCloseAnimator;
private TaskView mTaskView;
private LinearLayout mOptionLayout;
- private FastBitmapDrawable mMenuIconDrawable;
public TaskMenuView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -107,7 +77,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
mTaskName = findViewById(R.id.task_name);
- mTaskIcon = findViewById(R.id.task_icon);
mOptionLayout = findViewById(R.id.menu_option_layout);
}
@@ -134,15 +103,6 @@
}
@Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- // Remove all scale listeners when menu is removed
- mTaskView.getIconView().removeUpdateScaleListener(mTaskViewIconScaleListener);
- mTaskIcon.removeUpdateScaleListener(mMenuIconScaleListener);
- }
-
- @Override
protected boolean isOfType(int type) {
return (type & TYPE_TASK_MENU) != 0;
}
@@ -203,23 +163,9 @@
}
private void addMenuOptions(TaskView taskView) {
- Drawable icon = taskView.getTask().icon.getConstantState().newDrawable();
- mTaskIcon.setDrawable(icon);
- mTaskIcon.setOnClickListener(v -> close(true));
mTaskName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
mTaskName.setOnClickListener(v -> close(true));
- // Set the icons to match scale by listening to each other's changes
- mMenuIconDrawable = icon instanceof FastBitmapDrawable ? (FastBitmapDrawable) icon : null;
- taskView.getIconView().addUpdateScaleListener(mTaskViewIconScaleListener);
- mTaskIcon.addUpdateScaleListener(mMenuIconScaleListener);
-
- // Move the icon and text up half an icon size to lay over the TaskView
- LinearLayout.LayoutParams params =
- (LinearLayout.LayoutParams) mTaskIcon.getLayoutParams();
- params.topMargin = (int) -mThumbnailTopMargin;
- mTaskIcon.setLayoutParams(params);
-
TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption);
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 445e490..f2f4bc1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -51,6 +51,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SystemUiController;
@@ -480,9 +481,11 @@
float scale = thumbnailData.scale;
final float thumbnailScale;
- // Landscape vs portrait change
+ // Landscape vs portrait change.
+ // Note: Disable rotation in grid layout.
boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
- && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
+ && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN
+ && !(dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
isOrientationDifferent = isOrientationChange(deltaRotate)
&& windowingModeSupportsRotation;
if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 59cf3b2..dfbe6ce 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -30,6 +30,8 @@
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.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;
+import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
@@ -42,7 +44,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
@@ -50,8 +51,6 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
@@ -61,6 +60,7 @@
import android.view.Surface;
import android.view.TouchDelegate;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -72,6 +72,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -110,20 +111,14 @@
private static final String TAG = TaskView.class.getSimpleName();
- /** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
- private static final TimeInterpolator CURVE_INTERPOLATOR
- = x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
-
/**
* 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.
*/
public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
- /**
- * How much to scale down pages near the edge of the screen.
- */
- public static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
+ private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
+ private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
public static final long SCALE_ICON_DURATION = 120;
private static final long DIM_ANIM_DURATION = 700;
@@ -152,29 +147,29 @@
}
};
- private static final FloatProperty<TaskView> FILL_DISMISS_GAP_TRANSLATION_X =
- new FloatProperty<TaskView>("fillDismissGapTranslationX") {
+ private static final FloatProperty<TaskView> DISMISS_TRANSLATION_X =
+ new FloatProperty<TaskView>("dismissTranslationX") {
@Override
public void setValue(TaskView taskView, float v) {
- taskView.setFillDismissGapTranslationX(v);
+ taskView.setDismissTranslationX(v);
}
@Override
public Float get(TaskView taskView) {
- return taskView.mFillDismissGapTranslationX;
+ return taskView.mDismissTranslationX;
}
};
- private static final FloatProperty<TaskView> FILL_DISMISS_GAP_TRANSLATION_Y =
- new FloatProperty<TaskView>("fillDismissGapTranslationY") {
+ private static final FloatProperty<TaskView> DISMISS_TRANSLATION_Y =
+ new FloatProperty<TaskView>("dismissTranslationY") {
@Override
public void setValue(TaskView taskView, float v) {
- taskView.setFillDismissGapTranslationY(v);
+ taskView.setDismissTranslationY(v);
}
@Override
public Float get(TaskView taskView) {
- return taskView.mFillDismissGapTranslationY;
+ return taskView.mDismissTranslationY;
}
};
@@ -204,6 +199,32 @@
}
};
+ private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_X =
+ new FloatProperty<TaskView>("taskResistanceTranslationX") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setTaskResistanceTranslationX(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mTaskResistanceTranslationX;
+ }
+ };
+
+ private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_Y =
+ new FloatProperty<TaskView>("taskResistanceTranslationY") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setTaskResistanceTranslationY(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mTaskResistanceTranslationY;
+ }
+ };
+
private final OnAttachStateChangeListener mTaskMenuStateListener =
new OnAttachStateChangeListener() {
@Override
@@ -226,17 +247,23 @@
private TaskMenuView mMenuView;
private IconView mIconView;
private final DigitalWellBeingToast mDigitalWellBeingToast;
- private float mCurveScale;
private float mFullscreenProgress;
+ private float mScaleAtFullscreen = 1;
+ private float mFullscreenScale = 1;
private final FullscreenDrawParams mCurrentFullscreenParams;
private final StatefulActivity mActivity;
// Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
- // TODO: We should do this for secondary translation properties as well.
- private float mFillDismissGapTranslationX;
- private float mFillDismissGapTranslationY;
+ private float mDismissTranslationX;
+ private float mDismissTranslationY;
private float mTaskOffsetTranslationX;
private float mTaskOffsetTranslationY;
+ private float mTaskResistanceTranslationX;
+ private float mTaskResistanceTranslationY;
+ // The following translation variables should only be used in the same orientation as Launcher.
+ private float mFullscreenTranslationX;
+ private float mAccumulatedTranslationX;
+ private float mBoxTranslationY;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
@@ -401,10 +428,9 @@
mContextualChip.setScaleX(comp(modalness));
mContextualChip.setScaleY(comp(modalness));
}
- if (mContextualChipWrapper != null) {
- mContextualChipWrapper.setAlpha(comp(modalness));
- }
- mDigitalWellBeingToast.updateBannerOffset(modalness);
+ mDigitalWellBeingToast.updateBannerOffset(modalness,
+ mCurrentFullscreenParams.mCurrentDrawnInsets.top
+ + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
}
public TaskMenuView getMenuView() {
@@ -432,6 +458,10 @@
return mTask;
}
+ public boolean hasTaskId(int taskId) {
+ return mTask != null && mTask.key != null && mTask.key.id == taskId;
+ }
+
public TaskThumbnailView getThumbnail() {
return mSnapshotView;
}
@@ -581,6 +611,9 @@
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;
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
switch (orientationHandler.getRotation()) {
case ROTATION_90:
@@ -592,7 +625,8 @@
case ROTATION_180:
iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
iconParams.bottomMargin = -thumbnailPadding;
- iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
+ iconParams.leftMargin = iconParams.rightMargin = 0;
+ iconParams.topMargin = iconTopMargin;
break;
case ROTATION_270:
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
@@ -603,7 +637,8 @@
case Surface.ROTATION_0:
default:
iconParams.gravity = TOP | CENTER_HORIZONTAL;
- iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
+ iconParams.leftMargin = iconParams.rightMargin = 0;
+ iconParams.topMargin = iconTopMargin;
break;
}
mIconView.setLayoutParams(iconParams);
@@ -619,7 +654,7 @@
progress = 1 - progress;
}
mFocusTransitionProgress = progress;
- mSnapshotView.setDimAlphaMultipler(progress);
+ mSnapshotView.setDimAlphaMultipler(0);
float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
float lowerClamp = invert ? 1f - iconScalePercentage : 0;
float upperClamp = invert ? 1 : iconScalePercentage;
@@ -632,7 +667,9 @@
mContextualChip.setScaleX(scale);
mContextualChip.setScaleY(scale);
}
- mDigitalWellBeingToast.updateBannerOffset(1f - scale);
+ mDigitalWellBeingToast.updateBannerOffset(1f - scale,
+ mCurrentFullscreenParams.mCurrentDrawnInsets.top
+ + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
}
public void setIconScaleAnimStartProgress(float startProgress) {
@@ -667,11 +704,12 @@
}
protected void resetViewTransforms() {
- setCurveScale(1);
- mFillDismissGapTranslationX = mTaskOffsetTranslationX = 0f;
- mFillDismissGapTranslationY = mTaskOffsetTranslationY = 0f;
- setTranslationX(0f);
- setTranslationY(0f);
+ // fullscreenTranslation and accumulatedTranslation should not be reset, as
+ // resetViewTransforms is called during Quickswitch scrolling.
+ mDismissTranslationX = mTaskOffsetTranslationX = mTaskResistanceTranslationX = 0f;
+ mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = 0f;
+ applyTranslationX();
+ applyTranslationY();
setTranslationZ(0);
setAlpha(mStableAlpha);
setIconScaleAndDim(1);
@@ -684,6 +722,7 @@
@Override
public void onRecycle() {
+ mFullscreenTranslationX = mAccumulatedTranslationX = mBoxTranslationY = 0f;
resetViewTransforms();
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
@@ -699,13 +738,6 @@
return;
}
- float curveInterpolation =
- CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
- float curveScaleForCurveInterpolation = getCurveScaleForCurveInterpolation(
- curveInterpolation);
- mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
- setCurveScale(curveScaleForCurveInterpolation);
-
float dwbBannerAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation,
0f, 1f);
mDigitalWellBeingToast.updateBannerAlpha(dwbBannerAlpha);
@@ -736,30 +768,16 @@
layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
int expectedChipHeight = getExpectedViewHeight(view);
float chipOffset = getResources().getDimension(R.dimen.chip_hint_vertical_offset);
- layoutParams.bottomMargin = (int)
- (((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin
- - expectedChipHeight + chipOffset);
+ layoutParams.bottomMargin = -expectedChipHeight - (int) chipOffset;
mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
mContextualChip.setScaleX(0f);
mContextualChip.setScaleY(0f);
- GradientDrawable scrimDrawable = (GradientDrawable) getResources().getDrawable(
- R.drawable.chip_scrim_gradient, mActivity.getTheme());
- float cornerRadius = getTaskCornerRadius();
- scrimDrawable.setCornerRadii(
- new float[]{0, 0, 0, 0, cornerRadius, cornerRadius, cornerRadius,
- cornerRadius});
- InsetDrawable scrimDrawableInset = new InsetDrawable(scrimDrawable, 0, 0, 0,
- (int) (expectedChipHeight - chipOffset));
- mContextualChipWrapper.setBackground(scrimDrawableInset);
- mContextualChipWrapper.setPadding(0, 0, 0, 0);
- mContextualChipWrapper.setAlpha(0f);
addView(view, getChildCount(), layoutParams);
if (mContextualChip != null) {
mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
}
if (mContextualChipWrapper != null) {
mChipTouchDelegate = new TransformingTouchDelegate(mContextualChipWrapper);
- mContextualChipWrapper.animate().alpha(1f).setDuration(50);
}
}
}
@@ -787,40 +805,47 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- setPivotX((right - left) * 0.5f);
- setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
+ if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+ setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? (right - left) : 0);
+ setPivotY(0);
+ } else {
+ setPivotX((right - left) * 0.5f);
+ setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
+ }
if (Utilities.ATLEAST_Q) {
SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
}
}
- public static float getCurveScaleForInterpolation(float linearInterpolation) {
- float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation);
- return getCurveScaleForCurveInterpolation(curveInterpolation);
+ /**
+ * How much to scale down pages near the edge of the screen.
+ */
+ public static float getEdgeScaleDownFactor(DeviceProfile deviceProfile) {
+ if (deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+ return EDGE_SCALE_DOWN_FACTOR_GRID;
+ } else {
+ return EDGE_SCALE_DOWN_FACTOR_CAROUSEL;
+ }
}
- private static float getCurveScaleForCurveInterpolation(float curveInterpolation) {
- return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
+ private void setFullscreenScale(float fullscreenScale) {
+ mFullscreenScale = fullscreenScale;
+ applyScale();
}
- private void setCurveScale(float curveScale) {
- mCurveScale = curveScale;
- setScaleX(mCurveScale);
- setScaleY(mCurveScale);
+ private void applyScale() {
+ setScaleX(mFullscreenScale);
+ setScaleY(mFullscreenScale);
}
- public float getCurveScale() {
- return mCurveScale;
- }
-
- private void setFillDismissGapTranslationX(float x) {
- mFillDismissGapTranslationX = x;
+ private void setDismissTranslationX(float x) {
+ mDismissTranslationX = x;
applyTranslationX();
}
- private void setFillDismissGapTranslationY(float y) {
- mFillDismissGapTranslationY = y;
+ private void setDismissTranslationY(float y) {
+ mDismissTranslationY = y;
applyTranslationY();
}
@@ -834,17 +859,59 @@
applyTranslationY();
}
+ private void setTaskResistanceTranslationX(float x) {
+ mTaskResistanceTranslationX = x;
+ applyTranslationX();
+ }
+
+ private void setTaskResistanceTranslationY(float y) {
+ mTaskResistanceTranslationY = y;
+ applyTranslationY();
+ }
+
+ private void setFullscreenTranslationX(float fullscreenTranslationX) {
+ mFullscreenTranslationX = fullscreenTranslationX;
+ applyTranslationX();
+ }
+
+ public float getFullscreenTranslationX() {
+ return mFullscreenTranslationX;
+ }
+
+ public void setAccumulatedTranslationX(float accumulatedTranslationX) {
+ mAccumulatedTranslationX = accumulatedTranslationX;
+ applyTranslationX();
+ }
+
+ public float getAccumulatedTranslationX() {
+ return mAccumulatedTranslationX;
+ }
+
+ private void setBoxTranslationY(float boxTranslationY) {
+ mBoxTranslationY = boxTranslationY;
+ applyTranslationY();
+ }
+
private void applyTranslationX() {
- setTranslationX(mFillDismissGapTranslationX + mTaskOffsetTranslationX);
+ setTranslationX(
+ mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
+ + mFullscreenTranslationX + mAccumulatedTranslationX);
}
private void applyTranslationY() {
- setTranslationY(mFillDismissGapTranslationY + mTaskOffsetTranslationY);
+ setTranslationY(
+ mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY
+ + mBoxTranslationY);
}
- public FloatProperty<TaskView> getPrimaryFillDismissGapTranslationProperty() {
+ public FloatProperty<TaskView> getFillDismissGapTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
- FILL_DISMISS_GAP_TRANSLATION_X, FILL_DISMISS_GAP_TRANSLATION_Y);
+ DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
+ }
+
+ public FloatProperty<TaskView> getDismissTaskTranslationProperty() {
+ return getPagedOrientationHandler().getSecondaryValue(
+ DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() {
@@ -852,6 +919,11 @@
TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
}
+ public FloatProperty<TaskView> getTaskResistanceTranslationProperty() {
+ return getPagedOrientationHandler().getSecondaryValue(
+ TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
+ }
+
@Override
public boolean hasOverlappingRendering() {
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
@@ -982,10 +1054,10 @@
public void setFullscreenProgress(float progress) {
progress = Utilities.boundToRange(progress, 0, 1);
mFullscreenProgress = progress;
- boolean isFullscreen = mFullscreenProgress > 0;
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
- setClipChildren(!isFullscreen);
- setClipToPadding(!isFullscreen);
+ getThumbnail().getTaskOverlay().setFullscreenProgress(progress);
+
+ updateTaskScaling();
TaskThumbnailView thumbnail = getThumbnail();
updateCurrentFullscreenParams(thumbnail.getPreviewPositionHelper());
@@ -1012,6 +1084,84 @@
previewPositionHelper);
}
+ void updateTaskSize(boolean variableWidth) {
+ ViewGroup.LayoutParams params = getLayoutParams();
+ float thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio() : 0f;
+ if (variableWidth && mActivity.getDeviceProfile().isTablet
+ && FeatureFlags.ENABLE_OVERVIEW_GRID.get() && thumbnailRatio != 0f) {
+ final int thumbnailPadding = (int) getResources().getDimension(
+ R.dimen.task_thumbnail_top_margin);
+
+ Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
+ int taskWidth = lastComputedTaskSize.width();
+ int taskHeight = lastComputedTaskSize.height();
+ int boxLength = Math.max(taskWidth, taskHeight);
+
+ int expectedWidth;
+ int expectedHeight;
+ 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);
+
+ if (expectedWidth > taskWidth) {
+ // In full screen, expectedWidth should not be larger than taskWidth.
+ mScaleAtFullscreen = taskWidth / (float) expectedWidth;
+ } else if (expectedHeight - thumbnailPadding > taskHeight) {
+ // In full screen, expectedHeight should not be larger than taskHeight.
+ mScaleAtFullscreen = taskHeight / (float) (expectedHeight - thumbnailPadding);
+ } else {
+ mScaleAtFullscreen = 1f;
+ }
+
+ if (params.width != expectedWidth || params.height != expectedHeight) {
+ params.width = expectedWidth;
+ params.height = expectedHeight;
+ setLayoutParams(params);
+ }
+ } else {
+ setBoxTranslationY(0);
+ if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ setLayoutParams(params);
+ }
+ }
+ updateTaskScaling();
+ }
+
+ private void updateTaskScaling() {
+ if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+ ViewGroup.LayoutParams params = getLayoutParams();
+ if (params.width == ViewGroup.LayoutParams.MATCH_PARENT
+ || params.height == ViewGroup.LayoutParams.MATCH_PARENT) {
+ // Snapshot is not loaded yet, skip.
+ return;
+ }
+
+ float progress = EXAGGERATED_EASE.getInterpolation(mFullscreenProgress);
+ setFullscreenScale(Utilities.mapRange(progress, 1f, mScaleAtFullscreen));
+
+ float widthDiff = params.width * (1 - mFullscreenScale);
+ setFullscreenTranslationX(getFullscreenTrans(
+ getLayoutDirection() == LAYOUT_DIRECTION_RTL ? -widthDiff : widthDiff));
+ } else {
+ setFullscreenScale(1);
+ setFullscreenTranslationX(0);
+ }
+ }
+
+ private float getFullscreenTrans(float endTranslation) {
+ float progress = ACCEL_DEACCEL.getInterpolation(mFullscreenProgress);
+ return Utilities.mapRange(progress, 0, endTranslation);
+ }
+
public boolean isRunningTask() {
if (getRecentsView() == null) {
return false;
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index ea3c9bb..67840d1 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -168,7 +168,7 @@
Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
- "cmd overlay enable-exclusive " + overlayPackage);
+ "cmd overlay enable-exclusive --category " + overlayPackage);
if (currentSysUiNavigationMode() != expectedMode) {
final CountDownLatch latch = new CountDownLatch(1);
diff --git a/quickstep/res/layout/search_result_thumbnail.xml b/res/drawable/bg_widgets_searchbox.xml
similarity index 71%
copy from quickstep/res/layout/search_result_thumbnail.xml
copy to res/drawable/bg_widgets_searchbox.xml
index 5062b76..81dd2aa 100644
--- a/quickstep/res/layout/search_result_thumbnail.xml
+++ b/res/drawable/bg_widgets_searchbox.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.search.SearchResultThumbnailView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="125dp"
- android:layout_height="125dp"/>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <solid android:color="#FFFFF7" />
+ <corners android:radius="24dp" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_expand_less.xml b/res/drawable/ic_expand_less.xml
new file mode 100644
index 0000000..8360cee
--- /dev/null
+++ b/res/drawable/ic_expand_less.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorHint">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.59,16.41L20,15l-8,-8 -8,8 1.41,1.41L12,9.83"/>
+</vector>
diff --git a/res/drawable/ic_expand_more.xml b/res/drawable/ic_expand_more.xml
new file mode 100644
index 0000000..49e24f6
--- /dev/null
+++ b/res/drawable/ic_expand_more.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorHint">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17"/>
+</vector>
diff --git a/res/drawable/ic_widget_height_decrease.xml b/res/drawable/ic_widget_height_decrease.xml
new file mode 100644
index 0000000..df704ba
--- /dev/null
+++ b/res/drawable/ic_widget_height_decrease.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M8,19h3v3h2v-3h3l-4,-4 -4,4zM16,4h-3L13,1h-2v3L8,4l4,4 4,-4zM4,9v2h16L20,9L4,9zM4,12h16v2H4z"/>
+</vector>
diff --git a/res/drawable/ic_widget_height_increase.xml b/res/drawable/ic_widget_height_increase.xml
new file mode 100644
index 0000000..c263a4b
--- /dev/null
+++ b/res/drawable/ic_widget_height_increase.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M4,20h16v2L4,22zM4,2h16v2L4,4zM13,9h3l-4,-4 -4,4h3v6L8,15l4,4 4,-4h-3z"/>
+</vector>
diff --git a/res/drawable/ic_widget_width_decrease.xml b/res/drawable/ic_widget_width_decrease.xml
new file mode 100644
index 0000000..2a2fad7
--- /dev/null
+++ b/res/drawable/ic_widget_width_decrease.xml
@@ -0,0 +1,22 @@
+<!--
+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.
+-->
+<rotate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ic_widget_height_decrease"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fromDegrees="90"
+ android:toDegrees="90" />
diff --git a/res/drawable/ic_widget_width_increase.xml b/res/drawable/ic_widget_width_increase.xml
new file mode 100644
index 0000000..89b9f40
--- /dev/null
+++ b/res/drawable/ic_widget_width_increase.xml
@@ -0,0 +1,22 @@
+<!--
+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.
+-->
+<rotate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ic_widget_height_increase"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fromDegrees="90"
+ android:toDegrees="90" />
diff --git a/quickstep/res/layout/search_result_thumbnail.xml b/res/drawable/widgets_tray_expand_button.xml
similarity index 64%
copy from quickstep/res/layout/search_result_thumbnail.xml
copy to res/drawable/widgets_tray_expand_button.xml
index 5062b76..8316e0f 100644
--- a/quickstep/res/layout/search_result_thumbnail.xml
+++ b/res/drawable/widgets_tray_expand_button.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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,7 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.search.SearchResultThumbnailView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="125dp"
- android:layout_height="125dp"/>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_checked="true"
+ android:drawable="@drawable/ic_expand_less" />
+ <item android:state_checked="false"
+ android:drawable="@drawable/ic_expand_more" />
+</selector>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 0041c9a..8ed16c7 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -40,35 +40,7 @@
<include layout="@layout/floating_header_content" />
- <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
- android:id="@+id/tabs"
- android:layout_width="match_parent"
- android:layout_height="@dimen/all_apps_header_tab_height"
- android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
- android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
- android:orientation="horizontal"
- style="@style/TextHeadline">
-
- <Button
- android:id="@+id/tab_personal"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:background="?android:attr/selectableItemBackground"
- android:text="@string/all_apps_personal_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp" />
-
- <Button
- android:id="@+id/tab_work"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:background="?android:attr/selectableItemBackground"
- android:text="@string/all_apps_work_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp" />
- </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+ <include layout="@layout/personal_work_tabs" />
</com.android.launcher3.allapps.FloatingHeaderView>
<include
diff --git a/res/layout/keyboard_drag_and_drop.xml b/res/layout/keyboard_drag_and_drop.xml
new file mode 100644
index 0000000..e9463c4
--- /dev/null
+++ b/res/layout/keyboard_drag_and_drop.xml
@@ -0,0 +1,34 @@
+<?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.keyboard.KeyboardDragAndDropView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:orientation="vertical"
+ android:elevation="6dp">
+
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:background="?attr/folderFillColor"
+ android:padding="8dp"
+ android:textColor="?attr/folderTextColor"
+ />
+
+</com.android.launcher3.keyboard.KeyboardDragAndDropView>
\ No newline at end of file
diff --git a/res/layout/live_preview_widget_cell.xml b/res/layout/live_preview_widget_cell.xml
new file mode 100644
index 0000000..7a42d19
--- /dev/null
+++ b/res/layout/live_preview_widget_cell.xml
@@ -0,0 +1,28 @@
+<?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.dragndrop.LivePreviewWidgetCell
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:background="?android:attr/colorPrimaryDark"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/widget_cell_content" />
+
+</com.android.launcher3.dragndrop.LivePreviewWidgetCell>
\ No newline at end of file
diff --git a/res/layout/personal_work_tabs.xml b/res/layout/personal_work_tabs.xml
new file mode 100644
index 0000000..5fb5bcb
--- /dev/null
+++ b/res/layout/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/all_apps_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/all_apps_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/secondary_launcher.xml b/res/layout/secondary_launcher.xml
index fdf4446..e3c60ec 100644
--- a/res/layout/secondary_launcher.xml
+++ b/res/layout/secondary_launcher.xml
@@ -67,7 +67,7 @@
android:paddingTop="@dimen/all_apps_header_top_padding"
android:orientation="vertical" >
- <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
+ <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_header_tab_height"
@@ -97,7 +97,7 @@
android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
- </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+ </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
</com.android.launcher3.allapps.FloatingHeaderView>
<com.android.launcher3.allapps.search.AppsSearchContainerLayout
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 923352e..c230dad 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -27,8 +27,8 @@
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
android:paddingTop="16dp"
launcher:pageIndicator="@+id/folder_page_indicator" />
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index f507a88..28a8c6f 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.widget.WidgetsFullSheet
+<com.android.launcher3.widget.picker.WidgetsFullSheet
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -27,12 +27,6 @@
android:background="?android:attr/colorPrimary"
android:elevation="4dp">
- <com.android.launcher3.widget.WidgetsRecyclerView
- android:id="@+id/widgets_list_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipToPadding="false" />
-
<!-- Fast scroller popup -->
<TextView
android:id="@+id/fast_scroller_popup"
@@ -49,4 +43,4 @@
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/fastscroll_end_margin" />
</com.android.launcher3.views.TopRoundedCornerView>
-</com.android.launcher3.widget.WidgetsFullSheet>
\ No newline at end of file
+</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
new file mode 100644
index 0000000..8125db8
--- /dev/null
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -0,0 +1,46 @@
+<?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"
+ 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"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:descendantFocusability="afterDescendants"
+ launcher:pageIndicator="@+id/tabs">
+
+ <com.android.launcher3.widget.picker.WidgetsRecyclerView
+ android:id="@+id/primary_widgets_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false" />
+
+ <com.android.launcher3.widget.picker.WidgetsRecyclerView
+ android:id="@+id/work_widgets_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false" />
+
+ </com.android.launcher3.workprofile.PersonalWorkPagedView>
+
+</merge>
\ No newline at end of file
diff --git a/quickstep/res/layout/search_result_thumbnail.xml b/res/layout/widgets_full_sheet_recyclerview.xml
similarity index 70%
copy from quickstep/res/layout/search_result_thumbnail.xml
copy to res/layout/widgets_full_sheet_recyclerview.xml
index 5062b76..fbe559c 100644
--- a/quickstep/res/layout/search_result_thumbnail.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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,7 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.search.SearchResultThumbnailView
+<com.android.launcher3.widget.picker.WidgetsRecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="125dp"
- android:layout_height="125dp"/>
\ No newline at end of file
+ android:id="@+id/primary_widgets_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false" />
\ 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
new file mode 100644
index 0000000..9a6f922
--- /dev/null
+++ b/res/layout/widgets_full_sheet_search_and_recommendations.xml
@@ -0,0 +1,49 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/search_and_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:orientation="vertical">
+ <View
+ android:id="@+id/collapse_handle"
+ android:layout_width="48dp"
+ android:layout_height="2dp"
+ android:layout_gravity="center_horizontal"
+ android:background="@color/popup_color_primary_dark"/>
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textSize="24sp"
+ android:layout_marginTop="16dp"
+ android:text="@string/widget_button_text"/>
+ <!-- Disable the search bar because it has not been implemented. -->
+ <EditText
+ android:id="@+id/widgets_search_bar"
+ 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" />
+</LinearLayout>
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
new file mode 100644
index 0000000..faff10c
--- /dev/null
+++ b/res/layout/widgets_list_row_header.xml
@@ -0,0 +1,71 @@
+<?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.widget.picker.WidgetsListHeader xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ 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:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp"
+ android:importantForAccessibility="no"
+ tools:src="@drawable/ic_corp"/>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:descendantFocusability="afterDescendants">
+
+ <TextView
+ android:id="@+id/app_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="16sp"
+ tools:text="App name" />
+
+ <TextView
+ android:id="@+id/app_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ tools:text="n widgets" />
+
+ </LinearLayout>
+
+ <!-- This checkbox is not clickable. The outermost LinearLayout is responsible to handle all
+ click event and update the checkbox state. -->
+ <CheckBox
+ android:id="@+id/toggle"
+ android:layout_marginHorizontal="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_alignParentEnd="true"
+ android:clickable="false"
+ 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_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index eec57a5..5942ba6 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -45,5 +45,5 @@
launcher:iconSizeOverride="@dimen/widget_section_icon_size"
launcher:layoutHorizontal="true" />
- <include layout="@layout/widgets_scroll_container" />
+ <include layout="@layout/widgets_table_container" />
</LinearLayout>
diff --git a/quickstep/res/layout/search_result_thumbnail.xml b/res/layout/widgets_table_container.xml
similarity index 72%
rename from quickstep/res/layout/search_result_thumbnail.xml
rename to res/layout/widgets_table_container.xml
index 5062b76..ffa239a 100644
--- a/quickstep/res/layout/search_result_thumbnail.xml
+++ b/res/layout/widgets_table_container.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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,7 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.search.SearchResultThumbnailView
+<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="125dp"
- android:layout_height="125dp"/>
\ No newline at end of file
+ android:id="@+id/widgets_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/colorPrimaryDark" />
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
index f322e9f..c1e6eca 100644
--- a/res/values-sw720dp/styles.xml
+++ b/res/values-sw720dp/styles.xml
@@ -18,16 +18,6 @@
-->
<resources>
-
- <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:colorBackgroundCacheHint">@null</item>
- <item name="android:windowShowWallpaper">true</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowActionModeOverlay">true</item>
- <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
- </style>
-
<!-- Workspace -->
<style name="DropTargetButton" parent="DropTargetButtonBase">
<item name="android:paddingLeft">60dp</item>
@@ -36,5 +26,4 @@
<item name="android:shadowDy">0.0</item>
<item name="android:shadowRadius">2.0</item>
</style>
-
</resources>
\ No newline at end of file
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
new file mode 100644
index 0000000..6baf39e
--- /dev/null
+++ b/res/values-v31/colors.xml
@@ -0,0 +1,33 @@
+<?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.
+*/
+-->
+<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="workspace_text_color_light">@android:color/system_primary_50</color>
+ <color name="workspace_text_color_dark">@android:color/system_primary_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>
+</resources>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e593fb4..587df6d 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -135,6 +135,16 @@
<attr name="demoModeLayoutId" format="reference" />
</declare-styleable>
+ <declare-styleable name="DevicePadding">
+ <attr name="maxEmptySpace" format="dimension" />
+ </declare-styleable>
+
+ <declare-styleable name="DevicePaddingFormula">
+ <attr name="a" format="float|dimension" />
+ <attr name="b" format="float|dimension" />
+ <attr name="c" format="float|dimension" />
+ </declare-styleable>
+
<declare-styleable name="ProfileDisplayOption">
<attr name="name" />
<attr name="minWidthDps" format="float" />
@@ -185,4 +195,8 @@
<attr name="android:name" />
<attr name="android:id" />
</declare-styleable>
+
+ <declare-styleable name="WidgetsListRowHeader">
+ <attr name="appIconSize" format="dimension" />
+ </declare-styleable>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 78c2df6..0b30253 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -40,4 +40,19 @@
<color name="gesture_tutorial_fake_previous_task_view_color">#9CCC65</color> <!-- Light Green -->
<color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
<color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
+
+ <color name="popup_color_primary_light">#FFF</color>
+ <color name="popup_color_secondary_light">#F1F3F4</color>
+ <color name="popup_color_tertiary_light">#E0E0E0</color> <!-- Gray 300 -->
+ <color name="popup_color_primary_dark">#3C4043</color> <!-- Gray 800 -->
+ <color name="popup_color_secondary_dark">#202124</color>
+ <color name="popup_color_tertiary_dark">#757575</color> <!-- Gray 600 -->
+
+ <color name="workspace_text_color_light">#FFF</color>
+ <color name="workspace_text_color_dark">#FF212121</color>
+
+ <color name="text_color_primary_dark">#FFFFFFFF</color>
+ <color name="text_color_secondary_dark">#FFFFFFFF</color>
+ <color name="text_color_tertiary_dark">#CCFFFFFF</color>
+
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index cf51f77..acc6466 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,12 +20,14 @@
<!-- Dynamic Grid -->
<dimen name="dynamic_grid_edge_margin">8dp</dimen>
- <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
+ <dimen name="dynamic_grid_icon_drawable_padding">7dp</dimen>
<!-- Minimum space between workspace and hotseat in spring loaded mode -->
<dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
+ <dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
<dimen name="dynamic_grid_cell_layout_padding">5.5dp</dimen>
<dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
+ <dimen name="dynamic_grid_cell_padding_y">7dp</dimen>
<!-- Hotseat -->
<dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
@@ -139,6 +141,7 @@
<dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen>
<dimen name="spring_loaded_panel_border">1dp</dimen>
+ <dimen name="keyboard_drag_stroke_width">4dp</dimen>
<!-- Folders -->
<dimen name="page_indicator_dot_size">8dp</dimen>
@@ -146,9 +149,9 @@
<dimen name="folder_cell_x_padding">9dp</dimen>
<dimen name="folder_cell_y_padding">6dp</dimen>
<dimen name="folder_child_text_size">13sp</dimen>
- <dimen name="folder_label_padding_top">4dp</dimen>
+ <dimen name="folder_label_padding_top">12dp</dimen>
<dimen name="folder_label_padding_bottom">12dp</dimen>
- <dimen name="folder_label_text_size">14sp</dimen>
+ <dimen name="folder_label_text_size">16sp</dimen>
<!-- Sizes for managed profile badges -->
<dimen name="profile_badge_size">24dp</dimen>
@@ -167,7 +170,8 @@
<!-- Deep shortcuts -->
<dimen name="deep_shortcuts_elevation">9dp</dimen>
- <dimen name="bg_popup_item_width">260dp</dimen>
+ <!-- also update deep_shortcuts_divider_width -->
+ <dimen name="bg_popup_item_width">234dp</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>
@@ -190,7 +194,7 @@
<!-- 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">164dp</dimen>
+ <dimen name="deep_shortcuts_divider_width">178dp</dimen>
<dimen name="system_shortcut_icon_size">24dp</dimen>
<!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
<dimen name="system_shortcut_margin_start">16dp</dimen>
@@ -250,10 +254,15 @@
<!-- Search related -->
<dimen name="search_hero_title_size">16sp</dimen>
- <dimen name="search_hero_subtitle_size">15sp</dimen>
+ <dimen name="search_hero_subtitle_size">14sp</dimen>
<dimen name="search_hero_inline_button_size">12sp</dimen>
<dimen name="search_settings_icon_size">36dp</dimen>
<dimen name="search_settings_icon_vertical_offset">16dp</dimen>
<dimen name="search_line_spacing">4dp</dimen>
+ <dimen name="search_decoration_corner_radius">28dp</dimen>
+ <dimen name="search_decoration_padding">1dp</dimen>
+
+<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
+ <dimen name="taskbar_size">0dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 447c9ac..73f9e53 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -37,8 +37,6 @@
<string name="shortcut_not_available">Shortcut isn\'t available</string>
<!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
<string name="home_screen">Home</string>
- <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
- <string name="custom_actions">Custom actions</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
@@ -54,6 +52,17 @@
<string name="add_item_request_drag_hint">Touch & hold to place manually</string>
<!-- Button label to automatically add icon on home screen [CHAR_LIMIT=50] -->
<string name="place_automatically">Add automatically</string>
+ <!-- Label for showing the number of widgets an app has in the full widgets picker. [CHAR_LIMIT=25] -->
+ <plurals name="widgets_tray_subtitle">
+ <item quantity="one"><xliff:g id="widget_count" example="1">%1$d</xliff:g> widget</item>
+ <item quantity="other"><xliff:g id="widget_count" example="2">%1$d</xliff:g> widgets</item>
+ </plurals>
+ <!-- Text for both the tile of a popup view, which shows all available widgets installed on
+ the device, and the text of a button, which opens this popup view. [CHAR LIMIT=30]-->
+ <string name="widget_button_text">Widgets</string>
+ <!-- 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>
<!-- All Apps -->
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
@@ -182,8 +191,6 @@
<string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
<!-- Strings for the customization mode -->
- <!-- Text for widget add button [CHAR LIMIT=30]-->
- <string name="widget_button_text">Widgets</string>
<!-- Text for wallpaper change button [CHAR LIMIT=30]-->
<string name="wallpaper_button_text">Wallpapers</string>
<!-- Text for wallpaper change button [CHAR LIMIT=30]-->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index dc7182f..adc2238 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -35,12 +35,12 @@
<item name="allAppsInterimScrimAlpha">46</item>
<item name="allAppsNavBarScrimColor">#66FFFFFF</item>
<item name="allAppsTheme">@style/AllAppsTheme</item>
- <item name="popupColorPrimary">#FFF</item>
- <item name="popupColorSecondary">#F1F3F4</item>
- <item name="popupColorTertiary">#E0E0E0</item> <!-- Gray 300 -->
+ <item name="popupColorPrimary">@color/popup_color_primary_light</item>
+ <item name="popupColorSecondary">@color/popup_color_secondary_light</item>
+ <item name="popupColorTertiary">@color/popup_color_tertiary_light</item>
<item name="isMainColorDark">false</item>
<item name="isWorkspaceDarkText">false</item>
- <item name="workspaceTextColor">@android:color/white</item>
+ <item name="workspaceTextColor">@color/workspace_text_color_light</item>
<item name="workspaceShadowColor">#B0000000</item>
<item name="workspaceAmbientShadowColor">#33000000</item>
<item name="workspaceKeyShadowColor">#44000000</item>
@@ -74,7 +74,7 @@
</style>
<style name="LauncherTheme.DarkText" parent="@style/LauncherTheme">
- <item name="workspaceTextColor">#FF212121</item>
+ <item name="workspaceTextColor">@color/workspace_text_color_dark</item>
<item name="allAppsInterimScrimAlpha">128</item>
<item name="workspaceShadowColor">@android:color/transparent</item>
<item name="workspaceAmbientShadowColor">@android:color/transparent</item>
@@ -88,9 +88,9 @@
</style>
<style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
- <item name="android:textColorPrimary">#FFFFFFFF</item>
- <item name="android:textColorSecondary">#FFFFFFFF</item>
- <item name="android:textColorTertiary">#CCFFFFFF</item>
+ <item name="android:textColorPrimary">@color/text_color_primary_dark</item>
+ <item name="android:textColorSecondary">@color/text_color_secondary_dark</item>
+ <item name="android:textColorTertiary">@color/text_color_tertiary_dark</item>
<item name="android:textColorHint">#A0FFFFFF</item>
<item name="android:colorControlHighlight">#A0FFFFFF</item>
<item name="android:colorPrimary">#FF212121</item>
@@ -98,9 +98,9 @@
<item name="allAppsInterimScrimAlpha">102</item>
<item name="allAppsNavBarScrimColor">#80000000</item>
<item name="allAppsTheme">@style/AllAppsTheme.Dark</item>
- <item name="popupColorPrimary">#3C4043</item> <!-- Gray 800 -->
- <item name="popupColorSecondary">#202124</item>
- <item name="popupColorTertiary">#757575</item> <!-- Gray 600 -->
+ <item name="popupColorPrimary">@color/popup_color_primary_dark</item>
+ <item name="popupColorSecondary">@color/popup_color_secondary_dark</item>
+ <item name="popupColorTertiary">@color/popup_color_tertiary_dark</item>
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="folderDotColor">?android:attr/colorPrimary</item>
<item name="folderFillColor">?android:attr/colorBackground</item>
@@ -125,7 +125,7 @@
<item name="allAppsInterimScrimAlpha">25</item>
<item name="folderFillColor">#CDFFFFFF</item>
<item name="folderTextColor">?attr/workspaceTextColor</item>
- <item name="workspaceTextColor">#FF212121</item>
+ <item name="workspaceTextColor">@color/workspace_text_color_dark</item>
<item name="workspaceShadowColor">@android:color/transparent</item>
<item name="workspaceAmbientShadowColor">@android:color/transparent</item>
<item name="workspaceKeyShadowColor">@android:color/transparent</item>
diff --git a/res/xml/size_limits.xml b/res/xml/size_limits.xml
new file mode 100644
index 0000000..ba57014
--- /dev/null
+++ b/res/xml/size_limits.xml
@@ -0,0 +1,75 @@
+<?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.
+-->
+
+<device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+ <device-padding
+ launcher:maxEmptySpace="88dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ <workspaceBottomPadding
+ launcher:a="0.52"
+ launcher:b="0"/>
+ <hotseatBottomPadding
+ launcher:a="0.48"
+ launcher:b="0"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="97dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="16dp"/>
+ <workspaceBottomPadding
+ launcher:a="0.50"
+ launcher:b="0"
+ launcher:c="-16dp"/>
+ <hotseatBottomPadding
+ launcher:a="0.50"
+ launcher:b="0"
+ launcher:c="16dp"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="107dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="16dp"/>
+ <workspaceBottomPadding
+ launcher:a="0"
+ launcher:b="36dp"/>
+ <hotseatBottomPadding
+ launcher:a="1"
+ launcher:b="0"
+ launcher:c="52dp"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="9999dp">
+ <workspaceTopPadding
+ launcher:a="0.38"
+ launcher:c="36dp"/>
+ <workspaceBottomPadding
+ launcher:a="0.62"
+ launcher:c="36dp"/>
+ <hotseatBottomPadding
+ launcher:a="0"
+ launcher:b="36dp"/>
+ </device-padding>
+
+</device-paddings>
\ No newline at end of file
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 836ded5..405a458 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -19,6 +19,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := LauncherRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_SDK_VERSION := system_current
@@ -50,6 +53,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := RunLauncherRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_SDK_VERSION := system_current
LOCAL_JAVA_LIBRARIES := LauncherRoboTests
diff --git a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java
new file mode 100644
index 0000000..dbf4b3e
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.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.testing;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+/** An empty activity for {@link android.app.Fragment}s, {@link android.view.View}s testing. */
+public class TestActivity extends BaseActivity implements ActivityContext {
+
+ private DeviceProfile mDeviceProfile;
+
+ @Override
+ public BaseDragLayer getDragLayer() {
+ return null;
+ }
+
+ @Override
+ public DeviceProfile getDeviceProfile() {
+ return mDeviceProfile;
+ }
+
+ public void setDeviceProfile(DeviceProfile deviceProfile) {
+ mDeviceProfile = deviceProfile;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
index 957ae77..34cb2ad 100644
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -38,7 +38,7 @@
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
import org.junit.Before;
import org.junit.Test;
diff --git a/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
new file mode 100644
index 0000000..fbf4c63
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Collections;
+
+@RunWith(RobolectricTestRunner.class)
+public class SettingsCacheTest {
+
+ public static final Uri KEY_SYSTEM_URI_TEST1 = Uri.parse("content://settings/system/test1");
+ public static final Uri KEY_SYSTEM_URI_TEST2 = Uri.parse("content://settings/system/test2");;
+
+ private SettingsCache.OnChangeListener mChangeListener;
+ private SettingsCache mSettingsCache;
+
+ @Before
+ public void setup() {
+ mChangeListener = mock(SettingsCache.OnChangeListener.class);
+ Context targetContext = RuntimeEnvironment.application;
+ mSettingsCache = SettingsCache.INSTANCE.get(targetContext);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST1, mChangeListener);
+ }
+
+ @Test
+ public void listenerCalledOnChange() {
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ verify(mChangeListener, times(1)).onSettingsChanged(true);
+ }
+
+ @Test
+ public void getValueRespectsDefaultValue() {
+ // Case of key not found
+ boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertFalse(val);
+ }
+
+ @Test
+ public void getValueHitsCache() {
+ mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
+ boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertTrue(val);
+ }
+
+ @Test
+ public void getValueUpdatedCache() {
+ // First ensure there's nothing in cache
+ boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertFalse(val);
+
+ mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
+ val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+ assertTrue(val);
+ }
+
+ @Test
+ public void multipleListenersSingleKey() {
+ SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST1, secondListener);
+
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ verify(mChangeListener, times(1)).onSettingsChanged(true);
+ verify(secondListener, times(1)).onSettingsChanged(true);
+ }
+
+ @Test
+ public void singleListenerMultipleKeys() {
+ SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST2, secondListener);
+
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
+ verify(mChangeListener, times(1)).onSettingsChanged(true);
+ verify(secondListener, times(1)).onSettingsChanged(true);
+ }
+
+ @Test
+ public void sameListenerMultipleKeys() {
+ SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+ mSettingsCache.register(KEY_SYSTEM_URI_TEST2, mChangeListener);
+
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+ mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
+ verify(mChangeListener, times(2)).onSettingsChanged(true);
+ verify(secondListener, times(0)).onSettingsChanged(true);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
deleted file mode 100644
index 5ab3106..0000000
--- a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ /dev/null
@@ -1,151 +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.widget;
-
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.view.LayoutInflater;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.data.PackageItemInfo;
-
-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;
-
-@RunWith(RobolectricTestRunner.class)
-public class WidgetsListAdapterTest {
-
- @Mock private LayoutInflater mMockLayoutInflater;
- @Mock private WidgetPreviewLoader mMockWidgetCache;
- @Mock private RecyclerView.AdapterDataObserver mListener;
- @Mock private IconCache mIconCache;
-
- private WidgetsListAdapter mAdapter;
- private InvariantDeviceProfile mTestProfile;
- private Context mContext;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- mTestProfile = new InvariantDeviceProfile();
- mTestProfile.numRows = 5;
- mTestProfile.numColumns = 5;
- mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
- mIconCache, null, null);
- mAdapter.registerAdapterDataObserver(mListener);
- }
-
- @Test
- public void test_notifyDataSetChanged() throws Exception {
- mAdapter.setWidgets(generateSampleMap(1));
- verify(mListener, times(1)).onChanged();
- }
-
- @Test
- public void test_notifyItemInserted() throws Exception {
- mAdapter.setWidgets(generateSampleMap(1));
- mAdapter.setWidgets(generateSampleMap(2));
- verify(mListener, times(1)).onChanged();
- verify(mListener, times(1)).onItemRangeInserted(eq(1), eq(1));
- }
-
- @Test
- public void test_notifyItemRemoved() throws Exception {
- mAdapter.setWidgets(generateSampleMap(2));
- mAdapter.setWidgets(generateSampleMap(1));
- verify(mListener, times(1)).onChanged();
- verify(mListener, times(1)).onItemRangeRemoved(eq(1), eq(1));
- }
-
- @Test
- public void testNotifyItemChanged_PackageIconDiff() throws Exception {
- mAdapter.setWidgets(generateSampleMap(1));
- mAdapter.setWidgets(generateSampleMap(1));
- verify(mListener, times(1)).onChanged();
- verify(mListener, times(1)).onItemRangeChanged(eq(0), eq(1), isNull());
- }
-
- @Test
- public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception {
- // TODO: same package name but item number changed
- }
-
- @Test
- public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception {
- // TODO: insert and remove combined. curMap
- // newMap [A, C, D] [A, B, E]
- // B - C < 0, removed B from index 1 [A, E]
- // E - C > 0, C inserted to index 1 [A, C, E]
- // E - D > 0, D inserted to index 2 [A, C, D, E]
- // E - null = -1, E deleted from index 3 [A, C, D]
- }
-
- /**
- * Helper method to generate the sample widget model map that can be used for the tests
- * @param num the number of WidgetItem the map should contain
- */
- private ArrayList<WidgetListRowEntry> generateSampleMap(int num) {
- ArrayList<WidgetListRowEntry> result = new ArrayList<>();
- if (num <= 0) return result;
- ShadowPackageManager spm = shadowOf(mContext.getPackageManager());
-
- for (int i = 0; i < num; i++) {
- ComponentName cn = new ComponentName("com.placeholder.apk" + i, "PlaceholderWidet");
-
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = cn;
- ReflectionHelpers.setField(widgetInfo, "providerInfo", spm.addReceiverIfNotPresent(cn));
-
- WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
- .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache);
-
- PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
- pInfo.title = pInfo.packageName;
- pInfo.user = wi.user;
- pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
-
- result.add(new WidgetListRowEntry(pInfo, new ArrayList<>(Collections.singleton(wi))));
- }
-
- return result;
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
new file mode 100644
index 0000000..04797a6
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+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 androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+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.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
+
+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.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsDiffReporterTest {
+ private static final String TEST_PACKAGE_PREFIX = "com.google.test";
+ private static final WidgetListBaseRowEntryComparator COMPARATOR =
+ new WidgetListBaseRowEntryComparator();
+
+ @Mock private IconCache mIconCache;
+ @Mock private RecyclerView.Adapter mAdapter;
+
+ private InvariantDeviceProfile mTestProfile;
+ private WidgetsDiffReporter mWidgetsDiffReporter;
+ private Context mContext;
+ private WidgetsListHeaderEntry mHeaderA;
+ private WidgetsListHeaderEntry mHeaderB;
+ private WidgetsListHeaderEntry mHeaderC;
+ private WidgetsListHeaderEntry mHeaderD;
+ private WidgetsListHeaderEntry mHeaderE;
+ private WidgetsListContentEntry mContentC;
+ private WidgetsListContentEntry mContentE;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+
+ doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
+ .getComponent().getPackageName())
+ .when(mIconCache).getTitleNoCache(any());
+
+ mContext = RuntimeEnvironment.application;
+ mWidgetsDiffReporter = new WidgetsDiffReporter(mIconCache, mAdapter);
+ mHeaderA = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "A",
+ /* appName= */ "A", /* numOfWidgets= */ 3);
+ mHeaderB = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "B",
+ /* appName= */ "B", /* numOfWidgets= */ 3);
+ mHeaderC = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "C",
+ /* appName= */ "C", /* numOfWidgets= */ 3);
+ mContentC = createWidgetsContentEntry(TEST_PACKAGE_PREFIX + "C",
+ /* appName= */ "C", /* numOfWidgets= */ 3);
+ mHeaderD = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "D",
+ /* appName= */ "D", /* numOfWidgets= */ 3);
+ mHeaderE = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "E",
+ /* appName= */ "E", /* numOfWidgets= */ 3);
+ mContentE = createWidgetsContentEntry(TEST_PACKAGE_PREFIX + "E",
+ /* appName= */ "E", /* numOfWidgets= */ 3);
+ }
+
+ @Test
+ public void listNotChanged_shouldNotInvokeAnyCallbacks() {
+ // GIVEN the current list has app headers [A, B, C].
+ ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
+ List.of(mHeaderA, mHeaderB, mHeaderC));
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, currentList, COMPARATOR);
+
+ // THEN there is no adaptor callback.
+ verifyZeroInteractions(mAdapter);
+ // THEN the current list contains the same entries.
+ assertThat(currentList).containsExactly(mHeaderA, mHeaderB, mHeaderC);
+ }
+
+ @Test
+ public void headersOnly_emptyListToNonEmpty_shouldInvokeNotifyDataSetChanged() {
+ // GIVEN the current list has app headers [A, B, C].
+ ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>();
+
+ List<WidgetsListBaseEntry> newList = List.of(
+ createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "A", "A", 3),
+ createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "B", "B", 3),
+ createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "C", "C", 3));
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+ // THEN notifyDataSetChanged is called
+ verify(mAdapter).notifyDataSetChanged();
+ // THEN the current list contains all elements from the new list.
+ assertThat(currentList).containsExactlyElementsIn(newList);
+ }
+
+ @Test
+ public void headersOnly_nonEmptyToEmptyList_shouldInvokeNotifyDataSetChanged() {
+ // GIVEN the current list has app headers [A, B, C].
+ ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
+ List.of(mHeaderA, mHeaderB, mHeaderC));
+ // GIVEN the new list is empty.
+ List<WidgetsListBaseEntry> newList = List.of();
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+ // THEN notifyDataSetChanged is called.
+ verify(mAdapter).notifyDataSetChanged();
+ // THEN the current list isEmpty.
+ assertThat(currentList).isEmpty();
+ }
+
+ @Test
+ public void headersOnly_itemAddedAndRemovedInTheNewList_shouldInvokeCorrectCallbacks() {
+ // GIVEN the current list has app headers [A, B, D].
+ ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
+ List.of(mHeaderA, mHeaderB, mHeaderD));
+ // GIVEN the new list has app headers [A, C, E].
+ List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mHeaderC, mHeaderE);
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+ // THEN "B" is removed from position 1.
+ verify(mAdapter).notifyItemRemoved(/* position= */ 1);
+ // THEN "D" is removed from position 2.
+ verify(mAdapter).notifyItemRemoved(/* position= */ 2);
+ // THEN "C" is inserted at position 1.
+ verify(mAdapter).notifyItemInserted(/* position= */ 1);
+ // THEN "E" is inserted at position 2.
+ verify(mAdapter).notifyItemInserted(/* position= */ 2);
+ // THEN the current list contains all elements from the new list.
+ assertThat(currentList).containsExactlyElementsIn(newList);
+ }
+
+ @Test
+ public void headersContentsMix_itemAddedAndRemovedInTheNewList_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 app headers [A, C content, D].
+ List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mContentC, mHeaderD);
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+ // THEN "B" is removed from position 1.
+ verify(mAdapter).notifyItemRemoved(/* position= */ 1);
+ // THEN "C content" is inserted at position 1.
+ verify(mAdapter).notifyItemInserted(/* position= */ 1);
+ // THEN "D" is inserted at position 2.
+ verify(mAdapter).notifyItemInserted(/* position= */ 2);
+ // THEN "E content" is removed from position 3.
+ verify(mAdapter).notifyItemRemoved(/* position= */ 3);
+ // THEN the current list contains all elements from the new list.
+ assertThat(currentList).containsExactlyElementsIn(newList);
+ }
+
+ @Test
+ public void headersContentsMix_userInteractWithHeader_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 app headers [A, B, E content].
+ List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mHeaderB, mContentE);
+ // GIVEN the user has interacted with B.
+ mHeaderB.setIsWidgetListShown(true);
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+ // THEN notify "B" has been changed.
+ verify(mAdapter).notifyItemChanged(/* position= */ 1);
+ // THEN the current list contains all elements from the new list.
+ assertThat(currentList).containsExactlyElementsIn(newList);
+ }
+
+
+ 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/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
new file mode 100644
index 0000000..e94b253
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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.widget.picker;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+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 androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+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.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+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.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsListAdapterTest {
+ private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";
+
+ @Mock private LayoutInflater mMockLayoutInflater;
+ @Mock private WidgetPreviewLoader mMockWidgetCache;
+ @Mock private RecyclerView.AdapterDataObserver mListener;
+ @Mock private IconCache mIconCache;
+
+ private WidgetsListAdapter mAdapter;
+ private InvariantDeviceProfile mTestProfile;
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+ mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
+ mIconCache, null, null);
+ mAdapter.registerAdapterDataObserver(mListener);
+
+ doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
+ .getComponent().getPackageName())
+ .when(mIconCache).getTitleNoCache(any());
+ }
+
+ @Test
+ public void setWidgets_shouldNotifyDataSetChanged() {
+ mAdapter.setWidgets(generateSampleMap(1));
+
+ verify(mListener).onChanged();
+ }
+
+ @Test
+ public void setWidgets_withItemInserted_shouldNotifyItemInserted() {
+ mAdapter.setWidgets(generateSampleMap(1));
+ mAdapter.setWidgets(generateSampleMap(2));
+
+ verify(mListener).onItemRangeInserted(eq(1), eq(1));
+ }
+
+ @Test
+ public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() {
+ mAdapter.setWidgets(generateSampleMap(2));
+ mAdapter.setWidgets(generateSampleMap(1));
+
+ verify(mListener).onItemRangeRemoved(eq(1), eq(1));
+ }
+
+ @Test
+ public void setWidgets_appIconChanged_shouldNotifyItemChanged() {
+ mAdapter.setWidgets(generateSampleMap(1));
+ mAdapter.setWidgets(generateSampleMap(1));
+
+ verify(mListener).onItemRangeChanged(eq(0), eq(1), isNull());
+ }
+
+ @Test
+ public void headerClick_expanded_shouldNotifyItemChange() {
+ // 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].
+ mAdapter.setWidgets(generateSampleMap(3));
+
+ // WHEN com.google.test.1 header is expanded.
+ mAdapter.onHeaderClicked(/* isExpanded= */ true, TEST_PACKAGE_PLACEHOLDER + 1);
+
+ // THEN the visible entries list becomes:
+ // [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
+ // com.google.test.1 content is inserted into position 2.
+ verify(mListener).onItemRangeInserted(eq(2), eq(1));
+ }
+
+ @Test
+ public void setWidgets_expandedApp_moreWidgets_shouldNotifyItemChangedWithWidgetItemInfoDiff() {
+ // GIVEN the adapter was first populated with com.google.test0 & com.google.test1. Each app
+ // has one widget.
+ ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(2);
+ mAdapter.setWidgets(allEntries);
+ // 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);
+ Mockito.reset(mListener);
+
+ // WHEN the adapter is updated with the same list of apps but com.google.test1 has 2 widgets
+ // now.
+ WidgetsListContentEntry testPackage1ContentEntry =
+ (WidgetsListContentEntry) allEntries.get(3);
+ WidgetItem widgetItem = testPackage1ContentEntry.mWidgets.get(0);
+ WidgetsListContentEntry newTestPackage1ContentEntry = new WidgetsListContentEntry(
+ testPackage1ContentEntry.mPkgItem,
+ testPackage1ContentEntry.mTitleSectionName, List.of(widgetItem, widgetItem));
+ allEntries.set(3, newTestPackage1ContentEntry);
+ mAdapter.setWidgets(allEntries);
+
+ // THEN the onItemRangeChanged is invoked for "com.google.test1 content" at index 2.
+ verify(mListener).onItemRangeChanged(eq(2), eq(1), isNull());
+ }
+
+ @Test
+ public void setWidgets_hodgepodge_shouldInvokeExpectedDataObserverCallbacks() {
+ // GIVEN a widgets entry list:
+ // Index: 0| 1 | 2| 3 | 4| 5 | 6| 7 | 8| 9 |
+ // [A, A content, B, B content, C, C content, D, D content, E, E content]
+ List<WidgetsListBaseEntry> allAppsWithWidgets = generateSampleMap(5);
+ // GIVEN the current widgets list consist of [A, A content, B, B content, E, E content].
+ // GIVEN the visible widgets list consist of [A, B, E]
+ List<WidgetsListBaseEntry> currentList = List.of(
+ // A & A content
+ allAppsWithWidgets.get(0), allAppsWithWidgets.get(1),
+ // B & B content
+ allAppsWithWidgets.get(2), allAppsWithWidgets.get(3),
+ // E & E content
+ allAppsWithWidgets.get(8), allAppsWithWidgets.get(9));
+ mAdapter.setWidgets(currentList);
+
+ // WHEN the widgets list is updated to [A, A content, C, C content, D, D content].
+ // WHEN the visible widgets list is updated to [A, C, D].
+ List<WidgetsListBaseEntry> newList = List.of(
+ // A & A content
+ allAppsWithWidgets.get(0), allAppsWithWidgets.get(1),
+ // C & C content
+ allAppsWithWidgets.get(4), allAppsWithWidgets.get(5),
+ // D & D content
+ allAppsWithWidgets.get(6), allAppsWithWidgets.get(7));
+ mAdapter.setWidgets(newList);
+
+ // Computation logic | [Intermediate list during computation]
+ // THEN B <> C < 0, removed B from index 1 | [A, E]
+ verify(mListener).onItemRangeRemoved(/* positionStart= */ 1, /* itemCount= */ 1);
+ // THEN E <> C > 0, C inserted to index 1 | [A, C, E]
+ verify(mListener).onItemRangeInserted(/* positionStart= */ 1, /* itemCount= */ 1);
+ // THEN E <> D > 0, D inserted to index 2 | [A, C, D, E]
+ verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1);
+ // THEN E <> null = -1, E deleted from index 3 | [A, C, D]
+ verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
+ }
+
+ /**
+ * Generates a list of sample widget entries.
+ *
+ * <p>Each sample app has 1 widget only. An app is represented by 2 entries,
+ * {@link WidgetsListHeaderEntry} & {@link WidgetsListContentEntry}. Only
+ * {@link WidgetsListHeaderEntry} is always visible in the {@link WidgetsListAdapter}.
+ * {@link WidgetsListContentEntry} is only shown upon clicking the corresponding app's
+ * {@link WidgetsListHeaderEntry}. Only at most one {@link WidgetsListContentEntry} is shown at
+ * a time.
+ *
+ * @param num the number of apps that have widgets.
+ */
+ private ArrayList<WidgetsListBaseEntry> generateSampleMap(int num) {
+ ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
+ if (num <= 0) return result;
+
+ for (int i = 0; i < num; i++) {
+ String packageName = TEST_PACKAGE_PLACEHOLDER + i;
+
+ List<WidgetItem> widgetItems = generateWidgetItems(packageName, /* numOfWidgets= */ 1);
+
+ PackageItemInfo pInfo = new PackageItemInfo(packageName);
+ pInfo.title = pInfo.packageName;
+ pInfo.user = widgetItems.get(0).user;
+ pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+
+ result.add(new WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems));
+ result.add(new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems));
+ }
+
+ return result;
+ }
+
+ 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/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
new file mode 100644
index 0000000..ae5b9a5
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.Mockito.doAnswer;
+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.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.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+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.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;
+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 WidgetsListHeaderViewHolderBinderTest {
+ private static final String TEST_PACKAGE = "com.google.test";
+ private static final String APP_NAME = "Test app";
+
+ private Context mContext;
+ private WidgetsListHeaderViewHolderBinder mViewHolderBinder;
+ private InvariantDeviceProfile mTestProfile;
+ // Replace ActivityController with ActivityScenario, which is the recommended way for activity
+ // testing.
+ private ActivityController<TestActivity> mActivityController;
+ private TestActivity mTestActivity;
+ private FakeOnHeaderClickListener mFakeOnHeaderClickListener = new FakeOnHeaderClickListener();
+
+ @Mock
+ private IconCache mIconCache;
+ @Mock
+ private DeviceProfile mDeviceProfile;
+
+ @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());
+
+ mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
+ LayoutInflater.from(mTestActivity),
+ mFakeOnHeaderClickListener);
+ }
+
+ @After
+ public void tearDown() {
+ mActivityController.destroy();
+ }
+
+ @Test
+ public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
+ WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+ new FrameLayout(mTestActivity));
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ WidgetsListHeaderEntry entry = generateSampleAppHeader(
+ APP_NAME,
+ TEST_PACKAGE,
+ /* numOfWidgets= */ 3);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry);
+
+ 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("3 widgets");
+ }
+
+ private WidgetsListHeaderEntry generateSampleAppHeader(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 WidgetsListHeaderEntry(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;
+ }
+
+ 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/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
new file mode 100644
index 0000000..358e6e0
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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 android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+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.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.FrameLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+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.widget.WidgetCell;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+
+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 WidgetsListTableViewHolderBinderTest {
+ private static final String TEST_PACKAGE = "com.google.test";
+ private static final String APP_NAME = "Test app";
+
+ private Context mContext;
+ private WidgetsListTableViewHolderBinder 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 OnLongClickListener mOnLongClickListener;
+ @Mock
+ private OnClickListener mOnIconClickListener;
+ @Mock
+ private IconCache mIconCache;
+ @Mock
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+ @Mock
+ private DeviceProfile mDeviceProfile;
+
+ @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());
+
+ mViewHolderBinder = new WidgetsListTableViewHolderBinder(
+ mContext,
+ LayoutInflater.from(mTestActivity),
+ mOnIconClickListener,
+ mOnLongClickListener,
+ mWidgetPreviewLoader);
+ }
+
+ @After
+ public void tearDown() {
+ mActivityController.destroy();
+ }
+
+ @Test
+ public void bindViewHolder_appWith3Widgets_shouldHave3Widgets() {
+ WidgetsRowViewHolder viewHolder = mViewHolderBinder.newViewHolder(
+ new FrameLayout(mTestActivity));
+ WidgetsListContentEntry entry = generateSampleAppWithWidgets(
+ APP_NAME,
+ TEST_PACKAGE,
+ /* numOfWidgets= */ 3);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry);
+ shadowOf(getMainLooper()).idle();
+
+ // THEN the table container has one row, which contains 3 widgets.
+ // View: .SampleWidget0 | .SampleWidget1 | .SampleWidget2
+ assertThat(viewHolder.mTableContainer.getChildCount()).isEqualTo(1);
+ TableRow row = (TableRow) viewHolder.mTableContainer.getChildAt(0);
+ assertThat(row.getChildCount()).isEqualTo(3);
+ // Widget 0 label is .SampleWidget0.
+ assertWidgetCellWithLabel(row.getChildAt(0), ".SampleWidget0");
+ // Widget 1 label is .SampleWidget1.
+ assertWidgetCellWithLabel(row.getChildAt(1), ".SampleWidget1");
+ // Widget 2 label is .SampleWidget2.
+ assertWidgetCellWithLabel(row.getChildAt(2), ".SampleWidget2");
+ }
+
+ private WidgetsListContentEntry generateSampleAppWithWidgets(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 WidgetsListContentEntry(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;
+ }
+
+ 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);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
new file mode 100644
index 0000000..86df3f8
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.model;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+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.widget.model.WidgetsListContentEntry;
+
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsListContentEntryTest {
+ private static final String PACKAGE_NAME = "com.google.test";
+ private final PackageItemInfo mPackageItemInfo = new PackageItemInfo(PACKAGE_NAME);
+ private final ComponentName mWidget1 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget1");
+ private final ComponentName mWidget2 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget2");
+ private final ComponentName mWidget3 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget3");
+ private final Map<ComponentName, String> mWidgetsToLabels = new HashMap();
+
+ @Mock private IconCache mIconCache;
+
+ private Context mContext;
+ private InvariantDeviceProfile mTestProfile;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mWidgetsToLabels.put(mWidget1, "Cat");
+ mWidgetsToLabels.put(mWidget2, "Dog");
+ mWidgetsToLabels.put(mWidget3, "Bird");
+
+ mContext = RuntimeEnvironment.application;
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+
+ doAnswer(invocation -> {
+ ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ return mWidgetsToLabels.get(componentWithLabel.getComponent());
+ }).when(mIconCache).getTitleNoCache(any());
+ }
+
+ @Test
+ public void unsortedWidgets_diffLabels_shouldSortWidgetItems() {
+ // GIVEN a list of widgets in unsorted order.
+ // Cat 2x3
+ WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 3);
+ // Dog 2x3
+ WidgetItem widgetItem2 = createWidgetItem(mWidget2, /* spanX= */ 2, /* spanY= */ 3);
+ // Bird 2x3
+ WidgetItem widgetItem3 = createWidgetItem(mWidget3, /* spanX= */ 2, /* spanY= */ 3);
+
+ // WHEN creates a WidgetsListRowEntry with the unsorted widgets.
+ WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo,
+ /* titleSectionName= */ "T",
+ List.of(widgetItem1, widgetItem2, widgetItem3));
+
+ // THEN the widgets list is sorted by their labels alphabetically: [Bird, Cat, Dog].
+ assertThat(widgetsListRowEntry.mWidgets)
+ .containsExactly(widgetItem3, widgetItem1, widgetItem2)
+ .inOrder();
+ assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
+ assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo);
+ }
+
+ @Test
+ public void unsortedWidgets_sameLabels_differentSize_shouldSortWidgetItems() {
+ // GIVEN a list of widgets in unsorted order.
+ // Cat 3x3
+ WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 3, /* spanY= */ 3);
+ // Cat 1x2
+ WidgetItem widgetItem2 = createWidgetItem(mWidget1, /* spanX= */ 1, /* spanY= */ 2);
+ // Cat 2x2
+ WidgetItem widgetItem3 = createWidgetItem(mWidget1, /* spanX= */ 2, /* spanY= */ 2);
+
+ // WHEN creates a WidgetsListRowEntry with the unsorted widgets.
+ WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo,
+ /* titleSectionName= */ "T",
+ List.of(widgetItem1, widgetItem2, widgetItem3));
+
+ // THEN the widgets list is sorted by their gird sizes in an ascending order:
+ // [1x2, 2x2, 3x3].
+ assertThat(widgetsListRowEntry.mWidgets)
+ .containsExactly(widgetItem2, widgetItem3, widgetItem1)
+ .inOrder();
+ assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
+ assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo);
+ }
+
+ @Test
+ public void unsortedWidgets_hodgepodge_shouldSortWidgetItems() {
+ // GIVEN a list of widgets in unsorted order.
+ // Cat 3x3
+ WidgetItem widgetItem1 = createWidgetItem(mWidget1, /* spanX= */ 3, /* spanY= */ 3);
+ // Cat 1x2
+ WidgetItem widgetItem2 = createWidgetItem(mWidget1, /* spanX= */ 1, /* spanY= */ 2);
+ // Dog 2x2
+ WidgetItem widgetItem3 = createWidgetItem(mWidget2, /* spanX= */ 2, /* spanY= */ 2);
+ // Bird 2x2
+ WidgetItem widgetItem4 = createWidgetItem(mWidget3, /* spanX= */ 2, /* spanY= */ 2);
+
+ // WHEN creates a WidgetsListRowEntry with the unsorted widgets.
+ WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(mPackageItemInfo,
+ /* titleSectionName= */ "T",
+ List.of(widgetItem1, widgetItem2, widgetItem3, widgetItem4));
+
+ // THEN the widgets list is first sorted by labels alphabetically. Then, for widgets with
+ // same labels, they are sorted by their gird sizes in an ascending order:
+ // [Bird 2x2, Cat 1x2, Cat 3x3, Dog 2x2]
+ assertThat(widgetsListRowEntry.mWidgets)
+ .containsExactly(widgetItem4, widgetItem2, widgetItem1, widgetItem3)
+ .inOrder();
+ assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
+ assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(mPackageItemInfo);
+ }
+
+ private WidgetItem createWidgetItem(ComponentName componentName, int spanX, int spanY) {
+ String label = mWidgetsToLabels.get(componentName);
+ ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+ AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+ widgetInfo.provider = componentName;
+ ReflectionHelpers.setField(widgetInfo, "providerInfo",
+ packageManager.addReceiverIfNotPresent(componentName));
+
+ LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
+ LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo);
+ launcherAppWidgetProviderInfo.spanX = spanX;
+ launcherAppWidgetProviderInfo.spanY = spanY;
+ launcherAppWidgetProviderInfo.label = label;
+
+ return new WidgetItem(launcherAppWidgetProviderInfo, mTestProfile, mIconCache);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
new file mode 100644
index 0000000..5922223
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
+import com.android.launcher3.widget.util.WidgetsTableUtils;
+
+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.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsTableUtilsTest {
+ private static final String TEST_PACKAGE = "com.google.test";
+
+ @Mock
+ private IconCache mIconCache;
+
+ private Context mContext;
+ private InvariantDeviceProfile mTestProfile;
+ private WidgetItem mWidget1x1;
+ private WidgetItem mWidget2x2;
+ private WidgetItem mWidget2x3;
+ private WidgetItem mWidget2x4;
+ private WidgetItem mWidget4x4;
+
+ private WidgetItem mShortcut1;
+ private WidgetItem mShortcut2;
+ private WidgetItem mShortcut3;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+
+ initTestWidgets();
+ initTestShortcuts();
+
+ doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
+ .getComponent().getPackageName())
+ .when(mIconCache).getTitleNoCache(any());
+ }
+
+
+ @Test
+ public void groupWidgetItemsIntoTable_widgetsOnly_maxSpansPerRow5_shouldGroupWidgetsInTable() {
+ List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
+ mWidget2x2);
+
+ List<ArrayList<WidgetItem>> widgetItemInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(
+ widgetItems, /* maxSpansPerRow= */ 5);
+
+ // Row 0: 1x1, 2x2, 2x3
+ // Row 1: 2x4
+ // Row 2: 4x4
+ assertThat(widgetItemInTable).hasSize(3);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+ }
+
+ @Test
+ public void groupWidgetItemsIntoTable_widgetsOnly_maxSpansPerRow4_shouldGroupWidgetsInTable() {
+ List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
+ mWidget2x2);
+
+ List<ArrayList<WidgetItem>> widgetItemInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(
+ widgetItems, /* maxSpansPerRow= */ 4);
+
+ // Row 0: 1x1, 2x2
+ // Row 1: 2x3, 2x4
+ // Row 2: 4x4
+ assertThat(widgetItemInTable).hasSize(3);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+ }
+
+ @Test
+ public void groupWidgetItemsIntoTable_mixItems_maxSpansPerRow4_shouldGroupWidgetsInTable() {
+ List<WidgetItem> widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1,
+ mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2);
+
+ List<ArrayList<WidgetItem>> widgetItemInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(
+ widgetItems, /* maxSpansPerRow= */ 4);
+
+ // Row 0: 1x1, 2x2
+ // Row 1: 2x3, 2x4
+ // Row 2: 4x4
+ // Row 3: shortcut3, shortcut1, shortcut2
+ assertThat(widgetItemInTable).hasSize(4);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
+ }
+
+ private void initTestWidgets() {
+ List<Point> widgetSizes = List.of(new Point(1, 1), new Point(2, 2), new Point(2, 3),
+ new Point(2, 4), new Point(4, 4));
+
+ ArrayList<WidgetItem> widgetItems = new ArrayList<>();
+ widgetSizes.stream().forEach(
+ widgetSize -> {
+ ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+ AppWidgetProviderInfo info = new AppWidgetProviderInfo();
+ info.provider = ComponentName.createRelative(TEST_PACKAGE,
+ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y);
+ LauncherAppWidgetProviderInfo widgetInfo =
+ LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
+ widgetInfo.spanX = widgetSize.x;
+ widgetInfo.spanY = widgetSize.y;
+ ReflectionHelpers.setField(widgetInfo, "providerInfo",
+ packageManager.addReceiverIfNotPresent(widgetInfo.provider));
+ widgetItems.add(new WidgetItem(widgetInfo, mTestProfile, mIconCache));
+ }
+ );
+ mWidget1x1 = widgetItems.get(0);
+ mWidget2x2 = widgetItems.get(1);
+ mWidget2x3 = widgetItems.get(2);
+ mWidget2x4 = widgetItems.get(3);
+ mWidget4x4 = widgetItems.get(4);
+ }
+
+ private void initTestShortcuts() {
+ PackageManager packageManager = mContext.getPackageManager();
+ mShortcut1 = new WidgetItem(new TestShortcutConfigActivityInfo(
+ ComponentName.createRelative(TEST_PACKAGE, ".shortcut1"), UserHandle.CURRENT),
+ mIconCache, packageManager);
+ mShortcut2 = new WidgetItem(new TestShortcutConfigActivityInfo(
+ ComponentName.createRelative(TEST_PACKAGE, ".shortcut2"), UserHandle.CURRENT),
+ mIconCache, packageManager);
+ mShortcut3 = new WidgetItem(new TestShortcutConfigActivityInfo(
+ ComponentName.createRelative(TEST_PACKAGE, ".shortcut3"), UserHandle.CURRENT),
+ mIconCache, packageManager);
+
+ }
+
+ private final class TestShortcutConfigActivityInfo extends ShortcutConfigActivityInfo {
+
+ TestShortcutConfigActivityInfo(ComponentName componentName, UserHandle user) {
+ super(componentName, user);
+ }
+
+ @Override
+ public Drawable getFullResIcon(IconCache cache) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getLabel(PackageManager pm) {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 3c34444..95cdbdd 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -59,7 +59,7 @@
TYPE_SNACKBAR,
TYPE_LISTENER,
TYPE_ALL_APPS_EDU,
-
+ TYPE_DRAG_DROP_POPUP,
TYPE_TASK_MENU,
TYPE_OPTIONS_POPUP,
TYPE_ICON_SURFACE
@@ -76,17 +76,18 @@
public static final int TYPE_SNACKBAR = 1 << 7;
public static final int TYPE_LISTENER = 1 << 8;
public static final int TYPE_ALL_APPS_EDU = 1 << 9;
+ public static final int TYPE_DRAG_DROP_POPUP = 1 << 10;
// Popups related to quickstep UI
- public static final int TYPE_TASK_MENU = 1 << 10;
- public static final int TYPE_OPTIONS_POPUP = 1 << 11;
- public static final int TYPE_ICON_SURFACE = 1 << 12;
+ public static final int TYPE_TASK_MENU = 1 << 11;
+ public static final int TYPE_OPTIONS_POPUP = 1 << 12;
+ public static final int TYPE_ICON_SURFACE = 1 << 13;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
- | TYPE_ICON_SURFACE;
+ | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
@@ -103,7 +104,7 @@
// These view all have particular operation associated with swipe down interaction.
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_ON_BOARD_POPUP |
- TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU ;
+ TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU | TYPE_DRAG_DROP_POPUP;
protected boolean mIsOpen;
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index bc3e341..be92c5d 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -2,6 +2,8 @@
import static com.android.launcher3.LauncherAnimUtils.LAYOUT_HEIGHT;
import static com.android.launcher3.LauncherAnimUtils.LAYOUT_WIDTH;
+import static com.android.launcher3.Utilities.ATLEAST_S;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
import static com.android.launcher3.views.BaseDragLayer.LAYOUT_X;
import static com.android.launcher3.views.BaseDragLayer.LAYOUT_Y;
@@ -11,14 +13,19 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.FocusLogic;
@@ -279,8 +286,9 @@
* Based on the current deltas, we determine if and how to resize the widget.
*/
private void resizeWidgetIfNeeded(boolean onDismiss) {
- float xThreshold = mCellLayout.getCellWidth();
- float yThreshold = mCellLayout.getCellHeight();
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ float xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacingPx;
+ float yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacingPx;
int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
@@ -351,28 +359,99 @@
}
public static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
- int spanX, int spanY) {
- getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
- widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
- sTmpRect.right, sTmpRect.bottom);
+ int spanX, int spanY) {
+ List<PointF> sizes = getWidgetSizes(launcher, spanX, spanY);
+ if (ATLEAST_S) {
+ widgetView.updateAppWidgetSize(new Bundle(), sizes);
+ } else {
+ Rect bounds = getMinMaxSizes(sizes, null /* outRect */);
+ widgetView.updateAppWidgetSize(new Bundle(), bounds.left, bounds.top, bounds.right,
+ bounds.bottom);
+ }
}
- public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
- if (rect == null) {
- rect = new Rect();
- }
+ private static PointF getWidgetSize(Context context, Point cellSize, int spanX, int spanY) {
final float density = context.getResources().getDisplayMetrics().density;
+ float hBorderSpacing = 0;
+ float vBorderSpacing = 0;
+ if (ENABLE_FOUR_COLUMNS.get()) {
+ final int borderSpacing = context.getResources()
+ .getDimensionPixelSize(R.dimen.dynamic_grid_cell_border_spacing);
+ hBorderSpacing = (spanX - 1) * borderSpacing;
+ vBorderSpacing = (spanY - 1) * borderSpacing;
+ }
+ PointF widgetSize = new PointF();
+ widgetSize.x = ((spanX * cellSize.x) + hBorderSpacing) / density;
+ widgetSize.y = ((spanY * cellSize.y) + vBorderSpacing) / density;
+ return widgetSize;
+ }
+
+ /** Returns the actual widget size given its span. */
+ public static PointF getWidgetSize(Context context, int spanX, int spanY) {
+ final Point[] cellSize = CELL_SIZE.get(context);
+ if (isLandscape(context)) {
+ return getWidgetSize(context, cellSize[0], spanX, spanY);
+ }
+ return getWidgetSize(context, cellSize[1], spanX, spanY);
+ }
+
+ /** Returns true if the screen is in landscape mode. */
+ private static boolean isLandscape(Context context) {
+ return context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ /** Returns the list of sizes for a widget of given span, in dp. */
+ public static ArrayList<PointF> getWidgetSizes(Context context, int spanX, int spanY) {
final Point[] cellSize = CELL_SIZE.get(context);
- // Compute landscape size
- int landWidth = (int) ((spanX * cellSize[0].x) / density);
- int landHeight = (int) ((spanY * cellSize[0].y) / density);
+ PointF landSize = getWidgetSize(context, cellSize[0], spanX, spanY);
+ PointF portSize = getWidgetSize(context, cellSize[1], spanX, spanY);
- // Compute portrait size
- int portWidth = (int) ((spanX * cellSize[1].x) / density);
- int portHeight = (int) ((spanY * cellSize[1].y) / density);
- rect.set(portWidth, landHeight, landWidth, portHeight);
- return rect;
+ ArrayList<PointF> sizes = new ArrayList<>(2);
+ sizes.add(landSize);
+ sizes.add(portSize);
+ return sizes;
+ }
+
+ /**
+ * Returns the min and max widths and heights given a list of sizes, in dp.
+ *
+ * @param sizes List of sizes to get the min/max from.
+ * @param outRect Rectangle in which the result can be stored, to avoid extra allocations. If
+ * null, a new rectangle will be allocated.
+ * @return A rectangle with the left (resp. top) is used for the min width (resp. height) and
+ * the right (resp. bottom) for the max. The returned rectangle is set with 0s if the list is
+ * empty.
+ */
+ public static Rect getMinMaxSizes(List<PointF> sizes, @Nullable Rect outRect) {
+ if (outRect == null) {
+ outRect = new Rect();
+ }
+ if (sizes.isEmpty()) {
+ outRect.set(0, 0, 0, 0);
+ } else {
+ PointF first = sizes.get(0);
+ outRect.set((int) first.x, (int) first.y, (int) first.x, (int) first.y);
+ for (int i = 1; i < sizes.size(); i++) {
+ outRect.union((int) sizes.get(i).x, (int) sizes.get(i).y);
+ }
+ }
+ return outRect;
+ }
+
+ /**
+ * Returns the range of sizes a widget may be displayed, given its span.
+ *
+ * @param context Context in which the View is rendered.
+ * @param spanX Width of the widget, in cells.
+ * @param spanY Height of the widget, in cells.
+ * @param outRect Rectangle in which the result can be stored, to avoid extra allocations. If
+ * null, a new rectangle will be allocated.
+ */
+ public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY,
+ @Nullable Rect outRect) {
+ return getMinMaxSizes(getWidgetSizes(context, spanX, spanY), outRect);
}
@Override
@@ -384,8 +463,9 @@
}
private void onTouchUp() {
- int xThreshold = mCellLayout.getCellWidth();
- int yThreshold = mCellLayout.getCellHeight();
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ int xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacingPx;
+ int yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacingPx;
mDeltaXAddOn = mRunningHInc * xThreshold;
mDeltaYAddOn = mRunningVInc * yThreshold;
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 5e50e27..062ab71 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -31,7 +31,6 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import androidx.annotation.IntDef;
@@ -141,7 +140,10 @@
return mDeviceProfile;
}
- public final StatsLogManager getStatsLogManager() {
+ /**
+ * Returns {@link StatsLogManager} for user event logging.
+ */
+ public StatsLogManager getStatsLogManager() {
if (mStatsLogManager == null) {
mStatsLogManager = StatsLogManager.newInstance(this);
}
@@ -330,7 +332,7 @@
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
- } else if (context instanceof ContextThemeWrapper) {
+ } else if (context instanceof ContextWrapper) {
return fromContext(((ContextWrapper) context).getBaseContext());
} else {
throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index c55b46b..9369bdc 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -23,6 +23,7 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -188,4 +189,21 @@
super.onInitializeAccessibilityNodeInfo(info);
if (isLayoutSuppressed()) info.setScrollable(false);
}
+
+ /**
+ * Scrolls this recycler view to the top.
+ */
+ public void scrollToTop() {
+ if (mScrollbar != null) {
+ mScrollbar.reattachThumbToScroll();
+ }
+ if (getLayoutManager() instanceof LinearLayoutManager) {
+ LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
+ if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
+ // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
+ return;
+ }
+ }
+ scrollToPosition(0);
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 21297c9..ee4d7ec 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@@ -24,7 +25,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -34,8 +34,6 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -52,11 +50,9 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.core.graphics.ColorUtils;
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.allapps.AllAppsSectionDecorator;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DraggableView;
@@ -87,7 +83,7 @@
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
- IconLabelDotView, DraggableView, Reorderable, AllAppsSectionDecorator.SelfDecoratingView {
+ IconLabelDotView, DraggableView, Reorderable {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -198,6 +194,7 @@
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
defaultIconSize = grid.iconSizePx;
+ setCenterVertically(ENABLE_FOUR_COLUMNS.get());
} else if (mDisplay == DISPLAY_ALL_APPS) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
@@ -232,7 +229,6 @@
int shadowSize = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_click_shadow);
mHighlightShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.INNER);
-
}
@Override
@@ -508,7 +504,7 @@
* @param canvas The canvas to draw to.
*/
protected void drawDotIfNecessary(Canvas canvas) {
- if (mDisplay == DISPLAY_TASKBAR) {
+ if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) {
// TODO: support notification dots in Taskbar
return;
}
@@ -554,6 +550,14 @@
outBounds.set(left, top, right, bottom);
}
+
+ /**
+ * Sets whether to vertically center the content.
+ */
+ public void setCenterVertically(boolean centerVertically) {
+ mCenterVertically = centerVertically;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mCenterVertically) {
@@ -798,7 +802,7 @@
if (mIcon != null
&& mIcon instanceof PlaceHolderIconDrawable
&& iconUpdateAnimationEnabled()) {
- animateIconUpdate((PlaceHolderIconDrawable) mIcon, icon);
+ ((PlaceHolderIconDrawable) mIcon).animateIconUpdate(icon);
}
mDisableRelayout = false;
@@ -949,38 +953,4 @@
setCompoundDrawables(null, newIcon, null, null);
}
}
-
- private static void animateIconUpdate(PlaceHolderIconDrawable oldIcon, Drawable newIcon) {
- int placeholderColor = oldIcon.mPaint.getColor();
- int originalAlpha = Color.alpha(placeholderColor);
-
- ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0);
- iconUpdateAnimation.setDuration(ICON_UPDATE_ANIMATION_DURATION);
- iconUpdateAnimation.addUpdateListener(valueAnimator -> {
- int newAlpha = (int) valueAnimator.getAnimatedValue();
- int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha);
-
- newIcon.setColorFilter(new PorterDuffColorFilter(newColor, Mode.SRC_ATOP));
- });
- iconUpdateAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- newIcon.setColorFilter(null);
- }
- });
- iconUpdateAnimation.start();
- }
-
-
- @Override
- public void decorate(int color) {
- mHighlightColor = color;
- invalidate();
- }
-
- @Override
- public void removeDecoration() {
- mHighlightColor = Color.TRANSPARENT;
- invalidate();
- }
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index df005e6..459b9a8 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -240,7 +240,7 @@
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
- mActive = supportsDrop(dragObject.dragInfo);
+ mActive = !options.isKeyboardDrag && supportsDrop(dragObject.dragInfo);
mDrawable.setColorFilter(null);
if (mCurrentColorAnim != null) {
mCurrentColorAnim.cancel();
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 2809bd5..947388b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -19,6 +19,7 @@
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;
@@ -88,6 +89,8 @@
@Thunk int mCellHeight;
private int mFixedCellWidth;
private int mFixedCellHeight;
+ @ViewDebug.ExportedProperty(category = "launcher")
+ private final int mBorderSpacing;
@ViewDebug.ExportedProperty(category = "launcher")
private int mCountX;
@@ -208,6 +211,7 @@
DeviceProfile grid = mActivity.getDeviceProfile();
+ mBorderSpacing = grid.cellLayoutBorderSpacingPx;
mCellWidth = mCellHeight = -1;
mFixedCellWidth = mFixedCellHeight = -1;
@@ -288,7 +292,8 @@
}
mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+ mBorderSpacing);
addView(mShortcutsAndWidgets);
}
@@ -305,6 +310,8 @@
setImportantForAccessibility(accessibilityFlag);
getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
+ // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
+ setFocusable(delegate != null);
// Invalidate the accessibility hierarchy
if (getParent() != null) {
getParent().notifySubtreeAccessibilityStateChanged(
@@ -312,6 +319,13 @@
}
}
+ /**
+ * Returns the currently set accessibility delegate
+ */
+ public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
+ return mTouchHelper;
+ }
+
@Override
public boolean dispatchHoverEvent(MotionEvent event) {
// Always attempt to dispatch hover events to accessibility first.
@@ -338,7 +352,8 @@
public void setCellDimensions(int width, int height) {
mFixedCellWidth = mCellWidth = width;
mFixedCellHeight = mCellHeight = height;
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+ mBorderSpacing);
}
public void setGridSize(int x, int y) {
@@ -347,7 +362,8 @@
mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
mTempRectStack.clear();
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+ mBorderSpacing);
requestLayout();
}
@@ -468,8 +484,8 @@
for (int j = 0; j < mCountY; j++) {
canvas.save();
- int transX = i * mCellWidth;
- int transY = j * mCellHeight;
+ int transX = i * mCellWidth + (i * mBorderSpacing);
+ int transY = j * mCellHeight + (j * mBorderSpacing);
canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
@@ -584,6 +600,9 @@
if (child instanceof BubbleTextView) {
BubbleTextView bubbleChild = (BubbleTextView) child;
bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
+ if (ENABLE_FOUR_COLUMNS.get()) {
+ bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
+ }
}
child.setScaleX(mChildScale);
@@ -699,11 +718,9 @@
* @param result Array of 2 ints to hold the x and y coordinate of the point
*/
void cellToPoint(int cellX, int cellY, int[] result) {
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
-
- result[0] = hStartPadding + cellX * mCellWidth;
- result[1] = vStartPadding + cellY * mCellHeight;
+ cellToRect(cellX, cellY, 1, 1, mTempRect);
+ result[0] = mTempRect.left;
+ result[1] = mTempRect.top;
}
/**
@@ -727,25 +744,9 @@
* @param result Array of 2 ints to hold the x and y coordinate of the point
*/
void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
- result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
- result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
- }
-
- /**
- * Given a cell coordinate and span fills out a corresponding pixel rect
- *
- * @param cellX X coordinate of the cell
- * @param cellY Y coordinate of the cell
- * @param result Rect in which to write the result
- */
- void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
- final int left = hStartPadding + cellX * mCellWidth;
- final int top = vStartPadding + cellY * mCellHeight;
- result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
+ cellToRect(cellX, cellY, spanX, spanY, mTempRect);
+ result[0] = mTempRect.centerX();
+ result[1] = mTempRect.centerY();
}
public float getDistanceFromCell(float x, float y, int[] cell) {
@@ -776,12 +777,15 @@
int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
- int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
- int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
+ int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpacing,
+ mCountX);
+ int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpacing,
+ mCountY);
if (cw != mCellWidth || ch != mCellHeight) {
mCellWidth = cw;
mCellHeight = ch;
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+ mBorderSpacing);
}
}
@@ -831,10 +835,11 @@
/**
* Returns the amount of space left over after subtracting padding and cells. This space will be
* very small, a few pixels at most, and is a result of rounding down when calculating the cell
- * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
+ * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}.
*/
public int getUnusedHorizontalSpace() {
- return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
+ return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth)
+ - ((mCountX - 1) * mBorderSpacing);
}
public Drawable getScrimBackground() {
@@ -850,8 +855,8 @@
return mShortcutsAndWidgets;
}
- public View getChildAt(int x, int y) {
- return mShortcutsAndWidgets.getChildAt(x, y);
+ public View getChildAt(int cellX, int cellY) {
+ return mShortcutsAndWidgets.getChildAt(cellX, cellY);
}
public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
@@ -982,11 +987,11 @@
}
// Center horizontaly
- left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+ left += (r.width() - dragOutline.getWidth()) / 2;
if (v != null && v.getViewType() == DraggableView.DRAGGABLE_WIDGET) {
// Center vertically
- top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
+ top += (r.height() - dragOutline.getHeight()) / 2;
} else if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
int cHeight = getShortcutsAndWidgets().getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
@@ -2146,7 +2151,7 @@
findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
Rect dragRect = new Rect();
- regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
+ cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
Rect dropRegionRect = new Rect();
@@ -2156,7 +2161,7 @@
int dropRegionSpanX = dropRegionRect.width();
int dropRegionSpanY = dropRegionRect.height();
- regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
+ cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
dropRegionRect.height(), dropRegionRect);
int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
@@ -2514,10 +2519,11 @@
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- int width = cellHSpan * cellWidth;
- int height = cellVSpan * cellHeight;
- int x = hStartPadding + cellX * cellWidth;
- int y = vStartPadding + cellY * cellHeight;
+ int x = hStartPadding + (cellX * mBorderSpacing) + (cellX * cellWidth);
+ int y = vStartPadding + (cellY * mBorderSpacing) + (cellY * cellHeight);
+
+ int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpacing);
+ int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpacing);
resultRect.set(x, y, x + width, y + height);
}
@@ -2535,11 +2541,13 @@
}
public int getDesiredWidth() {
- return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
+ return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth)
+ + ((mCountX - 1) * mBorderSpacing);
}
public int getDesiredHeight() {
- return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
+ return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight)
+ + ((mCountY - 1) * mBorderSpacing);
}
public boolean isOccupied(int x, int y) {
@@ -2654,19 +2662,21 @@
this.cellVSpan = cellVSpan;
}
- public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
- setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
+ public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+ int rowCount, int borderSpacing) {
+ setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
+ borderSpacing);
}
/**
- * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
- * to be scaled.
+ * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, int)},
+ * if the view needs to be scaled.
*
* ie. In multi-window mode, we setup widgets so that they are measured and laid out
* using their full/invariant device profile sizes.
*/
public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
- float cellScaleX, float cellScaleY) {
+ int rowCount, float cellScaleX, float cellScaleY, int borderSpacing) {
if (isLockedToGrid) {
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
@@ -2677,17 +2687,23 @@
myCellX = colCount - myCellX - cellHSpan;
}
- width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
- height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
- x = (myCellX * cellWidth + leftMargin);
- y = (myCellY * cellHeight + topMargin);
+ int hBorderSpacing = (myCellHSpan - 1) * borderSpacing;
+ int vBorderSpacing = (myCellVSpan - 1) * borderSpacing;
+
+ float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
+ float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
+
+ width = Math.round(myCellWidth) - leftMargin - rightMargin;
+ height = Math.round(myCellHeight) - topMargin - bottomMargin;
+ x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpacing);
+ y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpacing);
}
}
/**
* Sets the position to the provided point
*/
- public void setXY(Point point) {
+ public void setCellXY(Point point) {
cellX = point.x;
cellY = point.y;
}
diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java
new file mode 100644
index 0000000..4827f36
--- /dev/null
+++ b/src/com/android/launcher3/DevicePaddings.java
@@ -0,0 +1,202 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Workspace items have a fixed height, so we need a way to distribute any unused workspace height.
+ *
+ * The unused or "extra" height is allocated to three different variable heights:
+ * - The space above the workspace
+ * - The space between the workspace and hotseat
+ * - The espace below the hotseat
+ */
+public class DevicePaddings {
+
+ private static final String DEVICE_PADDING = "device-paddings";
+ private static final String DEVICE_PADDINGS = "device-padding";
+
+ private static final String WORKSPACE_TOP_PADDING = "workspaceTopPadding";
+ private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding";
+ private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding";
+
+ private static final String TAG = DevicePaddings.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
+
+ public DevicePaddings(Context context) {
+ try (XmlResourceParser parser = context.getResources().getXml(R.xml.size_limits)) {
+ final int depth = parser.getDepth();
+ int type;
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+ if ((type == XmlPullParser.START_TAG) && DEVICE_PADDING.equals(parser.getName())) {
+ final int displayDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > displayDepth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ if ((type == XmlPullParser.START_TAG)
+ && DEVICE_PADDINGS.equals(parser.getName())) {
+ TypedArray a = context.obtainStyledAttributes(
+ Xml.asAttributeSet(parser), R.styleable.DevicePadding);
+ int maxWidthPx = a.getDimensionPixelSize(
+ R.styleable.DevicePadding_maxEmptySpace, 0);
+ a.recycle();
+
+ PaddingFormula workspaceTopPadding = null;
+ PaddingFormula workspaceBottomPadding = null;
+ PaddingFormula hotseatBottomPadding = null;
+
+ final int limitDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > limitDepth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ AttributeSet attr = Xml.asAttributeSet(parser);
+ if ((type == XmlPullParser.START_TAG)) {
+ if (WORKSPACE_TOP_PADDING.equals(parser.getName())) {
+ workspaceTopPadding = new PaddingFormula(context, attr);
+ } else if (WORKSPACE_BOTTOM_PADDING.equals(parser.getName())) {
+ workspaceBottomPadding = new PaddingFormula(context, attr);
+ } else if (HOTSEAT_BOTTOM_PADDING.equals(parser.getName())) {
+ hotseatBottomPadding = new PaddingFormula(context, attr);
+ }
+ }
+ }
+
+ if (workspaceTopPadding == null
+ || workspaceBottomPadding == null
+ || hotseatBottomPadding == null) {
+ throw new RuntimeException("DevicePadding missing padding.");
+ }
+
+ mDevicePaddings.add(new DevicePadding(maxWidthPx, workspaceTopPadding,
+ workspaceBottomPadding, hotseatBottomPadding));
+ }
+ }
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Sort ascending by maxEmptySpacePx
+ mDevicePaddings.sort((sl1, sl2) -> Integer.compare(sl1.maxEmptySpacePx,
+ sl2.maxEmptySpacePx));
+ }
+
+ public DevicePadding getDevicePadding(int extraSpacePx) {
+ for (DevicePadding limit : mDevicePaddings) {
+ if (extraSpacePx <= limit.maxEmptySpacePx) {
+ return limit;
+ }
+ }
+
+ return mDevicePaddings.get(mDevicePaddings.size() - 1);
+ }
+
+ /**
+ * Holds all the formulas to calculate the padding for a particular device based on the
+ * amount of extra space.
+ */
+ public static final class DevicePadding {
+
+ private final int maxEmptySpacePx;
+ private final PaddingFormula workspaceTopPadding;
+ private final PaddingFormula workspaceBottomPadding;
+ private final PaddingFormula hotseatBottomPadding;
+
+ public DevicePadding(int maxEmptySpacePx,
+ PaddingFormula workspaceTopPadding,
+ PaddingFormula workspaceBottomPadding,
+ PaddingFormula hotseatBottomPadding) {
+ this.maxEmptySpacePx = maxEmptySpacePx;
+ this.workspaceTopPadding = workspaceTopPadding;
+ this.workspaceBottomPadding = workspaceBottomPadding;
+ this.hotseatBottomPadding = hotseatBottomPadding;
+ }
+
+ public int getWorkspaceTopPadding(int extraSpacePx) {
+ return workspaceTopPadding.calculate(extraSpacePx);
+ }
+
+ public int getWorkspaceBottomPadding(int extraSpacePx) {
+ return workspaceBottomPadding.calculate(extraSpacePx);
+ }
+
+ public int getHotseatBottomPadding(int extraSpacePx) {
+ return hotseatBottomPadding.calculate(extraSpacePx);
+ }
+ }
+
+ /**
+ * Used to calculate a padding based on three variables: a, b, and c.
+ *
+ * Calculation: a * (extraSpace - c) + b
+ */
+ private static final class PaddingFormula {
+
+ private final float a;
+ private final float b;
+ private final float c;
+
+ public PaddingFormula(Context context, AttributeSet attrs) {
+ TypedArray t = context.obtainStyledAttributes(attrs,
+ R.styleable.DevicePaddingFormula);
+
+ a = getValue(t, R.styleable.DevicePaddingFormula_a);
+ b = getValue(t, R.styleable.DevicePaddingFormula_b);
+ c = getValue(t, R.styleable.DevicePaddingFormula_c);
+
+ t.recycle();
+ }
+
+ public int calculate(int extraSpacePx) {
+ if (DEBUG) {
+ Log.d(TAG, "a=" + a + " * (" + extraSpacePx + " - " + c + ") + b=" + b);
+ }
+ return Math.round(a * (extraSpacePx - c) + b);
+ }
+
+ private static float getValue(TypedArray a, int index) {
+ if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
+ return a.getDimensionPixelSize(index, 0);
+ } else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
+ return a.getFloat(index, 0);
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "a=" + a + ", b=" + b + ", c=" + c;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4d5bd5d..634093c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -23,8 +25,12 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.Surface;
+import android.view.WindowInsets;
+import android.view.WindowManager;
import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
@@ -74,12 +80,16 @@
// Workspace
public final int desiredWorkspaceLeftRightMarginPx;
+ public final int cellLayoutBorderSpacingPx;
public final int cellLayoutPaddingLeftRightPx;
public final int cellLayoutBottomPaddingPx;
public final int edgeMarginPx;
public float workspaceSpringLoadShrinkFactor;
public final int workspaceSpringLoadedBottomSpace;
+ public int workspaceTopPadding;
+ public int workspaceBottomPadding;
+
// Workspace page indicator
public final int workspacePageIndicatorHeight;
private final int mWorkspacePageIndicatorOverlapWorkspace;
@@ -92,6 +102,7 @@
public int cellWidthPx;
public int cellHeightPx;
+ public int cellYPaddingPx;
public int workspaceCellPaddingXPx;
// Folder
@@ -141,6 +152,10 @@
public DotRenderer mDotRendererWorkSpace;
public DotRenderer mDotRendererAllApps;
+ // Taskbar
+ public boolean isTaskbarPresent;
+ public int taskbarSize;
+
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
Point minSize, Point maxSize, int width, int height, boolean isLandscape,
boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
@@ -155,12 +170,13 @@
// Determine sizes.
widthPx = width;
heightPx = height;
+ int nonFinalAvailableHeightPx;
if (isLandscape) {
availableWidthPx = maxSize.x;
- availableHeightPx = minSize.y;
+ nonFinalAvailableHeightPx = minSize.y;
} else {
availableWidthPx = minSize.x;
- availableHeightPx = maxSize.y;
+ nonFinalAvailableHeightPx = maxSize.y;
}
mInfo = info;
@@ -184,9 +200,30 @@
: Configuration.ORIENTATION_PORTRAIT);
final Resources res = context.getResources();
+ isTaskbarPresent = isTablet && FeatureFlags.ENABLE_TASKBAR.get();
+ if (isTaskbarPresent) {
+ // Taskbar will be added later, but provides bottom insets that we should subtract
+ // from availableHeightPx.
+ taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size);
+ WindowInsets windowInsets = DisplayController.INSTANCE.get(context).getHolder(mInfo.id)
+ .getDisplayContext().getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics().getWindowInsets();
+ int nonOverlappingTaskbarInset =
+ taskbarSize - windowInsets.getSystemWindowInsetBottom();
+ if (nonOverlappingTaskbarInset > 0) {
+ nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
+ }
+ }
+ availableHeightPx = nonFinalAvailableHeightPx;
+
edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
+ cellYPaddingPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_y);
+ cellLayoutBorderSpacingPx = isVerticalBarLayout()
+ || isMultiWindowMode
+ || !ENABLE_FOUR_COLUMNS.get()
+ ? 0 : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_border_spacing);
int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
@@ -220,22 +257,31 @@
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
// Add a bit of space between nav bar and hotseat in vertical bar layout.
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
+ int hotseatExtraVerticalSize =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics)
+ (isVerticalBarLayout()
? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
- : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
- + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
+ : (hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx
+ + (ENABLE_FOUR_COLUMNS.get() ? 0 : hotseatExtraVerticalSize)));
// Calculate all of the remaining variables.
- updateAvailableDimensions(res);
-
+ int extraSpace = updateAvailableDimensions(res);
// Now that we have all of the variables calculated, we can tune certain sizes.
- if (!isVerticalBarLayout() && isPhone && isTallDevice) {
+ if (ENABLE_FOUR_COLUMNS.get()) {
+ DevicePadding padding = inv.devicePaddings.getDevicePadding(extraSpace);
+ workspaceTopPadding = padding.getWorkspaceTopPadding(extraSpace);
+ workspaceBottomPadding = padding.getWorkspaceBottomPadding(extraSpace);
+
+ float hotseatBarBottomPadding = padding.getHotseatBottomPadding(extraSpace);
+ hotseatBarSizePx += hotseatBarBottomPadding;
+ hotseatBarBottomPaddingPx += hotseatBarBottomPadding;
+ } 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.
- int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
+ extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
- workspacePageIndicatorHeight;
hotseatBarSizePx += extraSpace;
hotseatBarBottomPaddingPx += extraSpace;
@@ -328,17 +374,24 @@
+ topBottomPadding * 2;
}
- private void updateAvailableDimensions(Resources res) {
+ /**
+ * Returns the amount of extra (or unused) vertical space.
+ */
+ private int updateAvailableDimensions(Resources res) {
updateIconSize(1f, res);
// Check to see if the icons fit within the available height. If not, then scale down.
- float usedHeight = (cellHeightPx * inv.numRows);
+ float usedHeight = (cellHeightPx * inv.numRows)
+ + (cellLayoutBorderSpacingPx * (inv.numRows - 1));
int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
+ float extraHeight = Math.max(0, maxHeight - usedHeight);
if (usedHeight > maxHeight) {
float scale = maxHeight / usedHeight;
updateIconSize(scale, res);
+ extraHeight = 0;
}
updateAvailableFolderCellDimensions(res);
+ return Math.round(extraHeight);
}
/**
@@ -346,7 +399,7 @@
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
*/
- private void updateIconSize(float scale, Resources res) {
+ public void updateIconSize(float scale, Resources res) {
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
@@ -355,16 +408,23 @@
iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
- cellHeightPx = iconSizePx + iconDrawablePaddingPx
- + Utilities.calculateTextHeight(iconTextSizePx);
- int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
- if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout
- && !isMultiWindowMode) {
- // Ensures that the label is closer to its corresponding icon. This is not an issue
- // with vertical bar layout or multi-window mode since the issue is handled separately
- // with their calls to {@link #adjustToHideWorkspaceLabels}.
- cellHeightPx -= (iconDrawablePaddingPx - cellYPadding);
- iconDrawablePaddingPx = cellYPadding;
+ if (ENABLE_FOUR_COLUMNS.get()) {
+ cellHeightPx = iconSizePx + iconDrawablePaddingPx
+ + Utilities.calculateTextHeight(iconTextSizePx)
+ + (cellYPaddingPx * 2);
+ } else {
+ cellYPaddingPx = 0;
+ cellHeightPx = iconSizePx + iconDrawablePaddingPx
+ + Utilities.calculateTextHeight(iconTextSizePx);
+ int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
+ if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout
+ && !isMultiWindowMode) {
+ // Ensures that the label is closer to its corresponding icon. This is not an issue
+ // with vertical bar layout or multi-window mode since the issue is handled
+ // separately with their calls to {@link #adjustToHideWorkspaceLabels}.
+ cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY);
+ iconDrawablePaddingPx = cellPaddingY;
+ }
}
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
@@ -425,13 +485,15 @@
Point totalWorkspacePadding = getTotalWorkspacePadding();
// Check if the icons fit within the available height.
- float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
+ float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
+ + ((inv.numFolderRows - 1) * cellLayoutBorderSpacingPx);
int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
- folderMargin;
float scaleY = contentMaxHeight / contentUsedHeight;
// Check if the icons fit within the available width.
- float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
+ float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
+ + ((inv.numFolderColumns - 1) * cellLayoutBorderSpacingPx);
int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
float scaleX = contentMaxWidth / contentUsedWidth;
@@ -479,9 +541,9 @@
// not matter.
Point padding = getTotalWorkspacePadding();
result.x = calculateCellWidth(availableWidthPx - padding.x
- - cellLayoutPaddingLeftRightPx * 2, numColumns);
+ - cellLayoutPaddingLeftRightPx * 2, cellLayoutBorderSpacingPx, numColumns);
result.y = calculateCellHeight(availableHeightPx - padding.y
- - cellLayoutBottomPaddingPx, numRows);
+ - cellLayoutBottomPaddingPx, cellLayoutBorderSpacingPx, numRows);
return result;
}
@@ -509,7 +571,7 @@
}
} else {
int paddingBottom = hotseatBarSizePx + workspacePageIndicatorHeight
- - mWorkspacePageIndicatorOverlapWorkspace;
+ + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
if (isTablet) {
// Pad the left and right of the workspace to ensure consistent spacing
// between all icons
@@ -526,7 +588,7 @@
} else {
// Pad the top and bottom of the workspace with search/hotseat bar sizes
padding.set(desiredWorkspaceLeftRightMarginPx,
- edgeMarginPx,
+ workspaceTopPadding + edgeMarginPx,
desiredWorkspaceLeftRightMarginPx,
paddingBottom);
}
@@ -581,11 +643,11 @@
}
}
- public static int calculateCellWidth(int width, int countX) {
- return width / countX;
+ public static int calculateCellWidth(int width, int borderSpacing, int countX) {
+ return (width - ((countX - 1) * borderSpacing)) / countX;
}
- public static int calculateCellHeight(int height, int countY) {
- return height / countY;
+ public static int calculateCellHeight(int height, int borderSpacing, int countY) {
+ return (height - ((countY - 1) * borderSpacing)) / countY;
}
/**
@@ -610,8 +672,13 @@
public boolean updateIsSeascape(Context context) {
if (isVerticalBarLayout()) {
// Check an up-to-date info.
- boolean isSeascape = DisplayController.getDefaultDisplay(context)
- .createInfoForContext(context).rotation == Surface.ROTATION_270;
+ DisplayController.Info displayInfo = DisplayController.getDefaultDisplay(context)
+ .createInfoForContext(context);
+ if (displayInfo == null) {
+ return false;
+ }
+
+ boolean isSeascape = displayInfo.rotation == Surface.ROTATION_270;
if (mIsSeascape != isSeascape) {
mIsSeascape = isSeascape;
return true;
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index ca001a3..c768493 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -131,7 +131,10 @@
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
- if (mIsVertical) {
+ int visibleCount = getVisibleButtonsCount();
+ if (visibleCount == 0) {
+ // do nothing
+ } else if (mIsVertical) {
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
@@ -142,7 +145,6 @@
}
}
} else {
- int visibleCount = getVisibleButtonsCount();
int availableWidth = width / visibleCount;
boolean textVisible = true;
for (ButtonDropTarget buttons : mDropTargets) {
@@ -165,7 +167,10 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (mIsVertical) {
+ int visibleCount = getVisibleButtonsCount();
+ if (visibleCount == 0) {
+ // do nothing
+ } else if (mIsVertical) {
int gap = getResources().getDimensionPixelSize(R.dimen.drop_target_vertical_gap);
int start = gap;
int end;
@@ -178,7 +183,6 @@
}
}
} else {
- int visibleCount = getVisibleButtonsCount();
int frameSize = (right - left) / visibleCount;
int start = frameSize / 2;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 5fd9e01..2a08c50 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.Utilities.getPointString;
-import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
@@ -131,6 +130,8 @@
public DeviceProfile landscapeProfile;
public DeviceProfile portraitProfile;
+ public DevicePaddings devicePaddings;
+
public Point defaultWallpaperSize;
public Rect defaultWidgetPadding;
@@ -160,6 +161,7 @@
demoModeLayoutId = p.demoModeLayoutId;
mExtraAttrs = p.mExtraAttrs;
mOverlayMonitor = p.mOverlayMonitor;
+ devicePaddings = p.devicePaddings;
}
@TargetApi(23)
@@ -174,8 +176,7 @@
.putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(numColumns, numRows))
.apply();
- mConfigMonitor = new ConfigMonitor(context,
- APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
+ mConfigMonitor = new ConfigMonitor(context, this::onConfigChanged);
mOverlayMonitor = new OverlayMonitor(context);
}
@@ -212,6 +213,8 @@
result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
result.allAppsIconSize = Math.min(
defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize);
+
+ devicePaddings = new DevicePaddings(context);
initGrid(context, myInfo, result);
}
@@ -239,6 +242,7 @@
ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
+ devicePaddings = new DevicePaddings(context);
initGrid(context, displayInfo, displayOption);
return displayOption.grid.name;
}
@@ -317,11 +321,6 @@
mChangeListeners.remove(listener);
}
- private void killProcess(Context context) {
- Log.e("ConfigMonitor", "restarting launcher");
- android.os.Process.killProcess(android.os.Process.myPid());
- }
-
public void verifyConfigChangedInBackground(final Context context) {
String savedIconMaskPath = getDevicePrefs(context).getString(KEY_ICON_PATH_REF, "");
// Good place to check if grid size changed in themepicker when launcher was dead.
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 36963f1..f7ff262 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -37,9 +37,13 @@
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;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_EXIT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
@@ -72,6 +76,7 @@
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -104,6 +109,7 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.LauncherAction;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -120,10 +126,11 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ItemInstallQueue;
@@ -163,7 +170,6 @@
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
@@ -181,10 +187,10 @@
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetHostViewLoader;
-import com.android.launcher3.widget.WidgetListRowEntry;
import com.android.launcher3.widget.WidgetManagerHelper;
-import com.android.launcher3.widget.WidgetsFullSheet;
import com.android.launcher3.widget.custom.CustomWidgetManager;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.shared.LauncherExterns;
@@ -352,6 +358,13 @@
private SafeCloseable mUserChangedCallbackCloseable;
+ // New InstanceId is assigned to mAllAppsSessionLogId for each AllApps sessions.
+ // When Launcher is not in AllApps state mAllAppsSessionLogId will be null.
+ // User actions within AllApps state are logged with this InstanceId, to recreate AllApps
+ // session on the server side.
+ protected InstanceId mAllAppsSessionLogId;
+ private LauncherState mPrevLauncherState;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
@@ -383,7 +396,7 @@
idp.addOnChangeListener(this);
mSharedPrefs = Utilities.getPrefs(this);
mIconCache = app.getIconCache();
- mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
+ mAccessibilityDelegate = createAccessibilityDelegate();
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
@@ -905,7 +918,7 @@
}
logStopAndResume(false /* isResume */);
- mAppWidgetHost.setListenIfResumed(false);
+ mAppWidgetHost.setActivityStarted(false);
NotificationListener.removeNotificationsChangedListener();
}
@@ -918,7 +931,7 @@
mOverlayManager.onActivityStarted(this);
}
- mAppWidgetHost.setListenIfResumed(true);
+ mAppWidgetHost.setActivityStarted(true);
TraceHelper.INSTANCE.endSection(traceToken);
}
@@ -938,6 +951,7 @@
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
DiscoveryBounce.showForHomeIfNeeded(this);
+ mAppWidgetHost.setActivityResumed(true);
}
private void logStopAndResume(boolean isResume) {
@@ -1026,12 +1040,13 @@
}
// When multiple pages are visible, show persistent page indicator
mWorkspace.getPageIndicator().setShouldAutoHide(!state.hasFlag(FLAG_MULTI_PAGE));
+ mPrevLauncherState = mStateManager.getCurrentStableState();
}
@Override
public void onStateSetEnd(LauncherState state) {
super.onStateSetEnd(state);
- getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+ getAppWidgetHost().setStateIsNormal(state == LauncherState.NORMAL);
getWorkspace().setClipChildren(!state.hasFlag(FLAG_MULTI_PAGE));
finishAutoCancelActionMode();
@@ -1049,6 +1064,21 @@
// Clear any rotation locks when going to normal state
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.
+ && mAllAppsSessionLogId != null) {
+ getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_EXIT);
+ mAllAppsSessionLogId = null;
+ }
}
@Override
@@ -1088,6 +1118,7 @@
if (!mDeferOverlayCallbacks) {
mOverlayManager.onActivityPaused(this);
}
+ mAppWidgetHost.setActivityResumed(false);
}
class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
@@ -1772,6 +1803,43 @@
return newFolder;
}
+ @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;
+ }
+
+ @Override
+ public void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
+ int left = inOutPosition[0];
+ int top = inOutPosition[1];
+ DeviceProfile grid = getDeviceProfile();
+ int distFromEdgeOfScreen = getWorkspace().getPaddingLeft();
+ if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
+ // Center the folder if it is very close to being centered anyway, by virtue of
+ // filling the majority of the viewport. ie. remove it from the uncanny valley
+ // of centeredness.
+ left = (grid.availableWidthPx - width) / 2;
+ } else if (width >= bounds.width()) {
+ // If the folder doesn't fit within the bounds, center it about the desired bounds
+ left = bounds.left + (bounds.width() - width) / 2;
+ }
+ if (height >= bounds.height()) {
+ // Folder height is greater than page height, center on page
+ top = bounds.top + (bounds.height() - height) / 2;
+ } else {
+ // Folder height is less than page height, so bound it to the absolute open folder
+ // bounds if necessary
+ Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
+ left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
+ top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
+ }
+ inOutPosition[0] = left;
+ inOutPosition[1] = top;
+ }
+
/**
* Unbinds the view for the specified item, and removes the item and all its children.
*
@@ -2550,7 +2618,7 @@
}
@Override
- public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
+ public void bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets) {
mPopupDataProvider.setAllWidgets(allWidgets);
}
@@ -2631,19 +2699,9 @@
shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text),
KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
}
- final View currentFocus = getCurrentFocus();
- if (currentFocus != null) {
- if (new CustomActionsPopup(this, currentFocus).canShow()) {
- shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
- KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
- }
- if (currentFocus.getTag() instanceof ItemInfo
- && ShortcutUtil.supportsShortcuts((ItemInfo) currentFocus.getTag())) {
+ getSupportedActions(this, getCurrentFocus()).forEach(la ->
shortcutInfos.add(new KeyboardShortcutInfo(
- getString(R.string.shortcuts_menu_with_notifications_description),
- KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
- }
- }
+ la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
if (!shortcutInfos.isEmpty()) {
data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
}
@@ -2661,29 +2719,18 @@
return true;
}
break;
- case KeyEvent.KEYCODE_S: {
- View focusedView = getCurrentFocus();
- if (focusedView instanceof BubbleTextView
- && focusedView.getTag() instanceof ItemInfo
- && mAccessibilityDelegate.performAction(focusedView,
- (ItemInfo) focusedView.getTag(),
- LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
- PopupContainerWithArrow.getOpen(this).requestFocus();
- return true;
- }
- break;
- }
- case KeyEvent.KEYCODE_O:
- if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
- return true;
- }
- break;
case KeyEvent.KEYCODE_W:
if (isInState(NORMAL)) {
OptionsPopupView.openWidgets(this);
return true;
}
break;
+ default:
+ for (LauncherAction la : getSupportedActions(this, getCurrentFocus())) {
+ if (la.keyCode == keyCode) {
+ return la.invokeFromKeyboard(getCurrentFocus());
+ }
+ }
}
}
return super.onKeyShortcut(keyCode, event);
@@ -2741,6 +2788,9 @@
return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
+ protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
+ return new LauncherAccessibilityDelegate(this);
+ }
/**
* @see LauncherState#getOverviewScaleAndOffset(Launcher)
@@ -2815,4 +2865,9 @@
public Configuration config;
public Bitmap snapshot;
}
+
+ @Override
+ public StatsLogManager getStatsLogManager() {
+ return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId);
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index a4181c5..57d7600 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,8 +17,8 @@
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.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.content.ComponentName;
import android.content.Context;
@@ -37,10 +37,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.SecureSettingsObserver;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.widget.custom.CustomWidgetManager;
@@ -57,8 +57,9 @@
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
+ private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
- private SecureSettingsObserver mNotificationDotsObserver;
+ private SettingsCache mSettingsCache;
private InstallSessionTracker mInstallSessionTracker;
private SimpleBroadcastReceiver mModelChangeReceiver;
private SafeCloseable mCalendarChangeTracker;
@@ -108,10 +109,11 @@
.registerInstallTracker(mModel);
// Register an observer to rebind the notification listener when dots are re-enabled.
- mNotificationDotsObserver =
- newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
- mNotificationDotsObserver.register();
- mNotificationDotsObserver.dispatchOnChange();
+ mSettingsCache = SettingsCache.INSTANCE.get(mContext);
+ mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
}
public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -166,8 +168,9 @@
}
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
- if (mNotificationDotsObserver != null) {
- mNotificationDotsObserver.unregister();
+ if (mSettingsCache != null) {
+ mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
}
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 7ea6851..fea26df 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -49,8 +49,11 @@
public class LauncherAppWidgetHost extends AppWidgetHost {
private static final int FLAG_LISTENING = 1;
- private static final int FLAG_RESUMED = 1 << 1;
- private static final int FLAG_LISTEN_IF_RESUMED = 1 << 2;
+ private static final int FLAG_STATE_IS_NORMAL = 1 << 1;
+ private static final int FLAG_ACTIVITY_STARTED = 1 << 2;
+ private static final int FLAG_ACTIVITY_RESUMED = 1 << 3;
+ private static final int FLAGS_SHOULD_LISTEN =
+ FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;
public static final int APPWIDGET_HOST_ID = 1024;
@@ -59,7 +62,7 @@
private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
private final Context mContext;
- private int mFlags = FLAG_RESUMED;
+ private int mFlags = FLAG_STATE_IS_NORMAL;
private IntConsumer mAppWidgetRemovedCallback = null;
@@ -130,49 +133,45 @@
}
/**
- * Updates the resumed state of the host.
- * When a host is not resumed, it defers calls to startListening until host is resumed again.
- * But if the host was already listening, it will not call stopListening.
- *
- * @see #setListenIfResumed(boolean)
+ * Sets or unsets a flag the can change whether the widget host should be in the listening
+ * state.
*/
- public void setResumed(boolean isResumed) {
- if (isResumed == ((mFlags & FLAG_RESUMED) != 0)) {
- return;
- }
- if (isResumed) {
- mFlags |= FLAG_RESUMED;
- // Start listening if we were supposed to start listening on resume
- if ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0 && (mFlags & FLAG_LISTENING) == 0) {
- startListening();
- }
+ private void setShouldListenFlag(int flag, boolean on) {
+ if (on) {
+ mFlags |= flag;
} else {
- mFlags &= ~FLAG_RESUMED;
+ mFlags &= ~flag;
+ }
+
+ final boolean listening = isListening();
+ if (!listening && (mFlags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN) {
+ // Postpone starting listening until all flags are on.
+ startListening();
+ } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) {
+ // Postpone stopping listening until the activity is stopped.
+ stopListening();
}
}
/**
- * Updates the listening state of the host. If the host is not resumed, startListening is
- * deferred until next resume.
- *
- * @see #setResumed(boolean)
+ * Registers an "entering/leaving Normal state" event.
*/
- public void setListenIfResumed(boolean listenIfResumed) {
- if (listenIfResumed == ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0)) {
- return;
- }
- if (listenIfResumed) {
- mFlags |= FLAG_LISTEN_IF_RESUMED;
- if ((mFlags & FLAG_RESUMED) != 0) {
- // If we are resumed, start listening immediately. Note we do not check for
- // duplicate calls before calling startListening as startListening is safe to call
- // multiple times.
- startListening();
- }
- } else {
- mFlags &= ~FLAG_LISTEN_IF_RESUMED;
- stopListening();
- }
+ public void setStateIsNormal(boolean isNormal) {
+ setShouldListenFlag(FLAG_STATE_IS_NORMAL, isNormal);
+ }
+
+ /**
+ * Registers an "activity started/stopped" event.
+ */
+ public void setActivityStarted(boolean isStarted) {
+ setShouldListenFlag(FLAG_ACTIVITY_STARTED, isStarted);
+ }
+
+ /**
+ * Registers an "activity paused/resumed" event.
+ */
+ public void setActivityResumed(boolean isResumed) {
+ setShouldListenFlag(FLAG_ACTIVITY_RESUMED, isResumed);
}
@Override
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 51504ce..76c4518 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -4,12 +4,14 @@
import android.annotation.TargetApi;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.ViewDebug;
import android.view.WindowInsets;
+import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.statemanager.StatefulActivity;
import java.util.Collections;
@@ -31,6 +33,8 @@
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mForceHideBackArrow;
+ private SysUiScrim mSysUiScrim;
+
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
mActivity = StatefulActivity.fromContext(context);
@@ -89,6 +93,18 @@
}
}
+ public void setSysUiScrim(SysUiScrim scrim) {
+ mSysUiScrim = scrim;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mSysUiScrim != null) {
+ mSysUiScrim.draw(canvas);
+ }
+ super.dispatchDraw(canvas);
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
@@ -119,4 +135,4 @@
void onWindowVisibilityChanged(int visibility);
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index fe423ed..dfdc53c 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -75,6 +75,37 @@
public static final int ITEM_TYPE_SHORTCUT = 1;
/**
+ * The favorite is a user created folder
+ */
+ public static final int ITEM_TYPE_FOLDER = 2;
+
+ /**
+ * The favorite is a widget
+ */
+ public static final int ITEM_TYPE_APPWIDGET = 4;
+
+ /**
+ * The favorite is a custom widget provided by the launcher
+ */
+ public static final int ITEM_TYPE_CUSTOM_APPWIDGET = 5;
+
+ /**
+ * The gesture is an application created deep shortcut
+ */
+ public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
+
+ /**
+ * Type of the item is recents task.
+ * TODO(hyunyoungs): move constants not related to Favorites DB to a better location.
+ */
+ public static final int ITEM_TYPE_TASK = 7;
+
+ /**
+ * The item is QSB
+ */
+ public static final int ITEM_TYPE_QSB = 8;
+
+ /**
* The icon package name in Intent.ShortcutIconResource
* <P>Type: TEXT</P>
*/
@@ -170,6 +201,10 @@
public static final int CONTAINER_SHORTCUTS = -107;
public static final int CONTAINER_SETTINGS = -108;
public static final int CONTAINER_TASKSWITCHER = -109;
+ public static final int CONTAINER_QSB = -110;
+
+ // Represents any of the extended containers implemented in non-AOSP variants.
+ public static final int EXTENDED_CONTAINERS = -200;
public static final String containerToString(int container) {
switch (container) {
@@ -192,6 +227,8 @@
case ITEM_TYPE_APPWIDGET: return "WIDGET";
case ITEM_TYPE_CUSTOM_APPWIDGET: return "CUSTOMWIDGET";
case ITEM_TYPE_DEEP_SHORTCUT: return "DEEPSHORTCUT";
+ case ITEM_TYPE_TASK: return "TASK";
+ case ITEM_TYPE_QSB: return "QSB";
default: return String.valueOf(type);
}
}
@@ -237,32 +274,6 @@
public static final String PROFILE_ID = "profileId";
/**
- * The favorite is a user created folder
- */
- public static final int ITEM_TYPE_FOLDER = 2;
-
- /**
- * The favorite is a widget
- */
- public static final int ITEM_TYPE_APPWIDGET = 4;
-
- /**
- * The favorite is a custom widget provided by the launcher
- */
- public static final int ITEM_TYPE_CUSTOM_APPWIDGET = 5;
-
- /**
- * The gesture is an application created deep shortcut
- */
- public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
-
- /**
- * Type of the item is recents task.
- * TODO(hyunyoungs): move constants not related to Favorites DB to a better location.
- */
- public static final int ITEM_TYPE_TASK = 7;
-
- /**
* The appWidgetId of the widget
*
* <P>Type: INTEGER</P>
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 4303dee..af2d94a 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -96,6 +96,8 @@
private static final int MIN_FLING_VELOCITY = 250;
private boolean mFreeScroll = false;
+ /** If {@code false}, disable swipe gesture to switch between pages. */
+ private boolean mSwipeGestureEnabled = true;
protected final int mFlingThresholdVelocity;
protected final int mEasyFlingThresholdVelocity;
@@ -745,6 +747,11 @@
return mOrientationHandler.getChildStart(pageAtIndex);
}
+ protected int getChildVisibleSize(int index) {
+ View layout = getPageAt(index);
+ return mOrientationHandler.getMeasuredSize(layout);
+ }
+
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
int page = indexToPage(indexOfChild(child));
@@ -853,6 +860,14 @@
}
/**
+ * If {@code enableSwipeGesture} is {@code true}, enables swipe gesture to navigate between
+ * pages. Otherwise, disables the navigation gesture.
+ */
+ public void setSwipeGestureEnabled(boolean swipeGestureEnabled) {
+ mSwipeGestureEnabled = swipeGestureEnabled;
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -874,6 +889,8 @@
* scrolling there.
*/
+ if (!mSwipeGestureEnabled) return false;
+
// Skip touch handling if there are no pages to swipe
if (getChildCount() <= 0) return false;
@@ -1457,8 +1474,7 @@
}
private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) {
- View layout = getPageAt(childIndex);
- int childSize = mOrientationHandler.getMeasuredSize(layout);
+ int childSize = getChildVisibleSize(childIndex);
int halfChildSize = (childSize / 2);
int childCenter = getChildOffset(childIndex) + halfChildSize;
return childCenter - screenCenter;
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 92b88e6..7276887 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -37,6 +37,8 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
@@ -203,9 +205,13 @@
d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
super.onDrop(d, options);
- StatsLogger logger = mStatsLogManager.logger().withInstanceId(d.logInstanceId);
- if (d.originalDragInfo != null) {
- logger.withItemInfo(d.originalDragInfo);
+ doLog(d.logInstanceId, d.originalDragInfo);
+ }
+
+ private void doLog(InstanceId logInstanceId, ItemInfo itemInfo) {
+ StatsLogger logger = mStatsLogManager.logger().withInstanceId(logInstanceId);
+ if (itemInfo != null) {
+ logger.withItemInfo(itemInfo);
}
if (mCurrentAccessibilityAction == UNINSTALL) {
logger.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
@@ -296,6 +302,7 @@
@Override
public void onAccessibilityDrop(View view, ItemInfo item) {
+ doLog(new InstanceIdSequence().newInstanceId(), item);
performDropAction(view, item);
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index ee0c7bb..eab8272 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -26,10 +26,11 @@
import android.view.ViewGroup;
import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-public class ShortcutAndWidgetContainer extends ViewGroup {
+public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.FolderIconParent {
static final String TAG = "ShortcutAndWidgetContainer";
// These are temporary variables to prevent having to allocate a new object just to
@@ -42,8 +43,10 @@
private int mCellWidth;
private int mCellHeight;
+ private int mBorderSpacing;
private int mCountX;
+ private int mCountY;
private final ActivityContext mActivity;
private boolean mInvertIfRtl = false;
@@ -55,20 +58,23 @@
mContainerType = containerType;
}
- public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY) {
+ public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY,
+ int borderSpacing) {
mCellWidth = cellWidth;
mCellHeight = cellHeight;
mCountX = countX;
+ mCountY = countY;
+ mBorderSpacing = borderSpacing;
}
- public View getChildAt(int x, int y) {
+ public View getChildAt(int cellX, int cellY) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
- (lp.cellY <= y) && (y < lp.cellY + lp.cellVSpan)) {
+ if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan)
+ && (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) {
return child;
}
}
@@ -95,10 +101,11 @@
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (child instanceof LauncherAppWidgetHostView) {
DeviceProfile profile = mActivity.getDeviceProfile();
- lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
- profile.appWidgetScale.x, profile.appWidgetScale.y);
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+ profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing);
} else {
- lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+ mBorderSpacing);
}
}
@@ -117,11 +124,12 @@
final DeviceProfile profile = mActivity.getDeviceProfile();
if (child instanceof LauncherAppWidgetHostView) {
- lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
- profile.appWidgetScale.x, profile.appWidgetScale.y);
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+ profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing);
// Widgets have their own padding
} else {
- lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+ mBorderSpacing);
// Center the icon/folder
int cHeight = getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
@@ -221,4 +229,24 @@
child.cancelLongPress();
}
}
+
+ @Override
+ public void drawFolderLeaveBehindForIcon(FolderIcon child) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ // While the folder is open, the position of the icon cannot change.
+ lp.canReorder = false;
+ if (mContainerType == CellLayout.HOTSEAT) {
+ CellLayout cl = (CellLayout) getParent();
+ cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ }
+ }
+
+ @Override
+ public void clearFolderLeaveBehind(FolderIcon child) {
+ ((CellLayout.LayoutParams) child.getLayoutParams()).canReorder = true;
+ if (mContainerType == CellLayout.HOTSEAT) {
+ CellLayout cl = (CellLayout) getParent();
+ cl.clearFolderLeaveBehind();
+ }
+ }
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 51d8e66..c440303 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -605,7 +605,6 @@
outObj[0] = activityInfo;
return activityInfo.getFullResIcon(appState.getIconCache());
}
- if (info.getIntent() == null || info.getIntent().getPackage() == null) return null;
List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
.buildRequest(launcher)
.query(ShortcutRequest.ALL);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8a45c81..2528c03 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -81,6 +81,7 @@
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.graphics.WorkspaceDragScrim;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
@@ -199,6 +200,9 @@
private boolean mStripScreensOnPageStopMoving = false;
private DragPreviewProvider mOutlineProvider = null;
+
+ private WorkspaceDragScrim mWorkspaceDragScrim;
+
private boolean mWorkspaceFadeInAdjacentScreens;
final WallpaperOffsetInterpolator mWallpaperOffset;
@@ -1161,6 +1165,19 @@
}
}
+ public void setWorkspaceDragScrim(WorkspaceDragScrim workspaceDragScrim) {
+ mWorkspaceDragScrim = workspaceDragScrim;
+ }
+
+ @Override
+ public void invalidate() {
+ // The workspace scrim may need to be re-rendered based on the workspace scroll
+ if (mWorkspaceDragScrim != null) {
+ mWorkspaceDragScrim.invalidate();
+ }
+ super.invalidate();
+ }
+
@Override
public void computeScroll() {
super.computeScroll();
@@ -1706,10 +1723,9 @@
if (dropOverView instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) dropOverView;
if (fi.acceptDrop(d.dragInfo)) {
- mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
- .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
+ mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId)
+ .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED_ON_FOLDER_ICON);
fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
-
// if the drag started here, we need to remove it from the workspace
if (!external) {
getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
@@ -2028,7 +2044,7 @@
}
// Invalidating the scrim will also force this CellLayout
// to be invalidated so that it is highlighted if necessary.
- mLauncher.getDragLayer().getScrim().invalidate();
+ mLauncher.getDragLayer().getWorkspaceDragScrim().invalidate();
}
public CellLayout getCurrentDragOverlappingLayout() {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index cd938e1..0e0ddfb 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -32,7 +32,7 @@
import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
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.WorkspaceAndHotseatScrim.SYSUI_PROGRESS;
+import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
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;
@@ -49,7 +49,8 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
+import com.android.launcher3.graphics.SysUiScrim;
+import com.android.launcher3.graphics.WorkspaceDragScrim;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DynamicResource;
import com.android.systemui.plugins.ResourceProvider;
@@ -183,10 +184,12 @@
}
public void setScrim(PropertySetter propertySetter, LauncherState state) {
- WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
- propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
- LINEAR);
- propertySetter.setFloat(scrim, SYSUI_PROGRESS,
+ WorkspaceDragScrim workspaceDragScrim = mLauncher.getDragLayer().getWorkspaceDragScrim();
+ propertySetter.setFloat(workspaceDragScrim, SCRIM_PROGRESS,
+ state.getWorkspaceScrimAlpha(mLauncher), LINEAR);
+
+ SysUiScrim sysUiScrim = mLauncher.getDragLayer().getSysUiScrim();
+ propertySetter.setFloat(sysUiScrim, SYSUI_PROGRESS,
state.hasFlag(FLAG_HAS_SYS_UI_SCRIM) ? 1 : 0, LINEAR);
}
diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
index ddb547f..d0fc175 100644
--- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
@@ -31,6 +31,7 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragLayer;
import java.util.List;
@@ -41,30 +42,32 @@
implements OnClickListener, OnHoverListener {
protected static final int INVALID_POSITION = -1;
- private static final int[] sTempArray = new int[2];
+ protected final Rect mTempRect = new Rect();
+ protected final int[] mTempCords = new int[2];
protected final CellLayout mView;
protected final Context mContext;
protected final LauncherAccessibilityDelegate mDelegate;
-
- private final Rect mTempRect = new Rect();
+ protected final DragLayer mDragLayer;
public DragAndDropAccessibilityDelegate(CellLayout forView) {
super(forView);
mView = forView;
mContext = mView.getContext();
- mDelegate = Launcher.getLauncher(mContext).getAccessibilityDelegate();
+ Launcher launcher = Launcher.getLauncher(mContext);
+ mDelegate = launcher.getAccessibilityDelegate();
+ mDragLayer = launcher.getDragLayer();
}
@Override
- protected int getVirtualViewAt(float x, float y) {
+ public int getVirtualViewAt(float x, float y) {
if (x < 0 || y < 0 || x > mView.getMeasuredWidth() || y > mView.getMeasuredHeight()) {
return INVALID_ID;
}
- mView.pointToCellExact((int) x, (int) y, sTempArray);
+ mView.pointToCellExact((int) x, (int) y, mTempCords);
// Map cell to id
- int id = sTempArray[0] + sTempArray[1] * mView.getCountX();
+ int id = mTempCords[0] + mTempCords[1] * mView.getCountX();
return intersectsValidDropTarget(id);
}
@@ -75,7 +78,7 @@
protected abstract int intersectsValidDropTarget(int id);
@Override
- protected void getVisibleVirtualViews(List<Integer> virtualViews) {
+ public void getVisibleVirtualViews(List<Integer> virtualViews) {
// We create a virtual view for each cell of the grid
// The cell ids correspond to cells in reading order.
int nCells = mView.getCountX() * mView.getCountY();
@@ -88,7 +91,7 @@
}
@Override
- protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
+ public boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
if (action == AccessibilityNodeInfoCompat.ACTION_CLICK && viewId != INVALID_ID) {
String confirmation = getConfirmationForIconDrop(viewId);
mDelegate.handleAccessibleDrop(mView, getItemBounds(viewId), confirmation);
@@ -112,13 +115,25 @@
}
@Override
- protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
+ public void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
if (id == INVALID_ID) {
throw new IllegalArgumentException("Invalid virtual view id");
}
node.setContentDescription(getLocationDescriptionForIconDrop(id));
- node.setBoundsInParent(getItemBounds(id));
+
+ Rect itemBounds = getItemBounds(id);
+ node.setBoundsInParent(itemBounds);
+
+ // ExploreByTouchHelper does not currently handle view scale.
+ // Update BoundsInScreen to appropriate value.
+ mTempCords[0] = mTempCords[1] = 0;
+ float scale = mDragLayer.getDescendantCoordRelativeToSelf(mView, mTempCords);
+ mTempRect.left = mTempCords[0] + (int) (itemBounds.left * scale);
+ mTempRect.right = mTempCords[0] + (int) (itemBounds.right * scale);
+ mTempRect.top = mTempCords[1] + (int) (itemBounds.top * scale);
+ mTempRect.bottom = mTempCords[1] + (int) (itemBounds.bottom * scale);
+ node.setBoundsInScreen(mTempRect);
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
node.setClickable(true);
@@ -130,6 +145,13 @@
return dispatchHoverEvent(motionEvent);
}
+ /**
+ * Returns the target host container
+ */
+ public View getHost() {
+ return mView;
+ }
+
protected abstract String getLocationDescriptionForIconDrop(int id);
protected abstract String getConfirmationForIconDrop(int id);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 136d43e..db7fd3f 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -3,17 +3,18 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
-import android.app.AlertDialog;
import android.appwidget.AppWidgetProviderInfo;
-import android.content.DialogInterface;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
+import android.view.KeyEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -25,7 +26,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
@@ -33,21 +33,26 @@
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.keyboard.CustomActionsPopup;
+import com.android.launcher3.keyboard.KeyboardDragAndDropView;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.OptionsPopupView;
+import com.android.launcher3.views.OptionsPopupView.OptionItem;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
@@ -77,93 +82,105 @@
public View item;
}
- protected final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
- @Thunk final Launcher mLauncher;
+ protected final SparseArray<LauncherAction> mActions = new SparseArray<>();
+ protected final Launcher mLauncher;
private DragInfo mDragInfo = null;
public LauncherAccessibilityDelegate(Launcher launcher) {
mLauncher = launcher;
- mActions.put(REMOVE, new AccessibilityAction(REMOVE,
- launcher.getText(R.string.remove_drop_target_label)));
- mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
- launcher.getText(R.string.uninstall_drop_target_label)));
- mActions.put(DISMISS_PREDICTION, new AccessibilityAction(DISMISS_PREDICTION,
- launcher.getText(R.string.dismiss_prediction_label)));
- mActions.put(RECONFIGURE, new AccessibilityAction(RECONFIGURE,
- launcher.getText(R.string.gadget_setup_text)));
- mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
- launcher.getText(R.string.action_add_to_workspace)));
- mActions.put(MOVE, new AccessibilityAction(MOVE,
- launcher.getText(R.string.action_move)));
- mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE,
- launcher.getText(R.string.action_move_to_workspace)));
- mActions.put(RESIZE, new AccessibilityAction(RESIZE,
- launcher.getText(R.string.action_resize)));
- mActions.put(DEEP_SHORTCUTS, new AccessibilityAction(DEEP_SHORTCUTS,
- launcher.getText(R.string.action_deep_shortcut)));
- mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new AccessibilityAction(DEEP_SHORTCUTS,
- launcher.getText(R.string.shortcuts_menu_with_notifications_description)));
- }
-
- public void addAccessibilityAction(int action, int actionLabel) {
- mActions.put(action, new AccessibilityAction(action, mLauncher.getText(actionLabel)));
+ mActions.put(REMOVE, new LauncherAction(
+ REMOVE, R.string.remove_drop_target_label, KeyEvent.KEYCODE_X));
+ mActions.put(UNINSTALL, new LauncherAction(
+ UNINSTALL, R.string.uninstall_drop_target_label, KeyEvent.KEYCODE_U));
+ mActions.put(DISMISS_PREDICTION, new LauncherAction(DISMISS_PREDICTION,
+ R.string.dismiss_prediction_label, KeyEvent.KEYCODE_X));
+ mActions.put(RECONFIGURE, new LauncherAction(
+ RECONFIGURE, R.string.gadget_setup_text, KeyEvent.KEYCODE_E));
+ mActions.put(ADD_TO_WORKSPACE, new LauncherAction(
+ ADD_TO_WORKSPACE, R.string.action_add_to_workspace, KeyEvent.KEYCODE_P));
+ mActions.put(MOVE, new LauncherAction(
+ MOVE, R.string.action_move, KeyEvent.KEYCODE_M));
+ mActions.put(MOVE_TO_WORKSPACE, new LauncherAction(MOVE_TO_WORKSPACE,
+ R.string.action_move_to_workspace, KeyEvent.KEYCODE_P));
+ mActions.put(RESIZE, new LauncherAction(
+ RESIZE, R.string.action_resize, KeyEvent.KEYCODE_R));
+ mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS,
+ R.string.action_deep_shortcut, KeyEvent.KEYCODE_S));
+ mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new LauncherAction(DEEP_SHORTCUTS,
+ R.string.shortcuts_menu_with_notifications_description, KeyEvent.KEYCODE_S));
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- addSupportedActions(host, info, false);
+ if (host.getTag() instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) host.getTag();
+
+ List<LauncherAction> actions = new ArrayList<>();
+ getSupportedActions(host, item, actions);
+ actions.forEach(la -> info.addAction(la.accessibilityAction));
+
+ if (!itemSupportsLongClick(host, item)) {
+ info.setLongClickable(false);
+ info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
+ }
+ }
}
- public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
- if (!(host.getTag() instanceof ItemInfo)) return;
- ItemInfo item = (ItemInfo) host.getTag();
-
- if (host instanceof AccessibilityActionHandler) {
- ((AccessibilityActionHandler) host).addSupportedAccessibilityActions(info);
- }
-
+ /**
+ * Adds all the accessibility actions that can be handled.
+ */
+ protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
// If the request came from keyboard, do not add custom shortcuts as that is already
// exposed as a direct shortcut
- if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) {
- info.addAction(mActions.get(NotificationListener.getInstanceIfConnected() != null
+ if (ShortcutUtil.supportsShortcuts(item)) {
+ out.add(mActions.get(NotificationListener.getInstanceIfConnected() != null
? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS));
}
for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
if (target.supportsAccessibilityDrop(item, host)) {
- info.addAction(mActions.get(target.getAccessibilityAction()));
+ out.add(mActions.get(target.getAccessibilityAction()));
}
}
// Do not add move actions for keyboard request as this uses virtual nodes.
- if (!fromKeyboard && itemSupportsAccessibleDrag(item)) {
- info.addAction(mActions.get(MOVE));
+ if (itemSupportsAccessibleDrag(item)) {
+ out.add(mActions.get(MOVE));
if (item.container >= 0) {
- info.addAction(mActions.get(MOVE_TO_WORKSPACE));
+ out.add(mActions.get(MOVE_TO_WORKSPACE));
} else if (item instanceof LauncherAppWidgetInfo) {
if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) {
- info.addAction(mActions.get(RESIZE));
+ out.add(mActions.get(RESIZE));
}
}
}
- if (!fromKeyboard && !itemSupportsLongClick(host, item)) {
- info.setLongClickable(false);
- info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
- }
-
if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
- info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ out.add(mActions.get(ADD_TO_WORKSPACE));
}
}
+ /**
+ * Returns all the accessibility actions that can be handled by the host.
+ */
+ public static List<LauncherAction> getSupportedActions(Launcher launcher, View host) {
+ if (host == null || !(host.getTag() instanceof ItemInfo)) {
+ return Collections.emptyList();
+ }
+ PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
+ LauncherAccessibilityDelegate delegate = container != null
+ ? container.getAccessibilityDelegate() : launcher.getAccessibilityDelegate();
+ List<LauncherAction> result = new ArrayList<>();
+ delegate.getSupportedActions(host, (ItemInfo) host.getTag(), result);
+ return result;
+ }
+
private boolean itemSupportsLongClick(View host, ItemInfo info) {
- return PopupContainerWithArrow.canShow(host, info)
- || new CustomActionsPopup(mLauncher, host).canShow();
+ return PopupContainerWithArrow.canShow(host, info);
}
private boolean itemSupportsAccessibleDrag(ItemInfo item) {
@@ -178,13 +195,17 @@
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if ((host.getTag() instanceof ItemInfo)
- && performAction(host, (ItemInfo) host.getTag(), action)) {
+ && performAction(host, (ItemInfo) host.getTag(), action, false)) {
return true;
}
return super.performAccessibilityAction(host, action, args);
}
- public boolean performAction(final View host, final ItemInfo item, int action) {
+ /**
+ * Performs the provided action on the host
+ */
+ protected boolean performAction(final View host, final ItemInfo item, int action,
+ boolean fromKeyboard) {
if (action == ACTION_LONG_CLICK) {
if (PopupContainerWithArrow.canShow(host, item)) {
// Long press should be consumed for workspace items, and it should invoke the
@@ -192,20 +213,9 @@
// standard long press path does.
PopupContainerWithArrow.showForIcon((BubbleTextView) host);
return true;
- } else {
- CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
- if (popup.canShow()) {
- popup.show();
- return true;
- }
}
- }
- if (host instanceof AccessibilityActionHandler
- && ((AccessibilityActionHandler) host).performAccessibilityAction(action, item)) {
- return true;
- }
- if (action == MOVE) {
- beginAccessibleDrag(host, item);
+ } else if (action == MOVE) {
+ return beginAccessibleDrag(host, item, fromKeyboard);
} else if (action == ADD_TO_WORKSPACE) {
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
@@ -219,9 +229,7 @@
Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- ArrayList<ItemInfo> itemList = new ArrayList<>();
- itemList.add(info);
- mLauncher.bindItems(itemList, true);
+ mLauncher.bindItems(Collections.singletonList(info), true);
announceConfirmation(R.string.item_added_to_workspace);
} else if (item instanceof PendingAddItemInfo) {
PendingAddItemInfo info = (PendingAddItemInfo) item;
@@ -242,47 +250,31 @@
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
mLauncher.getModelWriter().moveItemInDatabase(info,
- LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
// Bind the item in next frame so that if a new workspace page was created,
// it will get laid out.
- new Handler().post(new Runnable() {
-
- @Override
- public void run() {
- ArrayList<ItemInfo> itemList = new ArrayList<>();
- itemList.add(item);
- mLauncher.bindItems(itemList, true);
- announceConfirmation(R.string.item_moved);
- }
+ new Handler().post(() -> {
+ mLauncher.bindItems(Collections.singletonList(item), true);
+ announceConfirmation(R.string.item_moved);
});
+ return true;
} else if (action == RESIZE) {
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
- final IntArray actions = getSupportedResizeActions(host, info);
- CharSequence[] labels = new CharSequence[actions.size()];
- for (int i = 0; i < actions.size(); i++) {
- labels[i] = mLauncher.getText(actions.get(i));
- }
-
- new AlertDialog.Builder(mLauncher)
- .setTitle(R.string.action_resize)
- .setItems(labels, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- performResizeAction(actions.get(which), host, info);
- dialog.dismiss();
- }
- })
- .show();
+ List<OptionItem> actions = getSupportedResizeActions(host, info);
+ Rect pos = new Rect();
+ mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos);
+ ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions);
+ popup.requestFocus();
+ popup.setOnCloseCallback(host::requestFocus);
return true;
- } else if (action == DEEP_SHORTCUTS) {
+ } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
} else {
for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
- if (dropTarget.supportsAccessibilityDrop(item, host) &&
- action == dropTarget.getAccessibilityAction()) {
+ if (dropTarget.supportsAccessibilityDrop(item, host)
+ && action == dropTarget.getAccessibilityAction()) {
dropTarget.onAccessibilityDrop(host, item);
return true;
}
@@ -291,9 +283,8 @@
return false;
}
- private IntArray getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
- IntArray actions = new IntArray();
-
+ private List<OptionItem> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
+ List<OptionItem> actions = new ArrayList<>();
AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo();
if (providerInfo == null) {
return actions;
@@ -303,28 +294,40 @@
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(R.string.action_increase_width);
+ actions.add(new OptionItem(
+ 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(R.string.action_decrease_width);
+ actions.add(new OptionItem(
+ R.string.action_decrease_width, R.drawable.ic_widget_width_decrease,
+ IGNORE,
+ v -> performResizeAction(R.string.action_decrease_width, host, info)));
}
}
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(R.string.action_increase_height);
+ actions.add(new OptionItem(
+ 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(R.string.action_decrease_height);
+ actions.add(new OptionItem(
+ R.string.action_decrease_height, R.drawable.ic_widget_height_decrease,
+ IGNORE,
+ v -> performResizeAction(R.string.action_decrease_height, host, info)));
}
}
return actions;
}
- @Thunk void performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
+ private boolean performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
CellLayout layout = (CellLayout) host.getParent().getParent();
layout.markCellsAsUnoccupiedForView(host);
@@ -354,13 +357,12 @@
}
layout.markCellsAsOccupiedForView(host);
- Rect sizeRange = new Rect();
- AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, sizeRange);
- ((LauncherAppWidgetHostView) host).updateAppWidgetSize(null,
- sizeRange.left, sizeRange.top, sizeRange.right, sizeRange.bottom);
+ AppWidgetResizeFrame.updateWidgetSizeRanges(((LauncherAppWidgetHostView) host), mLauncher,
+ info.spanX, info.spanY);
host.requestLayout();
mLauncher.getModelWriter().updateItemInDatabase(info);
announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
+ return true;
}
@Thunk void announceConfirmation(int resId) {
@@ -406,7 +408,11 @@
}
}
- public void beginAccessibleDrag(View item, ItemInfo info) {
+ private boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) {
+ if (!itemSupportsAccessibleDrag(info)) {
+ return false;
+ }
+
mDragInfo = new DragInfo();
mDragInfo.info = info;
mDragInfo.item = item;
@@ -423,8 +429,17 @@
DragOptions options = new DragOptions();
options.isAccessibleDrag = true;
+ options.isKeyboardDrag = fromKeyboard;
options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY());
- ItemLongClickListener.beginDrag(item, mLauncher, info, options);
+
+ if (fromKeyboard) {
+ KeyboardDragAndDropView popup = (KeyboardDragAndDropView) mLauncher.getLayoutInflater()
+ .inflate(R.layout.keyboard_drag_and_drop, mLauncher.getDragLayer(), false);
+ popup.showForIcon(item, info, options);
+ } else {
+ ItemLongClickListener.beginDrag(item, mLauncher, info, options);
+ }
+ return true;
}
@Override
@@ -475,19 +490,28 @@
return screenId;
}
- /**
- * An interface allowing views to handle their own action.
- */
- public interface AccessibilityActionHandler {
+ public class LauncherAction {
+ public final int keyCode;
+ public final AccessibilityAction accessibilityAction;
+
+ private final LauncherAccessibilityDelegate mDelegate;
+
+ public LauncherAction(int id, int labelRes, int keyCode) {
+ this.keyCode = keyCode;
+ accessibilityAction = new AccessibilityAction(id, mLauncher.getString(labelRes));
+ mDelegate = LauncherAccessibilityDelegate.this;
+ }
/**
- * performs accessibility action and returns true on success
+ * Invokes the action for the provided host
*/
- boolean performAccessibilityAction(int action, ItemInfo itemInfo);
-
- /**
- * adds all the accessibility actions that can be handled.
- */
- void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo);
+ public boolean invokeFromKeyboard(View host) {
+ if (host != null && host.getTag() instanceof ItemInfo) {
+ return mDelegate.performAction(
+ host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true);
+ } else {
+ return false;
+ }
+ }
}
}
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index d4ba11e..1733e5d 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -18,9 +18,8 @@
import static com.android.launcher3.LauncherState.NORMAL;
+import android.view.KeyEvent;
import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
@@ -31,7 +30,8 @@
import com.android.launcher3.notification.NotificationMainView;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Extension of {@link LauncherAccessibilityDelegate} with actions specific to shortcuts in
@@ -43,23 +43,23 @@
public ShortcutMenuAccessibilityDelegate(Launcher launcher) {
super(launcher);
- mActions.put(DISMISS_NOTIFICATION, new AccessibilityAction(DISMISS_NOTIFICATION,
- launcher.getText(R.string.action_dismiss_notification)));
+ mActions.put(DISMISS_NOTIFICATION, new LauncherAction(DISMISS_NOTIFICATION,
+ R.string.action_dismiss_notification, KeyEvent.KEYCODE_X));
}
@Override
- public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
+ protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
if ((host.getParent() instanceof DeepShortcutView)) {
- info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ out.add(mActions.get(ADD_TO_WORKSPACE));
} else if (host instanceof NotificationMainView) {
if (((NotificationMainView) host).canChildBeDismissed()) {
- info.addAction(mActions.get(DISMISS_NOTIFICATION));
+ out.add(mActions.get(DISMISS_NOTIFICATION));
}
}
}
@Override
- public boolean performAction(View host, ItemInfo item, int action) {
+ protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
if (action == ADD_TO_WORKSPACE) {
if (!(host.getParent() instanceof DeepShortcutView)) {
return false;
@@ -73,9 +73,7 @@
mLauncher.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- ArrayList<ItemInfo> itemList = new ArrayList<>();
- itemList.add(info);
- mLauncher.bindItems(itemList, true);
+ mLauncher.bindItems(Collections.singletonList(info), true);
AbstractFloatingView.closeAllOpenViews(mLauncher);
announceConfirmation(R.string.item_added_to_workspace);
}
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 65a261d..a331924 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -17,17 +17,12 @@
package com.android.launcher3.accessibility;
import android.content.Context;
-import android.graphics.Rect;
import android.text.TextUtils;
import android.view.View;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-
import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -38,9 +33,6 @@
*/
public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelegate {
- private final Rect mTempRect = new Rect();
- private final int[] mTempCords = new int[2];
-
public WorkspaceAccessibilityHelper(CellLayout layout) {
super(layout);
}
@@ -134,26 +126,6 @@
}
return "";
}
-
- @Override
- protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
- super.onPopulateNodeForVirtualView(id, node);
-
-
- // ExploreByTouchHelper does not currently handle view scale.
- // Update BoundsInScreen to appropriate value.
- DragLayer dragLayer = Launcher.getLauncher(mView.getContext()).getDragLayer();
- mTempCords[0] = mTempCords[1] = 0;
- float scale = dragLayer.getDescendantCoordRelativeToSelf(mView, mTempCords);
-
- node.getBoundsInParent(mTempRect);
- mTempRect.left = mTempCords[0] + (int) (mTempRect.left * scale);
- mTempRect.right = mTempCords[0] + (int) (mTempRect.right * scale);
- mTempRect.top = mTempCords[1] + (int) (mTempRect.top * scale);
- mTempRect.bottom = mTempCords[1] + (int) (mTempRect.bottom * scale);
- node.setBoundsInScreen(mTempRect);
- }
-
@Override
protected String getLocationDescriptionForIconDrop(int id) {
int x = id % mView.getCountX();
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 20f7c23..4fd2577 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,6 +16,8 @@
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;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -67,12 +69,13 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
/**
* The all apps view container.
*/
public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
- Insettable, OnDeviceProfileChangeListener {
+ Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener {
private static final float FLING_VELOCITY_MULTIPLIER = 135f;
// Starts the springs after at least 55% of the animation has passed.
@@ -213,10 +216,14 @@
if (insets == null) return;
if (insets.isVisible(WindowInsets.Type.ime())) {
- getWindowInsetsController().hide(WindowInsets.Type.ime());
+ hideIme();
}
}
+ protected void hideIme() {
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+
/**
* Returns whether the view itself will handle the touch event or not.
*/
@@ -427,10 +434,20 @@
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
findViewById(R.id.tab_personal)
- .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
+ .setOnClickListener((View view) -> {
+ if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
+ mLauncher.getStatsLogManager().logger()
+ .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
+ }
+ });
findViewById(R.id.tab_work)
- .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
- onTabChanged(mViewPager.getNextPage());
+ .setOnClickListener((View view) -> {
+ if (mViewPager.snapToPage(AdapterHolder.WORK)) {
+ mLauncher.getStatsLogManager().logger()
+ .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
+ }
+ });
+ onActivePageChanged(mViewPager.getNextPage());
} else {
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
mAH[AdapterHolder.WORK].recyclerView = null;
@@ -479,7 +496,7 @@
if (showTabs) {
mViewPager = (AllAppsPagedView) newView;
mViewPager.initParentViews(this);
- mViewPager.getPageIndicator().setContainerView(this);
+ mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
} else {
mViewPager = null;
}
@@ -489,14 +506,15 @@
return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
}
- public void onTabChanged(int pos) {
- mHeader.setMainActive(pos == 0);
- if (mAH[pos].recyclerView != null) {
- mAH[pos].recyclerView.bindFastScrollbar();
+ @Override
+ public void onActivePageChanged(int currentActivePage) {
+ mHeader.setMainActive(currentActivePage == 0);
+ if (mAH[currentActivePage].recyclerView != null) {
+ mAH[currentActivePage].recyclerView.bindFastScrollbar();
}
reset(true /* animate */);
if (mWorkModeSwitch != null) {
- mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK
+ mWorkModeSwitch.setWorkTabVisible(currentActivePage == AdapterHolder.WORK
&& mAllAppsStore.hasModelFlag(
FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
}
@@ -751,6 +769,7 @@
int bottomOffset = mWorkModeSwitch != null && mIsWork ? switchH : 0;
recyclerView.setPadding(padding.left, padding.top, padding.right,
padding.bottom + bottomOffset);
+ recyclerView.scrollToTop();
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 715c142..5030c5e 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -42,8 +42,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
-import com.android.launcher3.allapps.search.SearchSectionInfo;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.allapps.search.SectionDecorationInfo;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
@@ -109,7 +108,7 @@
// The index of this app not including sections
public int appIndex = -1;
// Search section associated to result
- public SearchSectionInfo searchSectionInfo = null;
+ public SectionDecorationInfo sectionDecorationInfo = null;
/**
* Factory method for AppIcon AdapterItem
@@ -372,10 +371,6 @@
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
- ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
- }
switch (holder.getItemViewType()) {
case VIEW_TYPE_ICON:
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
@@ -409,10 +404,6 @@
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
super.onViewRecycled(holder);
- if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
- if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
- ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
- }
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
index f6e54aa..b34c8b8 100644
--- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
@@ -86,6 +86,10 @@
mApps = appsView;
}
+ public void show() {
+ mApps.getWindowInsetsController().show(WindowInsets.Type.ime());
+ }
+
public void hide() {
if (!Utilities.ATLEAST_R) return;
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index e2550f5..14e3b51 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -15,19 +15,23 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SWIPE_TO_PERSONAL_TAB;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SWIPE_TO_WORK_TAB;
+
import android.content.Context;
import android.util.AttributeSet;
-import android.view.MotionEvent;
+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;
-public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
-
- static final float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
- static final float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
- static final float TOUCH_SLOP_DAMPING_FACTOR = 4;
+/**
+ * A {@link PagedView} for showing different views for the personal and work profile respectively
+ * in the {@link AllAppsContainerView}.
+ */
+public class AllAppsPagedView extends PersonalWorkPagedView {
public AllAppsPagedView(Context context) {
this(context, null);
@@ -46,50 +50,14 @@
}
@Override
- protected String getCurrentPageDescription() {
- // Not necessary, tab-bar already has two tabs with their own descriptions.
- return "";
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- mPageIndicator.setScroll(l, mMaxScroll);
- }
-
- @Override
- protected void determineScrollingStart(MotionEvent ev) {
- float absDeltaX = Math.abs(ev.getX() - getDownMotionX());
- float absDeltaY = Math.abs(ev.getY() - getDownMotionY());
-
- if (Float.compare(absDeltaX, 0f) == 0) return;
-
- float slope = absDeltaY / absDeltaX;
- float theta = (float) Math.atan(slope);
-
- if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
- cancelCurrentPageLongPress();
+ protected boolean snapToPageWithVelocity(int whichPage, int velocity) {
+ boolean resp = super.snapToPageWithVelocity(whichPage, velocity);
+ if (resp && whichPage != mCurrentPage) {
+ Launcher.getLauncher(getContext()).getStatsLogManager().logger()
+ .log(mCurrentPage < whichPage
+ ? LAUNCHER_ALLAPPS_SWIPE_TO_WORK_TAB
+ : LAUNCHER_ALLAPPS_SWIPE_TO_PERSONAL_TAB);
}
-
- if (theta > MAX_SWIPE_ANGLE) {
- return;
- } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
- theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
- float extraRatio = (float)
- Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
- super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
- } else {
- super.determineScrollingStart(ev);
- }
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- @Override
- protected boolean canScroll(float absVScroll, float absHScroll) {
- return (absHScroll > absVScroll) && super.canScroll(absVScroll, absHScroll);
+ return resp;
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index e61b95d..ace9938 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -36,7 +36,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
import com.android.launcher3.views.RecyclerViewFastScroller;
import java.util.ArrayList;
@@ -109,23 +108,6 @@
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
}
- /**
- * Scrolls this recycler view to the top.
- */
- public void scrollToTop() {
- // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
- if (mScrollbar != null) {
- mScrollbar.reattachThumbToScroll();
- }
- if (getLayoutManager() instanceof AppsGridLayoutManager) {
- AppsGridLayoutManager layoutManager = (AppsGridLayoutManager) getLayoutManager();
- if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
- // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
- return;
- }
- }
- scrollToPosition(0);
- }
@Override
public void onDraw(Canvas c) {
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index 1d31975..9328a3d 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -18,16 +18,18 @@
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.SearchSectionInfo;
+import com.android.launcher3.allapps.search.SectionDecorationInfo;
import com.android.launcher3.util.Themes;
import java.util.List;
@@ -45,52 +47,51 @@
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- // Iterate through views in recylerview and draw bounds around views in the same section.
- // Since views in the same section will follow each other, we can skip to a last view in
- // a section to get the bounds of the section without having to iterate on every item.
- int itemCount = parent.getChildCount();
List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
- SectionDecorationHandler lastDecorationHandler = null;
- int i = 0;
- while (i < itemCount) {
+ boolean drawFallbackFocusedView = true;
+ for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
- if (view instanceof SelfDecoratingView) {
- ((SelfDecoratingView) view).removeDecoration();
- }
int position = parent.getChildAdapterPosition(view);
AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
- if (adapterItem.searchSectionInfo != null) {
- SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo;
- int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1);
+ if (adapterItem.sectionDecorationInfo != null) {
+ SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
- if (decorationHandler != lastDecorationHandler && lastDecorationHandler != null) {
- drawDecoration(c, lastDecorationHandler, parent);
- }
- lastDecorationHandler = decorationHandler;
if (decorationHandler != null) {
decorationHandler.extendBounds(view);
- }
-
- if (endIndex > i) {
- i = endIndex;
- continue;
+ if (sectionInfo.isFocusedView()) {
+ decorationHandler.onFocusDraw(c, view);
+ drawFallbackFocusedView = false;
+ } else {
+ decorationHandler.onGroupDraw(c);
+ }
}
}
- i++;
}
- if (lastDecorationHandler != null) {
- drawDecoration(c, lastDecorationHandler, parent);
+ // fallback logic in case none of the SearchTarget is labeled as focused item
+ if (drawFallbackFocusedView) {
+ 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) {
+ drawDecoration(c, decorationHandler, parent);
+ }
+ }
+ }
}
}
- private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler,
- RecyclerView parent) {
- if (decorationHandler == null) return;
+ // 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();
}
- decorationHandler.onDraw(c);
if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
&& mAppsView.getApps().getFocusedChild() != null) {
int index = mAppsView.getApps().getFocusedChildIndex();
@@ -109,24 +110,41 @@
* Handles grouping and drawing of items in the same all apps sections.
*/
public static class SectionDecorationHandler {
- private static final int FILL_ALPHA = 0;
- private static final int FOCUS_ALPHA = (int) (.9f * 255);
-
protected RectF mBounds = new RectF();
private final boolean mIsFullWidth;
private final float mRadius;
- protected int mFocusColor;
- protected int mFillcolor;
+ 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) {
- public SectionDecorationHandler(Context context, boolean isFullWidth) {
mIsFullWidth = isFullWidth;
- int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
- mFillcolor = ColorUtils.setAlphaComponent(endScrim, FILL_ALPHA);
- mFocusColor = ColorUtils.setAlphaComponent(endScrim, FOCUS_ALPHA);
- mRadius = Themes.getDialogCornerRadius(context);
+ 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
+ };
+
}
/**
@@ -148,9 +166,9 @@
/**
* Draw bounds onto canvas.
*/
- public void onDraw(Canvas canvas) {
+ public void onGroupDraw(Canvas canvas) {
mPaint.setColor(mFillcolor);
- canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint);
+ onDraw(canvas);
}
/**
@@ -160,13 +178,20 @@
if (view == null) {
return;
}
- if (view instanceof SelfDecoratingView) {
- ((SelfDecoratingView) view).decorate(mFocusColor);
- return;
- }
mPaint.setColor(mFocusColor);
- canvas.drawRoundRect(view.getLeft(), view.getTop(),
- view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
+ 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);
}
/**
@@ -176,19 +201,4 @@
mBounds.setEmpty();
}
}
-
- /**
- * An interface for a view to draw highlight indicator
- */
- public interface SelfDecoratingView {
- /**
- * Removes decorations drawing if focus is acquired by another view
- */
- void removeDecoration();
-
- /**
- * Draws highlight indicator on view.
- */
- void decorate(int focusColor);
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 769cb5e..355ccad 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -21,6 +21,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -78,6 +80,11 @@
return (mModelFlags & mask) != 0;
}
+ /**
+ * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise
+ * null.
+ */
+ @Nullable
public AppInfo getApp(ComponentKey key) {
mTempInfo.componentName = key.componentName;
mTempInfo.user = key.user;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a4e1f27..dc58c99 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -33,6 +33,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.content.SharedPreferences;
import android.util.FloatProperty;
import android.view.View;
import android.view.animation.Interpolator;
@@ -63,7 +64,7 @@
* closer to top or closer to the page indicator.
*/
public class AllAppsTransitionController implements StateHandler<LauncherState>,
- OnDeviceProfileChangeListener {
+ OnDeviceProfileChangeListener, SharedPreferences.OnSharedPreferenceChangeListener {
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -80,6 +81,7 @@
};
private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0;
+ private static final String PREF_KEY_SHOW_SEARCH_IME = "pref_search_show_ime";
private AllAppsContainerView mAppsView;
private ScrimView mScrimView;
@@ -98,6 +100,7 @@
private float mScrollRangeDelta = 0;
private AllAppsInsetTransitionController mInsetController;
+ private boolean mSearchImeEnabled;
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
@@ -106,6 +109,9 @@
mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
mLauncher.addOnDeviceProfileChangeListener(this);
+
+ onSharedPreferenceChanged(mLauncher.getSharedPrefs(), PREF_KEY_SHOW_SEARCH_IME);
+ mLauncher.getSharedPrefs().registerOnSharedPreferenceChangeListener(this);
}
public float getShiftRange() {
@@ -142,8 +148,7 @@
float shiftCurrent = progress * mShiftRange;
mAppsView.setTranslationY(shiftCurrent);
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get()) {
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mSearchImeEnabled) {
mInsetController.setProgress(progress);
}
}
@@ -234,9 +239,7 @@
public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
mAppsView = appsView;
mScrimView = scrimView;
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get()
- && BuildCompat.isAtLeastR()) {
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -264,8 +267,8 @@
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
}
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get() && BuildCompat.isAtLeastR()) {
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mSearchImeEnabled
+ && BuildCompat.isAtLeastR()) {
mInsetController.onAnimationEnd(mProgress);
if (Float.compare(mProgress, 0f) == 0) {
EditText editText = mAppsView.getSearchUiManager().getEditText();
@@ -276,4 +279,11 @@
// TODO: should make the controller hide synchronously
}
}
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ if (s.equals(PREF_KEY_SHOW_SEARCH_IME)) {
+ mSearchImeEnabled = sharedPreferences.getBoolean(s, true);
+ }
+ }
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 1dc10fe..fefd97a 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -20,7 +20,7 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
-import com.android.launcher3.allapps.search.SearchSectionInfo;
+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;
@@ -185,7 +185,7 @@
if (results == null || mSearchResults != results) {
boolean same = mSearchResults != null && mSearchResults.equals(results);
mSearchResults = results;
- onAppsUpdated();
+ updateAdapterItems();
return !same;
}
return false;
@@ -201,20 +201,11 @@
}
void updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset) {
- SearchSectionInfo lastSection = null;
for (int i = 0; i < list.size(); i++) {
AdapterItem adapterItem = list.get(i);
adapterItem.position = offset + i;
mAdapterItems.add(adapterItem);
- if (adapterItem.searchSectionInfo != lastSection) {
- if (adapterItem.searchSectionInfo != null) {
- adapterItem.searchSectionInfo.setPosStart(adapterItem.position);
- }
- if (lastSection != null) {
- lastSection.setPosEnd(adapterItem.position - 1);
- }
- lastSection = adapterItem.searchSectionInfo;
- }
+
if (adapterItem.isCountedForAccessibility()) {
mAccessibilityResultsCount++;
}
@@ -266,11 +257,13 @@
}
// Recompose the set of adapter items from the current set of apps
- updateAdapterItems();
+ if (mSearchResults == null) {
+ updateAdapterItems();
+ }
}
/**
- * Updates the set of filtered apps with the current filter. At this point, we expect
+ * Updates the set of filtered apps with the current filter. At this point, we expect
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
private void updateAdapterItems() {
@@ -295,16 +288,16 @@
mFastScrollerSections.clear();
mAdapterItems.clear();
- SearchSectionInfo appSection = new SearchSectionInfo();
+ SectionDecorationInfo appSection = new SectionDecorationInfo();
appSection.setDecorationHandler(
- new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true));
+ 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
if (!hasFilter()) {
mAccessibilityResultsCount = mApps.size();
- appSection.setPosStart(position);
for (AppInfo info : mApps) {
String sectionName = info.sectionName;
@@ -321,11 +314,10 @@
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
}
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- appItem.searchSectionInfo = appSection;
+ appItem.sectionDecorationInfo = appSection;
}
mAdapterItems.add(appItem);
}
- appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1);
} else {
updateSearchAdapterItems(mSearchResults, 0);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index e357f61..31c6cc7 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -61,4 +61,11 @@
* Returns a child that has focus to be launched by the IME.
*/
View getFocusedChild();
+
+ /**
+ * Returns true if view is currently visible
+ */
+ default boolean isVisible() {
+ return shouldDraw();
+ }
}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 813db7d..9056e8a 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -200,7 +200,7 @@
public View getFocusedChild() {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
for (FloatingHeaderRow row : mAllRows) {
- if (row.hasVisibleContent() && row.shouldDraw()) {
+ if (row.hasVisibleContent() && row.isVisible()) {
return row.getFocusedChild();
}
}
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index a6bc6cf..13ddc12 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED;
+
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -83,14 +85,20 @@
}
@Override
- public void onTabChanged(int pos) {
- super.onTabChanged(pos);
+ public void onActivePageChanged(int currentActivePage) {
+ super.onActivePageChanged(currentActivePage);
if (mUsingTabs) {
- if (pos == AdapterHolder.WORK) {
+ if (currentActivePage == AdapterHolder.WORK) {
WorkEduView.showWorkEduIfNeeded(mLauncher);
} else {
mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
}
}
}
+
+ @Override
+ protected void hideIme() {
+ super.hideIme();
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED);
+ }
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 198c4b2..3319018 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps.search;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME;
+
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -31,10 +33,10 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
import com.android.launcher3.util.PackageManagerHelper;
-import java.util.ArrayList;
-
/**
* An interface to a search box that AllApps can command.
*/
@@ -43,11 +45,11 @@
OnFocusChangeListener {
protected BaseDraggingActivity mLauncher;
- protected Callbacks mCb;
+ protected SearchCallback<AdapterItem> mCallback;
protected ExtendedEditText mInput;
protected String mQuery;
- protected SearchAlgorithm mSearchAlgorithm;
+ protected SearchAlgorithm<AdapterItem> mSearchAlgorithm;
public void setVisibility(int visibility) {
mInput.setVisibility(visibility);
@@ -57,9 +59,9 @@
* Sets the references to the apps model and the search result callback.
*/
public final void initialize(
- SearchAlgorithm searchAlgorithm, ExtendedEditText input,
- BaseDraggingActivity launcher, Callbacks cb) {
- mCb = cb;
+ SearchAlgorithm<AdapterItem> searchAlgorithm, ExtendedEditText input,
+ BaseDraggingActivity launcher, SearchCallback<AdapterItem> callback) {
+ mCallback = callback;
mLauncher = launcher;
mInput = input;
@@ -85,10 +87,10 @@
mQuery = s.toString();
if (mQuery.isEmpty()) {
mSearchAlgorithm.cancel(true);
- mCb.clearSearchResult();
+ mCallback.clearSearchResult();
} else {
mSearchAlgorithm.cancel(false);
- mSearchAlgorithm.doSearch(mQuery, mCb);
+ mSearchAlgorithm.doSearch(mQuery, mCallback);
}
}
@@ -98,13 +100,15 @@
}
// If play store continues auto updating an app, we want to show partial result.
mSearchAlgorithm.cancel(false);
- mSearchAlgorithm.doSearch(mQuery, mCb);
+ mSearchAlgorithm.doSearch(mQuery, mCallback);
}
@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;
@@ -149,7 +153,7 @@
* Resets the search bar state.
*/
public void reset() {
- mCb.clearSearchResult();
+ mCallback.clearSearchResult();
mInput.reset();
mQuery = null;
}
@@ -167,31 +171,4 @@
public boolean isSearchFieldFocused() {
return mInput.isFocused();
}
-
- /**
- * Callback for getting search results.
- */
- public interface Callbacks {
-
- /**
- * Called when the search from primary source is complete.
- *
- * @param items sorted list of search result adapter items
- */
- void onSearchResult(String query, ArrayList<AdapterItem> items);
-
- /**
- * Called when the search from secondary source is complete.
- *
- * @param items sorted list of search result adapter items
- */
- void onAppendSearchResult(String query, ArrayList<AdapterItem> items);
-
- /**
- * Called when the search results should be cleared.
- */
- void clearSearchResult();
- }
-
-
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 4f79fb8..426fd0c 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -47,6 +47,7 @@
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;
@@ -54,7 +55,7 @@
* Layout to contain the All-apps search UI.
*/
public class AppsSearchContainerLayout extends ExtendedEditText
- implements SearchUiManager, AllAppsSearchBarController.Callbacks,
+ implements SearchUiManager, SearchCallback<AdapterItem>,
AllAppsStore.OnUpdateListener, Insettable {
private final BaseDraggingActivity mLauncher;
@@ -109,7 +110,8 @@
int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
- mAppsView.getActiveRecyclerView().getPaddingRight();
- int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.inv.numHotseatIcons);
+ int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.cellLayoutBorderSpacingPx,
+ dp.inv.numHotseatIcons);
int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx);
int iconPadding = cellWidth - iconVisibleSize;
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
index 84688e1..f9fb22e 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -37,14 +37,14 @@
private static final int MAX_RESULTS_COUNT = 5;
- private final SearchSectionInfo mSearchSectionInfo;
+ private final SectionDecorationInfo mSearchSectionInfo;
private final LauncherAppState mLauncherAppState;
public AppsSearchPipeline(Context context, LauncherAppState launcherAppState) {
mLauncherAppState = launcherAppState;
- mSearchSectionInfo = new SearchSectionInfo();
+ mSearchSectionInfo = new SectionDecorationInfo();
mSearchSectionInfo.setDecorationHandler(
- new SectionDecorationHandler(context, true));
+ new SectionDecorationHandler(context, true, 0, true, true));
}
@Override
@@ -81,7 +81,7 @@
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.searchSectionInfo = mSearchSectionInfo;
+ appItem.sectionDecorationInfo = mSearchSectionInfo;
items.add(appItem);
}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 66bbd2e..4e213b0 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -19,14 +19,17 @@
import android.os.Handler;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
import java.text.Collator;
/**
* The default search implementation.
*/
-public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
+public class DefaultAppSearchAlgorithm implements SearchAlgorithm<AdapterItem> {
protected final Handler mResultHandler;
private final AppsSearchPipeline mAppsSearchPipeline;
@@ -45,7 +48,7 @@
@Override
public void doSearch(final String query,
- final AllAppsSearchBarController.Callbacks callback) {
+ final SearchCallback<AdapterItem> callback) {
mAppsSearchPipeline.query(query,
results -> mResultHandler.post(
() -> callback.onSearchResult(query, results)),
diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
index 71aedb8..4ef154e 100644
--- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java
+++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
@@ -45,7 +45,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SafeCloseable;
@@ -53,7 +52,6 @@
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Optional;
/**
* Manages Lifecycle for Live search results
@@ -70,7 +68,6 @@
private final HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders =
new HashMap<>();
private SearchWidgetHost mSearchWidgetHost;
- private InstanceId mLogInstanceId;
public LiveSearchManager(Launcher launcher) {
mLauncher = launcher;
@@ -159,14 +156,6 @@
return () -> sliceLifeCycle.removeListener(listener);
}
- /**
- * Returns {@link InstanceId} that should be used for logging events within search session, if
- * available.
- */
- public Optional<InstanceId> getLogInstanceId() {
- return Optional.ofNullable(mLogInstanceId);
- }
-
static class SearchWidgetHost extends AppWidgetHost {
SearchWidgetHost(Context context) {
super(context, SEARCH_APPWIDGET_HOST_ID);
diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
similarity index 73%
rename from src/com/android/launcher3/allapps/search/SearchSectionInfo.java
rename to src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
index 464df68..0b64fca 100644
--- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
+++ b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
@@ -18,37 +18,30 @@
import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
/**
- * Info class for a search section
+ * Info class for a search section that is primarily used for decoration.
*/
-public class SearchSectionInfo {
+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 int getPosStart() {
- return mPosStart;
+ public boolean isFocusedView() {
+ return mFocused;
}
- public void setPosStart(int posStart) {
- mPosStart = posStart;
+ public void setFocusedView(boolean focused) {
+ mFocused = focused;
}
- public int getPosEnd() {
- return mPosEnd;
- }
-
- public void setPosEnd(int posEnd) {
- mPosEnd = posEnd;
- }
-
- private int mPosStart;
- private int mPosEnd;
-
- public SearchSectionInfo() {
+ public SectionDecorationInfo() {
this(null);
}
- public SearchSectionInfo(String sectionId) {
+ public SectionDecorationInfo(String sectionId) {
mSectionId = sectionId;
}
@@ -56,7 +49,6 @@
mDecorationHandler = sectionDecorationHandler;
}
-
public SectionDecorationHandler getDecorationHandler() {
return mDecorationHandler;
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index b61799f..ef7bdf4 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -72,9 +72,6 @@
"PROMISE_APPS_NEW_INSTALLS", true,
"Adds a promise icon to the home screen for new install sessions.");
- public static final BooleanFlag APPLY_CONFIG_AT_RUNTIME = getDebugFlag(
- "APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically");
-
public static final BooleanFlag QUICKSTEP_SPRINGS = getDebugFlag(
"QUICKSTEP_SPRINGS", true, "Enable springs for quickstep animations");
@@ -98,12 +95,6 @@
public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag(
"ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
- public static final BooleanFlag DISABLE_INITIAL_IME_IN_ALLAPPS = getDebugFlag(
- "DISABLE_INITIAL_IME_IN_ALLAPPS", false, "Disable default IME state in all apps");
-
- public static final BooleanFlag DISABLE_SLICE_IN_ALLAPPS = getDebugFlag(
- "DISABLE_SLICE_IN_ALLAPPS", true, "Disable slice in all apps");
-
public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
"FOLDER_NAME_SUGGEST", true,
"Suggests folder names instead of blank text.");
@@ -133,9 +124,6 @@
"ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
"Allow Launcher to handle nav bar gestures while Assistant is running over it");
- public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
- "ENABLE_HYBRID_HOTSEAT", true, "Fill gaps in hotseat with predicted apps");
-
public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = getDebugFlag(
"HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder");
@@ -175,10 +163,6 @@
"Replace Smartspace with the enhanced version. "
+ "Ignored if ENABLE_SMARTSPACE_UNIVERSAL is enabled.");
- public static final BooleanFlag ENABLE_SYSTEM_VELOCITY_PROVIDER = getDebugFlag(
- "ENABLE_SYSTEM_VELOCITY_PROVIDER", true,
- "Use system VelocityTracker's algorithm for motion pause detection.");
-
public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
getDebugFlag(
"ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
@@ -217,6 +201,10 @@
public static final BooleanFlag ENABLE_TASKBAR = new DeviceFlag(
"ENABLE_TASKBAR", false, "Allows a system Taskbar to be shown on larger devices.");
+ public static final BooleanFlag ENABLE_OVERVIEW_GRID = new DeviceFlag(
+ "ENABLE_OVERVIEW_GRID", false, "Uses grid overview layout. "
+ + "Only applicable on large screen devices.");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1cfe6ac..93df599 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -40,12 +40,14 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.TouchController;
import java.util.ArrayList;
+import java.util.Optional;
/**
* Class for initiating a drag within a view or across multiple views.
@@ -230,6 +232,11 @@
}
}
+ public Optional<InstanceId> getLogInstanceId() {
+ return Optional.ofNullable(mDragObject)
+ .map(dragObject -> dragObject.logInstanceId);
+ }
+
/**
* Call this from a drag source view like this:
*
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index ddf44ca..7a6b4f9 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -41,12 +41,14 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTargetBar;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherRootView;
import com.android.launcher3.R;
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.WorkspaceAndHotseatScrim;
+import com.android.launcher3.graphics.SysUiScrim;
+import com.android.launcher3.graphics.WorkspaceDragScrim;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.BaseDragLayer;
@@ -82,11 +84,10 @@
// Related to adjacent page hints
private final ViewGroupFocusHelper mFocusIndicatorHelper;
- private final WorkspaceAndHotseatScrim mWorkspaceScrim;
private final OverviewScrim mOverviewScrim;
-
- // View that should handle move events
- private View mMoveTarget;
+ private WorkspaceDragScrim mWorkspaceDragScrim;
+ private SysUiScrim mSysUiScrim;
+ private LauncherRootView mRootView;
/**
* Used to create a new DragLayer from XML.
@@ -102,15 +103,23 @@
setChildrenDrawingOrderEnabled(true);
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
- mWorkspaceScrim = new WorkspaceAndHotseatScrim(this);
mOverviewScrim = new OverviewScrim(this);
}
public void setup(DragController dragController, Workspace workspace) {
mDragController = dragController;
- mWorkspaceScrim.setWorkspace(workspace);
- mMoveTarget = workspace;
recreateControllers();
+
+ mWorkspaceDragScrim = new WorkspaceDragScrim((this));
+ mWorkspaceDragScrim.setWorkspace(workspace);
+
+ // We delegate drawing of the workspace scrim to LauncherRootView (one level up), so as
+ // to avoid artifacts when translating the entire drag layer in the -1 transition.
+ mRootView = (LauncherRootView) getParent();
+ mSysUiScrim = new SysUiScrim(mRootView);
+ mRootView.setSysUiScrim(mSysUiScrim);
+
+
}
@Override
@@ -215,12 +224,6 @@
}
@Override
- public boolean dispatchUnhandledMove(View focused, int direction) {
- return super.dispatchUnhandledMove(focused, direction)
- || mMoveTarget.dispatchUnhandledMove(focused, direction);
- }
-
- @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
ev.offsetLocation(getTranslationX(), 0);
try {
@@ -525,7 +528,7 @@
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the background below children.
- mWorkspaceScrim.draw(canvas);
+ mWorkspaceDragScrim.draw(canvas);
mOverviewScrim.updateCurrentScrimmedView(this);
mFocusIndicatorHelper.draw(canvas);
super.dispatchDraw(canvas);
@@ -545,18 +548,22 @@
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
- mWorkspaceScrim.setSize(w, h);
+ mSysUiScrim.setSize(w, h);
}
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
- mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
+ mSysUiScrim.onInsetsChanged(insets, mAllowSysuiScrims);
mOverviewScrim.onInsetsChanged(insets);
}
- public WorkspaceAndHotseatScrim getScrim() {
- return mWorkspaceScrim;
+ public WorkspaceDragScrim getWorkspaceDragScrim() {
+ return mWorkspaceDragScrim;
+ }
+
+ public SysUiScrim getSysUiScrim() {
+ return mSysUiScrim;
}
public OverviewScrim getOverviewScrim() {
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index 959602b..e8ff8da 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -28,6 +28,9 @@
/** Whether or not an accessible drag operation is in progress. */
public boolean isAccessibleDrag = false;
+ /** Whether or not the drag operation is controlled by keyboard. */
+ public boolean isKeyboardDrag = false;
+
/**
* Specifies the start location for a simulated DnD (like system drag or accessibility drag),
* null when using internal DnD
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
index a9389bc..be6a07f 100644
--- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -1,6 +1,9 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.view.View;
@@ -10,7 +13,9 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.WidgetCell;
/**
@@ -36,6 +41,15 @@
mPreview = view;
}
+ public RemoteViews getPreview() {
+ return mPreview;
+ }
+
+ /** Resets any resource. This should be called before recycling this view. */
+ public void reset() {
+ mPreview = null;
+ }
+
@Override
public void ensurePreview() {
if (mPreview != null && mActiveRequest == null) {
@@ -49,6 +63,19 @@
super.ensurePreview();
}
+ @Override
+ public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
+ if (ATLEAST_S
+ && mPreview == null
+ && item.widgetInfo != null
+ && item.widgetInfo.previewLayout != Resources.ID_NULL) {
+ mPreview = new RemoteViews(item.widgetInfo.provider.getPackageName(),
+ item.widgetInfo.previewLayout);
+ }
+
+ super.applyFromCellItem(item, loader);
+ }
+
/**
* Generates a bitmap by inflating {@param views}.
* @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 61938d1..504b29e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -46,6 +46,7 @@
import android.util.Pair;
import android.view.FocusFinder;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
@@ -82,7 +83,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
@@ -96,6 +96,8 @@
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.views.ClipPathView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -165,7 +167,11 @@
private AnimatorSet mCurrentAnimator;
private boolean mIsAnimatingClosed = false;
+ // Folder can be displayed in Launcher's activity or a separate window (e.g. Taskbar).
+ // Anything specific to Launcher should use mLauncher, otherwise should use mActivityContext.
protected final Launcher mLauncher;
+ protected final ActivityContext mActivityContext;
+
protected DragController mDragController;
public FolderInfo mInfo;
private CharSequence mFromTitle;
@@ -228,6 +234,7 @@
setAlwaysDrawnWithCacheEnabled(false);
mLauncher = Launcher.getLauncher(context);
+ mActivityContext = ActivityContext.lookupContext(context);
mStatsLogManager = StatsLogManager.newInstance(context);
// We need this view to be focusable in touch mode so that when text editing of the folder
// name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -457,9 +464,9 @@
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
if (lp == null) {
- lp = new DragLayer.LayoutParams(0, 0);
+ lp = new BaseDragLayer.LayoutParams(0, 0);
lp.customPosition = true;
setLayoutParams(lp);
}
@@ -513,13 +520,14 @@
/**
* Creates a new UserFolder, inflated from R.layout.user_folder.
*
- * @param launcher The main activity.
+ * @param activityContext The main ActivityContext in which to inflate this Folder. It must also
+ * be an instance or ContextWrapper around the Launcher activity context.
*
* @return A new UserFolder.
*/
@SuppressLint("InflateParams")
- static Folder fromXml(Launcher launcher) {
- return (Folder) launcher.getLayoutInflater()
+ static <T extends Context & ActivityContext> Folder fromXml(T activityContext) {
+ return (Folder) LayoutInflater.from(activityContext).cloneInContext(activityContext)
.inflate(R.layout.user_folder_icon_normalized, null);
}
@@ -597,7 +605,7 @@
* is played.
*/
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
- Folder openFolder = getOpen(mLauncher);
+ Folder openFolder = getOpen(mActivityContext);
if (openFolder != null && openFolder != this) {
// Close any open folder before opening a folder.
openFolder.close(true);
@@ -610,7 +618,7 @@
mIsOpen = true;
- DragLayer dragLayer = mLauncher.getDragLayer();
+ BaseDragLayer dragLayer = mActivityContext.getDragLayer();
// Just verify that the folder hasn't already been added to the DragLayer.
// There was a one-off crash where the folder had a parent already.
if (getParent() == null) {
@@ -724,7 +732,7 @@
// Notify the accessibility manager that this folder "window" has disappeared and no
// longer occludes the workspace items
- mLauncher.getDragLayer().sendAccessibilityEvent(
+ mActivityContext.getDragLayer().sendAccessibilityEvent(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
@@ -772,7 +780,7 @@
private void closeComplete(boolean wasAnimated) {
// TODO: Clear all active animations.
- DragLayer parent = (DragLayer) getParent();
+ BaseDragLayer parent = (BaseDragLayer) getParent();
if (parent != null) {
parent.removeView(this);
}
@@ -1011,7 +1019,7 @@
private void updateItemLocationsInDatabaseBatch(boolean isBind) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
- mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+ mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
ArrayList<ItemInfo> items = new ArrayList<>();
int total = mInfo.contents.size();
@@ -1048,10 +1056,8 @@
}
private void centerAboutIcon() {
- DeviceProfile grid = mLauncher.getDeviceProfile();
-
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
- DragLayer parent = mLauncher.getDragLayer();
+ BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
+ BaseDragLayer parent = mActivityContext.getDragLayer();
int width = getFolderWidth();
int height = getFolderHeight();
@@ -1061,38 +1067,13 @@
int centeredLeft = centerX - width / 2;
int centeredTop = centerY - height / 2;
- // We need to bound the folder to the currently visible workspace area
- if (mLauncher.getStateManager().getState().overviewUi) {
- parent.getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(), sTempRect);
- } else {
- mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
- }
- int left = Math.min(Math.max(sTempRect.left, centeredLeft),
- sTempRect.right- width);
- int top = Math.min(Math.max(sTempRect.top, centeredTop),
- sTempRect.bottom - height);
-
- int distFromEdgeOfScreen = mLauncher.getWorkspace().getPaddingLeft() + getPaddingLeft();
-
- if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
- // Center the folder if it is very close to being centered anyway, by virtue of
- // filling the majority of the viewport. ie. remove it from the uncanny valley
- // of centeredness.
- left = (grid.availableWidthPx - width) / 2;
- } else if (width >= sTempRect.width()) {
- // If the folder doesn't fit within the bounds, center it about the desired bounds
- left = sTempRect.left + (sTempRect.width() - width) / 2;
- }
- if (height >= sTempRect.height()) {
- // Folder height is greater than page height, center on page
- top = sTempRect.top + (sTempRect.height() - height) / 2;
- } else {
- // Folder height is less than page height, so bound it to the absolute open folder
- // bounds if necessary
- Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
- left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
- top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
- }
+ sTempRect.set(mActivityContext.getFolderBoundingBox());
+ int left = Utilities.boundToRange(centeredLeft, sTempRect.left, sTempRect.right - width);
+ int top = Utilities.boundToRange(centeredTop, sTempRect.top, sTempRect.bottom - height);
+ int[] inOutPosition = new int[] {left, top};
+ mActivityContext.updateOpenFolderPosition(inOutPosition, sTempRect, width, height);
+ left = inOutPosition[0];
+ top = inOutPosition[1];
int folderPivotX = width / 2 + (centeredLeft - left);
int folderPivotY = height / 2 + (centeredTop - top);
@@ -1106,7 +1087,7 @@
}
protected int getContentAreaHeight() {
- DeviceProfile grid = mLauncher.getDeviceProfile();
+ DeviceProfile grid = mActivityContext.getDeviceProfile();
int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
- mFooterHeight;
int height = Math.min(maxContentAreaHeight,
@@ -1208,7 +1189,9 @@
newIcon.requestFocus();
}
if (finalItem != null) {
- mStatsLogManager.logger().withItemInfo(finalItem)
+ StatsLogger logger = mStatsLogManager.logger().withItemInfo(finalItem);
+ mDragController.getLogInstanceId().map(logger::withInstanceId)
+ .orElse(logger)
.log(LAUNCHER_FOLDER_CONVERTED_TO_ICON);
}
}
@@ -1382,7 +1365,7 @@
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
- mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+ mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
verifier.updateRankAndPos(item, rank);
mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
item.cellY);
@@ -1589,8 +1572,8 @@
/**
* Returns a folder which is already open or null
*/
- public static Folder getOpen(Launcher launcher) {
- return getOpenView(launcher, TYPE_FOLDER);
+ public static Folder getOpen(ActivityContext activityContext) {
+ return getOpenView(activityContext, TYPE_FOLDER);
}
/**
@@ -1609,7 +1592,7 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- DragLayer dl = mLauncher.getDragLayer();
+ BaseDragLayer dl = (BaseDragLayer) getParent();
if (isEditingName()) {
if (!dl.isEventOverView(mFolderName, ev)) {
@@ -1633,6 +1616,11 @@
return false;
}
+ @Override
+ public boolean canInterceptEventsInSystemGestureRegion() {
+ return true;
+ }
+
/**
* Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
* rounded rect.
@@ -1661,9 +1649,9 @@
/** Returns the height of the current folder's bottom edge from the bottom of the screen. */
private int getHeightFromBottom() {
- DragLayer.LayoutParams layoutParams = (DragLayer.LayoutParams) getLayoutParams();
+ BaseDragLayer.LayoutParams layoutParams = (BaseDragLayer.LayoutParams) getLayoutParams();
int folderBottomPx = layoutParams.y + layoutParams.height;
- int windowBottomPx = mLauncher.getDeviceProfile().heightPx;
+ int windowBottomPx = mActivityContext.getDeviceProfile().heightPx;
return windowBottomPx - folderBottomPx;
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 3d72b49..1cac31e 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -39,14 +39,13 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
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;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.BaseDragLayer;
import java.util.List;
@@ -69,7 +68,6 @@
private PreviewBackground mPreviewBackground;
private Context mContext;
- private Launcher mLauncher;
private final boolean mIsOpening;
@@ -92,8 +90,7 @@
mPreviewBackground = mFolderIcon.mBackground;
mContext = folder.getContext();
- mLauncher = folder.mLauncher;
- mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
+ mPreviewVerifier = new FolderGridOrganizer(folder.mActivityContext.getDeviceProfile().inv);
mIsOpening = isOpening;
@@ -114,14 +111,15 @@
* Prepares the Folder for animating between open / closed states.
*/
public AnimatorSet getAnimator() {
- final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
+ final BaseDragLayer.LayoutParams lp =
+ (BaseDragLayer.LayoutParams) mFolder.getLayoutParams();
mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams();
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
- float scaleRelativeToDragLayer = mLauncher.getDragLayer()
+ float scaleRelativeToDragLayer = mFolder.mActivityContext.getDragLayer()
.getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos);
int scaledRadius = mPreviewBackground.getScaledRadius();
float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index fe310f6..6b02021 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -166,17 +166,19 @@
mDotParams = new DotRenderer.DrawParams();
}
- public static FolderIcon inflateFolderAndIcon(int resId, Launcher launcher, ViewGroup group,
- FolderInfo folderInfo) {
- Folder folder = Folder.fromXml(launcher);
- folder.setDragController(launcher.getDragController());
+ public static <T extends Context & ActivityContext> FolderIcon inflateFolderAndIcon(int resId,
+ T activityContext, ViewGroup group, FolderInfo folderInfo) {
+ Folder folder = Folder.fromXml(activityContext);
+ folder.setDragController(folder.mLauncher.getDragController());
- FolderIcon icon = inflateIcon(resId, launcher, group, folderInfo);
+ FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
folder.setFolderIcon(icon);
folder.bind(folderInfo);
icon.setFolder(folder);
- icon.setOnFocusChangeListener(launcher.getFocusHandler());
+ icon.setOnFocusChangeListener(folder.mLauncher.getFocusHandler());
+ icon.mBackground.paddingY = icon.isInHotseat()
+ ? 0 : activityContext.getDeviceProfile().cellYPaddingPx;
return icon;
}
@@ -199,7 +201,7 @@
icon.mFolderName.setText(folderInfo.title);
icon.mFolderName.setCompoundDrawablePadding(0);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
- lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
+ lp.topMargin = grid.cellYPaddingPx + grid.iconSizePx + grid.iconDrawablePaddingPx;
icon.setTag(folderInfo);
icon.setOnClickListener(ItemClickHandler.INSTANCE);
@@ -218,6 +220,7 @@
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
+ icon.mBackground.paddingY = icon.isInHotseat() ? 0 : grid.cellYPaddingPx;
icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
icon.mPreviewVerifier.setFolderInfo(folderInfo);
icon.updatePreviewItems(false);
@@ -579,6 +582,7 @@
public void setFolderBackground(PreviewBackground bg) {
mBackground = bg;
mBackground.setInvalidateDelegate(this);
+ mBackground.paddingY = isInHotseat() ? 0 : mActivity.getDeviceProfile().cellYPaddingPx;
}
@Override
@@ -745,21 +749,19 @@
mInfo.removeListener(mFolder);
}
+ private boolean isInHotseat() {
+ return mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ }
+
public void clearLeaveBehindIfExists() {
- ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
- if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- CellLayout cl = (CellLayout) getParent().getParent();
- cl.clearFolderLeaveBehind();
+ if (getParent() instanceof FolderIconParent) {
+ ((FolderIconParent) getParent()).clearFolderLeaveBehind(this);
}
}
public void drawLeaveBehindIfExists() {
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
- // While the folder is open, the position of the icon cannot change.
- lp.canReorder = false;
- if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- CellLayout cl = (CellLayout) getParent().getParent();
- cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ if (getParent() instanceof FolderIconParent) {
+ ((FolderIconParent) getParent()).drawFolderLeaveBehindForIcon(this);
}
}
@@ -828,4 +830,19 @@
MAX_NUM_ITEMS_IN_PREVIEW);
}
}
+
+ /**
+ * Interface that provides callbacks to a parent ViewGroup that hosts this FolderIcon.
+ */
+ public interface FolderIconParent {
+ /**
+ * Tells the FolderIconParent to draw a "leave-behind" when the Folder is open and leaving a
+ * gap where the FolderIcon would be when the Folder is closed.
+ */
+ void drawFolderLeaveBehindForIcon(FolderIcon child);
+ /**
+ * Tells the FolderIconParent to stop drawing the "leave-behind" as the Folder is closed.
+ */
+ void clearFolderLeaveBehind(FolderIcon child);
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index a08dd30..0235dfa 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -36,7 +36,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
@@ -193,7 +192,7 @@
int pageNo = rank / mOrganizer.getMaxItemsPerPage();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
- lp.setXY(mOrganizer.getPosForRank(rank));
+ lp.setCellXY(mOrganizer.getPosForRank(rank));
getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true);
}
@@ -230,7 +229,7 @@
}
private CellLayout createAndAddNewPage() {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = mFolder.mActivityContext.getDeviceProfile();
CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this);
page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
@@ -306,7 +305,7 @@
if (v != null) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
ItemInfo info = (ItemInfo) v.getTag();
- lp.setXY(mOrganizer.getPosForRank(rank));
+ lp.setCellXY(mOrganizer.getPosForRank(rank));
currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true);
if (mOrganizer.isItemInPreview(rank) && v instanceof BubbleTextView) {
@@ -624,7 +623,7 @@
@Override
protected boolean canScroll(float absVScroll, float absHScroll) {
- return AbstractFloatingView.getTopOpenViewWithType(mFolder.mLauncher,
+ return AbstractFloatingView.getTopOpenViewWithType(mFolder.mActivityContext,
TYPE_ALL & ~TYPE_FOLDER) == null;
}
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 27b906b..767fffe 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -74,6 +74,7 @@
int previewSize;
int basePreviewOffsetX;
int basePreviewOffsetY;
+ int paddingY;
private CellLayout mDrawingDelegate;
@@ -157,7 +158,7 @@
previewSize = grid.folderIconSizePx;
basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
- basePreviewOffsetY = topPadding + grid.folderIconOffsetYPx;
+ basePreviewOffsetY = paddingY + topPadding + grid.folderIconOffsetYPx;
// Stroke width is 1dp
mStrokeWidth = context.getResources().getDisplayMetrics().density;
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
index d347e8f..b6d25c4 100644
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
@@ -19,10 +19,19 @@
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;
@@ -53,4 +62,27 @@
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/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
similarity index 83%
rename from src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
rename to src/com/android/launcher3/graphics/SysUiScrim.java
index 7b7ab5e..d9c648b 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -34,7 +34,6 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
@@ -44,41 +43,39 @@
import androidx.core.graphics.ColorUtils;
-import com.android.launcher3.CellLayout;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.util.Themes;
/**
* View scrim which draws behind hotseat and workspace
*/
-public class WorkspaceAndHotseatScrim extends Scrim {
+public class SysUiScrim extends Scrim {
- public static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_PROGRESS =
- new FloatProperty<WorkspaceAndHotseatScrim>("sysUiProgress") {
+ public static final FloatProperty<SysUiScrim> SYSUI_PROGRESS =
+ new FloatProperty<SysUiScrim>("sysUiProgress") {
@Override
- public Float get(WorkspaceAndHotseatScrim scrim) {
+ public Float get(SysUiScrim scrim) {
return scrim.mSysUiProgress;
}
@Override
- public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
+ public void setValue(SysUiScrim scrim, float value) {
scrim.setSysUiProgress(value);
}
};
- private static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_ANIM_MULTIPLIER =
- new FloatProperty<WorkspaceAndHotseatScrim>("sysUiAnimMultiplier") {
+ private static final FloatProperty<SysUiScrim> SYSUI_ANIM_MULTIPLIER =
+ new FloatProperty<SysUiScrim>("sysUiAnimMultiplier") {
@Override
- public Float get(WorkspaceAndHotseatScrim scrim) {
+ public Float get(SysUiScrim scrim) {
return scrim.mSysUiAnimMultiplier;
}
@Override
- public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
+ public void setValue(SysUiScrim scrim, float value) {
scrim.mSysUiAnimMultiplier = value;
scrim.reapplySysUiAlpha();
}
@@ -108,10 +105,6 @@
private static final int ALPHA_MASK_BITMAP_DP = 200;
private static final int ALPHA_MASK_WIDTH_DP = 2;
- private final Rect mHighlightRect = new Rect();
-
- private Workspace mWorkspace;
-
private boolean mDrawTopScrim, mDrawBottomScrim;
private final RectF mFinalMaskRect = new RectF();
@@ -127,9 +120,8 @@
private boolean mAnimateScrimOnNextDraw = false;
private float mSysUiAnimMultiplier = 1;
- public WorkspaceAndHotseatScrim(View view) {
+ public SysUiScrim(View view) {
super(view);
-
mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
view.getResources().getDisplayMetrics());
mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
@@ -139,28 +131,10 @@
onExtractedColorsChanged(mWallpaperColorInfo);
}
- public void setWorkspace(Workspace workspace) {
- mWorkspace = workspace;
- }
-
+ /**
+ * Draw the top and bottom scrims
+ */
public void draw(Canvas canvas) {
- // Draw the background below children.
- if (mScrimAlpha > 0) {
- // Update the scroll position first to ensure scrim cutout is in the right place.
- mWorkspace.computeScrollWithoutInvalidation();
- CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout();
- canvas.save();
- if (currCellLayout != null && currCellLayout != mLauncher.getHotseat()) {
- // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
- mLauncher.getDragLayer()
- .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
- canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
- }
-
- super.draw(canvas);
- canvas.restore();
- }
-
if (!mHideSysUiScrim) {
if (mSysUiProgress <= 0) {
mAnimateScrimOnNextDraw = false;
@@ -247,6 +221,11 @@
super.onExtractedColorsChanged(wallpaperColorInfo);
}
+ /**
+ * Set the width and height of the view being scrimmed
+ * @param w
+ * @param h
+ */
public void setSize(int w, int h) {
if (mTopScrim != null) {
mTopScrim.setBounds(0, 0, w, h);
diff --git a/src/com/android/launcher3/graphics/WorkspaceDragScrim.java b/src/com/android/launcher3/graphics/WorkspaceDragScrim.java
new file mode 100644
index 0000000..d8dc563
--- /dev/null
+++ b/src/com/android/launcher3/graphics/WorkspaceDragScrim.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.View;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Workspace;
+
+/**
+ * Scrim drawn during SpringLoaded State (ie. Drag and Drop). Darkens the workspace except for
+ * the focused CellLayout.
+ */
+public class WorkspaceDragScrim extends Scrim {
+
+ private final Rect mHighlightRect = new Rect();
+
+ private Workspace mWorkspace;
+
+ public WorkspaceDragScrim(View view) {
+ super(view);
+ onExtractedColorsChanged(mWallpaperColorInfo);
+ }
+
+ /**
+ * Set the workspace that this scrim is acting on
+ * @param workspace
+ */
+ public void setWorkspace(Workspace workspace) {
+ mWorkspace = workspace;
+ mWorkspace.setWorkspaceDragScrim(this);
+ }
+
+ /**
+ * Cut out the focused paged of the Workspace and then draw the scrim
+ * @param canvas
+ */
+ public void draw(Canvas canvas) {
+ // Draw the background below children.
+ if (mScrimAlpha > 0) {
+ // Update the scroll position first to ensure scrim cutout is in the right place.
+ mWorkspace.computeScrollWithoutInvalidation();
+ CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout();
+ canvas.save();
+ if (currCellLayout != null && currCellLayout != mLauncher.getHotseat()) {
+ // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
+ mLauncher.getDragLayer()
+ .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
+ canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
+ }
+
+ super.draw(canvas);
+ canvas.restore();
+ }
+ }
+
+}
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
deleted file mode 100644
index 800598e..0000000
--- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java
+++ /dev/null
@@ -1,93 +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.keyboard;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.widget.PopupMenu;
-import android.widget.PopupMenu.OnMenuItemClickListener;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Handles showing a popup menu with available custom actions for a launcher icon.
- * This allows exposing various custom actions using keyboard shortcuts.
- */
-public class CustomActionsPopup implements OnMenuItemClickListener {
-
- private final Launcher mLauncher;
- private final LauncherAccessibilityDelegate mDelegate;
- private final View mIcon;
-
- public CustomActionsPopup(Launcher launcher, View icon) {
- mLauncher = launcher;
- mIcon = icon;
- PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
- if (container != null) {
- mDelegate = container.getAccessibilityDelegate();
- } else {
- mDelegate = launcher.getAccessibilityDelegate();
- }
- }
-
- private List<AccessibilityAction> getActionList() {
- if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
- return Collections.EMPTY_LIST;
- }
-
- AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
- mDelegate.addSupportedActions(mIcon, info, true);
- List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
- info.recycle();
- return result;
- }
-
- public boolean canShow() {
- return !getActionList().isEmpty();
- }
-
- public boolean show() {
- List<AccessibilityAction> actions = getActionList();
- if (actions.isEmpty()) {
- return false;
- }
-
- PopupMenu popup = new PopupMenu(mLauncher, mIcon);
- popup.setOnMenuItemClickListener(this);
- Menu menu = popup.getMenu();
- for (AccessibilityAction action : actions) {
- menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel());
- }
- popup.show();
- return true;
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem menuItem) {
- return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId());
- }
-}
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index ae7ad10..83003ff 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -16,233 +16,30 @@
package com.android.launcher3.keyboard;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.RectEvaluator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.Rect;
-import android.util.Property;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Themes;
/**
* A helper class to draw background of a focused view.
*/
-public abstract class FocusIndicatorHelper implements
- OnFocusChangeListener, AnimatorUpdateListener {
-
- private static final float MIN_VISIBLE_ALPHA = 0.2f;
- private static final long ANIM_DURATION = 150;
-
- public static final Property<FocusIndicatorHelper, Float> ALPHA =
- new Property<FocusIndicatorHelper, Float>(Float.TYPE, "alpha") {
- @Override
- public void set(FocusIndicatorHelper object, Float value) {
- object.setAlpha(value);
- }
-
- @Override
- public Float get(FocusIndicatorHelper object) {
- return object.mAlpha;
- }
- };
-
- public static final Property<FocusIndicatorHelper, Float> SHIFT =
- new Property<FocusIndicatorHelper, Float>(
- Float.TYPE, "shift") {
-
- @Override
- public void set(FocusIndicatorHelper object, Float value) {
- object.mShift = value;
- }
-
- @Override
- public Float get(FocusIndicatorHelper object) {
- return object.mShift;
- }
- };
-
- private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
- private static final Rect sTempRect1 = new Rect();
- private static final Rect sTempRect2 = new Rect();
-
- private final View mContainer;
- private final Paint mPaint;
- private final int mMaxAlpha;
-
- private final Rect mDirtyRect = new Rect();
- private boolean mIsDirty = false;
-
- private View mLastFocusedView;
-
- private View mCurrentView;
- private View mTargetView;
- /**
- * The fraction indicating the position of the focusRect between {@link #mCurrentView}
- * & {@link #mTargetView}
- */
- private float mShift;
-
- private ObjectAnimator mCurrentAnimation;
- private float mAlpha;
- private float mRadius;
+public abstract class FocusIndicatorHelper extends ItemFocusIndicatorHelper<View>
+ implements OnFocusChangeListener {
public FocusIndicatorHelper(View container) {
- mContainer = container;
-
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- int color = container.getResources().getColor(R.color.focused_background);
- mMaxAlpha = Color.alpha(color);
- mPaint.setColor(0xFF000000 | color);
-
- setAlpha(0);
- mShift = 0;
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- mRadius = Themes.getDialogCornerRadius(container.getContext());
- }
- }
-
- protected void setAlpha(float alpha) {
- mAlpha = alpha;
- mPaint.setAlpha((int) (mAlpha * mMaxAlpha));
- }
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidateDirty();
- }
-
- protected void invalidateDirty() {
- if (mIsDirty) {
- mContainer.invalidate(mDirtyRect);
- mIsDirty = false;
- }
-
- Rect newRect = getDrawRect();
- if (newRect != null) {
- mContainer.invalidate(newRect);
- }
- }
-
- public void draw(Canvas c) {
- if (mAlpha <= 0) return;
-
- Rect newRect = getDrawRect();
- if (newRect != null) {
- mDirtyRect.set(newRect);
- c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
- (float) mDirtyRect.right, (float) mDirtyRect.bottom,
- mRadius, mRadius, mPaint);
- mIsDirty = true;
- }
- }
-
- private Rect getDrawRect() {
- if (mCurrentView != null && mCurrentView.isAttachedToWindow()) {
- viewToRect(mCurrentView, sTempRect1);
-
- if (mShift > 0 && mTargetView != null) {
- viewToRect(mTargetView, sTempRect2);
- return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2);
- } else {
- return sTempRect1;
- }
- }
- return null;
+ super(container, container.getResources().getColor(R.color.focused_background));
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- endCurrentAnimation();
-
- if (mAlpha > MIN_VISIBLE_ALPHA) {
- mTargetView = v;
-
- mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
- PropertyValuesHolder.ofFloat(ALPHA, 1),
- PropertyValuesHolder.ofFloat(SHIFT, 1));
- mCurrentAnimation.addListener(new ViewSetListener(v, true));
- } else {
- setCurrentView(v);
-
- mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
- PropertyValuesHolder.ofFloat(ALPHA, 1));
- }
-
- mLastFocusedView = v;
- } else {
- if (mLastFocusedView == v) {
- mLastFocusedView = null;
- endCurrentAnimation();
- mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
- PropertyValuesHolder.ofFloat(ALPHA, 0));
- mCurrentAnimation.addListener(new ViewSetListener(null, false));
- }
- }
-
- // invalidate once
- invalidateDirty();
-
- mLastFocusedView = hasFocus ? v : null;
- if (mCurrentAnimation != null) {
- mCurrentAnimation.addUpdateListener(this);
- mCurrentAnimation.setDuration(ANIM_DURATION).start();
- }
+ changeFocus(v, hasFocus);
}
- protected void endCurrentAnimation() {
- if (mCurrentAnimation != null) {
- mCurrentAnimation.cancel();
- mCurrentAnimation = null;
- }
- }
-
- protected void setCurrentView(View v) {
- mCurrentView = v;
- mShift = 0;
- mTargetView = null;
- }
-
- /**
- * Gets the position of {@param v} relative to {@link #mContainer}.
- */
- public abstract void viewToRect(View v, Rect outRect);
-
- private class ViewSetListener extends AnimatorListenerAdapter {
- private final View mViewToSet;
- private final boolean mCallOnCancel;
- private boolean mCalled = false;
-
- public ViewSetListener(View v, boolean callOnCancel) {
- mViewToSet = v;
- mCallOnCancel = callOnCancel;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- if (!mCallOnCancel) {
- mCalled = true;
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCalled) {
- setCurrentView(mViewToSet);
- mCalled = true;
- }
- }
+ @Override
+ protected boolean shouldDraw(View item) {
+ return item.isAttachedToWindow();
}
/**
diff --git a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
new file mode 100644
index 0000000..57fab2d
--- /dev/null
+++ b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
@@ -0,0 +1,252 @@
+/*
+ * 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.keyboard;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.FloatProperty;
+import android.view.View;
+
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Themes;
+
+/**
+ * A helper class to draw background of a focused item.
+ * @param <T> Item type
+ */
+public abstract class ItemFocusIndicatorHelper<T> implements AnimatorUpdateListener {
+
+ private static final float MIN_VISIBLE_ALPHA = 0.2f;
+ private static final long ANIM_DURATION = 150;
+
+ public static final FloatProperty<ItemFocusIndicatorHelper> ALPHA =
+ new FloatProperty<ItemFocusIndicatorHelper>("alpha") {
+
+ @Override
+ public void setValue(ItemFocusIndicatorHelper object, float value) {
+ object.setAlpha(value);
+ }
+
+ @Override
+ public Float get(ItemFocusIndicatorHelper object) {
+ return object.mAlpha;
+ }
+ };
+
+ public static final FloatProperty<ItemFocusIndicatorHelper> SHIFT =
+ new FloatProperty<ItemFocusIndicatorHelper>("shift") {
+
+ @Override
+ public void setValue(ItemFocusIndicatorHelper object, float value) {
+ object.mShift = value;
+ }
+
+ @Override
+ public Float get(ItemFocusIndicatorHelper object) {
+ return object.mShift;
+ }
+ };
+
+ private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final Rect sTempRect1 = new Rect();
+ private static final Rect sTempRect2 = new Rect();
+
+ private final View mContainer;
+ protected final Paint mPaint;
+ private final int mMaxAlpha;
+
+ private final Rect mDirtyRect = new Rect();
+ private boolean mIsDirty = false;
+
+ private T mLastFocusedItem;
+
+ private T mCurrentItem;
+ private T mTargetItem;
+ /**
+ * The fraction indicating the position of the focusRect between {@link #mCurrentItem}
+ * & {@link #mTargetItem}
+ */
+ private float mShift;
+
+ private ObjectAnimator mCurrentAnimation;
+ private float mAlpha;
+ private float mRadius;
+
+ public ItemFocusIndicatorHelper(View container, int color) {
+ mContainer = container;
+
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mMaxAlpha = Color.alpha(color);
+ mPaint.setColor(0xFF000000 | color);
+
+ setAlpha(0);
+ mShift = 0;
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ mRadius = Themes.getDialogCornerRadius(container.getContext());
+ }
+ }
+
+ protected void setAlpha(float alpha) {
+ mAlpha = alpha;
+ mPaint.setAlpha((int) (mAlpha * mMaxAlpha));
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ invalidateDirty();
+ }
+
+ protected void invalidateDirty() {
+ if (mIsDirty) {
+ mContainer.invalidate(mDirtyRect);
+ mIsDirty = false;
+ }
+
+ Rect newRect = getDrawRect();
+ if (newRect != null) {
+ mContainer.invalidate(newRect);
+ }
+ }
+
+ /**
+ * Draws the indicator on the canvas
+ */
+ public void draw(Canvas c) {
+ if (mAlpha <= 0) return;
+
+ Rect newRect = getDrawRect();
+ if (newRect != null) {
+ mDirtyRect.set(newRect);
+ c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
+ (float) mDirtyRect.right, (float) mDirtyRect.bottom,
+ mRadius, mRadius, mPaint);
+ mIsDirty = true;
+ }
+ }
+
+ private Rect getDrawRect() {
+ if (mCurrentItem != null && shouldDraw(mCurrentItem)) {
+ viewToRect(mCurrentItem, sTempRect1);
+
+ if (mShift > 0 && mTargetItem != null) {
+ viewToRect(mTargetItem, sTempRect2);
+ return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2);
+ } else {
+ return sTempRect1;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the provided item is valid
+ */
+ protected boolean shouldDraw(T item) {
+ return true;
+ }
+
+ protected void changeFocus(T item, boolean hasFocus) {
+ if (hasFocus) {
+ endCurrentAnimation();
+
+ if (mAlpha > MIN_VISIBLE_ALPHA) {
+ mTargetItem = item;
+
+ mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofFloat(ALPHA, 1),
+ PropertyValuesHolder.ofFloat(SHIFT, 1));
+ mCurrentAnimation.addListener(new ViewSetListener(item, true));
+ } else {
+ setCurrentItem(item);
+
+ mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofFloat(ALPHA, 1));
+ }
+
+ mLastFocusedItem = item;
+ } else {
+ if (mLastFocusedItem == item) {
+ mLastFocusedItem = null;
+ endCurrentAnimation();
+ mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofFloat(ALPHA, 0));
+ mCurrentAnimation.addListener(new ViewSetListener(null, false));
+ }
+ }
+
+ // invalidate once
+ invalidateDirty();
+
+ mLastFocusedItem = hasFocus ? item : null;
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.addUpdateListener(this);
+ mCurrentAnimation.setDuration(ANIM_DURATION).start();
+ }
+ }
+
+ protected void endCurrentAnimation() {
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.cancel();
+ mCurrentAnimation = null;
+ }
+ }
+
+ protected void setCurrentItem(T item) {
+ mCurrentItem = item;
+ mShift = 0;
+ mTargetItem = null;
+ }
+
+ /**
+ * Gets the position of the item relative to {@link #mContainer}.
+ */
+ public abstract void viewToRect(T item, Rect outRect);
+
+ private class ViewSetListener extends AnimatorListenerAdapter {
+ private final T mItemToSet;
+ private final boolean mCallOnCancel;
+ private boolean mCalled = false;
+
+ ViewSetListener(T item, boolean callOnCancel) {
+ mItemToSet = item;
+ mCallOnCancel = callOnCancel;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (!mCallOnCancel) {
+ mCalled = true;
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCalled) {
+ setCurrentItem(mItemToSet);
+ mCalled = true;
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java b/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
new file mode 100644
index 0000000..a6c897f
--- /dev/null
+++ b/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
@@ -0,0 +1,342 @@
+/*
+ * 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.keyboard;
+
+import static android.app.Activity.DEFAULT_KEYS_SEARCH_LOCAL;
+
+import static com.android.launcher3.LauncherState.SPRING_LOADED;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.TextView;
+
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.util.Themes;
+
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.function.ToIntBiFunction;
+import java.util.function.ToIntFunction;
+
+/**
+ * A floating view to allow keyboard navigation across virtual nodes
+ */
+public class KeyboardDragAndDropView extends AbstractFloatingView
+ implements Insettable, StateListener<LauncherState> {
+
+ private static final long MINOR_AXIS_WEIGHT = 13;
+
+ private final ArrayList<Integer> mIntList = new ArrayList<>();
+ private final ArrayList<DragAndDropAccessibilityDelegate> mDelegates = new ArrayList<>();
+ private final ArrayList<VirtualNodeInfo> mNodes = new ArrayList<>();
+
+ private final Rect mTempRect = new Rect();
+ private final Rect mTempRect2 = new Rect();
+ private final AccessibilityNodeInfoCompat mTempNodeInfo = AccessibilityNodeInfoCompat.obtain();
+
+ private final RectFocusIndicator mFocusIndicator;
+
+ private final Launcher mLauncher;
+ private VirtualNodeInfo mCurrentSelection;
+
+
+ public KeyboardDragAndDropView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyboardDragAndDropView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ mFocusIndicator = new RectFocusIndicator(this);
+ setWillNotDraw(false);
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mLauncher.getDragLayer().removeView(this);
+ mLauncher.getStateManager().removeStateListener(this);
+ mLauncher.setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+ mIsOpen = false;
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_DRAG_DROP_POPUP) != 0;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ // Consume all touch
+ return true;
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ }
+
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ if (toState != SPRING_LOADED) {
+ close(false);
+ }
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (mCurrentSelection != null) {
+ setCurrentSelection(mCurrentSelection);
+ }
+ }
+
+ private void setCurrentSelection(VirtualNodeInfo nodeInfo) {
+ mCurrentSelection = nodeInfo;
+ ((TextView) findViewById(R.id.label))
+ .setText(nodeInfo.populate(mTempNodeInfo).getContentDescription());
+
+ Rect bounds = new Rect();
+ mTempNodeInfo.getBoundsInParent(bounds);
+ View host = nodeInfo.delegate.getHost();
+ ViewParent parent = host.getParent();
+ if (parent instanceof PagedView) {
+ PagedView pv = (PagedView) parent;
+ int pageIndex = pv.indexOfChild(host);
+
+ pv.setCurrentPage(pageIndex);
+ bounds.offset(pv.getScrollX() - pv.getScrollForPage(pageIndex), 0);
+ }
+ float[] pos = new float[] {bounds.left, bounds.top, bounds.right, bounds.bottom};
+ Utilities.getDescendantCoordRelativeToAncestor(host, mLauncher.getDragLayer(), pos, true);
+
+ new RectF(pos[0], pos[1], pos[2], pos[3]).roundOut(bounds);
+ mFocusIndicator.changeFocus(bounds, true);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mFocusIndicator.draw(canvas);
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ VirtualNodeInfo nodeInfo = getNextSelection(direction);
+ if (nodeInfo == null) {
+ return false;
+ }
+ setCurrentSelection(nodeInfo);
+ return true;
+ }
+
+ /**
+ * Focus finding logic:
+ * Collect all virtual nodes in reading order (used for forward and backwards).
+ * Then find the closest view by comparing the distances spatially. Since it is a move
+ * operation. consider all cell sizes to be approximately of the same size.
+ */
+ private VirtualNodeInfo getNextSelection(int direction) {
+ // Collect all virtual nodes
+ mDelegates.clear();
+ mNodes.clear();
+
+ Folder openFolder = Folder.getOpen(mLauncher);
+ PagedView pv = openFolder == null ? mLauncher.getWorkspace() : openFolder.getContent();
+ int count = pv.getPageCount();
+ for (int i = 0; i < count; i++) {
+ mDelegates.add(((CellLayout) pv.getChildAt(i)).getDragAndDropAccessibilityDelegate());
+ }
+ if (openFolder == null) {
+ mDelegates.add(pv.getNextPage() + 1,
+ mLauncher.getHotseat().getDragAndDropAccessibilityDelegate());
+ }
+ mDelegates.forEach(delegate -> {
+ mIntList.clear();
+ delegate.getVisibleVirtualViews(mIntList);
+ mIntList.forEach(id -> mNodes.add(new VirtualNodeInfo(delegate, id)));
+ });
+
+ if (mNodes.isEmpty()) {
+ return null;
+ }
+ int index = mNodes.indexOf(mCurrentSelection);
+ if (mCurrentSelection == null || index < 0) {
+ return null;
+ }
+ int totalNodes = mNodes.size();
+
+ final ToIntBiFunction<Rect, Rect> majorAxis;
+ final ToIntFunction<Rect> minorAxis;
+
+ switch (direction) {
+ case View.FOCUS_RIGHT:
+ majorAxis = (source, dest) -> dest.left - source.left;
+ minorAxis = Rect::centerY;
+ break;
+ case View.FOCUS_LEFT:
+ majorAxis = (source, dest) -> source.left - dest.left;
+ minorAxis = Rect::centerY;
+ break;
+ case View.FOCUS_UP:
+ majorAxis = (source, dest) -> source.top - dest.top;
+ minorAxis = Rect::centerX;
+ break;
+ case View.FOCUS_DOWN:
+ majorAxis = (source, dest) -> dest.top - source.top;
+ minorAxis = Rect::centerX;
+ break;
+ case View.FOCUS_FORWARD:
+ return mNodes.get((index + 1) % totalNodes);
+ case View.FOCUS_BACKWARD:
+ return mNodes.get((index + totalNodes - 1) % totalNodes);
+ default:
+ // Unknown direction
+ return null;
+ }
+ mCurrentSelection.populate(mTempNodeInfo).getBoundsInScreen(mTempRect);
+
+ float minWeight = Float.MAX_VALUE;
+ VirtualNodeInfo match = null;
+ for (int i = 0; i < totalNodes; i++) {
+ VirtualNodeInfo node = mNodes.get(i);
+ node.populate(mTempNodeInfo).getBoundsInScreen(mTempRect2);
+
+ int majorAxisWeight = majorAxis.applyAsInt(mTempRect, mTempRect2);
+ if (majorAxisWeight <= 0) {
+ continue;
+ }
+ int minorAxisWeight = minorAxis.applyAsInt(mTempRect2)
+ - minorAxis.applyAsInt(mTempRect);
+
+ float weight = majorAxisWeight * majorAxisWeight
+ + minorAxisWeight * minorAxisWeight * MINOR_AXIS_WEIGHT;
+ if (weight < minWeight) {
+ minWeight = weight;
+ match = node;
+ }
+ }
+ return match;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER && mCurrentSelection != null) {
+ mCurrentSelection.delegate.onPerformActionForVirtualView(
+ mCurrentSelection.id, AccessibilityNodeInfoCompat.ACTION_CLICK, null);
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ /**
+ * Shows the keyboard drag popup for the provided view
+ */
+ public void showForIcon(View icon, ItemInfo item, DragOptions dragOptions) {
+ mIsOpen = true;
+ mLauncher.getDragLayer().addView(this);
+ mLauncher.getStateManager().addStateListener(this);
+
+ // Find current selection
+ CellLayout currentParent = (CellLayout) icon.getParent().getParent();
+ float[] iconPos = new float[] {currentParent.getCellWidth() / 2,
+ currentParent.getCellHeight() / 2};
+ Utilities.getDescendantCoordRelativeToAncestor(icon, currentParent, iconPos, false);
+
+ ItemLongClickListener.beginDrag(icon, mLauncher, item, dragOptions);
+
+ DragAndDropAccessibilityDelegate dndDelegate =
+ currentParent.getDragAndDropAccessibilityDelegate();
+ setCurrentSelection(new VirtualNodeInfo(
+ dndDelegate, dndDelegate.getVirtualViewAt(iconPos[0], iconPos[1])));
+
+ mLauncher.setDefaultKeyMode(Activity.DEFAULT_KEYS_DISABLE);
+ requestFocus();
+ }
+
+ private static class VirtualNodeInfo {
+ public final DragAndDropAccessibilityDelegate delegate;
+ public final int id;
+
+ VirtualNodeInfo(DragAndDropAccessibilityDelegate delegate, int id) {
+ this.id = id;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof VirtualNodeInfo)) {
+ return false;
+ }
+ VirtualNodeInfo that = (VirtualNodeInfo) o;
+ return id == that.id && delegate.equals(that.delegate);
+ }
+
+ public AccessibilityNodeInfoCompat populate(AccessibilityNodeInfoCompat nodeInfo) {
+ delegate.onPopulateNodeForVirtualView(id, nodeInfo);
+ return nodeInfo;
+ }
+
+ public void getBounds(AccessibilityNodeInfoCompat nodeInfo, Rect out) {
+ delegate.onPopulateNodeForVirtualView(id, nodeInfo);
+ nodeInfo.getBoundsInScreen(out);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, delegate);
+ }
+ }
+
+ private static class RectFocusIndicator extends ItemFocusIndicatorHelper<Rect> {
+
+ RectFocusIndicator(View container) {
+ super(container, Themes.getColorAccent(container.getContext()));
+ mPaint.setStrokeWidth(container.getResources()
+ .getDimension(R.dimen.keyboard_drag_stroke_width));
+ mPaint.setStyle(Style.STROKE);
+ }
+
+ @Override
+ public void viewToRect(Rect item, Rect outRect) {
+ outRect.set(item);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 6bc1ecb..cdd0bda 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -10,7 +10,6 @@
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.IOUtils;
import java.io.BufferedReader;
@@ -43,7 +42,7 @@
private static Handler sHandler = null;
private static File sLogsDirectory = null;
- public static final int LOG_DAYS = FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() ? 10 : 4;
+ public static final int LOG_DAYS = 4;
public static void setDir(File logsDir) {
if (ENABLED) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2066cd3..876ed35 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -22,6 +22,9 @@
import android.content.Context;
+import androidx.annotation.Nullable;
+import androidx.slice.SliceItem;
+
import com.android.launcher3.R;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.FromState;
@@ -47,6 +50,7 @@
public static final int LAUNCHER_STATE_ALLAPPS = 4;
public static final int LAUNCHER_STATE_UNCHANGED = 5;
+ private InstanceId mInstanceId;
/**
* Returns event enum based on the two state transition information when swipe
* gesture happens(to be removed during UserEventDispatcher cleanup).
@@ -95,9 +99,13 @@
@UiEvent(doc = "User dragged a launcher item")
LAUNCHER_ITEM_DRAG_STARTED(383),
- @UiEvent(doc = "A dragged launcher item is successfully dropped")
+ @UiEvent(doc = "A dragged launcher item is successfully dropped onto workspace, hotseat "
+ + "open folder etc")
LAUNCHER_ITEM_DROP_COMPLETED(385),
+ @UiEvent(doc = "A dragged launcher item is successfully dropped onto a folder icon.")
+ LAUNCHER_ITEM_DROP_COMPLETED_ON_FOLDER_ICON(697),
+
@UiEvent(doc = "A dragged launcher item is successfully dropped on another item "
+ "resulting in a new folder creation")
LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
@@ -344,6 +352,58 @@
@UiEvent(doc = "Current grid size is changed to 2.")
LAUNCHER_GRID_SIZE_2(665),
+
+ @UiEvent(doc = "Launcher entered into AllApps state.")
+ LAUNCHER_ALLAPPS_ENTRY(692),
+
+ @UiEvent(doc = "Launcher exited from AllApps state.")
+ LAUNCHER_ALLAPPS_EXIT(693),
+
+ @UiEvent(doc = "User closed the AllApps keyboard.")
+ LAUNCHER_ALLAPPS_KEYBOARD_CLOSED(694),
+
+ @UiEvent(doc = "User switched to AllApps Main/Personal tab by swiping left.")
+ LAUNCHER_ALLAPPS_SWIPE_TO_PERSONAL_TAB(695),
+
+ @UiEvent(doc = "User switched to AllApps Work tab by swiping right.")
+ LAUNCHER_ALLAPPS_SWIPE_TO_WORK_TAB(696),
+
+ @UiEvent(doc = "Default event when dedicated UI event is not available for the user action"
+ + " on slice .")
+ LAUNCHER_SLICE_DEFAULT_ACTION(700),
+
+ @UiEvent(doc = "User toggled-on a Slice item.")
+ LAUNCHER_SLICE_TOGGLE_ON(701),
+
+ @UiEvent(doc = "User toggled-off a Slice item.")
+ LAUNCHER_SLICE_TOGGLE_OFF(702),
+
+ @UiEvent(doc = "User acted on a Slice item with a button.")
+ LAUNCHER_SLICE_BUTTON_ACTION(703),
+
+ @UiEvent(doc = "User acted on a Slice item with a slider.")
+ LAUNCHER_SLICE_SLIDER_ACTION(704),
+
+ @UiEvent(doc = "User tapped on the entire row of a Slice.")
+ LAUNCHER_SLICE_CONTENT_ACTION(705),
+
+ @UiEvent(doc = "User tapped on the see more button of a Slice.")
+ LAUNCHER_SLICE_SEE_MORE_ACTION(706),
+
+ @UiEvent(doc = "User selected from a selection row of Slice.")
+ LAUNCHER_SLICE_SELECTION_ACTION(707),
+
+ @UiEvent(doc = "IME is used for selecting the focused item on the AllApps screen.")
+ LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME(718),
+
+ @UiEvent(doc = "Launcher entered into AllApps state with device search enabled.")
+ LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH(720),
+
+ @UiEvent(doc = "User switched to AllApps Main/Personal tab by tapping on it.")
+ LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB(721),
+
+ @UiEvent(doc = "User switched to AllApps Work tab by tapping on it.")
+ LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB(722),
;
// ADD MORE
@@ -451,6 +511,13 @@
}
/**
+ * Sets logging fields from provided {@link SliceItem}.
+ */
+ default StatsLogger withSliceItem(SliceItem sliceItem) {
+ return this;
+ }
+
+ /**
* Builds the final message and logs it as {@link EventEnum}.
*/
default void log(EventEnum event) {
@@ -461,16 +528,32 @@
* Returns new logger object.
*/
public StatsLogger logger() {
+ StatsLogger logger = createLogger();
+ if (mInstanceId != null) {
+ return logger.withInstanceId(mInstanceId);
+ }
+ return logger;
+ }
+
+ protected StatsLogger createLogger() {
return new StatsLogger() {
};
}
/**
+ * Sets InstanceId to every new {@link StatsLogger} object returned by {@link #logger()} when
+ * not-null.
+ */
+ public StatsLogManager withDefaultInstanceId(@Nullable InstanceId instanceId) {
+ this.mInstanceId = instanceId;
+ return this;
+ }
+
+ /**
* Creates a new instance of {@link StatsLogManager} based on provided context.
*/
public static StatsLogManager newInstance(Context context) {
- StatsLogManager mgr = Overrides.getObject(StatsLogManager.class,
+ return Overrides.getObject(StatsLogManager.class,
context.getApplicationContext(), R.string.stats_log_manager_class);
- return mgr;
}
}
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index e3e4b69..92b5885 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -22,7 +22,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.os.LocaleList;
@@ -137,7 +136,7 @@
if (findAppInfo(info.componentName, info.user) != null) {
return;
}
- mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
+ mIconCache.getTitleAndIcon(info, activityInfo, false /* useLowResIcon */);
info.sectionName = mIndex.computeSectionName(info.title);
data.add(info);
@@ -145,10 +144,9 @@
}
public void addPromiseApp(Context context, PackageInstallInfo installInfo) {
- ApplicationInfo applicationInfo = new PackageManagerHelper(context)
- .getApplicationInfo(installInfo.packageName, installInfo.user, 0);
// only if not yet installed
- if (applicationInfo == null) {
+ if (!new PackageManagerHelper(context)
+ .isAppInstalled(installInfo.packageName, installInfo.user)) {
AppInfo info = new AppInfo(installInfo);
mIconCache.getTitleAndIcon(info, info.usingLowResIcon());
info.sectionName = mIndex.computeSectionName(info.title);
@@ -282,7 +280,7 @@
} else {
Intent launchIntent = AppInfo.makeLaunchIntent(info);
- mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
+ mIconCache.getTitleAndIcon(applicationInfo, info, false /* useLowResIcon */);
applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
applicationInfo.setProgressLevel(
PackageManagerHelper.getLoadingProgress(info),
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index d1e5017..543ee2a 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -28,7 +28,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.ArrayList;
import java.util.HashMap;
@@ -123,7 +123,7 @@
}
public void bindUpdatedWidgets(BgDataModel dataModel) {
- final ArrayList<WidgetListRowEntry> widgets =
+ final ArrayList<WidgetsListBaseEntry> widgets =
dataModel.widgetsModel.getWidgetsList(mApp.getContext());
scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 2d860a4..1d7d1a2 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -50,7 +50,7 @@
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -467,7 +467,7 @@
void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
void bindRestoreItemsChange(HashSet<ItemInfo> updates);
void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
- void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
+ void bindAllWidgets(List<WidgetsListBaseEntry> widgets);
void onPageBoundSynchronously(int page);
void executeOnNextDraw(ViewOnDrawExecutor executor);
void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index e3e8769..434776c 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.model;
-import android.content.ComponentName;
+import android.content.Intent;
import android.os.UserHandle;
import com.android.launcher3.LauncherAppState;
@@ -66,8 +66,8 @@
final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
synchronized (dataModel) {
dataModel.forAllWorkspaceItemInfos(mUser, si -> {
- ComponentName cn = si.getTargetComponent();
- if ((cn != null) && cn.getPackageName().equals(mPackageName)) {
+ Intent intent = si.getIntent();
+ if ((intent != null) && mPackageName.equals(intent.getPackage())) {
si.runtimeStatusFlags &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
si.setProgressLevel(downloadInfo);
updatedWorkspaceItems.add(si);
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 8215edd..1380e9e 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.model;
-import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -72,9 +72,9 @@
synchronized (dataModel) {
final HashSet<ItemInfo> updates = new HashSet<>();
dataModel.forAllWorkspaceItemInfos(mInstallInfo.user, si -> {
- ComponentName cn = si.getTargetComponent();
- if (si.hasPromiseIconUi() && (cn != null)
- && cn.getPackageName().equals(mInstallInfo.packageName)) {
+ Intent intent = si.getIntent();
+ if (si.hasPromiseIconUi() && (intent != null)
+ && mInstallInfo.packageName.equals(intent.getPackage())) {
int installProgress = mInstallInfo.progress;
si.setProgressLevel(installProgress, PackageInstallInfo.STATUS_INSTALLING);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index f13a109..7bfa3ef 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -30,6 +30,7 @@
import android.util.Log;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
@@ -228,7 +229,8 @@
isTargetValid = context.getSystemService(LauncherApps.class)
.isActivityEnabled(cn, mUser);
}
- if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
+ if (!isTargetValid && si.hasStatusFlag(
+ FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
if (updateWorkspaceItemIntent(context, si, packageName)) {
infoUpdated = true;
} else if (si.hasPromiseIconUi()) {
@@ -250,8 +252,7 @@
}
}
- if (isNewApkAvailable
- && si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ if (isNewApkAvailable) {
List<LauncherActivityInfo> activities = activitiesLists.get(
packageName);
si.setProgressLevel(
@@ -260,8 +261,10 @@
: PackageManagerHelper.getLoadingProgress(
activities.get(0)),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
- iconCache.getTitleAndIcon(si, si.usingLowResIcon());
- infoUpdated = true;
+ if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+ infoUpdated = true;
+ }
}
int oldRuntimeFlags = si.runtimeStatusFlags;
@@ -353,6 +356,11 @@
*/
private boolean updateWorkspaceItemIntent(Context context,
WorkspaceItemInfo si, String packageName) {
+ if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ // Do not update intent for deep shortcuts as they contain additional information
+ // about the shortcut.
+ return false;
+ }
// Try to find the best match activity.
Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
if (intent != null) {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 6fedad1..4296d32 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -25,6 +25,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
import java.util.HashSet;
@@ -66,6 +67,14 @@
}
if (!matchingWorkspaceItems.isEmpty()) {
+ if (mShortcuts.isEmpty()) {
+ // Verify that the app is indeed installed.
+ if (!new PackageManagerHelper(app.getContext())
+ .isAppInstalled(mPackageName, mUser)) {
+ // App is not installed, ignoring package events
+ return;
+ }
+ }
// Update the workspace to reflect the changes to updated shortcuts residing on it.
List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
.map(WorkspaceItemInfo::getDeepShortcutId)
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 37c089e..de2481a 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -43,4 +43,20 @@
activityInfo = info;
spanX = spanY = 1;
}
+
+ /**
+ * Returns {@code true} if this {@link WidgetItem} has the same type as the given
+ * {@code otherItem}.
+ *
+ * For example, both items are widgets or both items are shortcuts.
+ */
+ public boolean hasSameType(WidgetItem otherItem) {
+ if (widgetInfo != null && otherItem.widgetInfo != null) {
+ return true;
+ }
+ if (activityInfo != null && otherItem.activityInfo != null) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index cc783f7..cd2ef35 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -34,6 +34,7 @@
import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.Attribute;
+import com.android.launcher3.logger.LauncherAtom.FolderIcon;
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.model.ModelWriter;
@@ -208,8 +209,13 @@
@Override
public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
+ FolderIcon.Builder folderIcon = FolderIcon.newBuilder()
+ .setCardinality(contents.size());
+ if (LabelState.SUGGESTED.equals(getLabelState())) {
+ folderIcon.setLabelInfo(title.toString());
+ }
return getDefaultItemInfoBuilder()
- .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
+ .setFolderIcon(folderIcon)
.setRank(rank)
.setAttribute(getLabelState().mLogAttribute)
.setContainerInfo(getContainerInfo())
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 3851ab0..00ac12f 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
+import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
@@ -54,6 +55,7 @@
import com.android.launcher3.logger.LauncherAtom.Shortcut;
import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
+import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.util.ContentWriter;
@@ -403,12 +405,23 @@
return ContainerInfo.newBuilder()
.setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance())
.build();
-
+ case EXTENDED_CONTAINERS:
+ return ContainerInfo.newBuilder()
+ .setExtendedContainers(getExtendedContainer())
+ .build();
}
return ContainerInfo.getDefaultInstance();
}
/**
+ * Returns non-AOSP container wrapped by {@link ExtendedContainers} object. Should be overridden
+ * by build variants.
+ */
+ protected ExtendedContainers getExtendedContainer() {
+ return ExtendedContainers.getDefaultInstance();
+ }
+
+ /**
* Returns shallow copy of the object.
*/
public ItemInfo makeShallowCopy() {
diff --git a/src/com/android/launcher3/model/data/PackageItemInfo.java b/src/com/android/launcher3/model/data/PackageItemInfo.java
index b70d0d4..7617d7e 100644
--- a/src/com/android/launcher3/model/data/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/data/PackageItemInfo.java
@@ -60,6 +60,6 @@
@Override
public int hashCode() {
- return Objects.hash(packageName);
+ return Objects.hash(packageName, user);
}
}
diff --git a/src/com/android/launcher3/model/data/SearchActionItemInfo.java b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
index 8469569..b3057d5 100644
--- a/src/com/android/launcher3/model/data/SearchActionItemInfo.java
+++ b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model.data;
+import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS;
+
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.drawable.Icon;
@@ -23,6 +25,9 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.logger.LauncherAtom.ItemInfo;
+import com.android.launcher3.logger.LauncherAtom.SearchActionItem;
+
/**
* Represents a SearchAction with in launcher
*/
@@ -32,19 +37,24 @@
public static final int FLAG_SHOULD_START_FOR_RESULT = FLAG_SHOULD_START | 1 << 2;
public static final int FLAG_BADGE_WITH_PACKAGE = 1 << 3;
public static final int FLAG_PRIMARY_ICON_FROM_TITLE = 1 << 4;
+ public static final int FLAG_BADGE_WITH_COMPONENT_NAME = 1 << 5;
private final String mFallbackPackageName;
private int mFlags = 0;
private final Icon mIcon;
+ // If true title does not contain any personal info and eligible for logging.
+ private final boolean mIsPersonalTitle;
private Intent mIntent;
private PendingIntent mPendingIntent;
public SearchActionItemInfo(Icon icon, String packageName, UserHandle user,
- CharSequence title) {
+ CharSequence title, boolean isPersonalTitle) {
+ mIsPersonalTitle = isPersonalTitle;
this.user = user == null ? Process.myUserHandle() : user;
this.title = title;
+ this.container = EXTENDED_CONTAINERS;
mFallbackPackageName = packageName;
mIcon = icon;
}
@@ -55,6 +65,8 @@
mFallbackPackageName = info.mFallbackPackageName;
mFlags = info.mFlags;
title = info.title;
+ this.container = EXTENDED_CONTAINERS;
+ this.mIsPersonalTitle = info.mIsPersonalTitle;
}
/**
@@ -108,4 +120,18 @@
public ItemInfoWithIcon clone() {
return new SearchActionItemInfo(this);
}
+
+ @Override
+ public ItemInfo buildProto(FolderInfo fInfo) {
+ SearchActionItem.Builder itemBuilder = SearchActionItem.newBuilder()
+ .setPackageName(mFallbackPackageName);
+
+ if (!mIsPersonalTitle) {
+ itemBuilder.setTitle(title.toString());
+ }
+ return getDefaultItemInfoBuilder()
+ .setSearchActionItem(itemBuilder)
+ .setContainerInfo(getContainerInfo())
+ .build();
+ }
}
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 059ad18..2905dc3 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.SecureSettingsObserver.newNotificationSettingsObserver;
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.SecureSettingsObserver;
import java.util.ArrayList;
import java.util.Arrays;
@@ -81,7 +81,8 @@
/** The last notification key that was dismissed from launcher UI */
private String mLastKeyDismissedByLauncher;
- private SecureSettingsObserver mNotificationDotsObserver;
+ private SettingsCache mSettingsCache;
+ private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
public NotificationListener() {
mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
@@ -207,10 +208,12 @@
super.onListenerConnected();
sIsConnected = true;
- mNotificationDotsObserver =
- newNotificationSettingsObserver(this, this::onNotificationSettingsChanged);
- mNotificationDotsObserver.register();
- mNotificationDotsObserver.dispatchOnChange();
+ // Register an observer to rebind the notification listener when dots are re-enabled.
+ mSettingsCache = SettingsCache.INSTANCE.get(this);
+ mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
onNotificationFullRefresh();
}
@@ -229,7 +232,7 @@
public void onListenerDisconnected() {
super.onListenerDisconnected();
sIsConnected = false;
- mNotificationDotsObserver.unregister();
+ mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
onNotificationFullRefresh();
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index fa25114..0091af1 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -217,8 +217,8 @@
&& sessionInfo.getAppIcon() != null
&& !TextUtils.isEmpty(sessionInfo.getAppLabel())
&& !promiseIconAddedForId(sessionInfo.getSessionId())
- && new PackageManagerHelper(mAppContext).getApplicationInfo(
- sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
+ && !new PackageManagerHelper(mAppContext).isAppInstalled(
+ sessionInfo.getAppPackageName(), getUserHandle(sessionInfo))) {
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 90285c4..56438d0 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -40,6 +40,8 @@
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InsettableFrameLayout;
@@ -82,6 +84,8 @@
private final Rect mStartRect = new Rect();
private final Rect mEndRect = new Rect();
+ private Runnable mOnCloseCallback = () -> { };
+
public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mInflater = LayoutInflater.from(context);
@@ -555,6 +559,14 @@
mDeferContainerRemoval = false;
getPopupContainer().removeView(this);
getPopupContainer().removeView(mArrow);
+ mOnCloseCallback.run();
+ }
+
+ /**
+ * Callback to be called when the popup is closed
+ */
+ public void setOnCloseCallback(@NonNull Runnable callback) {
+ mOnCloseCallback = callback;
}
protected BaseDragLayer getPopupContainer() {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 59930ff..a1ba747 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -206,6 +206,7 @@
.filter(Objects::nonNull)
.collect(Collectors.toList()));
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
+ container.requestFocus();
return container;
}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 76048ba..1853632 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -31,10 +31,10 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -59,7 +59,7 @@
/** Maps packages to their DotInfo's . */
private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
/** Maps packages to their Widgets */
- private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>();
+ private List<WidgetsListBaseEntry> mAllWidgets = List.of();
private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
@@ -187,7 +187,7 @@
notificationListener.cancelNotificationFromLauncher(notificationKey);
}
- public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
+ public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
mAllWidgets = allWidgets;
mChangeListener.onWidgetsBound();
}
@@ -196,14 +196,15 @@
mChangeListener = listener == null ? PopupDataChangeListener.INSTANCE : listener;
}
- public ArrayList<WidgetListRowEntry> getAllWidgets() {
+ public List<WidgetsListBaseEntry> getAllWidgets() {
return mAllWidgets;
}
public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
return mAllWidgets.stream()
- .filter(row -> row.pkgItem.packageName.equals(packageUserKey.mPackageName))
- .flatMap(row -> row.widgets.stream())
+ .filter(row -> row instanceof WidgetsListContentEntry
+ && row.mPkgItem.packageName.equals(packageUserKey.mPackageName))
+ .flatMap(row -> ((WidgetsListContentEntry) row).mWidgets.stream())
.filter(widget -> packageUserKey.mUser.equals(widget.user))
.collect(Collectors.toList());
}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 289e0d8..459aefe 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -20,6 +20,8 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_PROVIDER;
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
import android.app.Activity;
import android.app.Fragment;
import android.app.SearchManager;
@@ -30,6 +32,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.provider.Settings;
@@ -50,6 +53,8 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.FragmentWithPreview;
+import java.util.ArrayList;
+
/**
* A frame layout which contains a QSB. This internally uses fragment to bind the view, which
* allows it to contain the logic for {@link Fragment#startActivityForResult(Intent, int)}.
@@ -294,12 +299,16 @@
InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
Bundle opts = new Bundle();
- Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(getContext(),
- idp.numColumns, 1, null);
+ ArrayList<PointF> sizes = AppWidgetResizeFrame
+ .getWidgetSizes(getContext(), idp.numColumns, 1);
+ Rect size = AppWidgetResizeFrame.getMinMaxSizes(sizes, null /* outRect */);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
+ if (ATLEAST_S) {
+ opts.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes);
+ }
return opts;
}
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
new file mode 100644
index 0000000..4653774
--- /dev/null
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.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.recyclerview;
+
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Creates and populates views with data
+ *
+ * @param <T> A data model which is used to populate the {@link ViewHolder}.
+ * @param <V> A subclass of {@link ViewHolder} which holds references to views.
+ */
+public interface ViewHolderBinder<T, V extends ViewHolder> {
+ /**
+ * Creates a new view, and attach it to the parent {@link ViewGroup}. Then, populates UI
+ * references in a {@link ViewHolder}.
+ */
+ V newViewHolder(ViewGroup parent);
+
+ /** Populate UI references in {@link ViewHolder} with data. */
+ void bindViewHolder(V viewHolder, T data);
+
+ /**
+ * Called when the view is recycled. Views are recycled in batches once they are sufficiently
+ * far off screen that it is unlikely the user will scroll back to them soon. Optionally
+ * override this to free expensive resources.
+ */
+ default void unbindViewHolder(V viewHolder) {}
+}
diff --git a/src/com/android/launcher3/allapps/search/SearchAlgorithm.java b/src/com/android/launcher3/search/SearchAlgorithm.java
similarity index 76%
rename from src/com/android/launcher3/allapps/search/SearchAlgorithm.java
rename to src/com/android/launcher3/search/SearchAlgorithm.java
index c409b1c..1665354 100644
--- a/src/com/android/launcher3/allapps/search/SearchAlgorithm.java
+++ b/src/com/android/launcher3/search/SearchAlgorithm.java
@@ -13,17 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.allapps.search;
+package com.android.launcher3.search;
/**
* An interface for handling search.
+ *
+ * @param <T> Search Result type
*/
-public interface SearchAlgorithm {
+public interface SearchAlgorithm<T> {
/**
- * Performs search and sends the result to the callback.
+ * Performs search and sends the result to {@link SearchCallback}.
*/
- void doSearch(String query, AllAppsSearchBarController.Callbacks callback);
+ void doSearch(String query, SearchCallback<T> callback);
/**
* Cancels any active request.
diff --git a/src/com/android/launcher3/search/SearchCallback.java b/src/com/android/launcher3/search/SearchCallback.java
new file mode 100644
index 0000000..5796116
--- /dev/null
+++ b/src/com/android/launcher3/search/SearchCallback.java
@@ -0,0 +1,46 @@
+/*
+ * 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.search;
+
+import java.util.ArrayList;
+
+/**
+ * An interface for receiving search results.
+ *
+ * @param <T> Search Result type
+ */
+public interface SearchCallback<T> {
+
+ /**
+ * Called when the search from primary source is complete.
+ *
+ * @param items list of search results
+ */
+ void onSearchResult(String query, ArrayList<T> items);
+
+ /**
+ * Called when the search from secondary source is complete.
+ *
+ * @param items list of search results
+ */
+ void onAppendSearchResult(String query, ArrayList<T> items);
+
+ /**
+ * Called when the search results should be cleared.
+ */
+ void clearSearchResult();
+}
+
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 0266345..0e8d4ae 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -46,7 +46,7 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.ArrayList;
import java.util.HashMap;
@@ -232,7 +232,7 @@
public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
@Override
- public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets) { }
+ public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
@Override
public void onPageBoundSynchronously(int page) { }
diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java
index a91303a..a354169 100644
--- a/src/com/android/launcher3/settings/NotificationDotsPreference.java
+++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java
@@ -35,14 +35,14 @@
import com.android.launcher3.R;
import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SettingsCache;
/**
* A {@link Preference} for indicating notification dots status.
* Also has utility methods for updating UI based on dots status changes.
*/
public class NotificationDotsPreference extends Preference
- implements SecureSettingsObserver.OnChangeListener {
+ implements SettingsCache.OnChangeListener {
private boolean mWidgetFrameVisible = false;
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 922425f..f03065c 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,13 +18,13 @@
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;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -45,8 +45,8 @@
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;
-import com.android.launcher3.util.SecureSettingsObserver;
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -59,8 +59,6 @@
private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging";
- /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
- private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
@@ -126,10 +124,12 @@
*/
public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
- private SecureSettingsObserver mNotificationDotsObserver;
+ private SettingsCache mSettingsCache;
private String mHighLightKey;
private boolean mPreferenceHighlighted = false;
+ private NotificationDotsPreference mNotificationSettingsChangedListener;
+ private Preference mDeveloperOptionPref;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -177,14 +177,16 @@
}
// Listen to system notification dot settings while this UI is active.
- mNotificationDotsObserver = newNotificationSettingsObserver(
- getActivity(), (NotificationDotsPreference) preference);
- mNotificationDotsObserver.register();
+ mSettingsCache = SettingsCache.INSTANCE.get(getActivity());
+ mNotificationSettingsChangedListener =
+ ((NotificationDotsPreference) preference);
+ mSettingsCache.register(NOTIFICATION_BADGING_URI,
+ (NotificationDotsPreference) mNotificationSettingsChangedListener);
// Also listen if notification permission changes
- mNotificationDotsObserver.getResolver().registerContentObserver(
- Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
- mNotificationDotsObserver);
- mNotificationDotsObserver.dispatchOnChange();
+ mSettingsCache.register(NOTIFICATION_ENABLED_LISTENERS,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
+ mSettingsCache.dispatchOnChange(NOTIFICATION_ENABLED_LISTENERS);
return true;
case ALLOW_ROTATION_PREFERENCE_KEY:
@@ -201,18 +203,37 @@
return FeatureFlags.showFlagTogglerUi(getContext());
case DEVELOPER_OPTIONS_KEY:
- // Show if plugins are enabled or flag UI is enabled.
- return FeatureFlags.showFlagTogglerUi(getContext()) ||
- PluginManagerWrapper.hasPlugins(getContext());
+ mDeveloperOptionPref = preference;
+ return updateDeveloperOption();
}
return true;
}
+ /**
+ * Show if plugins are enabled or flag UI is enabled.
+ * @return True if we should show the preference option.
+ */
+ private boolean updateDeveloperOption() {
+ boolean showPreference = FeatureFlags.showFlagTogglerUi(getContext())
+ || PluginManagerWrapper.hasPlugins(getContext());
+ if (mDeveloperOptionPref != null) {
+ mDeveloperOptionPref.setEnabled(showPreference);
+ if (showPreference) {
+ getPreferenceScreen().addPreference(mDeveloperOptionPref);
+ } else {
+ getPreferenceScreen().removePreference(mDeveloperOptionPref);
+ }
+ }
+ return showPreference;
+ }
+
@Override
public void onResume() {
super.onResume();
+ updateDeveloperOption();
+
if (isAdded() && !mPreferenceHighlighted) {
PreferenceHighlighter highlighter = createHighlighter();
if (highlighter != null) {
@@ -251,9 +272,11 @@
@Override
public void onDestroy() {
- if (mNotificationDotsObserver != null) {
- mNotificationDotsObserver.unregister();
- mNotificationDotsObserver = null;
+ if (mSettingsCache != null) {
+ mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
+ mNotificationSettingsChangedListener);
+ mSettingsCache.unregister(NOTIFICATION_ENABLED_LISTENERS,
+ mNotificationSettingsChangedListener);
}
super.onDestroy();
}
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 601e117..7abb653 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -150,8 +150,8 @@
private void handleDeferredResume() {
if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) {
- onDeferredResumed();
addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+ onDeferredResumed();
mDeferredResumePending = false;
} else {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index d4a132e..4261d08 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -33,7 +33,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 10cd04c..65df614 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -124,7 +124,7 @@
protected abstract boolean canInterceptTouch(MotionEvent ev);
@Override
- public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
@@ -193,6 +193,8 @@
: reachedToState ? mToState : mFromState;
LauncherState newToState = getTargetState(newFromState, isDragTowardPositive);
+ onReinitToState(newToState);
+
if (newFromState == mFromState && newToState == mToState || (newFromState == newToState)) {
return false;
}
@@ -231,6 +233,12 @@
return true;
}
+ protected void onReinitToState(LauncherState newToState) {
+ }
+
+ protected void onReachedFinalState(LauncherState newToState) {
+ }
+
protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
LauncherState toState) {
return (fromState == NORMAL || fromState == OVERVIEW)
@@ -262,7 +270,6 @@
mFlingBlockCheck.unblockFling();
// Must be called after all the animation controllers have been paused
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get()
&& BuildCompat.isAtLeastR()
&& (mToState == ALL_APPS || mToState == NORMAL)) {
mLauncher.getAllAppsController().getInsetController().onDragStart(
@@ -527,6 +534,7 @@
mAtomicComponentsController.getAnimationPlayer().end();
mAtomicComponentsController = null;
}
+ onReachedFinalState(mToState);
clearState();
boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState);
if (shouldGoToTargetState) {
@@ -543,7 +551,8 @@
// case the user started interacting with it before the animation finished.
mLauncher.getStateManager().goToState(targetState, false /* animated */);
}
- mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start();
+ mLauncher.getDragLayer().getSysUiScrim().createSysuiMultiplierAnim(
+ 1f).setDuration(0).start();
}
private void logReachedState(LauncherState targetState) {
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 2647d6f..098d90d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
@@ -272,6 +273,7 @@
Toast.LENGTH_SHORT).show();
}
}
+ launcher.getStatsLogManager().logger().withItemInfo(itemInfo).log(LAUNCHER_APP_LAUNCH_TAP);
}
private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 3b7bcc2..d0e8bb1 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -29,6 +29,7 @@
import android.util.SparseArray;
import android.view.Display;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
@@ -182,20 +183,30 @@
}
/** Creates and up-to-date DisplayController.Info for the given context. */
+ @Nullable
public Info createInfoForContext(Context context) {
- Display display = Utilities.ATLEAST_R
- ? context.getDisplay()
- : context
- .getSystemService(DisplayManager.class)
- .getDisplay(mId);
- return display == null
- ? new Info(context)
- : new Info(context, display);
+ Display display = Utilities.ATLEAST_R ? context.getDisplay() : null;
+ if (display == null) {
+ display = context.getSystemService(DisplayManager.class).getDisplay(mId);
+ }
+ if (display == null) {
+ return null;
+ }
+ // Refresh the Context the prevent stale DisplayMetrics.
+ Context displayContext = context.getApplicationContext().createDisplayContext(display);
+ return new Info(displayContext, display);
+ }
+
+ public Context getDisplayContext() {
+ return mDisplayContext;
}
protected void handleOnChange() {
Info oldInfo = mInfo;
Info newInfo = createInfoForContext(mDisplayContext);
+ if (newInfo == null) {
+ return;
+ }
int change = 0;
if (newInfo.hasDifferentSize(oldInfo)) {
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 7b26427..08ec591 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -92,6 +92,14 @@
}
/**
+ * Returns whether the target app is installed for a given user
+ */
+ public boolean isAppInstalled(String packageName, UserHandle user) {
+ ApplicationInfo info = getApplicationInfo(packageName, user, 0);
+ return info != null;
+ }
+
+ /**
* Returns the application info for the provided package or null
*/
public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
@@ -105,7 +113,7 @@
}
public boolean isSafeMode() {
- return mContext.getPackageManager().isSafeMode();
+ return mPm.isSafeMode();
}
public Intent getAppLaunchIntent(String pkg, UserHandle user) {
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
deleted file mode 100644
index 9fe72ad..0000000
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ /dev/null
@@ -1,104 +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.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
-
-/**
- * Utility class to listen for secure settings changes
- */
-public class SecureSettingsObserver extends ContentObserver {
-
- /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
- public static final String NOTIFICATION_BADGING = "notification_badging";
- /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
- public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
- /** 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";
-
- private final ContentResolver mResolver;
- private final String mKeySetting;
- private final int mDefaultValue;
- private final OnChangeListener mOnChangeListener;
-
- public SecureSettingsObserver(ContentResolver resolver, OnChangeListener listener,
- String keySetting, int defaultValue) {
- super(new Handler());
-
- mResolver = resolver;
- mOnChangeListener = listener;
- mKeySetting = keySetting;
- mDefaultValue = defaultValue;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mOnChangeListener.onSettingsChanged(getValue());
- }
-
- public boolean getValue() {
- return Settings.Secure.getInt(mResolver, mKeySetting, mDefaultValue) == 1;
- }
-
- public void register() {
- mResolver.registerContentObserver(Settings.Secure.getUriFor(mKeySetting), false, this);
- }
-
- public ContentResolver getResolver() {
- return mResolver;
- }
-
- public void dispatchOnChange() {
- onChange(true);
- }
-
- public void unregister() {
- mResolver.unregisterContentObserver(this);
- }
-
- public interface OnChangeListener {
- void onSettingsChanged(boolean isEnabled);
- }
-
- public static SecureSettingsObserver newNotificationSettingsObserver(Context context,
- OnChangeListener listener) {
- return new SecureSettingsObserver(
- context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
- }
-
- public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
- OnChangeListener listener) {
- return new SecureSettingsObserver(
- context.getContentResolver(), listener, ONE_HANDED_ENABLED, 0);
- }
-
- /**
- * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
- * preference.
- */
- public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
- OnChangeListener listener) {
- return new SecureSettingsObserver(
- context.getContentResolver(), listener,
- ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
- }
-}
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
new file mode 100644
index 0000000..22b4d38
--- /dev/null
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -0,0 +1,179 @@
+/*
+ * 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 static android.provider.Settings.System.ACCELEROMETER_ROTATION;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * ContentObserver over Settings keys that also has a caching layer.
+ * Consumers can register for callbacks via {@link #register(Uri, OnChangeListener)} and
+ * {@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
+ * 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).
+ */
+public class SettingsCache extends ContentObserver {
+
+ /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
+ public static final Uri NOTIFICATION_BADGING_URI =
+ Settings.Secure.getUriFor("notification_badging");
+ /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+ public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+ /** 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);
+
+ private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
+
+ /**
+ * Caches the last seen value for registered keys.
+ */
+ private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
+ private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
+ protected final ContentResolver mResolver;
+
+
+ /**
+ * Singleton instance
+ */
+ public static MainThreadInitializedObject<SettingsCache> INSTANCE =
+ new MainThreadInitializedObject<>(SettingsCache::new);
+
+ private SettingsCache(Context context) {
+ super(new Handler());
+ mResolver = context.getContentResolver();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ // We use default of 1, but if we're getting an onChange call, can assume a non-default
+ // value will exist
+ boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
+ if (!mListenerMap.containsKey(uri)) {
+ return;
+ }
+
+ for (OnChangeListener listener : mListenerMap.get(uri)) {
+ listener.onSettingsChanged(newVal);
+ }
+ }
+
+ /**
+ * 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);
+ } else {
+ return updateValue(keySetting, defaultValue);
+ }
+ }
+
+ /**
+ * Does not de-dupe if you add same listeners for the same key multiple times.
+ * Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
+ */
+ public void register(Uri uri, OnChangeListener changeListener) {
+ if (mListenerMap.containsKey(uri)) {
+ mListenerMap.get(uri).add(changeListener);
+ } else {
+ CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
+ l.add(changeListener);
+ mListenerMap.put(uri, l);
+ mResolver.registerContentObserver(uri, false, this);
+ }
+ }
+
+ private boolean updateValue(Uri keyUri, int defaultValue) {
+ String key = keyUri.getLastPathSegment();
+ boolean newVal;
+ if (keyUri.toString().startsWith(SYSTEM_URI_PREFIX)) {
+ newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1;
+ } else { // SETTING_SECURE
+ newVal = Settings.Secure.getInt(mResolver, key, defaultValue) == 1;
+ }
+
+ mKeyCache.put(keyUri, newVal);
+ return newVal;
+ }
+
+ /**
+ * 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)}
+ */
+ public void unregister(Uri uri, OnChangeListener listener) {
+ List<OnChangeListener> listenersToRemoveFrom = mListenerMap.get(uri);
+ if (!listenersToRemoveFrom.contains(listener)) {
+ return;
+ }
+
+ listenersToRemoveFrom.remove(listener);
+ if (listenersToRemoveFrom.isEmpty()) {
+ mListenerMap.remove(uri);
+ }
+ }
+
+ /**
+ * Don't use this. Ever.
+ * @param keyCache Cache to replace {@link #mKeyCache}
+ */
+ @VisibleForTesting
+ void setKeyCache(Map<Uri, Boolean> keyCache) {
+ mKeyCache = keyCache;
+ }
+
+ public interface OnChangeListener {
+ void onSettingsChanged(boolean isEnabled);
+ }
+}
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 55d17fc..512a286 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -82,6 +82,11 @@
}
/** Returns the floating background color attribute. */
+ public static int getColorBackground(Context context) {
+ return getAttrColor(context, android.R.attr.colorBackground);
+ }
+
+ /** Returns the floating background color attribute. */
public static int getColorBackgroundFloating(Context context) {
return getAttrColor(context, android.R.attr.colorBackgroundFloating);
}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index ae459e1..505c6ce 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -17,7 +17,7 @@
import android.content.Context;
import android.content.ContextWrapper;
-import android.view.ContextThemeWrapper;
+import android.graphics.Rect;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.DeviceProfile;
@@ -49,6 +49,23 @@
return null;
}
+ default Rect getFolderBoundingBox() {
+ return getDeviceProfile().getAbsoluteOpenFolderBounds();
+ }
+
+ /**
+ * After calling {@link #getFolderBoundingBox()}, we calculate a (left, top) position for a
+ * Folder of size width x height to be within those bounds. However, the chosen position may
+ * not be visually ideal (e.g. uncanny valley of centeredness), so here's a chance to update it.
+ * @param inOutPosition A 2-size array where the first element is the left position of the open
+ * folder and the second element is the top position. Should be updated in place if desired.
+ * @param bounds The bounds that the open folder should fit inside.
+ * @param width The width of the open folder.
+ * @param height The height of the open folder.
+ */
+ default void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
+ }
+
/**
* The root view to support drag-and-drop and popup support.
*/
@@ -56,10 +73,10 @@
DeviceProfile getDeviceProfile();
- static ActivityContext lookupContext(Context context) {
+ static <T extends ActivityContext> T lookupContext(Context context) {
if (context instanceof ActivityContext) {
- return (ActivityContext) context;
- } else if (context instanceof ContextThemeWrapper) {
+ return (T) context;
+ } else if (context instanceof ContextWrapper) {
return lookupContext(((ContextWrapper) context).getBaseContext());
} else {
throw new IllegalArgumentException("Cannot find ActivityContext in parent tree");
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 5464dd8..1939d15 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -206,15 +206,19 @@
protected boolean findActiveController(MotionEvent ev) {
mActiveController = null;
- if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
- | TOUCH_DISPATCHING_FROM_PROXY)) == 0) {
- // Only look for controllers if we are not dispatching from gesture area and proxy is
- // not active
+ if (canFindActiveController()) {
mActiveController = findControllerToHandleTouch(ev);
}
return mActiveController != null;
}
+ protected boolean canFindActiveController() {
+ // Only look for controllers if we are not dispatching from gesture area and proxy is
+ // not active
+ return (mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
+ | TOUCH_DISPATCHING_FROM_PROXY)) == 0;
+ }
+
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
// Shortcuts can appear above folder
@@ -455,12 +459,6 @@
}
@Override
- public boolean dispatchUnhandledMove(View focused, int direction) {
- // Consume the unhandled move if a container is open, to avoid switching pages underneath.
- return AbstractFloatingView.getTopOpenView(mActivity) != null;
- }
-
- @Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
View topView = AbstractFloatingView.getTopOpenView(mActivity);
if (topView != null) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 80f0981..899dcf7 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -49,7 +49,7 @@
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
import java.util.List;
@@ -118,7 +118,8 @@
mTargetRect.roundOut(outPos);
}
- public static void show(Launcher launcher, RectF targetRect, List<OptionItem> items) {
+ public static OptionsPopupView show(
+ Launcher launcher, RectF targetRect, List<OptionItem> items) {
OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
.inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
popup.mTargetRect = targetRect;
@@ -134,6 +135,7 @@
popup.mItemMap.put(view, item);
}
popup.show();
+ return popup;
}
@VisibleForTesting
@@ -156,6 +158,14 @@
*/
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,
+ 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,
+ LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
+ OptionsPopupView::onWidgetsClicked));
+ }
int resString = Utilities.existsStyleWallpapers(launcher) ?
R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
@@ -163,15 +173,6 @@
options.add(new OptionItem(resString, resDrawable,
IGNORE,
OptionsPopupView::startWallpaperPicker));
- if (!WidgetsModel.GO_DISABLE_WIDGETS) {
- options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
- LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
- OptionsPopupView::onWidgetsClicked));
- }
- options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
- LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
- OptionsPopupView::startSettings));
-
return options;
}
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 804fb3e..ae34257 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -34,6 +34,7 @@
import android.view.ViewConfiguration;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BaseRecyclerView;
@@ -99,6 +100,7 @@
private boolean mIsThumbDetached;
private final boolean mCanThumbDetach;
private boolean mIgnoreDragGesture;
+ private boolean mIsRecyclerViewFirstChildInParent = true;
// This is the offset from the top of the scrollbar when the user first starts touching. To
// prevent jumping, this offset is applied as the user scrolls.
@@ -112,6 +114,7 @@
protected BaseRecyclerView mRv;
private RecyclerView.OnScrollListener mOnScrollListener;
+ @Nullable private OnFastScrollChangeListener mOnFastScrollChangeListener;
private int mDownX;
private int mDownY;
@@ -188,6 +191,9 @@
updatePopupY(y);
mThumbOffsetY = y;
invalidate();
+ if (mOnFastScrollChangeListener != null) {
+ mOnFastScrollChangeListener.onThumbOffsetYChanged(mThumbOffsetY);
+ }
}
public int getThumbOffsetY() {
@@ -391,7 +397,9 @@
return false;
}
getHitRect(sTempRect);
- sTempRect.top += mRv.getScrollBarTop();
+ if (mIsRecyclerViewFirstChildInParent) {
+ sTempRect.top += mRv.getScrollBarTop();
+ }
if (outOffset != null) {
outOffset.set(sTempRect.left, sTempRect.top);
}
@@ -404,4 +412,23 @@
// alpha is so low, it does not matter.
return false;
}
+
+ public void setIsRecyclerViewFirstChildInParent(boolean isRecyclerViewFirstChildInParent) {
+ mIsRecyclerViewFirstChildInParent = isRecyclerViewFirstChildInParent;
+ }
+
+ public void setOnFastScrollChangeListener(
+ @Nullable OnFastScrollChangeListener onFastScrollChangeListener) {
+ mOnFastScrollChangeListener = onFastScrollChangeListener;
+ }
+
+ /**
+ * A callback that is invoked when there is a scroll change in {@link RecyclerViewFastScroller}.
+ */
+ public interface OnFastScrollChangeListener {
+ /**
+ * Called when the thumb offset vertical position, in pixels, has changed to {@code y}.
+ */
+ void onThumbOffsetYChanged(int y);
+ }
}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 77cec80..c9bd284 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -42,7 +42,7 @@
*/
public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
- private static final float SCRIM_ALPHA = .75f;
+ private static final float SCRIM_ALPHA = .95f;
protected final T mLauncher;
private final WallpaperColorInfo mWallpaperColorInfo;
protected final int mEndScrim;
@@ -63,6 +63,7 @@
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;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index a38e90d..15566a4 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -30,6 +30,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
@@ -99,12 +100,16 @@
return false;
}
+ PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
+ if (v instanceof LivePreviewWidgetCell) {
+ dragHelper.setPreview(((LivePreviewWidgetCell) v).getPreview());
+ }
+
int[] loc = new int[2];
getPopupContainer().getLocationInDragLayer(image, loc);
- new PendingItemDragHelper(v).startDrag(
- image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
- new Point(loc[0], loc[1]), this, new DragOptions());
+ dragHelper.startDrag(image.getBitmapBounds(), image.getBitmap().getWidth(),
+ image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
close(true);
return true;
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 780a1a1..41098f9 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -16,9 +16,12 @@
package com.android.launcher3.widget;
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.PointF;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
@@ -70,8 +73,6 @@
private boolean mIsAutoAdvanceRegistered;
private Runnable mAutoAdvanceRunnable;
-
-
public LauncherAppWidgetHostView(Context context) {
super(context);
mLauncher = Launcher.getLauncher(context);
@@ -219,6 +220,16 @@
}
@Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ if (ATLEAST_S) {
+ float density = getContext().getResources().getDisplayMetrics().density;
+ setCurrentSize(new PointF(w / density, h / density));
+ }
+ }
+
+ @Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(getClass().getName());
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index ca47728..8c3206d 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -46,6 +47,8 @@
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Themes;
+import java.util.List;
+
public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
implements OnClickListener, ItemInfoUpdateReceiver {
private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
@@ -109,6 +112,11 @@
}
@Override
+ public void updateAppWidgetSize(Bundle newOptions, List<PointF> sizes) {
+ // No-op
+ }
+
+ @Override
protected View getDefaultView() {
View defaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false);
defaultView.setOnClickListener(this);
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 3c11274..8fe42f4 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -25,6 +25,8 @@
import android.view.View;
import android.widget.RemoteViews;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.Launcher;
@@ -48,14 +50,14 @@
private final PendingAddItemInfo mAddInfo;
private int[] mEstimatedCellSize;
- private RemoteViews mPreview;
+ @Nullable private RemoteViews mPreview;
public PendingItemDragHelper(View view) {
super(view);
mAddInfo = (PendingAddItemInfo) view.getTag();
}
- public void setPreview(RemoteViews preview) {
+ public void setPreview(@Nullable RemoteViews preview) {
mPreview = preview;
}
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index c022374..2438bdf 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -3,6 +3,7 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -18,6 +19,8 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.util.Thunk;
+import java.util.ArrayList;
+
public class WidgetHostViewLoader implements DragController.DragListener {
private static final String TAG = "WidgetHostViewLoader";
private static final boolean LOGD = false;
@@ -152,24 +155,28 @@
}
public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
- Rect rect = new Rect();
- AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
+ ArrayList<PointF> sizes = AppWidgetResizeFrame
+ .getWidgetSizes(context, info.spanX, info.spanY);
+
Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
info.componentName, null);
-
float density = context.getResources().getDisplayMetrics().density;
- int xPaddingDips = (int) ((padding.left + padding.right) / density);
- int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
+ float xPaddingDips = (padding.left + padding.right) / density;
+ float yPaddingDips = (padding.top + padding.bottom) / density;
+
+ for (PointF size : sizes) {
+ size.x = Math.max(0.f, size.x - xPaddingDips);
+ size.y = Math.max(0.f, size.y - yPaddingDips);
+ }
+
+ Rect rect = AppWidgetResizeFrame.getMinMaxSizes(sizes, null /* outRect */);
Bundle options = new Bundle();
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
- rect.left - xPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
- rect.top - yPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
- rect.right - xPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
- rect.bottom - yPaddingDips);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, rect.left);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, rect.top);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, rect.right);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, rect.bottom);
+ options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes);
return options;
}
}
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
deleted file mode 100644
index 17e4673..0000000
--- a/src/com/android/launcher3/widget/WidgetListRowEntry.java
+++ /dev/null
@@ -1,48 +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.widget;
-
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.PackageItemInfo;
-
-import java.util.ArrayList;
-
-/**
- * Holder class to store all the information related to a single row in the widget list
- */
-public class WidgetListRowEntry {
-
- public final PackageItemInfo pkgItem;
-
- public final ArrayList<WidgetItem> widgets;
-
- /**
- * Character that is used as a section name for the {@link ItemInfo#title}.
- * (e.g., "G" will be stored if title is "Google")
- */
- public String titleSectionName;
-
- public WidgetListRowEntry(PackageItemInfo pkgItem, ArrayList<WidgetItem> items) {
- this.pkgItem = pkgItem;
- this.widgets = items;
- }
-
- @Override
- public String toString() {
- return pkgItem.packageName + ":" + widgets.size();
- }
-}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 3585a18..223cda2 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -36,6 +36,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.PackageUserKey;
@@ -136,8 +137,8 @@
}
protected WidgetCell addItemCell(ViewGroup parent) {
- WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
- R.layout.widget_cell, parent, false);
+ LivePreviewWidgetCell widget = (LivePreviewWidgetCell) LayoutInflater.from(
+ getContext()).inflate(R.layout.live_preview_widget_cell, parent, false);
widget.setOnClickListener(this);
widget.setOnLongClickListener(this);
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
deleted file mode 100644
index df6e2c3..0000000
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.widget;
-
-import android.util.Log;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-
-/**
- * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
- * methods accordingly.
- */
-public class WidgetsDiffReporter {
- private static final boolean DEBUG = false;
- private static final String TAG = "WidgetsDiffReporter";
-
- private final IconCache mIconCache;
- private final RecyclerView.Adapter mListener;
-
- public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) {
- mIconCache = iconCache;
- mListener = listener;
- }
-
- public void process(ArrayList<WidgetListRowEntry> currentEntries,
- ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator) {
- if (DEBUG) {
- Log.d(TAG, "process oldEntries#=" + currentEntries.size()
- + " newEntries#=" + newEntries.size());
- }
- // Early exit if either of the list is empty
- if (currentEntries.isEmpty() || newEntries.isEmpty()) {
- // Skip if both list are empty.
- // On rotation, we open the widget tray with empty. Then try to fetch the list again
- // when the animation completes (which still gives empty). And we get the final result
- // when the bind actually completes.
- if (currentEntries.size() != newEntries.size()) {
- currentEntries.clear();
- currentEntries.addAll(newEntries);
- mListener.notifyDataSetChanged();
- }
- return;
- }
- ArrayList<WidgetListRowEntry> orgEntries =
- (ArrayList<WidgetListRowEntry>) currentEntries.clone();
- Iterator<WidgetListRowEntry> orgIter = orgEntries.iterator();
- Iterator<WidgetListRowEntry> newIter = newEntries.iterator();
-
- WidgetListRowEntry orgRowEntry = orgIter.next();
- WidgetListRowEntry newRowEntry = newIter.next();
-
- do {
- int diff = comparePackageName(orgRowEntry, newRowEntry, comparator);
- if (DEBUG) {
- Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)",
- diff, orgRowEntry != null? orgRowEntry.toString() : null,
- newRowEntry != null? newRowEntry.toString() : null));
- }
- int index = -1;
- if (diff < 0) {
- index = currentEntries.indexOf(orgRowEntry);
- mListener.notifyItemRemoved(index);
- if (DEBUG) {
- Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index,
- orgRowEntry.titleSectionName));
- }
- currentEntries.remove(index);
- orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
- } else if (diff > 0) {
- index = orgRowEntry != null? currentEntries.indexOf(orgRowEntry):
- currentEntries.size();
- currentEntries.add(index, newRowEntry);
- if (DEBUG) {
- Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index,
- newRowEntry.titleSectionName));
- }
- newRowEntry = newIter.hasNext() ? newIter.next() : null;
- mListener.notifyItemInserted(index);
-
- } else {
- // same package name but,
- // did the icon, title, etc, change?
- // or did the widget size and desc, span, etc change?
- if (!isSamePackageItemInfo(orgRowEntry.pkgItem, newRowEntry.pkgItem) ||
- !orgRowEntry.widgets.equals(newRowEntry.widgets)) {
- index = currentEntries.indexOf(orgRowEntry);
- currentEntries.set(index, newRowEntry);
- mListener.notifyItemChanged(index);
- if (DEBUG) {
- Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index,
- newRowEntry.titleSectionName));
- }
- }
- orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
- newRowEntry = newIter.hasNext() ? newIter.next() : null;
- }
- } while(orgRowEntry != null || newRowEntry != null);
- }
-
- /**
- * Compare package name using the same comparator as in {@link WidgetsListAdapter}.
- * Also handle null row pointers.
- */
- private int comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow,
- WidgetListRowEntryComparator comparator) {
- if (curRow == null && newRow == null) {
- throw new IllegalStateException("Cannot compare PackageItemInfo if both rows are null.");
- }
-
- if (curRow == null && newRow != null) {
- return 1; // new row needs to be inserted
- } else if (curRow != null && newRow == null) {
- return -1; // old row needs to be deleted
- }
- return comparator.compare(curRow, newRow);
- }
-
- private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
- return curInfo.bitmap.icon.equals(newInfo.bitmap.icon)
- && !mIconCache.isDefaultIcon(curInfo.bitmap, curInfo.user);
- }
-}
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
deleted file mode 100644
index 4c8c339..0000000
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.PropertyValuesHolder;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.views.TopRoundedCornerView;
-
-/**
- * Popup for showing the full list of available widgets
- */
-public class WidgetsFullSheet extends BaseWidgetSheet
- implements Insettable, ProviderChangedListener {
-
- 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;
-
- private final Rect mInsets = new Rect();
-
- private final WidgetsListAdapter mAdapter;
-
- private WidgetsRecyclerView mRecyclerView;
-
- public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- LauncherAppState apps = LauncherAppState.getInstance(context);
- mAdapter = new WidgetsListAdapter(context,
- LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(),
- this, this);
-
- }
-
- public WidgetsFullSheet(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mContent = findViewById(R.id.container);
-
- mRecyclerView = findViewById(R.id.widgets_list_view);
- mRecyclerView.setAdapter(mAdapter);
- mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
-
- TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
- springLayout.addSpringView(R.id.widgets_list_view);
- mRecyclerView.setEdgeEffectFactory(springLayout.createEdgeEffectFactory());
- onWidgetsBound();
- }
-
- @VisibleForTesting
- public WidgetsRecyclerView getRecyclerView() {
- return mRecyclerView;
- }
-
- @Override
- protected Pair<View, String> getAccessibilityTarget() {
- return Pair.create(mRecyclerView, getContext().getString(
- mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLauncher.getAppWidgetHost().addProviderChangeListener(this);
- notifyWidgetProvidersChanged();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
-
- mRecyclerView.setPadding(
- mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
- mRecyclerView.getPaddingRight(), insets.bottom);
- if (insets.bottom > 0) {
- setupNavBarColor();
- } else {
- clearNavBarColor();
- }
-
- ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
- requestLayout();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthUsed;
- if (mInsets.bottom > 0) {
- widthUsed = mInsets.left + mInsets.right;
- } else {
- Rect padding = mLauncher.getDeviceProfile().workspacePadding;
- widthUsed = Math.max(padding.left + padding.right,
- 2 * (mInsets.left + mInsets.right));
- }
-
- int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx;
- measureChildWithMargins(mContent, widthMeasureSpec,
- widthUsed, heightMeasureSpec, heightUsed);
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
- MeasureSpec.getSize(heightMeasureSpec));
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int width = r - l;
- int height = b - t;
-
- // Content is laid out as center bottom aligned
- int contentWidth = mContent.getMeasuredWidth();
- int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
- mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
- contentLeft + contentWidth, height);
-
- setTranslationShift(mTranslationShift);
- }
-
- @Override
- public void notifyWidgetProvidersChanged() {
- mLauncher.refreshAndBindWidgetsForPackageUser(null);
- }
-
- @Override
- public void onWidgetsBound() {
- mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
- }
-
- private void open(boolean animate) {
- if (animate) {
- if (getPopupContainer().getInsets().bottom > 0) {
- mContent.setAlpha(0);
- setTranslationShift(VERTICAL_START_POSITION);
- }
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator
- .setDuration(DEFAULT_OPEN_DURATION)
- .setInterpolator(AnimationUtils.loadInterpolator(
- getContext(), android.R.interpolator.linear_out_slow_in));
- mRecyclerView.setLayoutFrozen(true);
- mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRecyclerView.setLayoutFrozen(false);
- mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
- mOpenCloseAnimator.removeListener(this);
- }
- });
- post(() -> {
- mOpenCloseAnimator.start();
- mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
- });
- } else {
- setTranslationShift(TRANSLATION_SHIFT_OPENED);
- mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
- post(this::announceAccessibilityChanges);
- }
- }
-
- @Override
- protected void handleClose(boolean animate) {
- handleClose(animate, DEFAULT_OPEN_DURATION);
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- // Disable swipe down when recycler view is scrolling
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mNoIntercept = false;
- RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
- if (scroller.getThumbOffsetY() >= 0 &&
- getPopupContainer().isEventOverView(scroller, ev)) {
- mNoIntercept = true;
- } else if (getPopupContainer().isEventOverView(mContent, ev)) {
- mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, getPopupContainer());
- }
- }
- return super.onControllerInterceptTouchEvent(ev);
- }
-
- public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
- WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
- .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
- sheet.attachToContainer();
- sheet.mIsOpen = true;
- sheet.open(animate);
- return sheet;
- }
-
- @VisibleForTesting
- public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
- return launcher.findViewById(R.id.widgets_list_view);
- }
-
- @Override
- public void addHintCloseAnim(
- float distanceToMove, Interpolator interpolator, PendingAnimation target) {
- target.setFloat(mRecyclerView, VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
- target.setViewAlpha(mRecyclerView, 0.5f, interpolator);
- }
-
- @Override
- protected void onCloseComplete() {
- super.onCloseComplete();
- AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
- }
-}
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
deleted file mode 100644
index 5bf9690..0000000
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-import android.view.ViewGroup;
-
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-
-import com.android.launcher3.R;
-import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.LabelComparator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * List view adapter for the widget tray.
- *
- * <p>Memory vs. Performance:
- * The less number of types of views are inserted into a {@link RecyclerView}, the more recycling
- * happens and less memory is consumed. {@link #getItemViewType} was not overridden as there is
- * only a single type of view.
- */
-public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
-
- private static final String TAG = "WidgetsListAdapter";
- private static final boolean DEBUG = false;
-
- private final WidgetPreviewLoader mWidgetPreviewLoader;
- private final LayoutInflater mLayoutInflater;
-
- private final OnClickListener mIconClickListener;
- private final OnLongClickListener mIconLongClickListener;
- private final int mIndent;
- private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
- private final WidgetsDiffReporter mDiffReporter;
-
- private boolean mApplyBitmapDeferred;
-
- public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
- WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
- OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
- mLayoutInflater = layoutInflater;
- mWidgetPreviewLoader = widgetPreviewLoader;
- mIconClickListener = iconClickListener;
- mIconLongClickListener = iconLongClickListener;
- mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
- mDiffReporter = new WidgetsDiffReporter(iconCache, this);
- }
-
- /**
- * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}
- *
- * @see WidgetCell#setApplyBitmapDeferred(boolean)
- */
- public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
- mApplyBitmapDeferred = isDeferred;
-
- for (int i = rv.getChildCount() - 1; i >= 0; i--) {
- WidgetsRowViewHolder holder = (WidgetsRowViewHolder)
- rv.getChildViewHolder(rv.getChildAt(i));
- for (int j = holder.cellContainer.getChildCount() - 1; j >= 0; j--) {
- View v = holder.cellContainer.getChildAt(j);
- if (v instanceof WidgetCell) {
- ((WidgetCell) v).setApplyBitmapDeferred(mApplyBitmapDeferred);
- }
- }
- }
- }
-
- /**
- * Update the widget list.
- */
- public void setWidgets(ArrayList<WidgetListRowEntry> tempEntries) {
- WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator();
- Collections.sort(tempEntries, rowComparator);
- mDiffReporter.process(mEntries, tempEntries, rowComparator);
- }
-
- @Override
- public int getItemCount() {
- return mEntries.size();
- }
-
- public String getSectionName(int pos) {
- return mEntries.get(pos).titleSectionName;
- }
-
- @Override
- public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
- WidgetListRowEntry entry = mEntries.get(pos);
- List<WidgetItem> infoList = entry.widgets;
-
- ViewGroup row = holder.cellContainer;
- if (DEBUG) {
- Log.d(TAG, String.format(
- "onBindViewHolder [pos=%d, widget#=%d, row.getChildCount=%d]",
- pos, infoList.size(), row.getChildCount()));
- }
-
- // Add more views.
- // if there are too many, hide them.
- int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1);
- int childCount = row.getChildCount();
-
- if (expectedChildCount > childCount) {
- for (int i = childCount; i < expectedChildCount; i++) {
- if ((i & 1) == 1) {
- // Add a divider for odd index
- mLayoutInflater.inflate(R.layout.widget_list_divider, row);
- } else {
- // Add cell for even index
- WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
- R.layout.widget_cell, row, false);
-
- // set up touch.
- widget.setOnClickListener(mIconClickListener);
- widget.setOnLongClickListener(mIconLongClickListener);
- row.addView(widget);
- }
- }
- } else if (expectedChildCount < childCount) {
- for (int i = expectedChildCount; i < childCount; i++) {
- row.getChildAt(i).setVisibility(View.GONE);
- }
- }
-
- // Bind the views in the application info section.
- holder.title.applyFromItemInfoWithIcon(entry.pkgItem);
-
- // Bind the view in the widget horizontal tray region.
- for (int i = 0; i < infoList.size(); i++) {
- WidgetCell widget = (WidgetCell) row.getChildAt(2 * i);
- widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
- widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
- widget.ensurePreview();
- widget.setVisibility(View.VISIBLE);
-
- if (i > 0) {
- row.getChildAt(2 * i - 1).setVisibility(View.VISIBLE);
- }
- }
- }
-
- @Override
- public WidgetsRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- if (DEBUG) {
- Log.v(TAG, "\nonCreateViewHolder");
- }
-
- ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
- R.layout.widgets_list_row_view, parent, false);
-
- // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
- // the end of the linear layout width + the start padding and doesn't allow scrolling.
- container.findViewById(R.id.widgets_cell_list).setPaddingRelative(mIndent, 0, 1, 0);
-
- return new WidgetsRowViewHolder(container);
- }
-
- @Override
- public void onViewRecycled(WidgetsRowViewHolder holder) {
- int total = holder.cellContainer.getChildCount();
- for (int i = 0; i < total; i += 2) {
- WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
- widget.clear();
- }
- }
-
- public boolean onFailedToRecycleView(WidgetsRowViewHolder holder) {
- // If child views are animating, then the RecyclerView may choose not to recycle the view,
- // causing extraneous onCreateViewHolder() calls. It is safe in this case to continue
- // recycling this view, and take care in onViewRecycled() to cancel any existing
- // animations.
- return true;
- }
-
- @Override
- public long getItemId(int pos) {
- return pos;
- }
-
- /**
- * Comparator for sorting WidgetListRowEntry based on package title
- */
- public static class WidgetListRowEntryComparator implements Comparator<WidgetListRowEntry> {
-
- private final LabelComparator mComparator = new LabelComparator();
-
- @Override
- public int compare(WidgetListRowEntry a, WidgetListRowEntry b) {
- return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString());
- }
- }
-}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
new file mode 100644
index 0000000..09517e1
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -0,0 +1,60 @@
+/*
+ * 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 static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
+
+import java.lang.annotation.Retention;
+
+/** Holder class to store the package information of an entry shown in the widgets list. */
+public abstract class WidgetsListBaseEntry {
+ public final PackageItemInfo mPkgItem;
+
+ /**
+ * Character that is used as a section name for the {@link ItemInfo#title}.
+ * (e.g., "G" will be stored if title is "Google")
+ */
+ public final String mTitleSectionName;
+
+ public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName) {
+ mPkgItem = pkgItem;
+ mTitleSectionName = titleSectionName;
+ }
+
+ /**
+ * Returns the ranking of this entry in the
+ * {@link com.android.launcher3.widget.picker.WidgetsListAdapter}.
+ *
+ * <p>Entries with smaller value should be shown first. See
+ * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter} for more details.
+ */
+ @Rank
+ public abstract int getRank();
+
+ @Retention(SOURCE)
+ @IntDef({RANK_WIDGETS_LIST_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;
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
new file mode 100644
index 0000000..b0cb8c7
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -0,0 +1,50 @@
+/*
+ * 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.widget.model;
+
+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
+ * shown in the {@link com.android.launcher3.widget.picker.WidgetsFullSheet}.
+ */
+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());
+ }
+
+ @Override
+ public String toString() {
+ return mPkgItem.packageName + ":" + mWidgets.size();
+ }
+
+ @Override
+ @Rank
+ public int getRank() {
+ return RANK_WIDGETS_LIST_CONTENT;
+ }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
new file mode 100644
index 0000000..6899647
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.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.widget.model;
+
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+
+import java.util.Collection;
+
+/** An information holder for an app which has widgets or/and shortcuts. */
+public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry {
+
+ public final int widgetsCount;
+ public final int shortcutsCount;
+
+ private boolean mIsWidgetListShown = false;
+ private boolean mHasEntryUpdated = false;
+
+ public WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
+ Collection<WidgetItem> items) {
+ super(pkgItem, titleSectionName);
+ widgetsCount = (int) items.stream().filter(item -> item.widgetInfo != null).count();
+ shortcutsCount = Math.max(0, items.size() - widgetsCount);
+ }
+
+ /** 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
+ @Rank
+ public int getRank() {
+ return RANK_WIDGETS_LIST_HEADER;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
new file mode 100644
index 0000000..a5ed20a
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.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.widget.picker;
+
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.views.RecyclerViewFastScroller;
+import com.android.launcher3.widget.picker.WidgetsFullSheet.SearchAndRecommendationViewHolder;
+import com.android.launcher3.workprofile.PersonalWorkPagedView;
+
+/**
+ * A controller which measures & updates {@link WidgetsFullSheet}'s views padding, margin and
+ * vertical displacement upon scrolling.
+ */
+final class SearchAndRecommendationsScrollController implements
+ RecyclerViewFastScroller.OnFastScrollChangeListener {
+ private final boolean mHasWorkProfile;
+ private final SearchAndRecommendationViewHolder mViewHolder;
+ private final RecyclerView mPrimaryRecyclerView;
+
+ // The following are only non null if mHasWorkProfile is true.
+ @Nullable private final RecyclerView mWorkRecyclerView;
+ @Nullable private final View mPrimaryWorkTabsView;
+ @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
+
+ private int mMaxCollapsibleHeight = 0;
+
+ SearchAndRecommendationsScrollController(
+ boolean hasWorkProfile,
+ SearchAndRecommendationViewHolder viewHolder,
+ RecyclerView primaryRecyclerView,
+ @Nullable RecyclerView workRecyclerView,
+ @Nullable View personalWorkTabsView,
+ @Nullable PersonalWorkPagedView primaryWorkViewPager) {
+ mHasWorkProfile = hasWorkProfile;
+ mViewHolder = viewHolder;
+ mPrimaryRecyclerView = primaryRecyclerView;
+ mWorkRecyclerView = workRecyclerView;
+ mPrimaryWorkTabsView = personalWorkTabsView;
+ mPrimaryWorkViewPager = primaryWorkViewPager;
+ }
+
+ /**
+ * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
+ */
+ 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()
+ + mViewHolder.mCollapseHandle.getMeasuredHeight()
+ + mViewHolder.mHeaderTitle.getMeasuredHeight();
+
+ int topContainerHeight = mViewHolder.mContainer.getMeasuredHeight();
+ if (mHasWorkProfile) {
+ // In a work profile setup, the full widget sheet contains the following views:
+ // ------- -|
+ // Widgets -|---> LinearLayout for search & recommendations
+ // Search bar -|
+ // Personal | Work
+ // 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());
+
+ // 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
+ // views are no longer visible on the screen.
+ // This value is set as the margin for the view pager.
+ // 2. mMaxCollapsibleDistance
+ // This value is set as the padding for the recycler views in order to work with
+ // clipToPadding="false", which is an attribute for not showing top / bottom padding
+ // when a recycler view has not reached the top or bottom of the list.
+ // e.g. a list of 10 entries, only 3 entries are visible at a time.
+ // case 1: recycler view is scrolled to the top. Top padding is visible/
+ // (top padding)
+ // item 1
+ // item 2
+ // item 3
+ //
+ // case 2: recycler view is scrolled to the middle. No padding is visible.
+ // item 4
+ // item 5
+ // item 6
+ //
+ // case 3: recycler view is scrolled to the end. bottom padding is visible.
+ // item 8
+ // item 9
+ // item 10
+ // (bottom padding): not set in this case.
+ //
+ // When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
+ // mMaxCollapsibleDistance should equal to the top container height.
+ int tabsViewActualHeight =
+ mPrimaryWorkTabsView.getMeasuredHeight() - mPrimaryWorkTabsView.getPaddingTop();
+ int topOffsetAfterAllViewsCollapsed =
+ topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
+
+ RelativeLayout.LayoutParams layoutParams =
+ (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
+ layoutParams.setMargins(0, topOffsetAfterAllViewsCollapsed, 0, 0);
+ mPrimaryWorkViewPager.setLayoutParams(layoutParams);
+ mPrimaryWorkViewPager.requestLayout();
+
+ mPrimaryRecyclerView.setPadding(
+ mPrimaryRecyclerView.getPaddingLeft(),
+ mMaxCollapsibleHeight,
+ mPrimaryRecyclerView.getPaddingRight(),
+ mPrimaryRecyclerView.getPaddingBottom());
+ mWorkRecyclerView.setPadding(
+ mWorkRecyclerView.getPaddingLeft(),
+ mMaxCollapsibleHeight,
+ mWorkRecyclerView.getPaddingRight(),
+ mWorkRecyclerView.getPaddingBottom());
+ } else {
+ mPrimaryRecyclerView.setPadding(
+ mPrimaryRecyclerView.getPaddingLeft(),
+ topContainerHeight,
+ mPrimaryRecyclerView.getPaddingRight(),
+ mPrimaryRecyclerView.getPaddingBottom());
+ }
+ }
+
+ /**
+ * Changes the displacement of collapsible views (e.g. title & widget recommendations) and fixed
+ * views (e.g. recycler views, tabs) upon scrolling.
+ */
+ @Override
+ public void onThumbOffsetYChanged(int y) {
+ if (mMaxCollapsibleHeight > 0) {
+ int yDisplacement = Math.max(-y, -mMaxCollapsibleHeight);
+ mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
+ mViewHolder.mSearchBar.setTranslationY(yDisplacement);
+ if (mHasWorkProfile) {
+ mPrimaryWorkTabsView.setTranslationY(yDisplacement);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
new file mode 100644
index 0000000..dbd1bdf
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
@@ -0,0 +1,189 @@
+/*
+ * 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.widget.picker;
+
+import android.util.Log;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.PackageItemInfo;
+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.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
+ * methods accordingly.
+ */
+public class WidgetsDiffReporter {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WidgetsDiffReporter";
+
+ private final IconCache mIconCache;
+ private final RecyclerView.Adapter mListener;
+
+ public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) {
+ mIconCache = iconCache;
+ mListener = listener;
+ }
+
+ /**
+ * Notifies the difference between {@code currentEntries} & {@code newEntries} by calling the
+ * relevant {@link androidx.recyclerview.widget.RecyclerView.RecyclerViewDataObserver} methods.
+ */
+ public void process(ArrayList<WidgetsListBaseEntry> currentEntries,
+ List<WidgetsListBaseEntry> newEntries,
+ WidgetListBaseRowEntryComparator comparator) {
+ if (DEBUG) {
+ Log.d(TAG, "process oldEntries#=" + currentEntries.size()
+ + " newEntries#=" + newEntries.size());
+ }
+ // Early exit if either of the list is empty
+ if (currentEntries.isEmpty() || newEntries.isEmpty()) {
+ // Skip if both list are empty.
+ // On rotation, we open the widget tray with empty. Then try to fetch the list again
+ // when the animation completes (which still gives empty). And we get the final result
+ // when the bind actually completes.
+ if (currentEntries.size() != newEntries.size()) {
+ currentEntries.clear();
+ currentEntries.addAll(newEntries);
+ mListener.notifyDataSetChanged();
+ }
+ return;
+ }
+ ArrayList<WidgetsListBaseEntry> orgEntries =
+ (ArrayList<WidgetsListBaseEntry>) currentEntries.clone();
+ Iterator<WidgetsListBaseEntry> orgIter = orgEntries.iterator();
+ Iterator<WidgetsListBaseEntry> newIter = newEntries.iterator();
+
+ WidgetsListBaseEntry orgRowEntry = orgIter.next();
+ WidgetsListBaseEntry newRowEntry = newIter.next();
+
+ do {
+ int diff = compareAppNameAndType(orgRowEntry, newRowEntry, comparator);
+ if (DEBUG) {
+ Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)",
+ diff, orgRowEntry != null ? orgRowEntry.toString() : null,
+ newRowEntry != null ? newRowEntry.toString() : null));
+ }
+ int index = -1;
+ if (diff < 0) {
+ index = currentEntries.indexOf(orgRowEntry);
+ mListener.notifyItemRemoved(index);
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index,
+ orgRowEntry.mTitleSectionName));
+ }
+ currentEntries.remove(index);
+ orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
+ } else if (diff > 0) {
+ index = orgRowEntry != null ? currentEntries.indexOf(orgRowEntry)
+ : currentEntries.size();
+ currentEntries.add(index, newRowEntry);
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index,
+ newRowEntry.mTitleSectionName));
+ }
+ newRowEntry = newIter.hasNext() ? newIter.next() : null;
+ mListener.notifyItemInserted(index);
+
+ } else {
+ // same app name & type but,
+ // did the icon, title, etc, change?
+ // 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)
+ || hasWidgetsListChanged(orgRowEntry, newRowEntry)) {
+ index = currentEntries.indexOf(orgRowEntry);
+ currentEntries.set(index, newRowEntry);
+ mListener.notifyItemChanged(index);
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index,
+ newRowEntry.mTitleSectionName));
+ }
+ }
+ orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
+ newRowEntry = newIter.hasNext() ? newIter.next() : null;
+ }
+ } while(orgRowEntry != null || newRowEntry != null);
+ }
+
+ /**
+ * Compares the app name and then entry type for the given {@link WidgetsListBaseEntry}s.
+ *
+ * @Return 0 if both entries' order is the same. Negative integer if {@code newRowEntry} should
+ * order before {@code orgRowEntry}. Positive integer if {@code orgRowEntry} should
+ * order before {@code newRowEntry}.
+ */
+ private int compareAppNameAndType(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow,
+ WidgetListBaseRowEntryComparator comparator) {
+ if (curRow == null && newRow == null) {
+ throw new IllegalStateException(
+ "Cannot compare PackageItemInfo if both rows are null.");
+ }
+
+ if (curRow == null && newRow != null) {
+ return 1; // new row needs to be inserted
+ } else if (curRow != null && newRow == null) {
+ return -1; // old row needs to be deleted
+ }
+ int diff = comparator.compare(curRow, newRow);
+ if (diff == 0) {
+ return newRow.getRank() - curRow.getRank();
+ }
+ return diff;
+ }
+
+ /**
+ * Returns {@code true} if both {@code curRow} & {@code newRow} are
+ * {@link WidgetsListContentEntry}s with a different list of widgets.
+ */
+ private boolean hasWidgetsListChanged(WidgetsListBaseEntry curRow,
+ WidgetsListBaseEntry newRow) {
+ if (!(curRow instanceof WidgetsListContentEntry)
+ || !(newRow instanceof WidgetsListContentEntry)) {
+ return false;
+ }
+ WidgetsListContentEntry orgRowEntry = (WidgetsListContentEntry) curRow;
+ WidgetsListContentEntry newRowEntry = (WidgetsListContentEntry) newRow;
+ return !orgRowEntry.mWidgets.equals(newRowEntry.mWidgets);
+ }
+
+ /**
+ * 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;
+ }
+ WidgetsListHeaderEntry newRowEntry = (WidgetsListHeaderEntry) newRow;
+ return newRowEntry.hasEntryUpdated();
+ }
+
+ private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
+ return curInfo.bitmap.icon.equals(newInfo.bitmap.icon)
+ && !mIconCache.isDefaultIcon(curInfo.bitmap, curInfo.user);
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
new file mode 100644
index 0000000..5a5c2ef
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -0,0 +1,425 @@
+/*
+ * 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.widget.picker;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.graphics.Rect;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.views.RecyclerViewFastScroller;
+import com.android.launcher3.views.TopRoundedCornerView;
+import com.android.launcher3.widget.BaseWidgetSheet;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.workprofile.PersonalWorkPagedView;
+import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Popup for showing the full list of available widgets
+ */
+public class WidgetsFullSheet extends BaseWidgetSheet
+ implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
+ WidgetsRecyclerView.HeaderViewDimensionsProvider {
+
+ 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;
+
+ private final Rect mInsets = new Rect();
+ private final boolean mHasWorkProfile;
+ private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
+ private final UserHandle mCurrentUser = Process.myUserHandle();
+ private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
+ mCurrentUser.equals(entry.mPkgItem.user);
+ private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
+ mPrimaryWidgetsFilter.negate();
+
+ @Nullable private PersonalWorkPagedView mViewPager;
+ private int mInitialTabsHeight = 0;
+ private View mTabsView;
+ private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
+ private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
+
+ public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
+ mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
+ mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
+ }
+
+ public WidgetsFullSheet(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mContent = findViewById(R.id.container);
+ TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
+
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
+ : R.layout.widgets_full_sheet_recyclerview;
+ layoutInflater.inflate(contentLayoutRes, springLayout, true);
+
+ RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
+ if (mHasWorkProfile) {
+ mViewPager = findViewById(R.id.widgets_view_pager);
+ // Temporarily disable swipe gesture until widgets list horizontal scrollviews per
+ // app are replaced by gird views.
+ mViewPager.setSwipeGestureEnabled(false);
+ mViewPager.initParentViews(this);
+ mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
+ mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
+ mTabsView = findViewById(R.id.tabs);
+ findViewById(R.id.tab_personal)
+ .setOnClickListener((View view) -> mViewPager.snapToPage(0));
+ 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,
+ mSearchAndRecommendationViewHolder,
+ findViewById(R.id.primary_widgets_list_view),
+ mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
+ mTabsView,
+ mViewPager);
+ fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
+
+ onWidgetsBound();
+ }
+
+ @Override
+ public void onActivePageChanged(int currentActivePage) {
+ mAdapters.get(currentActivePage).mWidgetsRecyclerView.bindFastScrollbar();
+
+ reset();
+ }
+
+ private void reset() {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
+ if (mHasWorkProfile) {
+ mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
+ }
+ }
+
+ @VisibleForTesting
+ public WidgetsRecyclerView getRecyclerView() {
+ if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
+ return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
+ }
+ return mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView;
+ }
+
+ @Override
+ protected Pair<View, String> getAccessibilityTarget() {
+ return Pair.create(getRecyclerView(), getContext().getString(
+ mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mLauncher.getAppWidgetHost().addProviderChangeListener(this);
+ notifyWidgetProvidersChanged();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+
+ setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
+ if (mHasWorkProfile) {
+ setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
+ }
+ if (insets.bottom > 0) {
+ setupNavBarColor();
+ } else {
+ clearNavBarColor();
+ }
+
+ ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
+ requestLayout();
+ }
+
+ private void setBottomPadding(RecyclerView recyclerView, int bottomPadding) {
+ recyclerView.setPadding(
+ recyclerView.getPaddingLeft(),
+ recyclerView.getPaddingTop(),
+ recyclerView.getPaddingRight(),
+ bottomPadding);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
+ int widthUsed;
+ if (mInsets.bottom > 0) {
+ widthUsed = mInsets.left + mInsets.right;
+ } else {
+ Rect padding = deviceProfile.workspacePadding;
+ widthUsed = Math.max(padding.left + padding.right,
+ 2 * (mInsets.left + mInsets.right));
+ }
+
+ int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
+ measureChildWithMargins(mContent, widthMeasureSpec,
+ widthUsed, heightMeasureSpec, heightUsed);
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec));
+
+ int maxSpansPerRow = getMeasuredWidth() / (deviceProfile.cellWidthPx
+ + deviceProfile.workspaceCellPaddingXPx);
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ maxSpansPerRow);
+ if (mHasWorkProfile) {
+ mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ maxSpansPerRow);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int width = r - l;
+ int height = b - t;
+
+ // Content is laid out as center bottom aligned
+ int contentWidth = mContent.getMeasuredWidth();
+ int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
+ mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
+ contentLeft + contentWidth, height);
+
+ setTranslationShift(mTranslationShift);
+
+ if (mInitialTabsHeight == 0 && mTabsView != null) {
+ mInitialTabsHeight = mTabsView.getMeasuredHeight();
+ }
+
+ mSearchAndRecommendationsScrollController.updateMarginAndPadding();
+ }
+
+ @Override
+ public void notifyWidgetProvidersChanged() {
+ mLauncher.refreshAndBindWidgetsForPackageUser(null);
+ }
+
+ @Override
+ public void onWidgetsBound() {
+ List<WidgetsListBaseEntry> allWidgets = mLauncher.getPopupDataProvider().getAllWidgets();
+
+ AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
+ primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
+ primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+ if (mHasWorkProfile) {
+ AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
+ workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
+ workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+ onActivePageChanged(mViewPager.getCurrentPage());
+ }
+ }
+
+ private void open(boolean animate) {
+ if (animate) {
+ if (getPopupContainer().getInsets().bottom > 0) {
+ mContent.setAlpha(0);
+ setTranslationShift(VERTICAL_START_POSITION);
+ }
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator
+ .setDuration(DEFAULT_OPEN_DURATION)
+ .setInterpolator(AnimationUtils.loadInterpolator(
+ getContext(), android.R.interpolator.linear_out_slow_in));
+ mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator.removeListener(this);
+ }
+ });
+ post(() -> {
+ mOpenCloseAnimator.start();
+ mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
+ });
+ } else {
+ setTranslationShift(TRANSLATION_SHIFT_OPENED);
+ post(this::announceAccessibilityChanges);
+ }
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(animate, DEFAULT_OPEN_DURATION);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ // Disable swipe down when recycler view is scrolling
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = false;
+ RecyclerViewFastScroller scroller = getRecyclerView().getScrollbar();
+ if (scroller.getThumbOffsetY() >= 0
+ && getPopupContainer().isEventOverView(scroller, ev)) {
+ mNoIntercept = true;
+ } else if (getPopupContainer().isEventOverView(mContent, ev)) {
+ mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
+ }
+ }
+ return super.onControllerInterceptTouchEvent(ev);
+ }
+
+ /** Shows the {@link WidgetsFullSheet} on the launcher. */
+ public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
+ WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
+ .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
+ sheet.attachToContainer();
+ sheet.mIsOpen = true;
+ sheet.open(animate);
+ return sheet;
+ }
+
+ /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
+ @VisibleForTesting
+ public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
+ return launcher.findViewById(R.id.primary_widgets_list_view);
+ }
+
+ @Override
+ public void addHintCloseAnim(
+ float distanceToMove, Interpolator interpolator, PendingAnimation target) {
+ target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
+ target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
+ }
+
+ @Override
+ protected void onCloseComplete() {
+ super.onCloseComplete();
+ AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
+ }
+
+ @Override
+ public int getHeaderViewHeight() {
+ // No need to check work profile here because mInitialTabHeight is always 0 if there is no
+ // work profile.
+ return mInitialTabsHeight
+ + mSearchAndRecommendationViewHolder.mContainer.getMeasuredHeight();
+ }
+
+ /** A holder class for holding adapters & their corresponding recycler view. */
+ private final class AdapterHolder {
+ static final int PRIMARY = 0;
+ static final int WORK = 1;
+
+ private final int mAdapterType;
+ private final WidgetsListAdapter mWidgetsListAdapter;
+
+ private WidgetsRecyclerView mWidgetsRecyclerView;
+
+ AdapterHolder(int adapterType) {
+ mAdapterType = adapterType;
+
+ Context context = getContext();
+ LauncherAppState apps = LauncherAppState.getInstance(context);
+ mWidgetsListAdapter = new WidgetsListAdapter(
+ context,
+ LayoutInflater.from(context),
+ apps.getWidgetCache(),
+ apps.getIconCache(),
+ /* iconClickListener= */ WidgetsFullSheet.this,
+ /* iconLongClickListener= */ WidgetsFullSheet.this);
+ mWidgetsListAdapter.setFilter(
+ mAdapterType == PRIMARY ? mPrimaryWidgetsFilter : mWorkWidgetsFilter);
+ }
+
+ void setup(WidgetsRecyclerView recyclerView) {
+ mWidgetsRecyclerView = recyclerView;
+ mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
+ mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
+ mWidgetsRecyclerView.setEdgeEffectFactory(
+ ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
+ mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
+ }
+ }
+
+ final class SearchAndRecommendationViewHolder {
+ final View mContainer;
+ final View mCollapseHandle;
+ final EditText mSearchBar;
+ final TextView mHeaderTitle;
+
+ SearchAndRecommendationViewHolder(View searchAndRecommendationContainer) {
+ mContainer = searchAndRecommendationContainer;
+ mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
+ mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
+ mHeaderTitle = mContainer.findViewById(R.id.title);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
new file mode 100644
index 0000000..8b49d1e
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.TableRow;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.LabelComparator;
+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 java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Recycler view adapter for the widget tray.
+ *
+ * <p>This adapter supports view binding of subclasses of {@link WidgetsListBaseEntry}. There are 2
+ * subclasses: {@link WidgetsListHeader} & {@link WidgetsListContentEntry}.
+ * {@link WidgetsListHeader} entries are always visible in the recycler view. At most one
+ * {@link WidgetsListContentEntry} is shown in the recycler view at any time. Clicking a
+ * {@link WidgetsListHeader} will result in expanding / collapsing a corresponding
+ * {@link WidgetsListContentEntry} of the same app.
+ */
+public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderClickListener {
+
+ private static final String TAG = "WidgetsListAdapter";
+ 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 final WidgetsDiffReporter mDiffReporter;
+ private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
+ private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
+ private final WidgetListBaseRowEntryComparator mRowComparator =
+ new WidgetListBaseRowEntryComparator();
+
+ private List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
+ private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
+ @Nullable private String mWidgetsContentVisiblePackage = null;
+
+ private Predicate<WidgetsListBaseEntry> mHeaderAndSelectedContentFilter = entry ->
+ entry instanceof WidgetsListHeaderEntry
+ || entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage);
+ @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
+
+ public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
+ WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
+ OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
+ mDiffReporter = new WidgetsDiffReporter(iconCache, this);
+ mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
+ layoutInflater, iconClickListener, iconLongClickListener, widgetPreviewLoader);
+ mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
+ mViewHolderBinders.put(VIEW_TYPE_WIDGETS_HEADER,
+ new WidgetsListHeaderViewHolderBinder(layoutInflater, this::onHeaderClicked));
+ }
+
+ public void setFilter(Predicate<WidgetsListBaseEntry> filter) {
+ mFilter = filter;
+ }
+
+ /**
+ * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}.
+ *
+ * @see WidgetCell#setApplyBitmapDeferred(boolean)
+ */
+ public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
+ mWidgetsListTableViewHolderBinder.setApplyBitmapDeferred(isDeferred);
+
+ for (int i = rv.getChildCount() - 1; i >= 0; i--) {
+ ViewHolder viewHolder = rv.getChildViewHolder(rv.getChildAt(i));
+ if (viewHolder.getItemViewType() == VIEW_TYPE_WIDGETS_LIST) {
+ WidgetsRowViewHolder holder = (WidgetsRowViewHolder) viewHolder;
+ for (int j = holder.mTableContainer.getChildCount() - 1; j >= 0; j--) {
+ TableRow row = (TableRow) holder.mTableContainer.getChildAt(j);
+ for (int k = row.getChildCount() - 1; k >= 0; k--) {
+ ((WidgetCell) row.getChildAt(k)).setApplyBitmapDeferred(isDeferred);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mVisibleEntries.size();
+ }
+
+ /** 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. */
+ public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
+ mAllEntries = tempEntries.stream().sorted(mRowComparator)
+ .collect(Collectors.toList());
+ updateVisibleEntries();
+ }
+
+ private void updateVisibleEntries() {
+ mAllEntries.forEach(entry -> {
+ if (entry instanceof WidgetsListHeaderEntry) {
+ ((WidgetsListHeaderEntry) entry).setIsWidgetListShown(
+ entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage));
+ }
+ });
+ List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
+ .filter(entry -> (mFilter == null || mFilter.test(entry))
+ && mHeaderAndSelectedContentFilter.test(entry))
+ .collect(Collectors.toList());
+ mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int pos) {
+ ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
+ viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos));
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (DEBUG) {
+ Log.v(TAG, "\nonCreateViewHolder");
+ }
+
+ return mViewHolderBinders.get(viewType).newViewHolder(parent);
+ }
+
+ @Override
+ public void onViewRecycled(ViewHolder holder) {
+ mViewHolderBinders.get(holder.getItemViewType()).unbindViewHolder(holder);
+ }
+
+ @Override
+ public boolean onFailedToRecycleView(ViewHolder holder) {
+ // If child views are animating, then the RecyclerView may choose not to recycle the view,
+ // causing extraneous onCreateViewHolder() calls. It is safe in this case to continue
+ // recycling this view, and take care in onViewRecycled() to cancel any existing
+ // animations.
+ return true;
+ }
+
+ @Override
+ public long getItemId(int pos) {
+ return pos;
+ }
+
+ @Override
+ public int getItemViewType(int pos) {
+ WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
+ if (entry instanceof WidgetsListContentEntry) {
+ return VIEW_TYPE_WIDGETS_LIST;
+ } else if (entry instanceof WidgetsListHeaderEntry) {
+ return VIEW_TYPE_WIDGETS_HEADER;
+ }
+ throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
+ }
+
+ @Override
+ public void onHeaderClicked(boolean showWidgets, String expandedPackage) {
+ if (showWidgets) {
+ mWidgetsContentVisiblePackage = expandedPackage;
+ updateVisibleEntries();
+ } else if (expandedPackage.equals(mWidgetsContentVisiblePackage)) {
+ mWidgetsContentVisiblePackage = null;
+ updateVisibleEntries();
+ }
+ }
+
+ /**
+ * Sets the max horizontal spans that are allowed for grouping more than one widgets in a table
+ * row.
+ *
+ * <p>If there is only one widget in a row, that widget horizontal span is allowed to exceed
+ * {@code maxHorizontalSpans}.
+ * <p>Let's say the max horizontal spans is set to 5. Widgets can be grouped in the same row if
+ * their total horizontal spans added don't exceed 5.
+ * Example 1: Row 1: 2x2, 2x3, 1x1. Total horizontal spans is 5. This is okay.
+ * Example 2: Row 1: 2x2, 4x3, 1x1. the total horizontal spans is 7. This is wrong.
+ * 4x3 and 1x1 should be moved to a new row.
+ * Example 3: Row 1: 6x4. This is okay because this is the only item in the row.
+ */
+ public void setMaxHorizontalSpansPerRow(int maxHorizontalSpans) {
+ mWidgetsListTableViewHolderBinder.setMaxSpansPerRow(maxHorizontalSpans);
+ }
+
+ /** Comparator for sorting WidgetListRowEntry based on package title. */
+ public static class WidgetListBaseRowEntryComparator implements
+ Comparator<WidgetsListBaseEntry> {
+
+ private final LabelComparator mComparator = new LabelComparator();
+
+ @Override
+ public int compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b) {
+ return mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
new file mode 100644
index 0000000..823fb7b
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -0,0 +1,205 @@
+/*
+ * 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.android.launcher3.FastBitmapDrawable.newIcon;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+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.IconCache.ItemInfoUpdateReceiver;
+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;
+
+/**
+ * A UI represents a header of an app shown in the full widgets tray.
+ *
+ * It is a {@link LinearLayout} which contains an app icon, an app name, a subtitle and a checkbox
+ * which indicates if the widgets content view underneath this header should be shown.
+ */
+public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpdateReceiver {
+
+ private boolean mEnableIconUpdateAnimation = false;
+
+ @Nullable private HandlerRunnable mIconLoadRequest;
+ @Nullable private Drawable mIconDrawable;
+ private final int mIconSize;
+
+ private ImageView mAppIcon;
+ private TextView mTitle;
+ private TextView mSubtitle;
+
+ private CheckBox mExpandToggle;
+ private boolean mIsExpanded = false;
+
+ public WidgetsListHeader(Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public WidgetsListHeader(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, /* defStyle= */ 0);
+ }
+
+ public WidgetsListHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ ActivityContext activity = ActivityContext.lookupContext(context);
+ DeviceProfile grid = activity.getDeviceProfile();
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
+ mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
+ grid.iconSizePx);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAppIcon = findViewById(R.id.app_icon);
+ mTitle = findViewById(R.id.app_title);
+ mSubtitle = findViewById(R.id.app_subtitle);
+ mExpandToggle = findViewById(R.id.toggle);
+ }
+
+ /**
+ * Sets a {@link OnExpansionChangeListener} to get a callback when this app widgets section
+ * expands / collapses.
+ */
+ @UiThread
+ public void setOnExpandChangeListener(
+ @Nullable OnExpansionChangeListener onExpandChangeListener) {
+ // Use the entire touch area of this view to expand / collapse an app widgets section.
+ setOnClickListener(view -> {
+ setExpanded(!mIsExpanded);
+ onExpandChangeListener.onExpansionChange(mIsExpanded);
+ });
+ }
+
+ /** Sets the expand toggle to expand / collapse. */
+ @UiThread
+ public void setExpanded(boolean isExpanded) {
+ this.mIsExpanded = isExpanded;
+ mExpandToggle.setChecked(isExpanded);
+ }
+
+ /** Apply app icon, labels and tag using a generic {@link WidgetsListHeaderEntry}. */
+ @UiThread
+ public void applyFromItemInfoWithIcon(WidgetsListHeaderEntry entry) {
+ applyIconAndLabel(entry);
+ }
+
+ @UiThread
+ private void applyIconAndLabel(WidgetsListHeaderEntry entry) {
+ PackageItemInfo info = entry.mPkgItem;
+ setIcon(info);
+ setTitles(entry);
+ setExpanded(entry.isWidgetListShown());
+
+ super.setTag(info);
+
+ verifyHighRes();
+ }
+
+ private void setIcon(PackageItemInfo info) {
+ FastBitmapDrawable icon = newIcon(getContext(), info);
+ applyDrawables(icon);
+ mIconDrawable = icon;
+ if (mIconDrawable != null) {
+ mIconDrawable.setVisible(
+ /* visible= */ getWindowVisibility() == VISIBLE && isShown(),
+ /* restart= */ false);
+ }
+ }
+
+ private void applyDrawables(Drawable icon) {
+ icon.setBounds(0, 0, mIconSize, mIconSize);
+
+ mAppIcon.setImageDrawable(icon);
+
+ // If the current icon is a placeholder color, animate its update.
+ if (mIconDrawable != null
+ && mIconDrawable instanceof PlaceHolderIconDrawable
+ && mEnableIconUpdateAnimation) {
+ ((PlaceHolderIconDrawable) mIconDrawable).animateIconUpdate(icon);
+ }
+ }
+
+ private void setTitles(WidgetsListHeaderEntry entry) {
+ mTitle.setText(entry.mPkgItem.title);
+
+ if (entry.widgetsCount > 0) {
+ Resources resources = getContext().getResources();
+ mSubtitle.setText(resources.getQuantityString(R.plurals.widgets_tray_subtitle,
+ entry.widgetsCount, entry.widgetsCount));
+ mSubtitle.setVisibility(VISIBLE);
+ } else {
+ mSubtitle.setVisibility(GONE);
+ }
+ }
+
+ @Override
+ public void reapplyItemInfo(ItemInfoWithIcon info) {
+ if (getTag() == info) {
+ mIconLoadRequest = null;
+ mEnableIconUpdateAnimation = true;
+
+ // Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
+ info.bitmap.icon.prepareToDraw();
+
+ setIcon((PackageItemInfo) info);
+
+ mEnableIconUpdateAnimation = false;
+ }
+ }
+
+ /** Verifies that the current icon is high-res otherwise posts a request to load the icon. */
+ public void verifyHighRes() {
+ if (mIconLoadRequest != null) {
+ mIconLoadRequest.cancel();
+ mIconLoadRequest = null;
+ }
+ if (getTag() instanceof ItemInfoWithIcon) {
+ ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
+ if (info.usingLowResIcon()) {
+ mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
+ .updateIconInBackground(this, info);
+ }
+ }
+ }
+
+ /** A listener for the widget section expansion / collapse events. */
+ public interface OnExpansionChangeListener {
+ /** Notifies that the widget section is expanded or collapsed. */
+ void onExpansionChange(boolean isExpanded);
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderHolder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderHolder.java
new file mode 100644
index 0000000..d4e1b1c
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderHolder.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 WidgetsListHeaderHolder extends ViewHolder {
+ final WidgetsListHeader mWidgetsListHeader;
+
+ public WidgetsListHeaderHolder(WidgetsListHeader view) {
+ super(view);
+
+ mWidgetsListHeader = view;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
new file mode 100644
index 0000000..ed53e6f
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.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.picker;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.launcher3.R;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+
+/**
+ * Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
+ */
+public final class WidgetsListHeaderViewHolderBinder implements
+ ViewHolderBinder<WidgetsListHeaderEntry, WidgetsListHeaderHolder> {
+ private final LayoutInflater mLayoutInflater;
+ private final OnHeaderClickListener mOnHeaderClickListener;
+
+ public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
+ OnHeaderClickListener onHeaderClickListener) {
+ mLayoutInflater = layoutInflater;
+ mOnHeaderClickListener = onHeaderClickListener;
+ }
+
+ @Override
+ public WidgetsListHeaderHolder newViewHolder(ViewGroup parent) {
+ WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
+ R.layout.widgets_list_row_header, parent, false);
+
+ return new WidgetsListHeaderHolder(header);
+ }
+
+ @Override
+ public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data) {
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ 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);
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
new file mode 100644
index 0000000..2355700
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -0,0 +1,176 @@
+/*
+ * 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.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.util.WidgetsTableUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Binds data from {@link WidgetsListContentEntry} to UI elements in {@link WidgetsRowViewHolder}.
+ */
+public final class WidgetsListTableViewHolderBinder
+ implements ViewHolderBinder<WidgetsListContentEntry, WidgetsRowViewHolder> {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WidgetsListRowViewHolderBinder";
+
+ private int mMaxSpansPerRow = 4;
+ private final LayoutInflater mLayoutInflater;
+ private final int mIndent;
+ private final OnClickListener mIconClickListener;
+ private final OnLongClickListener mIconLongClickListener;
+ private final WidgetPreviewLoader mWidgetPreviewLoader;
+ private boolean mApplyBitmapDeferred = false;
+
+ public WidgetsListTableViewHolderBinder(
+ Context context,
+ LayoutInflater layoutInflater,
+ OnClickListener iconClickListener,
+ OnLongClickListener iconLongClickListener,
+ WidgetPreviewLoader widgetPreviewLoader) {
+ mLayoutInflater = layoutInflater;
+ mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+ mWidgetPreviewLoader = widgetPreviewLoader;
+ }
+
+ /**
+ * Defers applying bitmap on all the {@link WidgetCell} at
+ * {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry)} if
+ * {@code applyBitmapDeferred} is {@code true}.
+ */
+ public void setApplyBitmapDeferred(boolean applyBitmapDeferred) {
+ mApplyBitmapDeferred = applyBitmapDeferred;
+ }
+
+ public void setMaxSpansPerRow(int maxSpansPerRow) {
+ mMaxSpansPerRow = maxSpansPerRow;
+ }
+
+ @Override
+ public WidgetsRowViewHolder newViewHolder(ViewGroup parent) {
+ if (DEBUG) {
+ Log.v(TAG, "\nonCreateViewHolder");
+ }
+
+ ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
+ R.layout.widgets_table_container, parent, false);
+
+ // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
+ // the end of the linear layout width + the start padding and doesn't allow scrolling.
+ container.findViewById(R.id.widgets_table).setPaddingRelative(mIndent, 0, 1, 0);
+
+ return new WidgetsRowViewHolder(container);
+ }
+
+ @Override
+ public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry) {
+ TableLayout table = holder.mTableContainer;
+ if (DEBUG) {
+ Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
+ entry.mWidgets.size(), table.getChildCount()));
+ }
+
+ List<ArrayList<WidgetItem>> widgetItemsTable =
+ WidgetsTableUtils.groupWidgetItemsIntoTable(entry.mWidgets, mMaxSpansPerRow);
+ recycleTableBeforeBinding(table, widgetItemsTable);
+ // Bind the widget items.
+ for (int i = 0; i < widgetItemsTable.size(); i++) {
+ List<WidgetItem> widgetItemsPerRow = widgetItemsTable.get(i);
+ for (int j = 0; j < widgetItemsPerRow.size(); j++) {
+ TableRow row = (TableRow) table.getChildAt(i);
+ row.setVisibility(View.VISIBLE);
+ WidgetCell widget = (WidgetCell) row.getChildAt(j);
+ WidgetItem widgetItem = widgetItemsPerRow.get(j);
+ widget.applyFromCellItem(widgetItem, mWidgetPreviewLoader);
+ widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
+ widget.ensurePreview();
+ widget.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ /**
+ * Adds and hides table rows and columns from {@code table} to ensure there is sufficient room
+ * to display {@code widgetItemsTable}.
+ *
+ * <p>Instead of recreating all UI elements in {@code table}, this function recycles all
+ * existing UI elements. Instead of deleting excessive elements, it hides them.
+ */
+ private void recycleTableBeforeBinding(TableLayout table,
+ List<ArrayList<WidgetItem>> widgetItemsTable) {
+ // Hide extra table rows.
+ for (int i = widgetItemsTable.size(); i < table.getChildCount(); i++) {
+ table.getChildAt(i).setVisibility(View.GONE);
+ }
+
+ for (int i = 0; i < widgetItemsTable.size(); i++) {
+ List<WidgetItem> widgetItems = widgetItemsTable.get(i);
+ TableRow tableRow;
+ if (i < table.getChildCount()) {
+ tableRow = (TableRow) table.getChildAt(i);
+ } else {
+ tableRow = new TableRow(table.getContext());
+ table.addView(tableRow);
+ }
+ if (tableRow.getChildCount() > widgetItems.size()) {
+ for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) {
+ tableRow.getChildAt(j).setVisibility(View.GONE);
+ }
+ } else {
+ for (int j = tableRow.getChildCount(); j < widgetItems.size(); j++) {
+ WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
+ R.layout.widget_cell, tableRow, false);
+ // set up touch.
+ widget.setOnClickListener(mIconClickListener);
+ widget.setOnLongClickListener(mIconLongClickListener);
+ tableRow.addView(widget);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void unbindViewHolder(WidgetsRowViewHolder holder) {
+ int numOfRows = holder.mTableContainer.getChildCount();
+ for (int i = 0; i < numOfRows; i++) {
+ TableRow tableRow = (TableRow) holder.mTableContainer.getChildAt(i);
+ int numOfCols = tableRow.getChildCount();
+ for (int j = 0; j < numOfCols; j++) {
+ WidgetCell widget = (WidgetCell) tableRow.getChildAt(j);
+ widget.clear();
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
similarity index 83%
rename from src/com/android/launcher3/widget/WidgetsRecyclerView.java
rename to src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 69de12b..d65a809 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.widget;
+package com.android.launcher3.widget.picker;
import android.content.Context;
import android.graphics.Point;
@@ -22,13 +22,13 @@
import android.view.MotionEvent;
import android.view.View;
-import com.android.launcher3.BaseRecyclerView;
-import com.android.launcher3.R;
-
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.R;
+
/**
* The widgets recycler view.
*/
@@ -40,6 +40,7 @@
private final Point mFastScrollerOffset = new Point();
private boolean mTouchDownOnScroller;
+ private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -89,7 +90,7 @@
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
- int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
+ int posInt = (int) ((touchFraction == 1) ? pos - 1 : pos);
return mAdapter.getSectionName(posInt);
}
@@ -135,8 +136,8 @@
@Override
protected int getAvailableScrollHeight() {
View child = getChildAt(0);
- return child.getMeasuredHeight() * mAdapter.getItemCount() - getScrollbarTrackHeight()
- - mScrollbarTop;
+ return child.getMeasuredHeight() * mAdapter.getItemCount() + getScrollBarTop()
+ + getPaddingBottom() - mScrollbar.getHeight();
}
private boolean isModelNotReady() {
@@ -145,7 +146,9 @@
@Override
public int getScrollBarTop() {
- return mScrollbarTop;
+ return mHeaderViewDimensionsProvider == null
+ ? mScrollbarTop
+ : mHeaderViewDimensionsProvider.getHeaderViewHeight() + mScrollbarTop;
}
@Override
@@ -171,4 +174,21 @@
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
-}
\ No newline at end of file
+
+ public void setHeaderViewDimensionsProvider(
+ HeaderViewDimensionsProvider headerViewDimensionsProvider) {
+ mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
+ }
+
+ /**
+ * Provides dimensions of the header view that is shown at the top of a
+ * {@link WidgetsRecyclerView}.
+ */
+ public interface HeaderViewDimensionsProvider {
+ /**
+ * Returns the height, in pixels, of the header view that is shown at the top of a
+ * {@link WidgetsRecyclerView}.
+ */
+ int getHeaderViewHeight();
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
similarity index 68%
rename from src/com/android/launcher3/widget/WidgetsRowViewHolder.java
rename to src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
index d26edb6..aef1103 100644
--- a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
@@ -13,25 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.widget;
+package com.android.launcher3.widget.picker;
import android.view.ViewGroup;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.R;
+import android.widget.TableLayout;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-public class WidgetsRowViewHolder extends ViewHolder {
+import com.android.launcher3.R;
- public final ViewGroup cellContainer;
- public final BubbleTextView title;
+/** A {@link ViewHolder} for showing widgets of an app in the full widget picker. */
+public final class WidgetsRowViewHolder extends ViewHolder {
+
+ public final TableLayout mTableContainer;
public WidgetsRowViewHolder(ViewGroup v) {
super(v);
- cellContainer = v.findViewById(R.id.widgets_cell_list);
- title = v.findViewById(R.id.section);
- title.setAccessibilityDelegate(null);
+ mTableContainer = v.findViewById(R.id.widgets_table);
}
}
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
new file mode 100644
index 0000000..e73d661
--- /dev/null
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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.util;
+
+import com.android.launcher3.model.WidgetItem;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** An utility class which groups {@link WidgetItem}s into a table. */
+public final class WidgetsTableUtils {
+
+ /**
+ * Groups widgets in the following order:
+ * 1. Widgets always go before shortcuts.
+ * 2. Widgets with smaller horizontal spans will be shown first.
+ * 3. If widgets have the same horizontal spans, then widgets with a smaller vertical spans will
+ * go first.
+ * 4. If both widgets have the same horizontal and vertical spans, they will use the same order
+ * from the given {@code widgetItems}.
+ */
+ private static final Comparator<WidgetItem> WIDGET_SHORTCUT_COMPARATOR = (item, otherItem) -> {
+ if (item.widgetInfo != null && otherItem.widgetInfo == null) return -1;
+
+ if (item.widgetInfo == null && otherItem.widgetInfo != null) return 1;
+ if (item.spanX == otherItem.spanX) {
+ if (item.spanY == otherItem.spanY) return 0;
+ return item.spanY > otherItem.spanY ? 1 : -1;
+ }
+ return item.spanX > otherItem.spanX ? 1 : -1;
+ };
+
+
+ /**
+ * Groups widgets items into a 2D array which matches their appearance in a UI table.
+ *
+ * <p>Grouping:
+ * 1. Widgets and shortcuts never group together in the same row.
+ * 2. The ordered widgets are grouped together in the same row until their total horizontal
+ * spans exceed the {@code maxSpansPerRow}.
+ * 3. The order shortcuts are grouped together in the same row until their total horizontal
+ * spans exceed the {@code maxSpansPerRow}.
+ */
+ public static List<ArrayList<WidgetItem>> groupWidgetItemsIntoTable(
+ List<WidgetItem> widgetItems, final int maxSpansPerRow) {
+ List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR)
+ .collect(Collectors.toList());
+ List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>();
+ ArrayList<WidgetItem> widgetItemsAtRow = null;
+ for (WidgetItem widgetItem : sortedWidgetItems) {
+ if (widgetItemsAtRow == null) {
+ widgetItemsAtRow = new ArrayList<>();
+ widgetItemsTable.add(widgetItemsAtRow);
+ }
+ int numOfWidgetItems = widgetItemsAtRow.size();
+ int totalHorizontalSpan = widgetItemsAtRow.stream().map(item -> item.spanX)
+ .reduce(/* default= */ 0, Integer::sum);
+ if (numOfWidgetItems == 0) {
+ widgetItemsAtRow.add(widgetItem);
+ } else if (widgetItem.spanX + totalHorizontalSpan <= maxSpansPerRow
+ && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))) {
+ // Group items in the same row if
+ // 1. they are with the same type, i.e. a row can only have widgets or shortcuts but
+ // never a mix of both.
+ // 2. the total number of horizontal spans are smaller than or equal to
+ // MAX_SPAN_PER_ROW. If an item has a horizontal span > MAX_SPAN_PER_ROW, we just
+ // place it in its own row regardless of the horizontal span limit.
+ widgetItemsAtRow.add(widgetItem);
+ } else {
+ widgetItemsAtRow = new ArrayList<>();
+ widgetItemsTable.add(widgetItemsAtRow);
+ widgetItemsAtRow.add(widgetItem);
+ }
+ }
+ return widgetItemsTable;
+ }
+}
diff --git a/src/com/android/launcher3/workprofile/PersonalWorkPagedView.java b/src/com/android/launcher3/workprofile/PersonalWorkPagedView.java
new file mode 100644
index 0000000..8b05a0d
--- /dev/null
+++ b/src/com/android/launcher3/workprofile/PersonalWorkPagedView.java
@@ -0,0 +1,92 @@
+/*
+ * 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.workprofile;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.PagedView;
+
+/**
+ * A {@link PagedView} for showing different views for the personal and work profile respectively.
+ */
+public class PersonalWorkPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
+
+ static final float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
+ static final float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
+ static final float TOUCH_SLOP_DAMPING_FACTOR = 4;
+
+ public PersonalWorkPagedView(Context context) {
+ this(context, null);
+ }
+
+ public PersonalWorkPagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PersonalWorkPagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected String getCurrentPageDescription() {
+ // Not necessary, tab-bar already has two tabs with their own descriptions.
+ return "";
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ mPageIndicator.setScroll(l, mMaxScroll);
+ }
+
+ @Override
+ protected void determineScrollingStart(MotionEvent ev) {
+ float absDeltaX = Math.abs(ev.getX() - getDownMotionX());
+ float absDeltaY = Math.abs(ev.getY() - getDownMotionY());
+
+ if (Float.compare(absDeltaX, 0f) == 0) return;
+
+ float slope = absDeltaY / absDeltaX;
+ float theta = (float) Math.atan(slope);
+
+ if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
+ cancelCurrentPageLongPress();
+ }
+
+ if (theta > MAX_SWIPE_ANGLE) {
+ return;
+ } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
+ theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
+ float extraRatio = (float)
+ Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
+ super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
+ } else {
+ super.determineScrollingStart(ev);
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ @Override
+ protected boolean canScroll(float absVScroll, float absHScroll) {
+ return (absHScroll > absVScroll) && super.canScroll(absVScroll, absHScroll);
+ }
+}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
similarity index 85%
rename from src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
rename to src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
index 2de425e..3a3028f 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
@@ -13,7 +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.workprofile;
import android.content.Context;
import android.graphics.Canvas;
@@ -35,9 +35,6 @@
* Supports two indicator colors, dedicated for personal and work tabs.
*/
public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageIndicator {
- private static final int POSITION_PERSONAL = 0;
- private static final int POSITION_WORK = 1;
-
private final Paint mSelectedIndicatorPaint;
private final Paint mDividerPaint;
@@ -47,7 +44,7 @@
private float mScrollOffset;
private int mSelectedPosition = 0;
- private AllAppsContainerView mContainerView;
+ private OnActivePageChangedListener mOnActivePageChangedListener;
private int mLastActivePage = 0;
private boolean mIsRtl;
@@ -123,7 +120,7 @@
float y = getHeight() - mDividerPaint.getStrokeWidth();
canvas.drawLine(getPaddingLeft(), y, getWidth() - getPaddingRight(), y, mDividerPaint);
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
- mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
+ mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
}
@Override
@@ -135,15 +132,15 @@
@Override
public void setActiveMarker(int activePage) {
updateTabTextColor(activePage);
- if (mContainerView != null && mLastActivePage != activePage) {
+ if (mOnActivePageChangedListener != null && mLastActivePage != activePage) {
updateIndicatorPosition(activePage);
- mContainerView.onTabChanged(activePage);
+ mOnActivePageChangedListener.onActivePageChanged(activePage);
}
mLastActivePage = activePage;
}
- public void setContainerView(AllAppsContainerView containerView) {
- mContainerView = containerView;
+ public void setOnActivePageChangedListener(OnActivePageChangedListener listener) {
+ mOnActivePageChangedListener = listener;
}
@Override
@@ -153,4 +150,12 @@
public boolean hasOverlappingRendering() {
return false;
}
+
+ /**
+ * Interface definition for a callback to be invoked when an active page has been changed.
+ */
+ public interface OnActivePageChangedListener {
+ /** Called when the active page has been changed. */
+ void onActivePageChanged(int currentActivePage);
+ }
}
diff --git a/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
new file mode 100644
index 0000000..f8a9a04
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.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.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/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 269af7b..73b1601 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -21,10 +21,10 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
@@ -47,7 +47,7 @@
@Override
public void bindWidgets() {
- final ArrayList<WidgetListRowEntry> widgets =
+ final List<WidgetsListBaseEntry> widgets =
mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index b4e45f8..a7e3472 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -9,7 +9,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
@@ -28,12 +27,13 @@
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.widget.WidgetItemComparator;
-import com.android.launcher3.widget.WidgetListRowEntry;
import com.android.launcher3.widget.WidgetManagerHelper;
+import 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.WidgetsDiffReporter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -60,25 +60,24 @@
private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
/**
- * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
- * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s
- * is not sorted. This list is sorted at the UI when using
- * {@link com.android.launcher3.widget.WidgetsDiffReporter}
+ * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row
+ * are sorted (based on label and user), but the overall list of
+ * {@link WidgetsListBaseEntry}s is not sorted. This list is sorted at the UI when using
+ * {@link WidgetsDiffReporter}
*
- * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList)
+ * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
*/
- public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) {
- ArrayList<WidgetListRowEntry> result = new ArrayList<>();
+ public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
+ ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
- WidgetItemComparator widgetComparator = new WidgetItemComparator();
for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
- WidgetListRowEntry row = new WidgetListRowEntry(
- entry.getKey(), new ArrayList<>(entry.getValue()));
- row.titleSectionName = (row.pkgItem.title == null) ? "" :
- indexer.computeSectionName(row.pkgItem.title);
- Collections.sort(row.widgets, widgetComparator);
- result.add(row);
+ PackageItemInfo pkgItem = entry.getKey();
+ List<WidgetItem> widgetItems = entry.getValue();
+ String sectionName = (pkgItem.title == null) ? "" :
+ indexer.computeSectionName(pkgItem.title);
+ result.add(new WidgetsListHeaderEntry(pkgItem, sectionName, widgetItems));
+ result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems));
}
return result;
}
@@ -115,7 +114,7 @@
widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
updatedItems.add(info);
}
- setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
+ setWidgetsAndShortcuts(widgetsAndShortcuts, app);
} 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
@@ -132,52 +131,28 @@
}
private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
- LauncherAppState app, @Nullable PackageUserKey packageUser) {
+ LauncherAppState app) {
if (DEBUG) {
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}
// Temporary list for {@link PackageItemInfos} to avoid having to go through
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
- HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
+ HashMap<PackageUserKey, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
// clear the lists.
- if (packageUser == null) {
- mWidgetsList.clear();
- } else {
- PackageItemInfo packageItem = mWidgetsList.keySet()
- .stream()
- .filter(item -> item.packageName.equals(packageUser.mPackageName))
- .findFirst()
- .orElse(null);
- if (packageItem != null) {
- // We want to preserve the user that was on the packageItem previously,
- // so add it to tmpPackageItemInfos here to avoid creating a new entry.
- tmpPackageItemInfos.put(packageItem.packageName, packageItem);
-
- // Add the widgets for other users in the rawList as it only contains widgets for
- // packageUser
- List<WidgetItem> otherUserItems = mWidgetsList.remove(packageItem);
- otherUserItems.removeIf(w -> w.user.equals(packageUser.mUser));
- rawWidgetsShortcuts.addAll(otherUserItems);
- }
- }
-
- UserHandle myUser = Process.myUserHandle();
-
+ mWidgetsList.clear();
// add and update.
mWidgetsList.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
.collect(Collectors.groupingBy(item -> {
- String packageName = item.componentName.getPackageName();
- PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
+ PackageUserKey packageUserKey = new PackageUserKey(
+ item.componentName.getPackageName(), item.user);
+ PackageItemInfo pInfo = tmpPackageItemInfos.get(packageUserKey);
if (pInfo == null) {
- pInfo = new PackageItemInfo(packageName);
+ pInfo = new PackageItemInfo(packageUserKey.mPackageName);
pInfo.user = item.user;
- tmpPackageItemInfos.put(packageName, pInfo);
- } else if (!myUser.equals(pInfo.user)) {
- // Keep updating the user, until we get the primary user.
- pInfo.user = item.user;
+ tmpPackageItemInfos.put(packageUserKey, pInfo);
}
return pInfo;
})));
diff --git a/tests/Android.mk b/tests/Android.mk
index 3d9077d..2c7d30a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -32,11 +32,13 @@
LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
../src/com/android/launcher3/ResourceUtils.java \
- ../src/com/android/launcher3/util/SecureSettingsObserver.java \
../src/com/android/launcher3/testing/TestProtocol.java
endif
LOCAL_MODULE := ub-launcher-aosp-tapl
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_SDK_VERSION := system_current
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 1648bdd..4c9a8e7 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -37,8 +37,8 @@
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.views.OptionsPopupView;
-import com.android.launcher3.widget.WidgetsFullSheet;
-import com.android.launcher3.widget.WidgetsRecyclerView;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsRecyclerView;
import org.junit.Before;
import org.junit.Ignore;
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 9d4ccff..737f891 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -92,9 +92,8 @@
// Drag widget to homescreen
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
- widgets.
- getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
- dragToWorkspace(true, false);
+ widgets.getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))
+ .dragToWorkspace(true, false);
// Widget id for which the config activity was opened
mWidgetId = monitor.getWidgetId();
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 3fc83ff..c4a566b 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -67,7 +67,7 @@
() -> "Launching an app didn't open a new window: " + label);
mLauncher.assertTrue(
- "App didn't start: " + label,
+ "App didn't start: " + label + " (" + selector + ")",
TestHelpers.wait(Until.hasObject(selector), LauncherInstrumentation.WAIT_TIME_MS));
return new Background(mLauncher);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 5e20f7c..f279a82 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -153,7 +153,7 @@
private static final String WORKSPACE_RES_ID = "workspace";
private static final String APPS_RES_ID = "apps_view";
private static final String OVERVIEW_RES_ID = "overview_panel";
- private static final String WIDGETS_RES_ID = "widgets_list_view";
+ 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;
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 49af616..22f4d31 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -27,10 +27,10 @@
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
import com.android.launcher3.testing.TestProtocol;
import java.util.Collection;
+import java.util.List;
/**
* All widgets container.
@@ -101,42 +101,30 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"getting widget " + labelText + " in widgets list")) {
- final UiObject2 widgetsContainer = verifyActiveContainer();
+ final UiObject2 fullWidgetsPicker = verifyActiveContainer();
mLauncher.assertTrue("Widgets container didn't become scrollable",
- widgetsContainer.wait(Until.scrollable(true), WAIT_TIME_MS));
+ fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
final Point displaySize = mLauncher.getRealDisplaySize();
- final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+ 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);
int i = 0;
for (; ; ) {
- final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
- widgetsContainer, "widgets_scroll_container");
- mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
- for (UiObject2 cell : cells) {
- final UiObject2 label = cell.findObject(labelSelector);
- if (label == null) continue;
-
- final UiObject2 widget = label.getParent().getParent();
- mLauncher.assertEquals(
- "View is not WidgetCell",
- "com.android.launcher3.widget.WidgetCell",
- widget.getClassName());
-
- int maxWidth = 0;
- for (UiObject2 sibling : widget.getParent().getChildren()) {
- maxWidth = Math.max(mLauncher.getVisibleBounds(sibling).width(), maxWidth);
- }
-
- if (mLauncher.getVisibleBounds(widget).bottom
- <= displaySize.y - mLauncher.getBottomGestureSize()) {
- int visibleDelta = maxWidth - mLauncher.getVisibleBounds(widget).width();
- if (visibleDelta > 0) {
- Rect parentBounds = mLauncher.getVisibleBounds(cell);
- mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
- + mLauncher.getTouchSlop(),
- parentBounds.centerY(), parentBounds.centerX(),
- parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+ 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);
+ if (label == null) {
+ continue;
}
+ mLauncher.assertEquals(
+ "View is not WidgetCell",
+ "com.android.launcher3.widget.WidgetCell",
+ widget.getClassName());
return new Widget(mLauncher, widget);
}
@@ -144,7 +132,7 @@
mLauncher.assertTrue("Too many attempts", ++i <= 40);
final int scroll = getWidgetsScroll();
- mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
+ mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, tableRows, 0);
final int newScroll = getWidgetsScroll();
mLauncher.assertTrue(
"Scrolled in a wrong direction in Widgets: from " + scroll + " to "
@@ -153,4 +141,51 @@
}
}
}
+
+ /** Finds the widgets list of this test app from the collapsed full widgets picker. */
+ private UiObject2 findTestAppWidgetsTableContainer() {
+ final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
+ "widgets_list_header");
+ final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
+ mLauncher.getContext().getPackageName());
+ final BySelector widgetsContainerSelector = By.res(mLauncher.getLauncherPackageName(),
+ "widgets_table");
+
+ boolean hasHeaderExpanded = false;
+ for (int i = 0; i < 40; i++) {
+ UiObject2 fullWidgetsPicker = verifyActiveContainer();
+
+ UiObject2 header = fullWidgetsPicker.findObject(headerSelector);
+ mLauncher.assertTrue("Can't find a widget header", header != null);
+
+ // Look for a header that has the test app name.
+ UiObject2 headerTitle = fullWidgetsPicker.findObject(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) {
+ 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);
+ if (widgetsContainer != null) {
+ return widgetsContainer;
+ }
+ mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, List.of(headerTitle), 0);
+ } else {
+ mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, fullWidgetsPicker.getChildren(),
+ 0);
+ }
+ }
+
+ return null;
+ }
}