Merge "Block recent work profile apps content capture" into sc-dev
diff --git a/Android.bp b/Android.bp
index 9d675a4..92cc36b 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: [
@@ -76,3 +93,121 @@
sdk_version: "current",
min_sdk_version: "28",
}
+
+// Library with all the dependencies for building Launcher3
+android_library {
+ name: "Launcher3ResLib",
+ srcs: [ ],
+ resource_dirs: ["res"],
+ static_libs: [
+ "LauncherPluginLib",
+ "launcher_quickstep_log_protos_lite",
+ "androidx-constraintlayout_constraintlayout",
+ "androidx.recyclerview_recyclerview",
+ "androidx.dynamicanimation_dynamicanimation",
+ "androidx.fragment_fragment",
+ "androidx.preference_preference",
+ "androidx.slice_slice-view",
+ "androidx.cardview_cardview",
+ "iconloader_base",
+ ],
+ manifest: "AndroidManifest-common.xml",
+ sdk_version: "current",
+ min_sdk_version: "26",
+}
+
+//
+// Build rule for Launcher3 dependencies lib.
+//
+android_library {
+ name: "Launcher3CommonDepsLib",
+ srcs: ["src_build_config/**/*.java"],
+ static_libs: ["Launcher3ResLib"],
+ sdk_version: "current",
+ min_sdk_version: "26",
+ manifest: "AndroidManifest-common.xml",
+}
+
+//
+// Build rule for Launcher3 app.
+//
+android_app {
+ name: "Launcher3",
+
+ static_libs: [
+ "Launcher3CommonDepsLib",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src_shortcuts_overrides/**/*.java",
+ "src_ui_overrides/**/*.java",
+ "ext_tests/src/**/*.java",
+ ],
+ resource_dirs: [
+ "ext_tests/res",
+ ],
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ // Proguard is disable for testing. Derivarive prjects to keep proguard enabled
+ enabled: false,
+ },
+
+ sdk_version: "current",
+ min_sdk_version: "26",
+ target_sdk_version: "29",
+ privileged: true,
+ system_ext_specific: true,
+
+ overrides: [
+ "Home",
+ "Launcher2",
+ ],
+ required: ["privapp_whitelist_com.android.launcher3"],
+
+ jacoco: {
+ include_filter: ["com.android.launcher3.**"],
+ },
+ additional_manifests: [
+ "AndroidManifest-common.xml",
+ ],
+}
+
+// Library with all the dependencies for building quickstep
+android_library {
+ name: "QuickstepResLib",
+ srcs: [ ],
+ resource_dirs: [
+ "quickstep/res",
+ "quickstep/overview_ui_overrides/res",
+ ],
+ static_libs: [
+ "Launcher3ResLib",
+ "SystemUISharedLib",
+ "SystemUI-statsd",
+ ],
+ manifest: "quickstep/AndroidManifest.xml",
+ min_sdk_version: "28",
+}
+
+
+// Source code used for test helpers
+filegroup {
+ name: "launcher-src-ext-tests",
+ srcs: ["ext_tests/src/**/*.java"],
+}
+
+// Common source files used to build launcher
+filegroup {
+ name: "launcher-src-no-build-config",
+ srcs: [
+ "src/**/*.java",
+ "src_shortcuts_overrides/**/*.java",
+ "quickstep/src/**/*.java",
+ ],
+}
+
+// Proguard files for Launcher3
+filegroup {
+ name: "launcher-proguard-rules",
+ srcs: ["proguard.flags"],
+}
diff --git a/Android.mk b/Android.mk
index 127df79..89870a6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,76 +17,6 @@
LOCAL_PATH := $(call my-dir)
#
-# Build rule for Launcher3 dependencies lib.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- androidx.recyclerview_recyclerview \
- androidx.dynamicanimation_dynamicanimation \
- androidx.preference_preference \
- androidx.slice_slice-view \
- iconloader_base
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- LauncherPluginLib \
- launcher_quickstep_log_protos_lite \
- search_ui
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src_build_config) \
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 26
-LOCAL_MODULE := Launcher3CommonDepsLib
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_MANIFEST_FILE := AndroidManifest-common.xml
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#
-# Build rule for Launcher3 app.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, src_shortcuts_overrides) \
- $(call all-java-files-under, src_ui_overrides) \
- $(call all-java-files-under, ext_tests/src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/ext_tests/res
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-# Proguard is disable for testing. Derivarive prjects to keep proguard enabled
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 26
-LOCAL_PACKAGE_NAME := Launcher3
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2
-LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-
-include $(BUILD_PACKAGE)
-
-#
# Build rule for Launcher3 Go app for Android Go devices.
#
include $(CLEAR_VARS)
@@ -137,6 +67,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
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 4fd2e40..4e72260 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -116,8 +116,7 @@
android:theme="@style/AppItemActivityTheme"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
- android:exported="true"
- android:label="@string/action_add_to_workspace" >
+ android:exported="true">
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
<action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 56a2595..1fad72d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -49,7 +49,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/OWNERS b/OWNERS
index 1d6ad8c..daad057 100644
--- a/OWNERS
+++ b/OWNERS
@@ -4,6 +4,13 @@
# People who can approve changes for submission
#
+petrcermak@google.com
+pbdr@google.com
+kideckel@google.com
+stevenckng@google.com
+ydixit@google.com
+boadway@google.com
+alinazaidi@google.com
adamcohen@google.com
hyunyoungs@google.com
mrcasey@google.com
diff --git a/SharedLibWrapper/build.gradle b/SharedLibWrapper/build.gradle
deleted file mode 100644
index 674e38a..0000000
--- a/SharedLibWrapper/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply plugin: 'java'
-
-final String ANDROID_TOP = "${rootDir}/../../.."
-final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
-
-sourceSets {
- main {
- java.srcDirs = ["${ANDROID_TOP}/frameworks/lib/systemui/SharedLibWrapper/src"]
- }
-}
-
-sourceCompatibility = 1.8
-
-dependencies {
- implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
- compileOnly fileTree(dir: "$ANDROID_TOP/prebuilts/fullsdk-${org.gradle.internal.os.OperatingSystem.current().isMacOsX() ? "darwin" : "linux"}/platforms/${COMPILE_SDK}", include: 'android.jar')
-}
diff --git a/go/AndroidManifest-launcher.xml b/go/AndroidManifest-launcher.xml
index 2223036..d2575b6 100644
--- a/go/AndroidManifest-launcher.xml
+++ b/go/AndroidManifest-launcher.xml
@@ -49,7 +49,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index f36439d..2671604 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -24,6 +24,8 @@
<uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
+ <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
+
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:fullBackupOnly="true"
diff --git a/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml b/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
index b438da3..e6af848 100644
--- a/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
+++ b/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
@@ -17,14 +17,14 @@
<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_height="wrap_content"
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:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal">
<Space
diff --git a/go/quickstep/res/values/config.xml b/go/quickstep/res/values/config.xml
index 9dca137..a21381c 100644
--- a/go/quickstep/res/values/config.xml
+++ b/go/quickstep/res/values/config.xml
@@ -20,5 +20,5 @@
<string name="niu_actions_package" translatable="false"/>
<!-- Feature Flags -->
- <bool name="enable_niu_actions">false</bool>
+ <bool name="enable_niu_actions">true</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 71e2f3a..61c8cd9 100644
--- a/go/quickstep/res/values/strings.xml
+++ b/go/quickstep/res/values/strings.xml
@@ -10,5 +10,5 @@
<!-- Label for a button that translates a screenshot of the current app. [CHAR_LIMIT=40] -->
<string name="action_translate">Translate</string>
<!-- Label for a button that triggers Search on a screenshot of the current app. [CHAR_LIMIT=40] -->
- <string name="action_search">Search</string>
+ <string name="action_search">Lens</string>
</resources>
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index ea808c6..872f168 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -20,11 +20,23 @@
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
import android.annotation.SuppressLint;
+import android.app.ActivityTaskManager;
+import android.app.IAssistDataReceiver;
+import android.app.assist.AssistContent;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.graphics.Matrix;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.quickstep.views.OverviewActionsView;
@@ -40,6 +52,9 @@
public static final String ACTION_TRANSLATE = "com.android.quickstep.ACTION_TRANSLATE";
public static final String ACTION_SEARCH = "com.android.quickstep.ACTION_SEARCH";
public static final String ELAPSED_NANOS = "niu_actions_elapsed_realtime_nanos";
+ public static final String ACTIONS_URL = "niu_actions_app_url";
+ private static final String ASSIST_KEY_CONTENT = "content";
+ private static final String TAG = "TaskOverlayFactoryGo";
// Empty constructor required for ResourceBasedOverride
public TaskOverlayFactoryGo(Context context) {}
@@ -56,11 +71,14 @@
* @param <T> The type of View in which the overlay will be placed
*/
public static final class TaskOverlayGo<T extends OverviewActionsView> extends TaskOverlay {
-
- private String mPackageName;
+ private String mNIUPackageName;
+ private int mTaskId;
+ private Bundle mAssistData;
+ private final Handler mMainThreadHandler;
private TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
super(taskThumbnailView);
+ mMainThreadHandler = new Handler(Looper.getMainLooper());
}
/**
@@ -70,29 +88,66 @@
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
boolean rotated) {
getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
- mPackageName =
+ mNIUPackageName =
mApplicationContext.getResources().getString(R.string.niu_actions_package);
- if (thumbnail == null || TextUtils.isEmpty(mPackageName)) {
+ if (thumbnail == null || TextUtils.isEmpty(mNIUPackageName)) {
return;
}
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
+
+ mTaskId = task.key.id;
+ AssistDataReceiverImpl receiver = new AssistDataReceiverImpl();
+ receiver.setOverlay(this);
+
+ try {
+ ActivityTaskManager.getService().requestAssistDataForTask(receiver, mTaskId,
+ mApplicationContext.getPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to request AssistData");
+ }
}
- private void sendNIUIntent(String actionType) {
+ /**
+ * Called when AssistDataReceiverImpl receives data from ActivityTaskManagerService's
+ * AssistDataRequester
+ */
+ public void onAssistDataReceived(Bundle data) {
+ mMainThreadHandler.post(() -> {
+ if (data != null) {
+ mAssistData = data;
+ }
+ });
+ }
+
+ /**
+ * Creates and sends an Intent corresponding to the button that was clicked
+ */
+ @VisibleForTesting
+ public void sendNIUIntent(String actionType) {
Intent intent = createNIUIntent(actionType);
mImageApi.shareAsDataWithExplicitIntent(/* crop */ null, intent);
}
private Intent createNIUIntent(String actionType) {
- return new Intent(actionType)
+ Intent intent = new Intent(actionType)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
- .setPackage(mPackageName)
+ .setPackage(mNIUPackageName)
.putExtra(ELAPSED_NANOS, SystemClock.elapsedRealtimeNanos());
+
+ if (mAssistData != null) {
+ final AssistContent content = mAssistData.getParcelable(ASSIST_KEY_CONTENT);
+ Uri webUri = (content == null) ? null : content.getWebUri();
+ if (webUri != null) {
+ intent.putExtra(ACTIONS_URL, webUri.toString());
+ }
+ }
+
+ return intent;
}
protected class OverlayUICallbacksGoImpl extends OverlayUICallbacksImpl
@@ -128,6 +183,31 @@
}
}
}
+
+ @VisibleForTesting
+ public void setImageActionsAPI(ImageActionsApi imageActionsApi) {
+ mImageApi = imageActionsApi;
+ }
+ }
+
+ /**
+ * Basic AssistDataReceiver. This is passed to ActivityTaskManagerService, which then requests
+ * the data.
+ */
+ private static final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
+ private TaskOverlayGo mOverlay;
+
+ public void setOverlay(TaskOverlayGo overlay) {
+ mOverlay = overlay;
+ }
+
+ @Override
+ public void onHandleAssistData(Bundle data) {
+ mOverlay.onAssistDataReceived(data);
+ }
+
+ @Override
+ public void onHandleAssistScreenshot(Bitmap screenshot) {}
}
/**
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index a202095..cc5e1cb 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -30,6 +30,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -53,10 +54,15 @@
*
* @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
*/
- public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
+ public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
return EMPTY_WIDGET_LIST;
}
+ /** Returns a mapping of packages to their widgets without static shortcuts. */
+ public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
+ return Map.of();
+ }
+
/**
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
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/Android.bp b/quickstep/Android.bp
new file mode 100644
index 0000000..38c9919
--- /dev/null
+++ b/quickstep/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
+filegroup {
+ name: "launcher3-quickstep-robolectric-src",
+ path: "robolectric_tests",
+ srcs: ["robolectric_tests/src/**/*.java"],
+}
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 53910e3..7fe9b08 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -49,7 +49,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 97f4a21..5e5cf73 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -71,7 +71,7 @@
android:stateNotNeeded="true"
android:theme="@style/LauncherTheme"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""/>
diff --git a/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml b/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
index d15a2d2..1da25e7 100644
--- a/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
+++ b/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
@@ -16,14 +16,14 @@
-->
<com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="@dimen/overview_actions_height"
+ android:layout_height="wrap_content"
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:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal">
<Space
diff --git a/quickstep/res/drawable/task_menu_bg.xml b/quickstep/res/drawable/task_menu_bg.xml
index 7334d98..a60defc 100644
--- a/quickstep/res/drawable/task_menu_bg.xml
+++ b/quickstep/res/drawable/task_menu_bg.xml
@@ -14,25 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:gravity="bottom">
- <!-- Shadow -->
- <shape>
- <gradient android:angle="270"
- android:endColor="@android:color/transparent"
- android:startColor="#26000000" />
- <size android:height="@dimen/task_card_menu_shadow_height" />
- </shape>
- </item>
- <item android:bottom="@dimen/task_card_menu_shadow_height">
- <!-- Background -->
- <shape>
- <corners
- android:topLeftRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp" />
- <solid android:color="?attr/popupColorPrimary" />
- </shape>
- </item>
-</layer-list>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="?attr/popupColorPrimary" />
+</shape>
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index cd64a94..55400a7 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -19,6 +19,14 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
+ <com.android.quickstep.views.SplitPlaceholderView
+ android:id="@+id/split_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_placeholder_size"
+ android:background="@android:color/white"
+ android:alpha=".8"
+ android:visibility="gone" />
+
<com.android.quickstep.fallback.RecentsDragLayer
android:id="@+id/drag_layer"
android:layout_width="match_parent"
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 34ff91d..c61610a 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -22,5 +22,4 @@
android:layout_height="wrap_content"
android:text="@string/recents_clear_all"
android:textColor="?attr/workspaceTextColor"
- android:textSize="14sp"
- android:translationY="@dimen/task_thumbnail_half_top_margin" />
\ No newline at end of file
+ android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index fe57e9b..394e880 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -15,6 +15,14 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.android.quickstep.views.SplitPlaceholderView
+ android:id="@+id/split_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_placeholder_size"
+ android:background="@android:color/white"
+ android:alpha=".8"
+ android:visibility="gone" />
+
<com.android.quickstep.views.LauncherRecentsView
android:id="@+id/overview_panel"
android:layout_width="match_parent"
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 0f9a6aa..7e5b85c 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -13,6 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!-- NOTE! don't add dimensions for margins / paddings / sizes that change per orientation to this
+ file, they need to be loaded at runtime. -->
<com.android.quickstep.views.TaskView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
@@ -24,8 +26,7 @@
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/task_thumbnail_top_margin"/>
+ android:layout_height="match_parent"/>
<com.android.quickstep.views.IconView
android:id="@+id/icon"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index b124b33..84e2304 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -25,7 +25,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/taskbar_background"
- android:gravity="center"
- android:animateLayoutChanges="true"/>
+ android:gravity="center"/>
</com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
index 6e1aa1e..87649f7 100644
--- a/quickstep/res/layout/taskbar_divider.xml
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -18,6 +18,4 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/taskbar_divider_thickness"
android:layout_height="@dimen/taskbar_divider_height"
- android:layout_marginStart="@dimen/taskbar_icon_spacing"
- android:layout_marginEnd="@dimen/taskbar_icon_spacing"
android:background="@color/taskbar_divider" />
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_view.xml b/quickstep/res/layout/taskbar_view.xml
new file mode 100644
index 0000000..34a88ea
--- /dev/null
+++ b/quickstep/res/layout/taskbar_view.xml
@@ -0,0 +1,26 @@
+<?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.taskbar.TaskbarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/taskbar_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/taskbar_size"
+ android:background="@android:color/transparent"
+ android:layout_gravity="bottom"
+ android:gravity="center"
+ android:visibility="gone" />
+
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index c03eaa2..7cb01f6 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -16,4 +16,6 @@
-->
<resources>
<dimen name="task_card_menu_horizontal_padding">24dp</dimen>
+
+ <dimen name="overview_task_margin">8dp</dimen>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index a0e87cf..3036341 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -15,20 +15,29 @@
-->
<resources>
-
- <dimen name="task_thumbnail_top_margin">24dp</dimen>
- <dimen name="task_thumbnail_half_top_margin">12dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
- <dimen name="task_icon_top_margin">-16dp</dimen>
+ <dimen name="task_thumbnail_icon_size_grid">32dp</dimen>
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
+ <dimen name="overview_proactive_row_height">48dp</dimen>
+ <dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
+
+ <dimen name="overview_minimum_next_prev_size">48dp</dimen>
+ <dimen name="overview_task_margin">16dp</dimen>
+
<!-- Overrideable in overlay that provides the Overview Actions. -->
- <dimen name="overview_actions_height">66dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">16dp</dimen>
+ <dimen name="overview_actions_height">48dp</dimen>
+ <dimen name="overview_actions_bottom_margin_gesture">12dp</dimen>
<dimen name="overview_actions_bottom_margin_three_button">8dp</dimen>
<dimen name="overview_actions_horizontal_margin">16dp</dimen>
+ <dimen name="overview_grid_top_margin">77dp</dimen>
+ <dimen name="overview_grid_bottom_margin">90dp</dimen>
+ <dimen name="overview_grid_side_margin">54dp</dimen>
+ <dimen name="overview_grid_row_spacing">42dp</dimen>
+ <dimen name="split_placeholder_size">110dp</dimen>
+
<dimen name="recents_page_spacing">16dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
@@ -58,10 +67,6 @@
<dimen name="task_card_menu_option_vertical_padding">8dp</dimen>
<dimen name="task_card_menu_shadow_height">3dp</dimen>
<dimen name="task_card_menu_horizontal_padding">0dp</dimen>
- <dimen name="portrait_task_card_horz_space_big_overview">132dp</dimen>
- <dimen name="portrait_modal_task_card_horz_space">60dp</dimen>
- <dimen name="landscape_task_card_horz_space">200dp</dimen>
- <dimen name="multi_window_task_card_horz_space">100dp</dimen>
<!-- Copied from framework resource:
docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
<dimen name="multi_window_task_divider_size">10dp</dimen>
@@ -122,13 +127,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/override.xml b/quickstep/res/values/override.xml
index 605774d..705ec9d 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -17,8 +17,6 @@
<!-- Class overrides for launcher with quickstep. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
-
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
<string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 9a4487c..0764bb3 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -22,8 +22,6 @@
<string name="derived_app_name" translatable="false">Quickstep</string>
<!-- Options for recent tasks -->
- <!-- Title for an option to enter split screen mode for a given app -->
- <string name="recent_task_option_split_screen">Split screen</string>
<!-- Title for an option to keep an app pinned to the screen until it is unpinned -->
<string name="recent_task_option_pin">Pin</string>
<!-- Title for an option to enter freeform mode for a given app -->
diff --git a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
new file mode 100644
index 0000000..70a143e
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.model;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+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.app.prediction.AppTarget;
+import android.app.prediction.AppTargetId;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
+import com.android.launcher3.model.data.AppInfo;
+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.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowAppWidgetManager;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsPredicationUpdateTaskTest {
+
+ private AppWidgetProviderInfo mApp1Provider1 = new AppWidgetProviderInfo();
+ private AppWidgetProviderInfo mApp1Provider2 = new AppWidgetProviderInfo();
+ private AppWidgetProviderInfo mApp2Provider1 = new AppWidgetProviderInfo();
+ private AppWidgetProviderInfo mApp4Provider1 = new AppWidgetProviderInfo();
+ private AppWidgetProviderInfo mApp4Provider2 = new AppWidgetProviderInfo();
+ private AppWidgetProviderInfo mApp5Provider1 = new AppWidgetProviderInfo();
+
+ private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
+ private Context mContext;
+ private LauncherModelHelper mModelHelper;
+ private UserHandle mUserHandle;
+ private InvariantDeviceProfile mTestProfile;
+
+ @Mock
+ private IconCache mIconCache;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ComponentWithLabel componentWithLabel = invocation.getArgument(0);
+ return componentWithLabel.getComponent().getShortClassName();
+ }).when(mIconCache).getTitleNoCache(any());
+
+ mContext = RuntimeEnvironment.application;
+ mModelHelper = new LauncherModelHelper();
+ mUserHandle = Process.myUserHandle();
+ mTestProfile = new InvariantDeviceProfile();
+ // 2 widgets, app4/provider1 & app5/provider1, have already been added to the workspace.
+ mModelHelper.initializeData("/widgets_predication_update_task_data.txt");
+
+ ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+ mApp1Provider1.provider = ComponentName.createRelative("app1", "provider1");
+ ReflectionHelpers.setField(mApp1Provider1, "providerInfo",
+ packageManager.addReceiverIfNotPresent(mApp1Provider1.provider));
+ mApp1Provider2.provider = ComponentName.createRelative("app1", "provider2");
+ ReflectionHelpers.setField(mApp1Provider2, "providerInfo",
+ packageManager.addReceiverIfNotPresent(mApp1Provider2.provider));
+ mApp2Provider1.provider = ComponentName.createRelative("app2", "provider1");
+ ReflectionHelpers.setField(mApp2Provider1, "providerInfo",
+ packageManager.addReceiverIfNotPresent(mApp2Provider1.provider));
+ mApp4Provider1.provider = ComponentName.createRelative("app4", "provider1");
+ ReflectionHelpers.setField(mApp4Provider1, "providerInfo",
+ packageManager.addReceiverIfNotPresent(mApp4Provider1.provider));
+ mApp4Provider2.provider = ComponentName.createRelative("app4", ".provider2");
+ ReflectionHelpers.setField(mApp4Provider2, "providerInfo",
+ packageManager.addReceiverIfNotPresent(mApp4Provider2.provider));
+ mApp5Provider1.provider = ComponentName.createRelative("app5", "provider1");
+ ReflectionHelpers.setField(mApp5Provider1, "providerInfo",
+ packageManager.addReceiverIfNotPresent(mApp5Provider1.provider));
+
+ ShadowAppWidgetManager shadowAppWidgetManager =
+ shadowOf(mContext.getSystemService(AppWidgetManager.class));
+ shadowAppWidgetManager.addInstalledProvider(mApp1Provider1);
+ shadowAppWidgetManager.addInstalledProvider(mApp1Provider2);
+ shadowAppWidgetManager.addInstalledProvider(mApp2Provider1);
+ shadowAppWidgetManager.addInstalledProvider(mApp4Provider1);
+ shadowAppWidgetManager.addInstalledProvider(mApp4Provider2);
+ shadowAppWidgetManager.addInstalledProvider(mApp5Provider1);
+
+ mModelHelper.getModel().addCallbacks(mCallback);
+
+ MODEL_EXECUTOR.post(() -> mModelHelper.getBgDataModel().widgetsModel.update(
+ LauncherAppState.getInstance(mContext), /* packageUser= */ null));
+ waitUntilIdle();
+ }
+
+
+ @Test
+ public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder()
+ throws Exception {
+ // WHEN newPredicationTask is executed with app predication of 5 apps.
+ AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "className",
+ mUserHandle);
+ AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "className",
+ mUserHandle);
+ AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
+ mUserHandle);
+ AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "className",
+ mUserHandle);
+ AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "className",
+ mUserHandle);
+ mModelHelper.executeTaskForTest(
+ newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)))
+ .forEach(Runnable::run);
+
+ // THEN only 3 widgets are returned because
+ // 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
+ // excluded from the result.
+ // 2. app3 doesn't have a widget.
+ // 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
+ List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
+ .stream()
+ .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
+ .collect(Collectors.toList());
+ assertThat(recommendedWidgets).hasSize(3);
+ assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
+ assertWidgetInfo(recommendedWidgets.get(1).info, mApp4Provider2);
+ assertWidgetInfo(recommendedWidgets.get(2).info, mApp1Provider1);
+ }
+
+ private void assertWidgetInfo(
+ LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
+ assertThat(actual.provider).isEqualTo(expected.provider);
+ assertThat(actual.getUser()).isEqualTo(expected.getProfile());
+ }
+
+ private void waitUntilIdle() {
+ shadowOf(MODEL_EXECUTOR.getLooper()).idle();
+ shadowOf(MAIN_EXECUTOR.getLooper()).idle();
+ }
+
+ private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List<AppTarget> appTargets) {
+ return new WidgetsPredictionUpdateTask(
+ new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"),
+ appTargets);
+ }
+
+ private final class FakeBgDataModelCallback implements BgDataModel.Callbacks {
+
+ private FixedContainerItems mRecommendedWidgets = null;
+
+ @Override
+ public void bindExtraContainerItems(FixedContainerItems item) {
+ mRecommendedWidgets = item;
+ }
+
+ @Override
+ public int getPageToBindSynchronously() {
+ return 0;
+ }
+
+ @Override
+ public void clearPendingBinds() { }
+
+ @Override
+ public void startBinding() { }
+
+ @Override
+ public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+
+ @Override
+ public void bindScreens(IntArray orderedScreenIds) { }
+
+ @Override
+ public void finishFirstPageBind(ViewOnDrawExecutor executor) { }
+
+ @Override
+ public void finishBindingItems(int pageBoundFirst) { }
+
+ @Override
+ public void preAddApps() { }
+
+ @Override
+ public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
+ ArrayList<ItemInfo> addAnimated) { }
+
+ @Override
+ public void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
+
+ @Override
+ public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
+
+ @Override
+ public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+
+ @Override
+ public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+ @Override
+ public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+
+ @Override
+ public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
+
+ @Override
+ public void onPageBoundSynchronously(int page) { }
+
+ @Override
+ public void executeOnNextDraw(ViewOnDrawExecutor executor) { }
+
+ @Override
+ public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
+
+ @Override
+ public void bindAllApplications(AppInfo[] apps, int flags) { }
+ }
+}
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index 7049af0..9df9ab1 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -37,6 +37,7 @@
@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
+@org.junit.Ignore
public class RecentsActivityTest {
@Test
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 688f323..88079ae 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -17,6 +17,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -29,6 +31,7 @@
import com.android.launcher3.shadows.LShadowDisplay;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.LauncherActivityInterface;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import org.hamcrest.Description;
@@ -162,7 +165,7 @@
@Override
public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
- proxy.onBuildTargetParams(builder, null, this);
+ proxy.onBuildTargetParams(builder, mock(RemoteAnimationTargetCompat.class), this);
return new SurfaceParams[] {builder.build()};
}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index d1fa2fd..6ba7414 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -21,11 +21,11 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_SIZE;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.app.ActivityOptions;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
@@ -35,6 +35,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -45,7 +46,9 @@
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarController;
import com.android.launcher3.taskbar.TaskbarStateHandler;
+import com.android.launcher3.taskbar.TaskbarView;
import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.RecentsModel;
@@ -56,12 +59,15 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.util.List;
import java.util.stream.Stream;
/**
@@ -71,31 +77,34 @@
implements NavigationModeChangeListener {
private DepthController mDepthController = new DepthController(this);
+ private QuickstepTransitionManager mAppTransitionManager;
/**
* Reusable command for applying the back button alpha on the background thread.
*/
public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
- (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
+ (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setNavBarButtonAlpha(
Float.intBitsToFloat(arg1), arg2 != 0);
private OverviewActionsView mActionsView;
private @Nullable TaskbarController mTaskbarController;
private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
+ // Will be updated when dragging from taskbar.
+ private @Nullable DragOptions mNextWorkspaceDragOptions = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
addMultiWindowModeChangedListener(mDepthController);
}
@Override
public void onDestroy() {
- SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+ mAppTransitionManager.onActivityDestroyed();
+ SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
if (mTaskbarController != null) {
mTaskbarController.cleanup();
}
@@ -103,6 +112,10 @@
super.onDestroy();
}
+ public QuickstepTransitionManager getAppTransitionManager() {
+ return mAppTransitionManager;
+ }
+
@Override
public void onNavigationModeChanged(Mode newMode) {
getDragLayer().recreateControllers();
@@ -202,9 +215,17 @@
SysUINavigationMode.INSTANCE.get(this).updateMode();
mActionsView = findViewById(R.id.overview_actions_view);
- ((RecentsView) getOverviewPanel()).init(mActionsView);
+ SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
+ RecentsView overviewPanel = (RecentsView) getOverviewPanel();
+ splitPlaceholderView.init(
+ new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this))
+ );
+ overviewPanel.init(mActionsView, splitPlaceholderView);
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
+ mAppTransitionManager = new QuickstepTransitionManager(this);
+ mAppTransitionManager.registerRemoteAnimations();
+
addTaskbarIfNecessary();
addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
}
@@ -222,10 +243,11 @@
mTaskbarController.cleanup();
mTaskbarController = null;
}
- if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) {
+ if (mDeviceProfile.isTaskbarPresent) {
+ TaskbarView taskbarViewOnHome = (TaskbarView) mHotseat.getTaskbarView();
TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
mTaskbarController = new TaskbarController(this,
- taskbarActivityContext.getTaskbarContainerView());
+ taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome);
mTaskbarController.init();
}
}
@@ -241,15 +263,12 @@
}
@Override
- protected StateHandler<LauncherState>[] createStateHandlers() {
- return new StateHandler[] {
- getAllAppsController(),
- getWorkspace(),
- getDepthController(),
- new RecentsViewStateController(this),
- new BackButtonAlphaHandler(this),
- getTaskbarStateHandler(),
- };
+ protected void collectStateHandlers(List<StateHandler> out) {
+ super.collectStateHandlers(out);
+ out.add(getDepthController());
+ out.add(new RecentsViewStateController(this));
+ out.add(new BackButtonAlphaHandler(this));
+ out.add(getTaskbarStateHandler());
}
public DepthController getDepthController() {
@@ -269,10 +288,29 @@
return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v);
}
+ public boolean supportsAdaptiveIconAnimation(View clickedView) {
+ return mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+ && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
+ && !isViewInTaskbar(clickedView);
+ }
+
+ @Override
+ public DragOptions getDefaultWorkspaceDragOptions() {
+ if (mNextWorkspaceDragOptions != null) {
+ DragOptions options = mNextWorkspaceDragOptions;
+ mNextWorkspaceDragOptions = null;
+ return options;
+ }
+ return super.getDefaultWorkspaceDragOptions();
+ }
+
+ public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
+ mNextWorkspaceDragOptions = dragOptions;
+ }
+
@Override
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
- QuickstepAppTransitionManagerImpl appTransitionManager =
- (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
+ QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
@Override
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
@@ -341,8 +379,10 @@
*/
private void onLauncherStateOrFocusChanged() {
boolean shouldBackButtonBeHidden = shouldBackButtonBeHidden(getStateManager().getState());
- UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
- shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+ if (SysUINavigationMode.getMode(this) == TWO_BUTTONS) {
+ UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
+ shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+ }
if (getDragLayer() != null) {
getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
}
@@ -364,10 +404,14 @@
}
@Override
- public ActivityOptions getActivityLaunchOptions(View v) {
- ActivityOptions activityOptions = super.getActivityLaunchOptions(v);
- if (activityOptions != null && mLastTouchUpTime > 0) {
- ActivityOptionsCompat.setLauncherSourceInfo(activityOptions, mLastTouchUpTime);
+ public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+ ActivityOptionsWrapper activityOptions =
+ mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+ ? mAppTransitionManager.getActivityLaunchOptions(this, v)
+ : super.getActivityLaunchOptions(v);
+ if (mLastTouchUpTime > 0) {
+ ActivityOptionsCompat.setLauncherSourceInfo(
+ activityOptions.options, mLastTouchUpTime);
}
return activityOptions;
}
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 588d676..be98157 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
@@ -29,6 +30,7 @@
import android.os.Handler;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -37,8 +39,6 @@
@TargetApi(Build.VERSION_CODES.P)
public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
- private static final String TAG = "LauncherAnimationRunner";
-
private final Handler mHandler;
private final boolean mStartAtFrontOfQueue;
private AnimationResult mAnimationResult;
@@ -66,10 +66,7 @@
Runnable runnable) {
Runnable r = () -> {
finishExistingAnimation();
- mAnimationResult = new AnimationResult(() -> {
- UI_HELPER_EXECUTOR.execute(runnable);
- mAnimationResult = null;
- });
+ mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);
onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,
mAnimationResult);
};
@@ -126,37 +123,60 @@
public static final class AnimationResult {
- private final Runnable mFinishRunnable;
+ private final Runnable mSyncFinishRunnable;
+ private final Runnable mASyncFinishRunnable;
private AnimatorSet mAnimator;
+ private Runnable mOnCompleteCallback;
private boolean mFinished = false;
private boolean mInitialized = false;
- private AnimationResult(Runnable finishRunnable) {
- mFinishRunnable = finishRunnable;
+ private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) {
+ mSyncFinishRunnable = syncFinishRunnable;
+ mASyncFinishRunnable = asyncFinishRunnable;
}
@UiThread
private void finish() {
if (!mFinished) {
- mFinishRunnable.run();
+ mSyncFinishRunnable.run();
+ UI_HELPER_EXECUTOR.execute(() -> {
+ mASyncFinishRunnable.run();
+ if (mOnCompleteCallback != null) {
+ MAIN_EXECUTOR.execute(mOnCompleteCallback);
+ }
+ });
mFinished = true;
}
}
@UiThread
public void setAnimation(AnimatorSet animation, Context context) {
+ setAnimation(animation, context, null);
+
+ }
+
+ /**
+ * Sets the animation to play for this app launch
+ */
+ @UiThread
+ public void setAnimation(AnimatorSet animation, Context context,
+ @Nullable Runnable onCompleteCallback) {
if (mInitialized) {
throw new IllegalStateException("Animation already initialized");
}
mInitialized = true;
mAnimator = animation;
+ mOnCompleteCallback = onCompleteCallback;
if (mAnimator == null) {
finish();
} else if (mFinished) {
// Animation callback was already finished, skip the animation.
mAnimator.start();
mAnimator.end();
+ if (mOnCompleteCallback != null) {
+ mOnCompleteCallback.run();
+ }
} else {
// Start the animation
mAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
deleted file mode 100644
index ae4bd96..0000000
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.quickstep.TaskViewUtils;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
- * {@link RecentsView}.
- */
-public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
-
- public LauncherAppTransitionManagerImpl(Context context) {
- super(context);
- }
-
- @Override
- protected boolean isLaunchingFromRecents(@NonNull View v,
- @Nullable RemoteAnimationTargetCompat[] targets) {
- return mLauncher.getStateManager().getState().overviewUi
- && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
- }
-
- @Override
- protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
- @NonNull RemoteAnimationTargetCompat[] appTargets,
- @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
- TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
- launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
- mLauncher.getDepthController());
- }
-
- @Override
- protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, float[] alphas,
- float[] trans) {
- RecentsView overview = mLauncher.getOverviewPanel();
- ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
- RecentsView.CONTENT_ALPHA, alphas);
- alpha.setDuration(CONTENT_ALPHA_DURATION);
- alpha.setInterpolator(LINEAR);
- anim.play(alpha);
- overview.setFreezeViewVisibility(true);
-
- ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
- transY.setInterpolator(AGGRESSIVE_EASE);
- transY.setDuration(CONTENT_TRANSLATION_DURATION);
- anim.play(transY);
-
- return () -> {
- overview.setFreezeViewVisibility(false);
- overview.setTranslationY(0);
- mLauncher.getStateManager().reapplyState();
- };
- }
-}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 7fb0d43..5fc79f0 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -46,8 +46,8 @@
@Override
public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
if (mRemoteAnimationProvider != null) {
- QuickstepAppTransitionManagerImpl appTransitionManager =
- (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
+ QuickstepTransitionManager appTransitionManager =
+ ((BaseQuickstepLauncher) launcher).getAppTransitionManager();
// Set a one-time animation provider. After the first call, this will get cleared.
// TODO: Probably also check the intended target id.
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
similarity index 81%
rename from quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
rename to quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c4b6961..64b22d4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -16,6 +16,9 @@
package com.android.launcher3;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -24,7 +27,6 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
@@ -34,6 +36,7 @@
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -44,7 +47,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
-import android.app.ActivityOptions;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -64,22 +66,24 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -91,14 +95,14 @@
import com.android.systemui.shared.system.RemoteTransitionCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.wm.shell.startingsurface.IStartingWindowListener;
+
+import java.util.LinkedHashMap;
/**
- * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
- * home and/or all-apps. Not used for 3p launchers.
+ * Manages the opening and closing app transitions from Launcher
*/
-@SuppressWarnings("unused")
-public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
- implements OnDeviceProfileChangeListener {
+public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
private static final String TAG = "QuickstepTransition";
@@ -109,8 +113,8 @@
public static final int STATUS_BAR_TRANSITION_DURATION = 120;
/**
- * Since our animations decelerate heavily when finishing, we want to start status bar animations
- * x ms before the ending.
+ * Since our animations decelerate heavily when finishing, we want to start status bar
+ * animations x ms before the ending.
*/
public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
@@ -142,8 +146,7 @@
public static final int CONTENT_ALPHA_DURATION = 217;
protected static final int CONTENT_TRANSLATION_DURATION = 350;
- // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
- public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
+ private static final int MAX_NUM_TASKS = 5;
protected final BaseQuickstepLauncher mLauncher;
@@ -180,7 +183,10 @@
}
};
- public QuickstepAppTransitionManagerImpl(Context context) {
+ // Will never be larger than MAX_NUM_TASKS
+ private LinkedHashMap<Integer, Integer> mTypeForTaskId;
+
+ public QuickstepTransitionManager(Context context) {
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mDragLayer = mLauncher.getDragLayer();
mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
@@ -194,6 +200,23 @@
mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
mLauncher.addOnDeviceProfileChangeListener(this);
+
+ if (supportsSSplashScreen()) {
+ mTypeForTaskId = new LinkedHashMap<Integer, Integer>(MAX_NUM_TASKS) {
+ @Override
+ protected boolean removeEldestEntry(Entry<Integer, Integer> entry) {
+ return size() > MAX_NUM_TASKS;
+ }
+ };
+
+ SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
+ new IStartingWindowListener.Stub() {
+ @Override
+ public void onTaskLaunching(int taskId, int supportedType) {
+ mTypeForTaskId.put(taskId, supportedType);
+ }
+ });
+ }
}
@Override
@@ -201,37 +224,29 @@
mDeviceProfile = dp;
}
- @Override
- public boolean supportsAdaptiveIconAnimation(View clickedView) {
- return hasControlRemoteAppTransitionPermission()
- && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
- && !mLauncher.isViewInTaskbar(clickedView);
- }
-
/**
* @return ActivityOptions with remote animations that controls how the window of the opening
* targets are displayed.
*/
- @Override
- public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
- if (hasControlRemoteAppTransitionPermission()) {
- boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
- mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v);
- RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
- mAppLaunchRunner, true /* startAtFrontOfQueue */);
+ public ActivityOptionsWrapper getActivityLaunchOptions(Launcher launcher, View v) {
+ boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
+ RunnableList onEndCallback = new RunnableList();
+ mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v, onEndCallback);
+ RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
+ mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
- // Note that this duration is a guess as we do not know if the animation will be a
- // recents launch or not for sure until we know the opening app targets.
- long duration = fromRecents
- ? RECENTS_LAUNCH_DURATION
- : APP_LAUNCH_DURATION;
+ // Note that this duration is a guess as we do not know if the animation will be a
+ // recents launch or not for sure until we know the opening app targets.
+ long duration = fromRecents
+ ? RECENTS_LAUNCH_DURATION
+ : APP_LAUNCH_DURATION;
- long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
- - STATUS_BAR_TRANSITION_PRE_DELAY;
- return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
- runner, duration, statusBarTransitionDelay));
- }
- return super.getActivityLaunchOptions(launcher, v);
+ long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
+ - STATUS_BAR_TRANSITION_PRE_DELAY;
+ RemoteAnimationAdapterCompat adapterCompat =
+ new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay);
+ return new ActivityOptionsWrapper(
+ ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
}
/**
@@ -244,8 +259,11 @@
* @param targets apps that are opening/closing
* @return true if the app is launching from recents, false if it most likely is not
*/
- protected abstract boolean isLaunchingFromRecents(@NonNull View v,
- @Nullable RemoteAnimationTargetCompat[] targets);
+ protected boolean isLaunchingFromRecents(@NonNull View v,
+ @Nullable RemoteAnimationTargetCompat[] targets) {
+ return mLauncher.getStateManager().getState().overviewUi
+ && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
+ }
/**
* Composes the animations for a launch from the recents list.
@@ -255,9 +273,13 @@
* @param appTargets the apps that are opening/closing
* @param launcherClosing true if the launcher app is closing
*/
- protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+ protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
- @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
+ TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
+ launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
+ mLauncher.getDepthController());
+ }
private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTargetCompat[] targets) {
boolean isAllOpeningTargetTrs = true;
@@ -287,9 +309,11 @@
// before our internal listeners.
mLauncher.getStateManager().setCurrentAnimation(anim);
- Rect windowTargetBounds = getWindowTargetBounds(appTargets);
+ final int rotationChange = getRotationChange(appTargets);
+ // Note: the targetBounds are relative to the launcher
+ Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange);
anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds,
- areAllTargetsTranslucent(appTargets)));
+ areAllTargetsTranslucent(appTargets), rotationChange));
if (launcherClosing) {
Pair<AnimatorSet, Runnable> launcherContentAnimator =
getLauncherContentAnimator(true /* isAppOpening */,
@@ -318,24 +342,43 @@
* In multiwindow mode, we need to get the final size of the opening app window target to help
* figure out where the floating view should animate to.
*/
- private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets) {
- Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
- if (mLauncher.isInMultiWindowMode()) {
- for (RemoteAnimationTargetCompat target : appTargets) {
- if (target.mode == MODE_OPENING) {
- bounds.set(target.screenSpaceBounds);
- if (target.localBounds != null) {
- bounds.set(target.localBounds);
- } else {
- bounds.offsetTo(target.position.x, target.position.y);
- }
- return bounds;
- }
+ private Rect getWindowTargetBounds(@NonNull RemoteAnimationTargetCompat[] appTargets,
+ int rotationChange) {
+ RemoteAnimationTargetCompat target = null;
+ for (RemoteAnimationTargetCompat t : appTargets) {
+ if (t.mode != MODE_OPENING) continue;
+ target = t;
+ break;
+ }
+ if (target == null) return new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
+ final Rect bounds = new Rect(target.screenSpaceBounds);
+ if (target.localBounds != null) {
+ bounds.set(target.localBounds);
+ } else {
+ bounds.offsetTo(target.position.x, target.position.y);
+ }
+ if (rotationChange != 0) {
+ if ((rotationChange % 2) == 1) {
+ // undoing rotation, so our "original" parent size is actually flipped
+ Utilities.rotateBounds(bounds, mDeviceProfile.heightPx, mDeviceProfile.widthPx,
+ 4 - rotationChange);
+ } else {
+ Utilities.rotateBounds(bounds, mDeviceProfile.widthPx, mDeviceProfile.heightPx,
+ 4 - rotationChange);
}
}
return bounds;
}
+ private int getOpeningTaskId(RemoteAnimationTargetCompat[] appTargets) {
+ for (RemoteAnimationTargetCompat target : appTargets) {
+ if (target.mode == MODE_OPENING) {
+ return target.taskId;
+ }
+ }
+ return -1;
+ }
+
public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
CancellationSignal cancellationSignal) {
mRemoteAnimationProvider = animationProvider;
@@ -393,9 +436,6 @@
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
};
} else if (mLauncher.isInState(OVERVIEW)) {
- AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
- allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
} else {
mDragLayerAlpha.setValue(alphas[0]);
@@ -446,8 +486,27 @@
* @param trans the translation Y values to animator to over time
* @return listener to run when the animation ends
*/
- protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
- float[] alphas, float[] trans);
+ protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
+ float[] alphas, float[] trans) {
+ RecentsView overview = mLauncher.getOverviewPanel();
+ ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+ RecentsView.CONTENT_ALPHA, alphas);
+ alpha.setDuration(CONTENT_ALPHA_DURATION);
+ alpha.setInterpolator(LINEAR);
+ anim.play(alpha);
+ overview.setFreezeViewVisibility(true);
+
+ ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+ transY.setInterpolator(AGGRESSIVE_EASE);
+ transY.setDuration(CONTENT_TRANSLATION_DURATION);
+ anim.play(transY);
+
+ return () -> {
+ overview.setFreezeViewVisibility(false);
+ overview.setTranslationY(0);
+ mLauncher.getStateManager().reapplyState();
+ };
+ }
/**
* @return Animator that controls the window of the opening targets from app icons.
@@ -455,7 +514,7 @@
private Animator getOpeningWindowAnimators(View v,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
- Rect windowTargetBounds, boolean appTargetsAreTranslucent) {
+ Rect windowTargetBounds, boolean appTargetsAreTranslucent, int rotationChange) {
RectF launcherIconBounds = new RectF();
FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
!appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);
@@ -471,8 +530,19 @@
int[] dragLayerBounds = new int[2];
mDragLayer.getLocationOnScreen(dragLayerBounds);
+ final boolean hasSplashScreen;
+ if (supportsSSplashScreen()) {
+ int taskId = getOpeningTaskId(appTargets);
+ int type = mTypeForTaskId.getOrDefault(taskId, STARTING_WINDOW_TYPE_NONE);
+ mTypeForTaskId.remove(taskId);
+ hasSplashScreen = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+ } else {
+ hasSplashScreen = false;
+ }
+
AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,
- windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1]);
+ windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],
+ hasSplashScreen);
int left = (int) (prop.cropCenterXStart - prop.cropWidthStart / 2);
int top = (int) (prop.cropCenterYStart - prop.cropHeightStart / 2);
int right = (int) (left + prop.cropWidthStart);
@@ -544,6 +614,10 @@
final int windowCropWidth = crop.width();
final int windowCropHeight = crop.height();
+ if (rotationChange != 0) {
+ Utilities.rotateBounds(crop, mDeviceProfile.widthPx,
+ mDeviceProfile.heightPx, rotationChange);
+ }
// Scale the size of the icon to match the size of the window crop.
float scaleX = iconWidth / windowCropWidth;
@@ -562,7 +636,7 @@
Utilities.scaleRectFAboutCenter(tmpRectF, mIconScaleToFitScreen.value);
float windowTransX0 = tmpRectF.left - offsetX;
float windowTransY0 = tmpRectF.top - offsetY;
- if (ENABLE_SHELL_STARTING_SURFACE) {
+ if (hasSplashScreen) {
windowTransX0 -= crop.left * scale;
windowTransY0 -= crop.top * scale;
}
@@ -583,7 +657,20 @@
if (target.mode == MODE_OPENING) {
matrix.setScale(scale, scale);
- matrix.postTranslate(windowTransX0, windowTransY0);
+ if (rotationChange == 1) {
+ matrix.postTranslate(windowTransY0,
+ mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth));
+ } else if (rotationChange == 2) {
+ matrix.postTranslate(
+ mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth),
+ mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight));
+ } else if (rotationChange == 3) {
+ matrix.postTranslate(
+ mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight),
+ windowTransX0);
+ } else {
+ matrix.postTranslate(windowTransX0, windowTransY0);
+ }
floatingView.update(floatingIconBounds, mIconAlpha.value, percent, 0f,
mWindowRadius.value * scale, true /* isOpening */);
@@ -592,17 +679,25 @@
.withAlpha(1f - mIconAlpha.value)
.withCornerRadius(mWindowRadius.value)
.withShadowRadius(mShadowRadius.value);
- } else {
+ } else if (target.mode == MODE_CLOSING) {
if (target.localBounds != null) {
final Rect localBounds = target.localBounds;
tmpPos.set(target.localBounds.left, target.localBounds.top);
} else {
tmpPos.set(target.position.x, target.position.y);
}
-
- matrix.setTranslate(tmpPos.x, tmpPos.y);
final Rect crop = new Rect(target.screenSpaceBounds);
crop.offsetTo(0, 0);
+
+ if ((rotationChange % 2) == 1) {
+ int tmp = crop.right;
+ crop.right = crop.bottom;
+ crop.bottom = tmp;
+ tmp = tmpPos.x;
+ tmpPos.x = tmpPos.y;
+ tmpPos.y = tmp;
+ }
+ matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
.withWindowCrop(crop)
.withAlpha(1f);
@@ -638,7 +733,6 @@
/**
* Registers remote animations used when closing apps to home screen.
*/
- @Override
public void registerRemoteAnimations() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
@@ -650,7 +744,7 @@
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
new RemoteAnimationAdapterCompat(
- new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
+ new WrappedLauncherAnimationRunner<>(mHandler, mWallpaperOpenRunner,
false /* startAtFrontOfQueue */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
@@ -659,7 +753,8 @@
definition.addRemoteAnimation(
WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
new RemoteAnimationAdapterCompat(
- new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner,
+ new WrappedLauncherAnimationRunner<>(
+ mHandler, mKeyguardGoingAwayRunner,
true /* startAtFrontOfQueue */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
}
@@ -671,7 +766,6 @@
/**
* Registers remote animations used when closing apps to home screen.
*/
- @Override
public void registerRemoteTransitions() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
@@ -679,18 +773,20 @@
if (hasControlRemoteAppTransitionPermission()) {
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
- new WrappedLauncherAnimationRunner<>(mWallpaperOpenTransitionRunner,
+ new WrappedLauncherAnimationRunner<>(mHandler, mWallpaperOpenTransitionRunner,
false /* startAtFrontOfQueue */));
mLauncherOpenTransition.addHomeOpenCheck();
SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
}
}
- /**
- * Unregisters all remote animations.
- */
- @Override
- public void unregisterRemoteAnimations() {
+ public void onActivityDestroyed() {
+ unregisterRemoteAnimations();
+ unregisterRemoteTransitions();
+ SystemUiProxy.INSTANCE.getNoCreate().setStartingWindowListener(null);
+ }
+
+ private void unregisterRemoteAnimations() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
@@ -705,8 +801,7 @@
}
}
- @Override
- public void unregisterRemoteTransitions() {
+ private void unregisterRemoteTransitions() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
@@ -759,14 +854,26 @@
return unlockAnimator;
}
+ private static int getRotationChange(RemoteAnimationTargetCompat[] appTargets) {
+ int rotationChange = 0;
+ for (RemoteAnimationTargetCompat target : appTargets) {
+ if (Math.abs(target.rotationChange) > Math.abs(rotationChange)) {
+ rotationChange = target.rotationChange;
+ }
+ }
+ return rotationChange;
+ }
+
/**
* Animator that controls the transformations of the windows the targets that are closing.
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
+ final int rotationChange = getRotationChange(appTargets);
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
Matrix matrix = new Matrix();
Point tmpPos = new Point();
+ Rect tmpRect = new Rect();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
float windowCornerRadius = mDeviceProfile.isMultiWindowMode
@@ -793,26 +900,32 @@
tmpPos.set(target.position.x, target.position.y);
}
+ final Rect crop = new Rect(target.screenSpaceBounds);
+ crop.offsetTo(0, 0);
if (target.mode == MODE_CLOSING) {
+ tmpRect.set(target.screenSpaceBounds);
+ if ((rotationChange % 2) != 0) {
+ final int right = crop.right;
+ crop.right = crop.bottom;
+ crop.bottom = right;
+ }
matrix.setScale(mScale.value, mScale.value,
- target.screenSpaceBounds.centerX(),
- target.screenSpaceBounds.centerY());
+ tmpRect.centerX(),
+ tmpRect.centerY());
matrix.postTranslate(0, mDy.value);
matrix.postTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
+ .withWindowCrop(crop)
.withAlpha(mAlpha.value)
.withCornerRadius(windowCornerRadius)
.withShadowRadius(mShadowRadius.value);
- } else {
+ } else if (target.mode == MODE_OPENING) {
matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.withMatrix(matrix)
+ .withWindowCrop(crop)
.withAlpha(1f);
}
- final Rect crop = new Rect(target.screenSpaceBounds);
- crop.offsetTo(0, 0);
- params[i] = builder
- .withWindowCrop(crop)
- .build();
+ params[i] = builder.build();
}
surfaceApplier.scheduleApply(params);
}
@@ -821,7 +934,16 @@
return closingAnimator;
}
- private boolean hasControlRemoteAppTransitionPermission() {
+ private boolean supportsSSplashScreen() {
+ return hasControlRemoteAppTransitionPermission()
+ && Utilities.ATLEAST_S
+ && ENABLE_SHELL_STARTING_SURFACE;
+ }
+
+ /**
+ * Returns true if we have permission to control remote app transisions
+ */
+ public boolean hasControlRemoteAppTransitionPermission() {
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
}
@@ -861,11 +983,6 @@
}
@Override
- public Handler getHandler() {
- return mHandler;
- }
-
- @Override
public void onCreateAnimation(int transit,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
@@ -955,15 +1072,12 @@
private final Handler mHandler;
private final View mV;
+ private final RunnableList mOnEndCallback;
- AppLaunchAnimationRunner(Handler handler, View v) {
+ AppLaunchAnimationRunner(Handler handler, View v, RunnableList onEndCallback) {
mHandler = handler;
mV = v;
- }
-
- @Override
- public Handler getHandler() {
- return mHandler;
+ mOnEndCallback = onEndCallback;
}
@Override
@@ -998,7 +1112,7 @@
anim.addListener(mForceInvisibleListener);
}
- result.setAnimation(anim, mLauncher);
+ result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy);
}
}
@@ -1030,7 +1144,8 @@
public final float iconAlphaStart;
AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds,
- RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop) {
+ RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop,
+ boolean hasSplashScreen) {
// Scale the app icon to take up the entire screen. This simplifies the math when
// animating the app window position / scale.
float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width());
@@ -1063,7 +1178,7 @@
alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
: APP_LAUNCH_ALPHA_DOWN_DURATION;
- if (ENABLE_SHELL_STARTING_SURFACE) {
+ if (hasSplashScreen) {
iconAlphaStart = 0;
// TOOD: Share value from shell when available.
diff --git a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
index 03cc28e..16727ec 100644
--- a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
+++ b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import android.os.Handler;
-
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
@@ -25,7 +23,7 @@
* implementation.
*/
public interface WrappedAnimationRunnerImpl {
- Handler getHandler();
+
void onCreateAnimation(int transit,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
diff --git a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
index 1e1631b..e319275 100644
--- a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import android.os.Handler;
+
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.lang.ref.WeakReference;
@@ -40,8 +42,9 @@
extends LauncherAnimationRunner {
private WeakReference<R> mImpl;
- public WrappedLauncherAnimationRunner(R animationRunnerImpl, boolean startAtFrontOfQueue) {
- super(animationRunnerImpl.getHandler(), startAtFrontOfQueue);
+ public WrappedLauncherAnimationRunner(
+ Handler handler, R animationRunnerImpl, boolean startAtFrontOfQueue) {
+ super(handler, startAtFrontOfQueue);
mImpl = new WeakReference<>(animationRunnerImpl);
}
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index b891120..d17a5ae 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -16,7 +16,6 @@
package com.android.launcher3.appprediction;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.ALL_APPS;
import android.annotation.TargetApi;
@@ -30,7 +29,6 @@
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.Interpolator;
import androidx.annotation.ColorInt;
import androidx.core.content.ContextCompat;
@@ -41,8 +39,6 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.Themes;
@@ -183,11 +179,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));
@@ -293,13 +284,6 @@
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- // Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(this, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
- }
-
- @Override
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
setTranslationY(scroll);
mIsScrolledOut = isScrolledOut;
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index f53a5ef..8e92b59 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -16,20 +16,14 @@
package com.android.launcher3.appprediction;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.IntProperty;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
@@ -41,11 +35,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsSectionDecorator;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
@@ -54,8 +46,6 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.util.Themes;
-import com.android.quickstep.AnimatedFloat;
import java.util.ArrayList;
import java.util.List;
@@ -64,22 +54,6 @@
public class PredictionRowView extends LinearLayout implements
OnDeviceProfileChangeListener, FloatingHeaderRow {
- private static final IntProperty<PredictionRowView> TEXT_ALPHA =
- new IntProperty<PredictionRowView>("textAlpha") {
- @Override
- public void setValue(PredictionRowView view, int alpha) {
- view.setTextAlpha(alpha);
- }
-
- @Override
- public Integer get(PredictionRowView view) {
- return view.mIconLastSetTextAlpha;
- }
- };
-
- private static final Interpolator ALPHA_FACTOR_INTERPOLATOR =
- (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
-
private final Launcher mLauncher;
private int mNumPredictedAppsPerRow;
@@ -89,26 +63,13 @@
// The set of predicted apps resolved from the component names and the current set of apps
private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
- private final int mIconTextColor;
- private final int mIconFullTextAlpha;
- private int mIconLastSetTextAlpha;
- // Might use mIconFullTextAlpha instead of mIconLastSetTextAlpha if we are translucent.
- private int mIconCurrentTextAlpha;
-
private FloatingHeaderView mParent;
private boolean mScrolledOut;
- private float mScrollTranslation = 0;
- private final AnimatedFloat mContentAlphaFactor =
- new AnimatedFloat(this::updateTranslationAndAlpha);
- private final AnimatedFloat mOverviewScrollFactor =
- new AnimatedFloat(this::updateTranslationAndAlpha);
-
private boolean mPredictionsEnabled = false;
- AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
-
- @Nullable private List<ItemInfo> mPendingPredictedItems;
+ @Nullable
+ private List<ItemInfo> mPendingPredictedItems;
public PredictionRowView(@NonNull Context context) {
this(context, null);
@@ -119,20 +80,9 @@
setOrientation(LinearLayout.HORIZONTAL);
mFocusHelper = new SimpleFocusIndicatorHelper(this);
-
mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numAllAppsColumns;
mLauncher = Launcher.getLauncher(context);
mLauncher.addOnDeviceProfileChangeListener(this);
-
- mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary);
- mIconFullTextAlpha = Color.alpha(mIconTextColor);
- mIconCurrentTextAlpha = mIconFullTextAlpha;
-
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- mDecorationHandler = new AllAppsSectionDecorator.SectionDecorationHandler(getContext(),
- false);
- }
-
updateVisibility();
}
@@ -158,16 +108,6 @@
@Override
protected void dispatchDraw(Canvas canvas) {
- if (mDecorationHandler != null) {
- mDecorationHandler.reset();
- int childrenCount = getChildCount();
- for (int i = 0; i < childrenCount; i++) {
- mDecorationHandler.extendBounds(getChildAt(i));
- }
- mDecorationHandler.onDraw(canvas);
- mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
- mLauncher.getAppsView().getActiveRecyclerView().invalidateItemDecorations();
- }
mFocusHelper.draw(canvas);
super.dispatchDraw(canvas);
}
@@ -181,7 +121,7 @@
@Override
public boolean shouldDraw() {
- return getVisibility() == VISIBLE;
+ return getVisibility() != GONE;
}
@Override
@@ -189,6 +129,11 @@
return mPredictionsEnabled;
}
+ @Override
+ public boolean isVisible() {
+ return getVisibility() == VISIBLE;
+ }
+
/**
* Returns the predicted apps.
*/
@@ -258,7 +203,6 @@
}
int predictionCount = mPredictedApps.size();
- int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
for (int i = 0; i < getChildCount(); i++) {
BubbleTextView icon = (BubbleTextView) getChildAt(i);
@@ -266,7 +210,6 @@
if (predictionCount > i) {
icon.setVisibility(View.VISIBLE);
icon.applyFromWorkspaceItem(mPredictedApps.get(i));
- icon.setTextColor(iconColor);
} else {
icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
}
@@ -281,27 +224,6 @@
mParent.onHeightUpdated();
}
- public void setTextAlpha(int textAlpha) {
- mIconLastSetTextAlpha = textAlpha;
- if (getAlpha() < 1 && textAlpha > 0) {
- // If the entire header is translucent, make sure the text is at full opacity so it's
- // not double-translucent. However, we support keeping the text invisible (alpha == 0).
- textAlpha = mIconFullTextAlpha;
- }
- mIconCurrentTextAlpha = textAlpha;
- int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
- for (int i = 0; i < getChildCount(); i++) {
- ((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
- }
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- // Reapply text alpha so that we update it to be full alpha if the row is now translucent.
- setTextAlpha(mIconLastSetTextAlpha);
- }
-
@Override
public boolean hasOverlappingRendering() {
return false;
@@ -311,34 +233,11 @@
@Override
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
mScrolledOut = isScrolledOut;
- updateTranslationAndAlpha();
if (!isScrolledOut) {
- mScrollTranslation = scroll;
- updateTranslationAndAlpha();
+ setTranslationY(scroll);
}
- }
-
- private void updateTranslationAndAlpha() {
- if (mPredictionsEnabled) {
- setTranslationY((1 - mOverviewScrollFactor.value) * mScrollTranslation);
-
- float factor = ALPHA_FACTOR_INTERPOLATOR.getInterpolation(mOverviewScrollFactor.value);
- float endAlpha = factor + (1 - factor) * (mScrolledOut ? 0 : 1);
- setAlpha(mContentAlphaFactor.value * endAlpha);
- AlphaUpdateListener.updateVisibility(this);
- }
- }
-
- @Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- // Text follows all apps visibility
- int textAlpha = hasHeaderExtra && hasAllAppsContent ? mIconFullTextAlpha : 0;
- setter.setInt(this, TEXT_ALPHA, textAlpha, allAppsFade);
- setter.setFloat(mOverviewScrollFactor, AnimatedFloat.VALUE,
- (hasHeaderExtra && !hasAllAppsContent) ? 1 : 0, LINEAR);
- setter.setFloat(mContentAlphaFactor, AnimatedFloat.VALUE, hasHeaderExtra ? 1 : 0,
- headerFade);
+ setAlpha(mScrolledOut ? 0 : 1);
+ AlphaUpdateListener.updateVisibility(this);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 0156e8f..f297343 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -107,7 +107,8 @@
WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
v.setVisibility(View.INVISIBLE);
mLauncher.getWorkspace().beginDragShared(
- v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
+ v, null, this, dragItem, new DragPreviewProvider(v),
+ mLauncher.getDefaultWorkspaceDragOptions());
return true;
};
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 9944270..eed493d 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -42,6 +42,7 @@
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ShortcutInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
@@ -62,9 +63,11 @@
import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
import java.util.Locale;
+import java.util.Optional;
import java.util.function.ObjIntConsumer;
import java.util.function.Predicate;
@@ -174,6 +177,7 @@
return null;
}
ComponentName cn = null;
+ ShortcutInfo shortcutInfo = null;
String id = null;
switch (info.getItemCase()) {
@@ -188,6 +192,14 @@
LauncherAtom.Shortcut si = info.getShortcut();
if (!TextUtils.isEmpty(si.getShortcutId())
&& (cn = parseNullable(si.getShortcutName())) != null) {
+ Optional<ShortcutInfo> opt = new ShortcutRequest(mContext,
+ userHandle).forPackage(cn.getPackageName(), si.getShortcutId()).query(
+ ShortcutRequest.ALL).stream().findFirst();
+ if (opt.isPresent()) {
+ shortcutInfo = opt.get();
+ } else {
+ return null;
+ }
id = "shortcut:" + si.getShortcutId();
}
break;
@@ -210,6 +222,9 @@
return createTempFolderTarget();
}
if (id != null && cn != null) {
+ if (shortcutInfo != null) {
+ return new AppTarget.Builder(new AppTargetId(id), shortcutInfo).build();
+ }
return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
.setClassName(cn.getClassName())
.build();
@@ -217,6 +232,7 @@
return null;
}
+
private AppTarget createTempFolderTarget() {
return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
mContext.getPackageName(), Process.myUserHandle())
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 225823e..df3657d 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
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.Utilities.getDevicePrefs;
@@ -72,6 +73,7 @@
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
+ private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final boolean IS_DEBUG = false;
private static final String TAG = "QuickstepModelDelegate";
@@ -80,6 +82,8 @@
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
private final PredictorState mHotseatState =
new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
+ private final PredictorState mWidgetsRecommendationState =
+ new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction");
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
@@ -111,6 +115,9 @@
mHotseatState.items.setItems(
mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
mDataModel.extraItems.put(CONTAINER_HOTSEAT_PREDICTION, mHotseatState.items);
+
+ // Widgets prediction isn't used frequently. And thus, it is not persisted on disk.
+ mDataModel.extraItems.put(CONTAINER_WIDGETS_PREDICTION, mWidgetsRecommendationState.items);
mActive = true;
}
@@ -148,16 +155,22 @@
? (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();
if (mAllAppsState.predictor != null) {
mAllAppsState.predictor.requestPredictionUpdate();
}
+ if (mWidgetsRecommendationState.predictor != null) {
+ mWidgetsRecommendationState.predictor.requestPredictionUpdate();
+ }
}
@Override
@@ -173,6 +186,7 @@
private void destroyPredictors() {
mAllAppsState.destroyPredictor();
mHotseatState.destroyPredictor();
+ mWidgetsRecommendationState.destroyPredictor();
}
@WorkerThread
@@ -200,6 +214,12 @@
.setPredictedTargetCount(mIDP.numHotseatIcons)
.setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
.build()));
+
+ registerWidgetsPredictor(apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(context)
+ .setUiSurface("widgets")
+ .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
+ .build()));
}
private void registerPredictor(PredictorState state, AppPredictor predictor) {
@@ -217,6 +237,20 @@
mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
}
+ private void registerWidgetsPredictor(AppPredictor predictor) {
+ mWidgetsRecommendationState.predictor = predictor;
+ mWidgetsRecommendationState.predictor.registerPredictionUpdates(
+ Executors.MODEL_EXECUTOR, targets -> {
+ if (mWidgetsRecommendationState.setTargets(targets)) {
+ // No diff, skip
+ return;
+ }
+ mApp.getModel().enqueueModelUpdateTask(
+ new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets));
+ });
+ mWidgetsRecommendationState.predictor.requestPredictionUpdate();
+ }
+
@Override
public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
new file mode 100644
index 0000000..a29ac1a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.app.prediction.AppTarget;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Task to update model as a result of predicted widgets update */
+public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
+ private final PredictorState mPredictorState;
+ private final List<AppTarget> mTargets;
+
+ WidgetsPredictionUpdateTask(PredictorState predictorState, List<AppTarget> targets) {
+ mPredictorState = predictorState;
+ mTargets = targets;
+ }
+
+ /**
+ * Uses the app predication result to infer widgets that the user may want to use.
+ *
+ * <p>The algorithm uses the app prediction ranking to create a widgets ranking which only
+ * includes one widget per app and excludes widgets that have already been added to the
+ * workspace.
+ */
+ @Override
+ public void execute(LauncherAppState appState, BgDataModel dataModel, AllAppsList apps) {
+ Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
+ widget -> new ComponentKey(widget.providerName, widget.user)).collect(
+ Collectors.toSet());
+ Map<PackageUserKey, List<WidgetItem>> allWidgets =
+ dataModel.widgetsModel.getAllWidgetsWithoutShortcuts();
+
+ ArrayList<ItemInfo> recommendedWidgetsInDescendingOrder = new ArrayList<>();
+ for (AppTarget app : mTargets) {
+ PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser());
+ if (allWidgets.containsKey(packageUserKey)) {
+ List<WidgetItem> notAddedWidgets = allWidgets.get(packageUserKey).stream()
+ .filter(item ->
+ !widgetsInWorkspace.contains(
+ new ComponentKey(item.componentName, item.user)))
+ .collect(Collectors.toList());
+ if (notAddedWidgets.size() > 0) {
+ // Even an apps have more than one widgets, we only include one widget.
+ recommendedWidgetsInDescendingOrder.add(
+ new PendingAddWidgetInfo(notAddedWidgets.get(0).widgetInfo));
+ }
+ }
+ }
+ FixedContainerItems fixedContainerItems = mPredictorState.items;
+ fixedContainerItems.items.clear();
+ fixedContainerItems.items.addAll(recommendedWidgetsInDescendingOrder);
+ bindExtraContainerItems(fixedContainerItems);
+
+ // Don't store widgets prediction to disk because it is not used frequently.
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
index 13501a4..ce94305 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AnimatedFloat.VALUE;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
@@ -30,7 +31,7 @@
import com.android.quickstep.SystemUiProxy;
/**
- * State handler for animating back button alpha
+ * State handler for animating back button alpha in two-button nav mode.
*/
public class BackButtonAlphaHandler implements StateHandler<LauncherState> {
@@ -51,14 +52,11 @@
return;
}
- if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
- // If the nav mode is not gestural, then force back button alpha to be 1
- UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
- BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */);
+ if (SysUINavigationMode.getMode(mLauncher) != TWO_BUTTONS) {
return;
}
- mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
+ mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastNavButtonAlpha();
animation.setFloat(mBackAlpha, VALUE,
mLauncher.shouldBackButtonBeHidden(toState) ? 0 : 1, LINEAR);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 06372fe..5513c16 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -53,10 +53,7 @@
return mTaskbarContainerView;
}
- /**
- * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will
- * be able to access this TaskbarActivityContext via ActivityContext.lookupContext().
- */
+ @Override
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index ddd0d15..5202d91 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -83,10 +83,15 @@
private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
return insetsInfo -> {
- if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
- // We're invisible, let touches pass through us.
+ if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD
+ || mTaskbarView.getVisibility() != VISIBLE || mTaskbarView.isDraggingItem()) {
+ // We're invisible or dragging out of taskbar, let touches pass through us.
insetsInfo.touchableRegion.setEmpty();
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ // TODO(b/182234653): Shouldn't need to do this, but for the meantime, reporting
+ // that visibleInsets is empty allows DragEvents through. Setting them as completely
+ // empty reverts to default behavior, so set 1 px instead.
+ insetsInfo.visibleInsets.set(0, 0, 0, 1);
} else {
// We're visible again, accept touches anywhere in our bounds.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
@@ -97,7 +102,12 @@
// 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;
+ float scale = mTaskbarView.getScaleX();
+ mTaskbarView.setScaleX(1);
+ mTaskbarView.setScaleY(1);
mTaskbarView.getLocationInWindow(loc);
+ mTaskbarView.setScaleX(scale);
+ mTaskbarView.setScaleY(scale);
insetsInfo.contentInsets.left = loc[0];
insetsInfo.contentInsets.top = loc[1];
insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth());
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index ab05fbf..eccc41b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,14 +19,18 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.graphics.PixelFormat;
import android.graphics.Point;
+import android.graphics.Rect;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -36,8 +40,9 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
+import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.folder.Folder;
@@ -46,6 +51,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.AnimatedFloat;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -62,7 +68,8 @@
private static final String WINDOW_TITLE = "Taskbar";
private final TaskbarContainerView mTaskbarContainerView;
- private final TaskbarView mTaskbarView;
+ private final TaskbarView mTaskbarViewInApp;
+ private final TaskbarView mTaskbarViewOnHome;
private final BaseQuickstepLauncher mLauncher;
private final WindowManager mWindowManager;
// Layout width and height of the Taskbar in the default state.
@@ -81,16 +88,21 @@
// Contains all loaded Hotseat items.
private ItemInfo[] mLatestLoadedHotseatItems;
+ private @Nullable Animator mAnimator;
+ private boolean mIsAnimatingToLauncher;
+ private boolean mIsAnimatingToApp;
+
public TaskbarController(BaseQuickstepLauncher launcher,
- TaskbarContainerView taskbarContainerView) {
+ TaskbarContainerView taskbarContainerView, TaskbarView taskbarViewOnHome) {
mLauncher = launcher;
mTaskbarContainerView = taskbarContainerView;
mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
- mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
- mTaskbarView.construct(createTaskbarViewCallbacks());
+ mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+ mTaskbarViewInApp.construct(createTaskbarViewCallbacks());
+ mTaskbarViewOnHome = taskbarViewOnHome;
+ mTaskbarViewOnHome.construct(createTaskbarViewCallbacks());
mWindowManager = mLauncher.getWindowManager();
- mTaskbarSize = new Point(MATCH_PARENT,
- mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
+ mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
createTaskbarVisibilityControllerCallbacks());
@@ -105,12 +117,13 @@
return new TaskbarVisibilityControllerCallbacks() {
@Override
public void updateTaskbarBackgroundAlpha(float alpha) {
- mTaskbarView.setBackgroundAlpha(alpha);
+ mTaskbarViewInApp.setBackgroundAlpha(alpha);
}
@Override
public void updateTaskbarVisibilityAlpha(float alpha) {
mTaskbarContainerView.setAlpha(alpha);
+ mTaskbarViewOnHome.setAlpha(alpha);
}
};
}
@@ -165,7 +178,28 @@
@Override
public View.OnLongClickListener getItemOnLongClickListener() {
- return mDragController::startDragOnLongClick;
+ return mDragController::startSystemDragOnLongClick;
+ }
+
+ @Override
+ public int getEmptyHotseatViewVisibility(TaskbarView taskbarView) {
+ // When on the home screen, we want the empty hotseat views to take up their full
+ // space so that the others line up with the home screen hotseat.
+ boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome
+ || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher;
+ return isOnHomeScreen ? View.INVISIBLE : View.GONE;
+ }
+
+ @Override
+ public float getNonIconScale(TaskbarView taskbarView) {
+ return taskbarView == mTaskbarViewOnHome ? getTaskbarScaleOnHome() : 1f;
+ }
+
+ @Override
+ public void onItemPositionsChanged(TaskbarView taskbarView) {
+ if (taskbarView == mTaskbarViewOnHome) {
+ alignRealHotseatWithTaskbar();
+ }
}
};
}
@@ -174,7 +208,7 @@
return new TaskbarHotseatControllerCallbacks() {
@Override
public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
- mTaskbarView.updateHotseatItems(hotseatItemInfos);
+ mTaskbarViewInApp.updateHotseatItems(hotseatItemInfos);
mLatestLoadedHotseatItems = hotseatItemInfos;
dedupeAndUpdateRecentItems();
}
@@ -191,7 +225,8 @@
@Override
public void updateRecentTaskAtIndex(int taskIndex, Task task) {
- mTaskbarView.updateRecentTaskAtIndex(taskIndex, task);
+ mTaskbarViewInApp.updateRecentTaskAtIndex(taskIndex, task);
+ mTaskbarViewOnHome.updateRecentTaskAtIndex(taskIndex, task);
}
};
}
@@ -200,14 +235,20 @@
* Initializes the Taskbar, including adding it to the screen.
*/
public void init() {
- mTaskbarView.init(mHotseatController.getNumHotseatIcons(),
+ mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(),
mRecentsController.getNumRecentIcons());
- mTaskbarContainerView.init(mTaskbarView);
+ mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(),
+ mRecentsController.getNumRecentIcons());
+ mTaskbarContainerView.init(mTaskbarViewInApp);
addToWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
mTaskbarVisibilityController.init();
mHotseatController.init();
mRecentsController.init();
+
+ SCALE_PROPERTY.set(mTaskbarViewInApp, mLauncher.hasBeenResumed()
+ ? getTaskbarScaleOnHome() : 1f);
+ updateWhichTaskbarViewIsVisible();
}
private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
@@ -223,7 +264,13 @@
* Removes the Taskbar from the screen, and removes any obsolete listeners etc.
*/
public void cleanup() {
- mTaskbarView.cleanup();
+ if (mAnimator != null) {
+ // End this first, in case it relies on properties that are about to be cleaned up.
+ mAnimator.end();
+ }
+
+ mTaskbarViewInApp.cleanup();
+ mTaskbarViewOnHome.cleanup();
mTaskbarContainerView.cleanup();
removeFromWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(null);
@@ -233,14 +280,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(
@@ -266,7 +309,7 @@
TaskbarContainerView.LayoutParams taskbarLayoutParams =
new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y);
taskbarLayoutParams.gravity = gravity;
- mTaskbarView.setLayoutParams(taskbarLayoutParams);
+ mTaskbarViewInApp.setLayoutParams(taskbarLayoutParams);
mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
}
@@ -275,14 +318,22 @@
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
public void onLauncherResumedOrPaused(boolean isResumed) {
- long duration = QuickstepAppTransitionManagerImpl.CONTENT_ALPHA_DURATION;
- final Animator anim;
- if (isResumed) {
- anim = createAnimToLauncher(null, duration);
- } else {
- anim = createAnimToApp(duration);
+ long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
+ if (mAnimator != null) {
+ mAnimator.cancel();
}
- anim.start();
+ if (isResumed) {
+ mAnimator = createAnimToLauncher(null, duration);
+ } else {
+ mAnimator = createAnimToApp(duration);
+ }
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ }
+ });
+ mAnimator.start();
}
/**
@@ -295,11 +346,44 @@
if (toState != null) {
mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
}
+ anim.addFloat(mTaskbarViewInApp, SCALE_PROPERTY, mTaskbarViewInApp.getScaleX(),
+ getTaskbarScaleOnHome(), LINEAR);
+
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimatingToLauncher = true;
+ mTaskbarViewInApp.updateHotseatItemsVisibility();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimatingToLauncher = false;
+ updateWhichTaskbarViewIsVisible();
+ }
+ });
+
return anim.buildAnim();
}
private Animator createAnimToApp(long duration) {
- return mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration);
+ PendingAnimation anim = new PendingAnimation(duration);
+ anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration));
+ anim.addFloat(mTaskbarViewInApp, SCALE_PROPERTY, mTaskbarViewInApp.getScaleX(), 1f, LINEAR);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsAnimatingToApp = true;
+ mTaskbarViewInApp.updateHotseatItemsVisibility();
+ updateWhichTaskbarViewIsVisible();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimatingToApp = false;
+ }
+ });
+ return anim.buildAnim();
}
/**
@@ -321,11 +405,11 @@
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
*/
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
- return mTaskbarView.isEventOverAnyItem(ev);
+ return mTaskbarViewInApp.isEventOverAnyItem(ev);
}
public boolean isDraggingItem() {
- return mTaskbarView.isDraggingItem();
+ return mTaskbarViewInApp.isDraggingItem() || mTaskbarViewOnHome.isDraggingItem();
}
private void dedupeAndUpdateRecentItems() {
@@ -370,7 +454,8 @@
tasksArray[tasksArray.length - 1 - i] = task;
}
- mTaskbarView.updateRecentTasks(tasksArray);
+ mTaskbarViewInApp.updateRecentTasks(tasksArray);
+ mTaskbarViewOnHome.updateRecentTasks(tasksArray);
mRecentsController.loadIconsForTasks(tasksArray);
}
@@ -378,7 +463,49 @@
* @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());
+ }
+
+ /**
+ * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
+ */
+ public void alignRealHotseatWithTaskbar() {
+ Rect hotseatBounds = new Rect();
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
+ int hotseatTopDiff = hotseatHeight - grid.taskbarSize;
+
+ mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds);
+ mLauncher.getHotseat().setPadding(hotseatBounds.left,
+ hotseatBounds.top + hotseatTopDiff,
+ mTaskbarViewOnHome.getWidth() - hotseatBounds.right,
+ mTaskbarViewOnHome.getHeight() - hotseatBounds.bottom);
+ }
+
+ private void updateWhichTaskbarViewIsVisible() {
+ boolean isInApp = !mLauncher.hasBeenResumed() || mIsAnimatingToLauncher
+ || mIsAnimatingToApp;
+ if (isInApp) {
+ mTaskbarViewInApp.setVisibility(View.VISIBLE);
+ mTaskbarViewOnHome.setVisibility(View.INVISIBLE);
+ mLauncher.getHotseat().setIconsAlpha(0);
+ } else {
+ mTaskbarViewInApp.setVisibility(View.INVISIBLE);
+ mTaskbarViewOnHome.setVisibility(View.VISIBLE);
+ mLauncher.getHotseat().setIconsAlpha(1);
+ }
+ }
+
+ /**
+ * Returns the ratio of the taskbar icon size on home vs in an app.
+ */
+ public float getTaskbarScaleOnHome() {
+ DeviceProfile inAppDp = mTaskbarContainerView.getTaskbarActivityContext()
+ .getDeviceProfile();
+ DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext())
+ .getDeviceProfile();
+ return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
}
/**
@@ -424,6 +551,10 @@
protected interface TaskbarViewCallbacks {
View.OnClickListener getItemOnClickListener();
View.OnLongClickListener getItemOnLongClickListener();
+ int getEmptyHotseatViewVisibility(TaskbarView taskbarView);
+ /** Returns how much to scale non-icon elements such as spacing and dividers. */
+ float getNonIconScale(TaskbarView taskbarView);
+ void onItemPositionsChanged(TaskbarView taskbarView);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index baec899..5eb34cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -57,7 +57,7 @@
* generate the ClipDescription and Intent.
* @return Whether {@link View#startDragAndDrop} started successfully.
*/
- protected boolean startDragOnLongClick(View view) {
+ protected boolean startSystemDragOnLongClick(View view) {
if (!(view instanceof BubbleTextView)) {
return false;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
index 082343e..b1bafdb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -78,7 +78,10 @@
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
// Since the hotseat might be laid out vertically or horizontally, use whichever
// index is higher.
- hotseatItemInfos[Math.max(lp.cellX, lp.cellY)] = itemInfo;
+ int index = Math.max(lp.cellX, lp.cellY);
+ if (0 <= index && index < hotseatItemInfos.length) {
+ hotseatItemInfos[index] = itemInfo;
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index b4b5d8b..0a3819d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -16,14 +16,12 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherState.TASKBAR;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_TASKBAR_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_TASKBAR;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
@@ -65,13 +63,9 @@
if (mTaskbarCallbacks == null) {
return;
}
- if (config.hasAnimationFlag(SKIP_TASKBAR)) {
- return;
- }
AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
- animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f,
- config.getInterpolator(ANIM_TASKBAR_FADE, Interpolators.LINEAR));
+ animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 7a13b89..3567c17 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,9 +15,14 @@
*/
package com.android.launcher3.taskbar;
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -34,7 +39,9 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -45,11 +52,13 @@
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
-public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent {
+public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
private final ColorDrawable mBackgroundDrawable;
- private final int mItemMarginLeftRight;
+ private final int mDividerWidth;
+ private final int mDividerHeight;
private final int mIconTouchSize;
+ private final boolean mIsRtl;
private final int mTouchSlop;
private final RectF mTempDelegateBounds = new RectF();
private final RectF mDelegateSlopBounds = new RectF();
@@ -57,8 +66,12 @@
// Initialized in TaskbarController constructor.
private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+ // Scale on elements that aren't icons.
+ private float mNonIconScale;
+ private int mItemMarginLeftRight;
// Initialized in init().
+ private LayoutTransition mLayoutTransition;
private int mHotseatStartIndex;
private int mHotseatEndIndex;
private View mHotseatRecentsDivider;
@@ -92,16 +105,26 @@
Resources resources = getResources();
mBackgroundDrawable = (ColorDrawable) getBackground();
- mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ mDividerWidth = resources.getDimensionPixelSize(R.dimen.taskbar_divider_thickness);
+ mDividerHeight = resources.getDimensionPixelSize(R.dimen.taskbar_divider_height);
mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
+ mIsRtl = Utilities.isRtl(resources);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
mControllerCallbacks = taskbarViewCallbacks;
+ mNonIconScale = mControllerCallbacks.getNonIconScale(this);
+ mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale);
}
protected void init(int numHotseatIcons, int numRecentIcons) {
+ mLayoutTransition = new LayoutTransition();
+ addUpdateListenerForAllLayoutTransitions(
+ () -> mControllerCallbacks.onItemPositionsChanged(this));
+ setLayoutTransition(mLayoutTransition);
+
mHotseatStartIndex = 0;
mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
updateHotseatItems(new ItemInfo[numHotseatIcons]);
@@ -114,8 +137,30 @@
updateRecentTasks(new Task[numRecentIcons]);
}
+ private void addUpdateListenerForAllLayoutTransitions(Runnable onUpdate) {
+ addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_APPEARING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_DISAPPEARING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.CHANGING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.APPEARING, onUpdate);
+ addUpdateListenerForLayoutTransition(LayoutTransition.DISAPPEARING, onUpdate);
+ }
+
+ private void addUpdateListenerForLayoutTransition(int transitionType, Runnable onUpdate) {
+ Animator anim = mLayoutTransition.getAnimator(transitionType);
+ if (anim instanceof ValueAnimator) {
+ ((ValueAnimator) anim).addUpdateListener(valueAnimator -> onUpdate.run());
+ } else {
+ AnimatorSet animSet = new AnimatorSet();
+ ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+ updateAnim.addUpdateListener(valueAnimator -> onUpdate.run());
+ animSet.playTogether(anim, updateAnim);
+ mLayoutTransition.setAnimator(transitionType, animSet);
+ }
+ }
+
protected void cleanup() {
removeAllViews();
+ mHotseatRecentsDivider = null;
}
/**
@@ -131,7 +176,8 @@
*/
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
for (int i = 0; i < hotseatItemInfos.length; i++) {
- ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+ ItemInfo hotseatItemInfo = hotseatItemInfos[!mIsRtl ? i
+ : hotseatItemInfos.length - i - 1];
int hotseatIndex = mHotseatStartIndex + i;
View hotseatView = getChildAt(hotseatIndex);
@@ -154,12 +200,11 @@
if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
|| needsReinflate) {
removeView(hotseatView);
- TaskbarActivityContext activityContext =
- ActivityContext.lookupContext(getContext());
+ ActivityContext activityContext = ActivityContext.lookupContext(getContext());
if (isFolder) {
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
- activityContext, this, folderInfo);
+ ActivityContext.lookupContext(getContext()), this, folderInfo);
folderIcon.setTextVisible(false);
hotseatView = folderIcon;
} else {
@@ -176,28 +221,52 @@
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
((BubbleTextView) hotseatView).applyFromWorkspaceItem(
(WorkspaceItemInfo) hotseatItemInfo);
- hotseatView.setVisibility(VISIBLE);
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);
hotseatView.setOnLongClickListener(null);
+ hotseatView.setTag(null);
}
+ updateHotseatItemVisibility(hotseatView);
}
updateHotseatRecentsDividerVisibility();
}
+ protected void updateHotseatItemsVisibility() {
+ for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
+ updateHotseatItemVisibility(getChildAt(i));
+ }
+ }
+
+ private void updateHotseatItemVisibility(View hotseatView) {
+ if (hotseatView.getTag() != null) {
+ hotseatView.setVisibility(VISIBLE);
+ } else {
+ int oldVisibility = hotseatView.getVisibility();
+ int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(this);
+ hotseatView.setVisibility(newVisibility);
+ if (oldVisibility == GONE && newVisibility != GONE) {
+ // By default, the layout transition only runs when going to VISIBLE,
+ // but we want it to run when going to GONE to INVISIBLE as well.
+ getLayoutTransition().showChild(this, hotseatView, oldVisibility);
+ }
+ }
+ }
+
private View addDivider(int dividerIndex) {
View divider = inflate(R.layout.taskbar_divider);
- addView(divider, dividerIndex);
+ LayoutParams lp = new LayoutParams(mDividerWidth, mDividerHeight);
+ lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+ divider.setScaleX(mNonIconScale);
+ divider.setScaleY(mNonIconScale);
+ addView(divider, dividerIndex, lp);
return divider;
}
@@ -390,6 +459,32 @@
return mIsDraggingItem;
}
+ /**
+ * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
+ */
+ protected RectF getHotseatBounds() {
+ View firstHotseatView = null, lastHotseatView = null;
+ for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ if (firstHotseatView == null) {
+ firstHotseatView = child;
+ }
+ lastHotseatView = child;
+ }
+ }
+ if (firstHotseatView == null || lastHotseatView == null) {
+ return new RectF();
+ }
+ View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView;
+ View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView;
+ return new RectF(
+ leftmostHotseatView.getLeft() - mItemMarginLeftRight,
+ leftmostHotseatView.getTop(),
+ rightmostHotseatView.getRight() + mItemMarginLeftRight,
+ rightmostHotseatView.getBottom());
+ }
+
// FolderIconParent implemented methods.
@Override
@@ -418,7 +513,12 @@
}
private View inflate(@LayoutRes int layoutResId) {
- TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
- return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+ return ActivityContext.lookupContext(getContext()).getLayoutInflater()
+ .inflate(layoutResId, this, false);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ // Ignore, we just implement Insettable to draw behind system insets.
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
index 4cf55d8..8745a7c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
@@ -36,13 +36,13 @@
private final TaskbarController.TaskbarVisibilityControllerCallbacks mTaskbarCallbacks;
// Background alpha.
- private AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
+ private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
this::onTaskbarBackgroundAlphaChanged);
// Overall visibility.
- private AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
+ private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
this::updateVisibilityAlpha);
- private AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
+ private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
this::updateVisibilityAlpha);
public TaskbarVisibilityController(BaseQuickstepLauncher launcher,
@@ -59,9 +59,13 @@
boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
& QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
+
+ onTaskbarBackgroundAlphaChanged();
+ updateVisibilityAlpha();
}
protected void cleanup() {
+ setNavBarButtonAlpha(1f);
}
protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
@@ -90,6 +94,14 @@
float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
mTaskbarVisibilityAlphaForLauncherState.value);
float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value;
- mTaskbarCallbacks.updateTaskbarVisibilityAlpha(alphaDueToLauncher * alphaDueToOther);
+ float taskbarAlpha = alphaDueToLauncher * alphaDueToOther;
+ mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
+
+ // Make the nav bar invisible if taskbar is visible.
+ setNavBarButtonAlpha(1f - taskbarAlpha);
+ }
+
+ private void setNavBarButtonAlpha(float navBarAlpha) {
+ SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index aad7e17..d65c59e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -30,7 +30,9 @@
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.util.FloatProperty;
@@ -43,6 +45,7 @@
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
@@ -73,6 +76,8 @@
SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
SCRIM_MULTIPLIER.set(scrim, 1f);
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
+ RECENTS_GRID_PROGRESS.set(mRecentsView,
+ state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
}
@Override
@@ -102,7 +107,12 @@
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
- setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+ PagedOrientationHandler orientationHandler =
+ ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
+ FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
+ TASK_PRIMARY_TRANSLATION, TASK_SECONDARY_TRANSLATION, mLauncher.getDeviceProfile());
+ setter.setFloat(mRecentsView, taskViewsFloat,
+ toState.getOverviewSecondaryTranslation(mLauncher),
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
@@ -117,6 +127,8 @@
mRecentsView, getTaskModalnessProperty(),
toState.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
+ setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
+ toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
index 010694b..bb1f6fc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -60,6 +60,14 @@
mListeners.add(r);
}
+ @Override
+ public void removeChangeListener(Runnable r) {
+ if (mListeners == null) {
+ return;
+ }
+ mListeners.remove(r);
+ }
+
private void registerDeviceConfigChangedListener(Context context) {
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_LAUNCHER,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 98551fb..b4aa596 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -34,11 +34,11 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
@@ -85,8 +85,13 @@
public void onDraw(Canvas canvas) {
int count = canvas.save();
if (!mIsPinned) {
- boolean isBadged = getTag() instanceof WorkspaceItemInfo
- && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
+ boolean isBadged = false;
+ if (getTag() instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
+ isBadged = !Process.myUserHandle().equals(info.user)
+ || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+ || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+ }
drawEffect(canvas, isBadged);
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
@@ -228,17 +233,13 @@
*/
public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
- private int mOffsetX;
- private int mOffsetY;
- private int mIconRadius;
- private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final PredictedAppIcon mIcon;
+ private final Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) {
mDelegateCellX = cellX;
mDelegateCellY = cellY;
- mOffsetX = icon.getOutlineOffsetX();
- mOffsetY = icon.getOutlineOffsetY();
- mIconRadius = icon.mNormalizedIconRadius;
+ mIcon = icon;
mOutlinePaint.setStyle(Paint.Style.FILL);
mOutlinePaint.setColor(Color.argb(24, 245, 245, 245));
}
@@ -248,7 +249,8 @@
*/
@Override
public void drawUnderItem(Canvas canvas) {
- getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint);
+ getShape().drawShape(canvas, mIcon.getOutlineOffsetX(), mIcon.getOutlineOffsetY(),
+ mIcon.mNormalizedIconRadius, mOutlinePaint);
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 55dde45..bae97d7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -30,7 +30,6 @@
import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import android.content.Intent;
@@ -218,6 +217,8 @@
.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
mHotseatPredictionController.setPredictedItems(item);
+ } else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
+ getPopupDataProvider().setRecommendedWidgets(item.items);
}
}
@@ -233,7 +234,7 @@
@Override
public void onDestroy() {
super.onDestroy();
- getAppsView().getSearchUiManager().destroy();
+ getAppsView().getSearchUiManager().destroySearch();
mHotseatPredictionController.destroy();
}
@@ -260,14 +261,13 @@
RecentsView rv = getOverviewPanel();
TaskView tasktolaunch = rv.getTaskViewAt(0);
if (tasktolaunch != null) {
- tasktolaunch.launchTask(false, success -> {
+ tasktolaunch.launchTask(success -> {
if (!success) {
getStateManager().goToState(OVERVIEW);
- tasktolaunch.notifyTaskLaunchFailed(TAG);
} else {
getStateManager().moveToRestState();
}
- }, MAIN_EXECUTOR.getHandler());
+ });
} else {
getStateManager().goToState(NORMAL);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 5ccc1e8..e1456b1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,12 +15,17 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
+import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
+import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
import android.annotation.TargetApi;
import android.os.Build;
@@ -69,26 +74,49 @@
if (toState.overviewUi) {
// While animating into recents, update the visible task data as needed
- builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+ builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
} else {
builder.addListener(
AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
+ // Create or dismiss split screen select animations
+ LauncherState currentState = mLauncher.getStateManager().getState();
+ if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
+ builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+ } else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
+ builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
+ }
+
setAlphas(builder, config, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
toState.getOverviewFullscreenProgress(), LINEAR);
}
+ /**
+ * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
+ */
+ private boolean isSplitSelectionState(@NonNull LauncherState toState) {
+ return toState == OVERVIEW_SPLIT_SELECT;
+ }
+
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
LauncherState state) {
- float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
+ float clearAllButtonAlpha = (state.getVisibleElements(mLauncher) & CLEAR_ALL_BUTTON) != 0
+ ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
- buttonAlpha, LINEAR);
+ clearAllButtonAlpha, LINEAR);
+ float overviewButtonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0
+ ? 1 : 0;
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
- MultiValueAlpha.VALUE, buttonAlpha, config.getInterpolator(
+ MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
+
+ float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
+ 1 : 0;
+ propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
+ splitPlaceholderAlpha, LINEAR);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 37c774b..b2f8a40 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -78,7 +78,7 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+ return ALL_APPS_CONTENT;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 2cf65af..aa770d2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -20,6 +20,7 @@
import android.content.Context;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.util.LayoutUtils;
@@ -69,23 +70,15 @@
@Override
public int getVisibleElements(Launcher launcher) {
return super.getVisibleElements(launcher)
- & ~OVERVIEW_BUTTONS
+ & ~OVERVIEW_ACTIONS
+ & ~CLEAR_ALL_BUTTON
& ~VERTICAL_SWIPE_INDICATOR
| TASKBAR;
}
@Override
- public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
- if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
- // Translate hotseat offscreen if we show it in overview.
- RecentsView recentsView = launcher.getOverviewPanel();
- ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
- scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
- launcher.getDeviceProfile(),
- recentsView.getPagedOrientationHandler());
- return scaleAndTranslation;
- }
- return super.getHotseatScaleAndTranslation(launcher);
+ public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+ return false;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 41c689d..bdba482 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -45,7 +45,7 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return OVERVIEW_BUTTONS;
+ return OVERVIEW_ACTIONS | CLEAR_ALL_BUTTON;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index b295e79..d8a5f9b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -28,6 +28,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -77,40 +78,11 @@
}
@Override
- public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
- if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
- DeviceProfile dp = launcher.getDeviceProfile();
- if (dp.allAppsIconSizePx >= dp.iconSizePx) {
- return new ScaleAndTranslation(1, 0, 0);
- } else {
- float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
- // Distance between the screen center (which is the pivotY for hotseat) and the
- // bottom of the hotseat (which we want to preserve)
- float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
- // On scaling, the bottom edge is moved closer to the pivotY. We move the
- // hotseat back down so that the bottom edge's position is preserved.
- float translationY = distanceFromBottom * (1 - scale);
- return new ScaleAndTranslation(scale, 0, translationY);
- }
- }
- return getWorkspaceScaleAndTranslation(launcher);
- }
-
- @Override
public float[] getOverviewScaleAndOffset(Launcher launcher) {
return new float[] {NO_SCALE, NO_OFFSET};
}
@Override
- public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- if (this == OVERVIEW) {
- // Treat the QSB as part of the hotseat so they move together.
- return getHotseatScaleAndTranslation(launcher);
- }
- return super.getQsbScaleAndTranslation(launcher);
- }
-
- @Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
return new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -122,7 +94,8 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return OVERVIEW_BUTTONS;
+ return displayOverviewTasksAsGrid(launcher.getDeviceProfile()) ? CLEAR_ALL_BUTTON
+ : CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
}
@Override
@@ -131,6 +104,11 @@
}
@Override
+ public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+ return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+ }
+
+ @Override
public String getDescription(Launcher launcher) {
return launcher.getString(R.string.accessibility_recent_apps);
}
@@ -148,7 +126,7 @@
public void onBackPressed(Launcher launcher) {
TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
if (taskView != null) {
- taskView.launchTask(true);
+ taskView.launchTaskAnimated();
} else {
super.onBackPressed(launcher);
}
@@ -168,4 +146,12 @@
public static OverviewState newModalTaskState(int id) {
return new OverviewModalTaskState(id);
}
+
+ /**
+ * New Overview substate representing state where 1 app for split screen has been selected and
+ * pinned and user is selecting the second one
+ */
+ public static OverviewState newSplitSelectState(int id) {
+ return new SplitScreenSelectState(id);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 69b8aca..de7be5d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -36,7 +36,6 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_TASKBAR_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
@@ -48,7 +47,6 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.AllAppsContainerView;
@@ -62,7 +60,7 @@
* Animation factory for quickstep specific transitions
*/
public class QuickstepAtomicAnimationFactory extends
- RecentsAtomicAnimationFactory<Launcher, LauncherState> {
+ RecentsAtomicAnimationFactory<QuickstepLauncher, LauncherState> {
// Scale recents takes before animating in
private static final float RECENTS_PREPARE_SCALE = 1.33f;
@@ -81,7 +79,6 @@
if (toState == NORMAL && fromState == OVERVIEW) {
config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
- config.setInterpolator(ANIM_TASKBAR_FADE, ACCEL);
config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
@@ -140,7 +137,6 @@
config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_2);
- config.setInterpolator(ANIM_TASKBAR_FADE, OVERSHOOT_1_2);
} else if (fromState == HINT_STATE && toState == NORMAL) {
config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
if (mHintToNormalDuration == -1) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
new file mode 100644
index 0000000..722d74a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.states;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * New Overview substate representing state where 1 app for split screen has been selected and
+ * pinned and user is selecting the second one
+ */
+public class SplitScreenSelectState extends OverviewState {
+ public SplitScreenSelectState(int id) {
+ super(id);
+ }
+
+ @Override
+ public void onBackPressed(Launcher launcher) {
+ launcher.getStateManager().goToState(OVERVIEW);
+ }
+
+ @Override
+ public int getVisibleElements(Launcher launcher) {
+ return SPLIT_PLACHOLDER_VIEW;
+ }
+
+ @Override
+ public float getOverviewSecondaryTranslation(Launcher launcher) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
+ .getActiveSplitPositionOption().mStagePosition;
+ int direction = orientationHandler.getSplitTranslationDirectionFactor(splitPosition);
+ return launcher.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 6b9c340..a990f3e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -24,8 +24,8 @@
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.ValueAnimator;
@@ -145,7 +145,7 @@
OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER,
PULLBACK_INTERPOLATOR);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
builder.addOnFrameCallback(recentsView::redrawLiveTile);
}
@@ -194,7 +194,7 @@
boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
|| (velocity < 0 && fling);
if (success) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
RecentsView recentsView = mLauncher.getOverviewPanel();
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 45cb46f..6c71995 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -155,7 +155,7 @@
super.onDragEnd(velocity);
}
- View searchView = mLauncher.getAppsView().getSearchView();
+ View searchView = mLauncher.getHotseat().getQsb();
if (searchView instanceof FeedbackHandler) {
((FeedbackHandler) searchView).resetFeedback();
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index df433f8..4766870 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -18,7 +18,7 @@
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
+import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
@@ -222,7 +222,7 @@
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
mLauncher.getActionsView().getVisibilityAlpha().setValue(
- (fromState.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1f : 0f);
+ (fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
// As we drag right, animate the following properties:
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 845699a..4df0f63 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -76,7 +76,7 @@
* @return the animation
*/
PendingAnimation createSwipeDownToTaskAppAnimation(long duration, Interpolator interpolator) {
- mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
+ mRecentsView.setCurrentPage(mRecentsView.getDestinationPage());
TaskView taskView = mRecentsView.getCurrentPageTaskView();
if (taskView == null) {
throw new IllegalStateException("There is no task view to animate to.");
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 73f4ff2..facfb9d 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.
@@ -125,26 +126,11 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "PortraitStatesTouchController.getTargetState");
- }
if (fromState == ALL_APPS && !isDragTowardPositive) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
- "PortraitStatesTouchController.getTargetState 1");
- }
return NORMAL;
} else if (fromState == OVERVIEW) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
- "PortraitStatesTouchController.getTargetState 2");
- }
return isDragTowardPositive ? OVERVIEW : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
- "PortraitStatesTouchController.getTargetState 3");
- }
return ALL_APPS;
}
return fromState;
@@ -319,4 +305,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/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
index 6271a44..faf5054 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
@@ -31,6 +31,7 @@
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.AllAppsEduView;
/**
@@ -92,7 +93,8 @@
mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
return draggingFromNav ? OVERVIEW : NORMAL;
} else {
- return isDragTowardPositive ^ (fromState == OVERVIEW) ? OVERVIEW : NORMAL;
+ LauncherState startState = mStartState != null ? mStartState : fromState;
+ return isDragTowardPositive ^ (startState == OVERVIEW) ? OVERVIEW : NORMAL;
}
}
@@ -106,8 +108,8 @@
@Override
protected float getShiftRange() {
- return mLauncher.getDeviceProfile().isVerticalBarLayout()
- ? mLauncher.getDragLayer().getWidth() : super.getShiftRange();
+ // Should be in sync with TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT
+ return LayoutUtils.getDefaultSwipeHeight(mLauncher, mLauncher.getDeviceProfile());
}
@Override
@@ -116,7 +118,7 @@
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
maxAccuracy, animComponent);
- return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
+ return (mLauncher.getDeviceProfile().isSeascape() ? 1 : -1) / range;
}
@Override
@@ -134,5 +136,6 @@
AllAppsEduView.show(mLauncher);
}
}
+ mStartState = null;
}
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index feeee50..f4ef1f7 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,11 +22,10 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -46,12 +45,16 @@
import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -78,9 +81,9 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -102,7 +105,6 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -123,14 +125,15 @@
* Handles the navigation gestures when Launcher is the default home activity.
*/
@TargetApi(Build.VERSION_CODES.R)
-public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
+ Q extends RecentsView, S extends BaseState<S>>
extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
RecentsAnimationCallbacks.RecentsAnimationListener {
private static final String TAG = "AbsSwipeUpHandler";
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
- protected final BaseActivityInterface<?, T> mActivityInterface;
+ protected final BaseActivityInterface<S, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
// Callbacks to be made once the recents animation starts
@@ -316,7 +319,7 @@
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
this::notifyTransitionCancelled);
- if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (!LIVE_TILE.get()) {
mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
| STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
(b) -> mRecentsView.setRunningTaskHidden(!b));
@@ -351,7 +354,6 @@
mRecentsView = activity.getOverviewPanel();
mRecentsView.setOnPageTransitionEndCallback(null);
- addLiveTileOverlay();
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
if (alreadyOnHome) {
@@ -457,7 +459,7 @@
}
private void onDeferredActivityLaunch() {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
mActivityInterface.switchRunningTaskViewToScreenshot(
null, () -> {
mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
@@ -615,7 +617,7 @@
final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
if (passed != mPassedOverviewThreshold) {
mPassedOverviewThreshold = passed;
- if (!mDeviceState.isFullyGesturalNavMode()) {
+ if (mDeviceState.isTwoButtonNavMode()) {
performHapticFeedback();
}
}
@@ -721,6 +723,9 @@
@UiThread
public void onGestureStarted(boolean isLikelyToStartNewTask) {
+ mActivityInterface.closeOverlay();
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+
if (mRecentsView != null) {
InteractionJankMonitorWrapper.begin(mRecentsView,
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
@@ -848,6 +853,10 @@
private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
boolean isCancel) {
+ if (mDeviceState.isButtonNavMode()) {
+ // Button mode, this is only used to go to recents
+ return RECENTS;
+ }
final GestureEndTarget endTarget;
final boolean goingToNewTask;
if (mRecentsView != null) {
@@ -914,28 +923,20 @@
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
isFling, isCancel);
+ // Set the state, but don't notify until the animation completes
+ mGestureState.setEndTarget(endTarget, false /* isAtomic */);
+
float endShift = endTarget.isLauncher ? 1 : 0;
final float startShift;
- Interpolator interpolator = DEACCEL;
if (!isFling) {
long expectedDuration = Math.abs(Math.round((endShift - currentShift)
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
startShift = currentShift;
- interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
} else {
startShift = Utilities.boundToRange(currentShift - velocity.y
* getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
if (mTransitionDragLength > 0) {
- if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
- Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
- startShift, endShift, endShift, endVelocity,
- mTransitionDragLength, mContext);
- endShift = overshoot.end;
- interpolator = overshoot.interpolator;
- duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
- MAX_SWIPE_DURATION);
- } else {
float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
// we want the page's snap velocity to approximately match the velocity at
@@ -943,13 +944,17 @@
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocity.y));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
-
- if (endTarget == RECENTS) {
- interpolator = OVERSHOOT_1_2;
- }
- }
}
}
+ Interpolator interpolator;
+ S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
+ if (state.displayOverviewTasksAsGrid(mDp)) {
+ interpolator = ACCEL_DEACCEL;
+ } else if (endTarget == RECENTS) {
+ interpolator = OVERSHOOT_1_2;
+ } else {
+ interpolator = DEACCEL;
+ }
if (endTarget.isLauncher) {
mInputConsumerProxy.enable();
@@ -958,16 +963,21 @@
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
} else if (endTarget == RECENTS) {
if (mRecentsView != null) {
- int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
+ int nearestPage = mRecentsView.getDestinationPage();
+ boolean isScrolling = false;
if (mRecentsView.getNextPage() != nearestPage) {
// We shouldn't really scroll to the next page when swiping up to recents.
// Only allow settling on the next page if it's nearest to the center.
mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
+ isScrolling = true;
}
if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
+ isScrolling = true;
}
- duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+ if (!mDeviceState.isButtonNavMode() || isScrolling) {
+ duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+ }
}
}
@@ -1048,8 +1058,6 @@
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
- // Set the state, but don't notify until the animation completes
- mGestureState.setEndTarget(target, false /* isAtomic */);
maybeUpdateRecentsAttachedState();
// If we are transitioning to launcher, then listen for the activity to be restarted while
@@ -1102,8 +1110,8 @@
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
mLauncherTransitionController = null;
} else {
+ AnimatorSet animatorSet = new AnimatorSet();
ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
- windowAnim.setDuration(duration).setInterpolator(interpolator);
windowAnim.addUpdateListener(valueAnimator -> {
computeRecentsScrollIfInvisible();
});
@@ -1138,8 +1146,14 @@
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
- windowAnim.start();
- mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+ animatorSet.play(windowAnim);
+ S state = mActivityInterface.stateFromGestureEndTarget(mGestureState.getEndTarget());
+ if (mRecentsView != null && state.displayOverviewTasksAsGrid(mDp)) {
+ animatorSet.play(ObjectAnimator.ofFloat(mRecentsView, RECENTS_GRID_PROGRESS, 1));
+ }
+ animatorSet.setDuration(duration).setInterpolator(interpolator);
+ animatorSet.start();
+ mRunningWindowAnim = RunningWindowAnim.wrap(animatorSet);
}
}
@@ -1232,13 +1246,6 @@
});
anim.addAnimatorListener(new AnimationSuccessListener() {
@Override
- public void onAnimationStart(Animator animation) {
- if (mActivity != null) {
- removeLiveTileOverlay();
- }
- }
-
- @Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsView != null) {
mRecentsView.post(mRecentsView::resetTaskVisuals);
@@ -1259,7 +1266,7 @@
// In the off chance that the gesture ends before Launcher is started, we should clear
// the callback here so that it doesn't update with the wrong state
mActivity.clearRunOnceOnStartCallback();
- resetLauncherListenersAndOverlays();
+ resetLauncherListeners();
}
if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
cancelCurrentAnimation();
@@ -1326,7 +1333,11 @@
}
private void invalidateHandler() {
- mInputConsumerProxy.destroy();
+ if (!LIVE_TILE.get() || !mActivityInterface.isInLiveTileMode()
+ || mGestureState.getEndTarget() != RECENTS) {
+ mInputConsumerProxy.destroy();
+ mTaskAnimationManager.setLiveTileCleanUpHandler(null);
+ }
endRunningWindowAnim(false /* cancel */);
if (mGestureEndCallback != null) {
@@ -1342,7 +1353,7 @@
endLauncherTransitionController();
mRecentsView.onGestureAnimationEnd();
- resetLauncherListenersAndOverlays();
+ resetLauncherListeners();
}
private void endLauncherTransitionController() {
@@ -1355,13 +1366,12 @@
}
}
- private void resetLauncherListenersAndOverlays() {
+ private void resetLauncherListeners() {
// Reset the callback for deferred activity launches
- if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (!LIVE_TILE.get()) {
mActivityInterface.setOnDeferredActivityLaunchCallback(null);
}
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
- removeLiveTileOverlay();
}
private void notifyTransitionCancelled() {
@@ -1378,7 +1388,7 @@
protected void switchToScreenshot() {
final int runningTaskId = mGestureState.getRunningTaskId();
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
if (mRecentsAnimationController != null) {
mRecentsAnimationController.getController().setWillFinishToHome(true);
// Update the screenshot of the task
@@ -1447,7 +1457,7 @@
}
private void finishCurrentTransitionToRecents() {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else if (!hasTargets() || mRecentsAnimationController == null) {
// If there are no targets or the animation not started, then there is nothing to finish
@@ -1483,7 +1493,9 @@
mSwipePipToHomeAnimator.getDestinationBounds());
mRecentsAnimationController.setFinishTaskBounds(
mSwipePipToHomeAnimator.getTaskId(),
- mSwipePipToHomeAnimator.getDestinationBounds());
+ mSwipePipToHomeAnimator.getDestinationBounds(),
+ mSwipePipToHomeAnimator.getFinishWindowCrop(),
+ mSwipePipToHomeAnimator.getFinishTransform());
mIsSwipingPipToHome = false;
}
}
@@ -1494,7 +1506,9 @@
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (mRecentsAnimationTargets.hasTask(task.taskId)) {
+ if (mRecentsView.getRunningTaskIndex() != -1
+ && mRecentsView.getRunningTaskId() == task.taskId
+ && mRecentsAnimationTargets.hasTask(task.taskId)) {
launchOtherTaskInLiveTileMode(task.taskId, mRecentsAnimationTargets.apps);
}
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
@@ -1506,7 +1520,7 @@
endLauncherTransitionController();
mActivityInterface.onSwipeUpToRecentsComplete();
mRecentsView.onSwipeUpAnimationSuccess();
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
appearedTaskTarget -> {
RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
@@ -1515,6 +1529,7 @@
apps[apps.length - 1] = appearedTaskTarget;
launchOtherTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
});
+ mTaskAnimationManager.setLiveTileCleanUpHandler(mInputConsumerProxy::destroy);
ActivityManagerWrapper.getInstance().registerTaskStackListener(
mLiveTileRestartListener);
}
@@ -1583,17 +1598,6 @@
anim.start();
}
- private void addLiveTileOverlay() {
- if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
- mRecentsView.setLiveTileOverlayAttached(true);
- }
- }
-
- private void removeLiveTileOverlay() {
- LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
- mRecentsView.setLiveTileOverlayAttached(false);
- }
-
private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
return app.isNotInRecents
|| app.activityType == ACTIVITY_TYPE_HOME;
@@ -1647,19 +1651,17 @@
mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
- nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
- success -> {
- resultCallback.accept(success);
- if (success) {
- if (hasTaskPreviouslyAppeared) {
- onRestartPreviouslyAppearedTask();
- }
- } else {
- mActivityInterface.onLaunchTaskFailed();
- nextTask.notifyTaskLaunchFailed(TAG);
- mRecentsAnimationController.finish(true /* toRecents */, null);
- }
- }, MAIN_EXECUTOR.getHandler());
+ nextTask.launchTask(success -> {
+ resultCallback.accept(success);
+ if (success) {
+ if (hasTaskPreviouslyAppeared) {
+ onRestartPreviouslyAppearedTask();
+ }
+ } else {
+ mActivityInterface.onLaunchTaskFailed();
+ mRecentsAnimationController.finish(true /* toRecents */, null);
+ }
+ }, true /* freezeTaskList */);
} else {
mActivityInterface.onLaunchTaskFailed();
Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
@@ -1746,19 +1748,14 @@
if (mWindowTransitionController != null) {
mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
}
- if (mRecentsAnimationTargets != null) {
+ // No need to apply any transform if there is ongoing swipe-pip-to-home animator since
+ // that animator handles the leash solely.
+ if (mRecentsAnimationTargets != null && !mIsSwipingPipToHome) {
if (mRecentsViewScrollLinked) {
mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
}
mTaskViewSimulator.apply(mTransformParams);
}
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mRecentsAnimationTargets != null) {
- LiveTileOverlay.INSTANCE.update(
- mTaskViewSimulator.getCurrentRect(),
- mTaskViewSimulator.getCurrentCornerRadius());
- LiveTileOverlay.INSTANCE.setRotation(
- mRecentsView.getPagedViewOrientedState().getDisplayRotation());
- }
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
@@ -1784,7 +1781,6 @@
public interface Factory {
- AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
- GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
+ AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
}
}
diff --git a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
deleted file mode 100644
index d159fa0..0000000
--- a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.taskbar.TaskbarController;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.SurfaceTransactionApplier;
-import com.android.quickstep.util.TaskViewSimulator;
-import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
- *
- * @param <T> activity that contains the overview
- */
-final class AppToOverviewAnimationProvider<T extends StatefulActivity<?>> extends
- RemoteAnimationProvider {
-
- private static final long RECENTS_LAUNCH_DURATION = 250;
- private static final String TAG = "AppToOverviewAnimationProvider";
-
- private final BaseActivityInterface<?, T> mActivityInterface;
- // The id of the currently running task that is transitioning to overview.
- private final RunningTaskInfo mTargetTask;
- private final RecentsAnimationDeviceState mDeviceState;
-
- private T mActivity;
- private RecentsView mRecentsView;
-
- AppToOverviewAnimationProvider(
- BaseActivityInterface<?, T> activityInterface, RunningTaskInfo targetTask,
- RecentsAnimationDeviceState deviceState) {
- mActivityInterface = activityInterface;
- mTargetTask = targetTask;
- mDeviceState = deviceState;
- }
-
- /**
- * Callback for when the activity is ready/initialized.
- *
- * @param activity the activity that is ready
- * @param wasVisible true if it was visible before
- */
- boolean onActivityReady(T activity, Boolean wasVisible) {
- activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTask);
- AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
- BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
- mDeviceState,
- wasVisible, (controller) -> {
- controller.getNormalController().dispatchOnStart();
- controller.getNormalController().getAnimationPlayer().end();
- });
- factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
- factory.setRecentsAttachedToAppWindow(true, false);
- mActivity = activity;
- mRecentsView = mActivity.getOverviewPanel();
- return false;
- }
-
- /**
- * Create remote window animation from the currently running app to the overview panel.
- *
- * @param appTargets the target apps
- * @return animation from app to overview
- */
- @Override
- public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
- PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- if (mActivity == null) {
- Log.e(TAG, "Animation created, before activity");
- return pa.buildAnim();
- }
-
- mRecentsView.setRunningTaskIconScaledDown(true);
- pa.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- mActivityInterface.onSwipeUpToRecentsComplete();
- mRecentsView.animateUpRunningTaskIconScale();
- }
- });
-
- DepthController depthController = mActivityInterface.getDepthController();
- if (depthController != null) {
- pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity),
- OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR);
- }
-
- TaskbarController taskbarController = mActivityInterface.getTaskbarController();
- if (taskbarController != null) {
- pa.add(taskbarController.createAnimToLauncher(OVERVIEW, getRecentsLaunchDuration()));
- }
-
- RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
- wallpaperTargets, MODE_CLOSING);
-
- // Use the top closing app to determine the insets for the animation
- RemoteAnimationTargetCompat runningTaskTarget = mTargetTask == null ? null
- : targets.findTask(mTargetTask.taskId);
- if (runningTaskTarget == null) {
- Log.e(TAG, "No closing app");
- return pa.buildAnim();
- }
-
- TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy());
- tsv.setDp(mActivity.getDeviceProfile());
- tsv.setOrientationState(mRecentsView.getPagedViewOrientedState());
- tsv.setPreview(runningTaskTarget);
-
- TransformParams params = new TransformParams()
- .setTargetSet(targets)
- .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView()));
-
- AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
- params.setBaseBuilderProxy((builder, app, p)
- -> builder.withAlpha(recentsAlpha.value));
-
- Interpolator taskInterpolator;
- if (targets.isAnimatingHome()) {
- params.setHomeBuilderProxy((builder, app, p) -> builder.withAlpha(1 - p.getProgress()));
-
- taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR;
- pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR);
- } else {
- // When animation from app to recents, the recents layer is drawn on top of the app. To
- // prevent the overlap, we animate the task first and then quickly fade in the recents.
- taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f);
- pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1,
- clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1));
- }
-
- pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator);
- tsv.addAppToOverviewAnim(pa, taskInterpolator);
- pa.addOnFrameCallback(() -> tsv.apply(params));
- return pa.buildAnim();
- }
-
- /**
- * Get duration of animation from app to overview.
- *
- * @return duration of animation
- */
- long getRecentsLaunchDuration() {
- return RECENTS_LAUNCH_DURATION;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index ce14197..0415009 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -31,8 +31,10 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
+import android.view.Gravity;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -42,6 +44,7 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -53,6 +56,7 @@
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.SplitScreenBounds;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -193,90 +197,118 @@
}
/**
- * Calculates the taskView size for the provided device configuration
+ * Calculates the taskView size for the provided device configuration.
*/
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
PagedOrientationHandler orientedState) {
- calculateTaskSize(context, dp, getExtraSpace(context, dp, orientedState), outRect);
- }
-
- protected abstract float getExtraSpace(Context context, DeviceProfile dp,
- PagedOrientationHandler orientedState);
-
- private void calculateTaskSize(Context context, DeviceProfile dp, float extraVerticalSpace,
- Rect outRect) {
Resources res = context.getResources();
+ if (dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+ Rect gridRect = new Rect();
+ calculateGridSize(context, dp, gridRect);
- final int paddingResId;
- if (dp.isMultiWindowMode) {
- paddingResId = R.dimen.multi_window_task_card_horz_space;
- } else if (dp.isVerticalBarLayout()) {
- paddingResId = R.dimen.landscape_task_card_horz_space;
+ int rowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
+ float rowHeight = (gridRect.height() - rowSpacing) / 2f;
+
+ PointF taskDimension = getTaskDimension(context, dp);
+ float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / Math.max(
+ taskDimension.x, taskDimension.y);
+ int outWidth = Math.round(scale * taskDimension.x);
+ int outHeight = Math.round(scale * taskDimension.y);
+
+ int gravity = Gravity.TOP;
+ gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
+ gridRect.inset(0, dp.overviewTaskThumbnailTopMarginPx, 0, 0);
+ Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
} else {
- paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
+ int taskMargin = dp.overviewTaskMarginPx;
+ int proactiveRowAndMargin;
+ if (dp.isVerticalBarLayout()) {
+ // In Vertical Bar Layout the proactive row doesn't have its own space, it's inside
+ // the actions row.
+ proactiveRowAndMargin = 0;
+ } else {
+ proactiveRowAndMargin = res.getDimensionPixelSize(
+ R.dimen.overview_proactive_row_height)
+ + res.getDimensionPixelSize(R.dimen.overview_proactive_row_bottom_margin);
+ }
+ calculateTaskSizeInternal(context, dp,
+ dp.overviewTaskThumbnailTopMarginPx,
+ proactiveRowAndMargin + getOverviewActionsHeight(context) + taskMargin,
+ res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+ outRect);
}
- float paddingHorz = res.getDimension(paddingResId);
- float paddingVert = 0;
-
- calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
- res.getDimension(R.dimen.task_thumbnail_top_margin), outRect);
}
private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
- float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin,
+ int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding,
Rect outRect) {
- float taskWidth, taskHeight;
+ PointF taskDimension = getTaskDimension(context, dp);
Rect insets = dp.getInsets();
+
+ Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
+ potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);
+ potentialTaskRect.inset(
+ minimumHorizontalPadding,
+ claimedSpaceAbove,
+ minimumHorizontalPadding,
+ claimedSpaceBelow);
+
+ float scale = Math.min(
+ potentialTaskRect.width() / taskDimension.x,
+ potentialTaskRect.height() / taskDimension.y);
+ int outWidth = Math.round(scale * taskDimension.x);
+ int outHeight = Math.round(scale * taskDimension.y);
+
+ Gravity.apply(Gravity.CENTER, outWidth, outHeight, potentialTaskRect, outRect);
+ }
+
+ private PointF getTaskDimension(Context context, DeviceProfile dp) {
+ PointF dimension = new PointF();
if (dp.isMultiWindowMode) {
WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
- taskWidth = bounds.availableSize.x;
- taskHeight = bounds.availableSize.y;
+ dimension.x = bounds.availableSize.x;
+ dimension.y = bounds.availableSize.y;
+ } else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ dimension.x = dp.availableWidthPx;
+ dimension.y = dp.availableHeightPx;
} else {
- taskWidth = dp.availableWidthPx;
- taskHeight = dp.availableHeightPx;
+ dimension.x = dp.widthPx;
+ dimension.y = dp.heightPx;
}
+ return dimension;
+ }
- // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
- // we override the insets ourselves.
- int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
- int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
+ /**
+ * Calculates the overview grid size for the provided device configuration.
+ */
+ public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
+ Resources res = context.getResources();
+ int topMargin = res.getDimensionPixelSize(R.dimen.overview_grid_top_margin);
+ int bottomMargin = res.getDimensionPixelSize(R.dimen.overview_grid_bottom_margin);
+ int sideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
- float availableHeight = launcherVisibleHeight
- - topIconMargin - extraVerticalSpace - paddingVert;
- float availableWidth = launcherVisibleWidth - paddingHorz;
-
- float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
- float outWidth = scale * taskWidth;
- float outHeight = scale * taskHeight;
-
- // Center in the visible space
- float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
- float y = insets.top + Math.max(topIconMargin,
- (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
- outRect.set(Math.round(x), Math.round(y),
- Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
+ Rect insets = dp.getInsets();
+ outRect.set(0, 0, dp.widthPx, dp.heightPx);
+ outRect.inset(Math.max(insets.left, sideMargin), Math.max(insets.top, topMargin),
+ Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
}
/**
* Calculates the modal taskView size for the provided device configuration
*/
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode
- ? R.dimen.multi_window_task_card_horz_space
- : dp.isVerticalBarLayout()
- ? R.dimen.landscape_task_card_horz_space
- : R.dimen.portrait_modal_task_card_horz_space);
- float extraVerticalSpace = getOverviewActionsHeight(context);
- float paddingVert = 0;
- float topIconMargin = 0;
- calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
- topIconMargin, outRect);
+ calculateTaskSizeInternal(
+ context, dp,
+ dp.overviewTaskMarginPx,
+ getOverviewActionsHeight(context) + dp.overviewTaskMarginPx,
+ dp.overviewTaskMarginPx,
+ outRect);
}
- /** Gets the space that the overview actions will take, including margins. */
- public final float getOverviewActionsHeight(Context context) {
+ /** Gets the space that the overview actions will take, including bottom margin. */
+ public final int getOverviewActionsHeight(Context context) {
Resources res = context.getResources();
- float actionsBottomMargin = 0;
+ int actionsBottomMargin = 0;
if (getMode(context) == Mode.THREE_BUTTONS) {
actionsBottomMargin = res.getDimensionPixelSize(
R.dimen.overview_actions_bottom_margin_three_button);
@@ -284,9 +316,8 @@
actionsBottomMargin = res.getDimensionPixelSize(
R.dimen.overview_actions_bottom_margin_gesture);
}
- float overviewActionsHeight = actionsBottomMargin
+ return actionsBottomMargin
+ res.getDimensionPixelSize(R.dimen.overview_actions_height);
- return overviewActionsHeight;
}
/**
@@ -303,6 +334,11 @@
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
}
+ /**
+ * Returns the expected STATE_TYPE from the provided GestureEndTarget.
+ */
+ public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
+
public interface AnimationFactory {
void createActivityInterface(long transitionLength);
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 96e4f38..e13d1a4 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -18,6 +18,7 @@
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
import static com.android.quickstep.fallback.RecentsState.DEFAULT;
+import static com.android.quickstep.fallback.RecentsState.HOME;
import android.content.Context;
import android.graphics.Rect;
@@ -26,7 +27,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.ActivityInitListener;
@@ -82,6 +82,7 @@
@Override
public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
+ notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
factory.initUI();
return factory;
@@ -104,7 +105,7 @@
@Override
public RecentsView getVisibleRecentsView() {
RecentsActivity activity = getCreatedActivity();
- if (activity != null && activity.hasWindowFocus()) {
+ if (activity != null && activity.hasBeenResumed()) {
return activity.getOverviewPanel();
}
return null;
@@ -157,8 +158,23 @@
}
@Override
- protected float getExtraSpace(Context context, DeviceProfile dp,
- PagedOrientationHandler orientationHandler) {
- return context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height);
+ public RecentsState stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget) {
+ switch (endTarget) {
+ case RECENTS:
+ return DEFAULT;
+ case NEW_TASK:
+ case LAST_TASK:
+ return BACKGROUND_APP;
+ case HOME:
+ default:
+ return HOME;
+ }
+ }
+
+ private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+ // reset layout on swipe to home
+ RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+ recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+ rotationTouchHelper.getDisplayRotation());
}
}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index a80c111..7e4a352 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -55,6 +55,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
@@ -73,7 +74,7 @@
*/
@TargetApi(Build.VERSION_CODES.R)
public class FallbackSwipeHandler extends
- AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+ AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView, RecentsState> {
/**
* Message used for receiving gesture nav contract information. We use a static messenger to
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 8d67ee6..ebdc1e6 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.os.Build;
+import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.GestureStateProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -213,7 +214,8 @@
/**
* @return the interface to the activity handing the UI updates for this gesture.
*/
- public <T extends StatefulActivity<?>> BaseActivityInterface<?, T> getActivityInterface() {
+ public <S extends BaseState<S>,
+ T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() {
return mActivityInterface;
}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 8b0d782..98b96b2 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -18,14 +18,12 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.SysUINavigationMode.getMode;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Rect;
-import android.util.Log;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -36,13 +34,11 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.taskbar.TaskbarController;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -181,20 +177,18 @@
@UiThread
private Launcher getVisibleLauncher() {
Launcher launcher = getCreatedActivity();
- return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus()
+ return (launcher != null) && launcher.isStarted() && launcher.hasBeenResumed()
? launcher : null;
}
@Override
public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "switchToRecentsIfVisible");
- }
Launcher launcher = getVisibleLauncher();
if (launcher == null) {
return false;
}
+ closeOverlay();
launcher.getStateManager().goToState(OVERVIEW,
launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
return true;
@@ -266,27 +260,6 @@
}
@Override
- protected float getExtraSpace(Context context, DeviceProfile dp,
- PagedOrientationHandler orientationHandler) {
- Resources res = context.getResources();
- //TODO: this needs to account for the swipe gesture height and accessibility
- // UI when shown.
- float actionsBottomMargin = 0;
- if (!dp.isVerticalBarLayout()) {
- if (getMode(context) == Mode.THREE_BUTTONS) {
- actionsBottomMargin = res.getDimensionPixelSize(
- R.dimen.overview_actions_bottom_margin_three_button);
- } else {
- actionsBottomMargin = res.getDimensionPixelSize(
- R.dimen.overview_actions_bottom_margin_gesture);
- }
- }
- float actionsHeight = actionsBottomMargin
- + res.getDimensionPixelSize(R.dimen.overview_actions_height);
- return actionsHeight;
- }
-
- @Override
void onOverviewServiceBound() {
final BaseQuickstepLauncher activity = getCreatedActivity();
if (activity == null) return;
@@ -299,7 +272,7 @@
if (taskbarController == null) {
return;
}
- LauncherState toState = endTarget == GestureEndTarget.RECENTS ? OVERVIEW : NORMAL;
+ LauncherState toState = stateFromGestureEndTarget(endTarget);
taskbarController.createAnimToLauncher(toState, duration).start();
}
@@ -330,4 +303,18 @@
}
return taskbarController.isDraggingItem();
}
+
+ @Override
+ public LauncherState stateFromGestureEndTarget(GestureEndTarget endTarget) {
+ switch (endTarget) {
+ case RECENTS:
+ return OVERVIEW;
+ case NEW_TASK:
+ case LAST_TASK:
+ return QUICK_SWITCH;
+ case HOME:
+ default:
+ return NORMAL;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 842fb84..1ce4201 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -27,6 +27,7 @@
import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.util.RectFSpringAnim;
@@ -39,7 +40,7 @@
* Temporary class to allow easier refactoring
*/
public class LauncherSwipeHandlerV2 extends
- AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
+ AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView, LauncherState> {
public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 6a32dc4..a749f9a 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -273,7 +273,6 @@
Point size = display.realSize;
int rotation = display.rotation;
int touchHeight = mNavBarGesturalHeight;
- int largerGesturalHeight = mNavBarLargerGesturalHeight;
OrientationRectF orientationRectF =
new OrientationRectF(0, 0, size.x, size.y, rotation);
if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 985389e..923d4f1 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -16,29 +16,28 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.annotation.TargetApi;
-import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
import android.os.Build;
import android.os.SystemClock;
import android.os.Trace;
-import android.view.ViewConfiguration;
import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.ArrayList;
/**
* Helper class to handle various atomic commands for switching between Overview.
@@ -46,66 +45,191 @@
@TargetApi(Build.VERSION_CODES.P)
public class OverviewCommandHelper {
- private final Context mContext;
- private final RecentsAnimationDeviceState mDeviceState;
+ public static final int TYPE_SHOW = 1;
+ public static final int TYPE_SHOW_NEXT_FOCUS = 2;
+ public static final int TYPE_HIDE = 3;
+ public static final int TYPE_TOGGLE = 4;
+
+ private static final String TRANSITION_NAME = "Transition:toOverview";
+
+ private final TouchInteractionService mService;
private final OverviewComponentObserver mOverviewComponentObserver;
+ private final TaskAnimationManager mTaskAnimationManager;
+ private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
- private long mLastToggleTime;
-
- public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
- OverviewComponentObserver observer) {
- mContext = context;
- mDeviceState = deviceState;
+ public OverviewCommandHelper(TouchInteractionService service,
+ OverviewComponentObserver observer,
+ TaskAnimationManager taskAnimationManager) {
+ mService = service;
mOverviewComponentObserver = observer;
+ mTaskAnimationManager = taskAnimationManager;
}
- @BinderThread
- public void onOverviewToggle() {
- // If currently screen pinning, do not enter overview
- if (mDeviceState.isScreenPinningActive()) {
+ /**
+ * Called when the command finishes execution.
+ */
+ private void scheduleNextTask(CommandInfo command) {
+ if (!mPendingCommands.isEmpty() && mPendingCommands.get(0) == command) {
+ mPendingCommands.remove(0);
+ executeNext();
+ }
+ }
+
+ /**
+ * Executes the next command from the queue. If the command finishes immediately (returns true),
+ * it continues to execute the next command, until the queue is empty of a command defer's its
+ * completion (returns false).
+ */
+ @UiThread
+ private void executeNext() {
+ if (mPendingCommands.isEmpty()) {
return;
}
-
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
+ CommandInfo cmd = mPendingCommands.get(0);
+ if (executeCommand(cmd)) {
+ scheduleNextTask(cmd);
+ }
}
+ @UiThread
+ private void addCommand(CommandInfo cmd) {
+ boolean wasEmpty = mPendingCommands.isEmpty();
+ mPendingCommands.add(cmd);
+ if (wasEmpty) {
+ executeNext();
+ }
+ }
+
+ /**
+ * Adds a command to be executed next, after all pending tasks are completed
+ */
@BinderThread
- public void onOverviewShown(boolean triggeredFromAltTab) {
- if (triggeredFromAltTab) {
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- }
- MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
+ public void addCommand(int type) {
+ CommandInfo cmd = new CommandInfo(type);
+ MAIN_EXECUTOR.execute(() -> addCommand(cmd));
}
- @BinderThread
- public void onOverviewHidden() {
- MAIN_EXECUTOR.execute(new HideRecentsCommand());
+ private TaskView getNextTask(RecentsView view) {
+ final TaskView runningTaskView = view.getRunningTaskView();
+
+ if (runningTaskView == null) {
+ return view.getTaskViewCount() > 0 ? view.getTaskViewAt(0) : null;
+ } else {
+ final TaskView nextTask = view.getNextTaskView();
+ return nextTask != null ? nextTask : runningTaskView;
+ }
}
- private class ShowRecentsCommand extends RecentsActivityCommand {
-
- private final boolean mTriggeredFromAltTab;
-
- ShowRecentsCommand(boolean triggeredFromAltTab) {
- mTriggeredFromAltTab = triggeredFromAltTab;
+ private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
+ RunnableList callbackList = null;
+ if (taskView != null) {
+ taskView.setEndQuickswitchCuj(true);
+ callbackList = taskView.launchTaskAnimated();
}
- @Override
- protected boolean handleCommand(long elapsedTime) {
- // TODO: Go to the next page if started from alt-tab.
- return mActivityInterface.getVisibleRecentsView() != null;
+ if (callbackList != null) {
+ callbackList.add(() -> scheduleNextTask(cmd));
+ return false;
+ } else {
+ recents.startHome();
+ return true;
}
+ }
- @Override
- protected void onTransitionComplete() {
- // TODO(b/138729100) This doesn't execute first time launcher is run
- if (mTriggeredFromAltTab) {
- RecentsView rv = mActivityInterface.getVisibleRecentsView();
- if (rv == null) {
- return;
+ /**
+ * Executes the task and returns true if next task can be executed. If false, then the next
+ * task is deferred until {@link #scheduleNextTask} is called
+ */
+ private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
+ BaseActivityInterface<?, T> activityInterface =
+ mOverviewComponentObserver.getActivityInterface();
+ RecentsView recents = activityInterface.getVisibleRecentsView();
+ if (recents == null) {
+ if (cmd.type == TYPE_HIDE) {
+ // already hidden
+ return true;
+ }
+ } else {
+ switch (cmd.type) {
+ case TYPE_SHOW:
+ // already visible
+ return true;
+ case TYPE_HIDE: {
+ int currentPage = recents.getNextPage();
+ TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
+ ? (TaskView) recents.getPageAt(currentPage)
+ : null;
+ return launchTask(recents, tv, cmd);
}
+ case TYPE_TOGGLE:
+ return launchTask(recents, getNextTask(recents), cmd);
+ }
+ }
+ if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
+ // If successfully switched, wait until animation finishes
+ return false;
+ }
+
+ final T activity = activityInterface.getCreatedActivity();
+ if (activity != null) {
+ InteractionJankMonitorWrapper.begin(
+ activity.getRootView(),
+ InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ }
+
+ GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE);
+ AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
+ .newHandler(gestureState, cmd.createTime);
+ interactionHandler.setGestureEndCallback(
+ () -> onTransitionComplete(cmd, interactionHandler));
+
+ Intent intent = new Intent(interactionHandler.getLaunchIntent());
+ interactionHandler.initWhenReady(intent);
+
+ RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ interactionHandler.onGestureEnded(0, new PointF(), new PointF());
+ cmd.removeListener(this);
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ interactionHandler.onGestureCancelled();
+ cmd.removeListener(this);
+ }
+ };
+
+ if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+ cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
+ cmd.mActiveCallbacks.addListener(interactionHandler);
+ mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
+ interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
+
+ cmd.mActiveCallbacks.addListener(recentAnimListener);
+ mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
+ } else {
+ intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
+ cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
+ gestureState, intent, interactionHandler);
+ interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
+ cmd.mActiveCallbacks.addListener(recentAnimListener);
+ }
+
+ Trace.beginAsyncSection(TRANSITION_NAME, 0);
+ return false;
+ }
+
+ private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
+ cmd.removeListener(handler);
+ Trace.endAsyncSection(TRANSITION_NAME, 0);
+
+ if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
+ RecentsView rv =
+ mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+ if (rv != null) {
// Ensure that recents view has focus so that it receives the followup key inputs
TaskView taskView = rv.getNextTaskView();
if (taskView == null) {
@@ -120,130 +244,22 @@
}
}
}
+ scheduleNextTask(cmd);
}
- private class HideRecentsCommand extends RecentsActivityCommand {
+ private static class CommandInfo {
+ public final long createTime = SystemClock.elapsedRealtime();
+ public final int type;
+ RecentsAnimationCallbacks mActiveCallbacks;
- @Override
- protected boolean handleCommand(long elapsedTime) {
- RecentsView recents = mActivityInterface.getVisibleRecentsView();
- if (recents == null) {
- return false;
- }
- int currentPage = recents.getNextPage();
- if (currentPage >= 0 && currentPage < recents.getTaskViewCount()) {
- ((TaskView) recents.getPageAt(currentPage)).launchTask(true);
- } else {
- recents.startHome();
- }
- return true;
- }
- }
-
- private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
-
- private static final String TRANSITION_NAME = "Transition:toOverview";
- protected final BaseActivityInterface<?, T> mActivityInterface;
- private final long mCreateTime;
- private final AppToOverviewAnimationProvider<T> mAnimationProvider;
-
- private final long mToggleClickedTime = SystemClock.uptimeMillis();
- private ActivityInitListener mListener;
-
- public RecentsActivityCommand() {
- mActivityInterface = mOverviewComponentObserver.getActivityInterface();
- mCreateTime = SystemClock.elapsedRealtime();
- mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
- ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
-
- // Preload the plan
- RecentsModel.INSTANCE.get(mContext).getTasks(null);
+ CommandInfo(int type) {
+ this.type = type;
}
- @Override
- public void run() {
- long elapsedTime = mCreateTime - mLastToggleTime;
- mLastToggleTime = mCreateTime;
-
- if (handleCommand(elapsedTime)) {
- // Command already handled.
- return;
+ void removeListener(RecentsAnimationListener listener) {
+ if (mActiveCallbacks != null) {
+ mActiveCallbacks.removeListener(listener);
}
-
- if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
- // If successfully switched, then return
- return;
- }
-
- final T activity = mActivityInterface.getCreatedActivity();
- if (activity != null) {
- InteractionJankMonitorWrapper.begin(
- activity.getRootView(),
- InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
- }
-
- // Otherwise, start overview.
- mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
- mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
- new RemoteAnimationProvider() {
- @Override
- public AnimatorSet createWindowAnimation(
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
- return RecentsActivityCommand.this.createWindowAnimation(appTargets,
- wallpaperTargets);
- }
- }, mContext, MAIN_EXECUTOR.getHandler(),
- mAnimationProvider.getRecentsLaunchDuration());
}
-
- protected boolean handleCommand(long elapsedTime) {
- // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
- // the menu activity which takes window focus, preventing the right condition from
- // being run below
- RecentsView recents = mActivityInterface.getVisibleRecentsView();
- if (recents != null) {
- // Launch the next task
- recents.showNextTask();
- return true;
- } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
- // The user tried to launch back into overview too quickly, either after
- // launching an app, or before overview has actually shown, just ignore for now
- return true;
- }
- return false;
- }
-
- private boolean onActivityReady(Boolean wasVisible) {
- final T activity = mActivityInterface.getCreatedActivity();
- return mAnimationProvider.onActivityReady(activity, wasVisible);
- }
-
- private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets) {
- LatencyTrackerCompat.logToggleRecents(
- mContext, (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
-
- mListener.unregister();
-
- AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(appTargets,
- wallpaperTargets);
- animatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- Trace.beginAsyncSection(TRANSITION_NAME, 0);
- super.onAnimationStart(animation);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onTransitionComplete();
- Trace.endAsyncSection(TRANSITION_NAME, 0);
- }
- });
- return animatorSet;
- }
-
- protected void onTransitionComplete() { }
}
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 65847f1..192738f 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -59,6 +61,8 @@
super.init(context);
+ LIVE_TILE.initialize(context);
+
// Elevate GPU priority for Quickstep and Remote animations.
ThreadedRendererCompat.setContextPriority(
ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 70b4f20..2f1538b 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -22,10 +22,12 @@
import android.app.ActivityManager;
import android.os.Build;
import android.os.Process;
+import android.util.Log;
import android.util.SparseBooleanArray;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.LooperExecutor;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -51,6 +53,9 @@
// The list change id, increments as the task list changes in the system
private int mChangeId;
+ // Whether we are currently updating the tasks in the background (up to when the result is
+ // posted back on the main thread)
+ private boolean mLoadingTasksInBackground;
private TaskLoadResult mResultsBg = INVALID_RESULT;
private TaskLoadResult mResultsUi = INVALID_RESULT;
@@ -64,6 +69,11 @@
mActivityManagerWrapper.registerTaskStackListener(this);
}
+ @VisibleForTesting
+ public boolean isLoadingTasksInBackground() {
+ return mLoadingTasksInBackground;
+ }
+
/**
* Fetches the task keys skipping any local cache.
*/
@@ -83,6 +93,10 @@
* @return The change id of the current task list
*/
public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: keysOnly=" + loadKeysOnly
+ + " callback=" + callback);
+ }
final int requestLoadId = mChangeId;
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
// The list is up to date, send the callback on the next frame,
@@ -90,22 +104,38 @@
if (callback != null) {
// Copy synchronously as the changeId might change by next frame
ArrayList<Task> result = copyOf(mResultsUi);
- mMainThreadExecutor.post(() -> callback.accept(result));
+ mMainThreadExecutor.post(() -> {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: no new tasks");
+ }
+ callback.accept(result);
+ });
}
return requestLoadId;
}
// Kick off task loading in the background
+ mLoadingTasksInBackground = true;
UI_HELPER_EXECUTOR.execute(() -> {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: loading in bg start");
+ }
if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
}
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: loading in bg end");
+ }
TaskLoadResult loadResult = mResultsBg;
mMainThreadExecutor.execute(() -> {
+ mLoadingTasksInBackground = false;
mResultsUi = loadResult;
if (callback != null) {
ArrayList<Task> result = copyOf(mResultsUi);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: callback w/ bg results");
+ }
callback.accept(result);
}
});
@@ -191,6 +221,7 @@
} else {
task = new Task(taskKey);
}
+ task.setLastSnapshotData(rawTask);
allTasks.add(task);
}
@@ -200,9 +231,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 8aa0842..d3ed791 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,9 +18,9 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
import static com.android.launcher3.Utilities.createHomeIntent;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
@@ -30,7 +30,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.app.ActivityOptions;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -46,7 +45,6 @@
import com.android.launcher3.R;
import com.android.launcher3.WrappedAnimationRunnerImpl;
import com.android.launcher3.WrappedLauncherAnimationRunner;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -54,7 +52,9 @@
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
@@ -63,7 +63,9 @@
import com.android.quickstep.fallback.RecentsDragLayer;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -71,6 +73,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/**
* A recents activity that shows the recently launched tasks as swipable task cards.
@@ -93,7 +96,6 @@
// Strong refs to runners which are cleared when the activity is destroyed
private WrappedAnimationRunnerImpl mActivityLaunchAnimationRunner;
- private SearchAdapterProvider mSearchAdapterProvider;
/**
* Init drag layer and overview panel views.
@@ -105,8 +107,14 @@
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
+ SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
+ splitPlaceholderView.init(
+ new SplitSelectStateController(
+ SystemUiProxy.INSTANCE.get(this))
+ );
+
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView);
+ mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
}
@Override
@@ -171,42 +179,40 @@
}
@Override
- public ActivityOptions getActivityLaunchOptions(final View v) {
+ public ActivityOptionsWrapper getActivityLaunchOptions(final View v) {
if (!(v instanceof TaskView)) {
- return null;
+ return super.getActivityLaunchOptions(v);
}
final TaskView taskView = (TaskView) v;
- mActivityLaunchAnimationRunner = new WrappedAnimationRunnerImpl() {
- @Override
- public Handler getHandler() {
- return mUiHandler;
- }
+ RunnableList onEndCallback = new RunnableList();
- @Override
- public void onCreateAnimation(int transit,
+ mActivityLaunchAnimationRunner = (int transit,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
RemoteAnimationTargetCompat[] nonAppTargets,
- AnimationResult result) {
- AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
- wallpaperTargets);
- anim.addListener(resetStateListener());
- result.setAnimation(anim, RecentsActivity.this);
- }
+ AnimationResult result) -> {
+ AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
+ wallpaperTargets);
+ anim.addListener(resetStateListener());
+ result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy);
};
+
final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner<>(
- mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */);
- return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+ mUiHandler, mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */);
+ RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(
wrapper, RECENTS_LAUNCH_DURATION,
RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
- - STATUS_BAR_TRANSITION_PRE_DELAY));
+ - STATUS_BAR_TRANSITION_PRE_DELAY);
+ return new ActivityOptionsWrapper(
+ ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
+ onEndCallback);
}
/**
* Composes the animations for a launch from the recents list if possible.
*/
- private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
+ private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
AnimatorSet target = new AnimatorSet();
@@ -317,8 +323,8 @@
}
@Override
- protected StateHandler<RecentsState>[] createStateHandlers() {
- return new StateHandler[] { new FallbackRecentsStateController(this) };
+ protected void collectStateHandlers(List<StateHandler> out) {
+ out.add(new FallbackRecentsStateController(this));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 646c5a0..ec585cc 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -149,10 +149,14 @@
* accordingly. This should be called before `finish`
* @param taskId for which the leash should be updated
* @param destinationBounds bounds of the final PiP window
+ * @param windowCrop bounds to crop as part of final transform.
+ * @param float9 An array of 9 floats to be used as matrix transform.
*/
- public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+ public void setFinishTaskBounds(int taskId, Rect destinationBounds, Rect windowCrop,
+ float[] float9) {
UI_HELPER_EXECUTOR.execute(
- () -> mController.setFinishTaskBounds(taskId, destinationBounds));
+ () -> mController.setFinishTaskBounds(taskId, destinationBounds, windowCrop,
+ float9));
}
/**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index f99b7e6..458f45a 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,12 +17,13 @@
import static android.content.Intent.ACTION_USER_UNLOCKED;
-import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
-import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -60,11 +61,11 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayHolder;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
@@ -304,6 +305,13 @@
}
/**
+ * @return whether the current nav mode is 2-button-based.
+ */
+ public boolean isTwoButtonNavMode() {
+ return mMode == TWO_BUTTONS;
+ }
+
+ /**
* @return whether the current nav mode is button-based.
*/
public boolean isButtonNavMode() {
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index d47217b..ba24e6a 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -28,6 +28,8 @@
import android.os.Process;
import android.os.UserHandle;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -102,6 +104,14 @@
}
/**
+ * @return Whether the task list is currently updating in the background
+ */
+ @VisibleForTesting
+ public boolean isLoadingTasksInBackground() {
+ return mTaskList.isLoadingTasksInBackground();
+ }
+
+ /**
* Finds and returns the task key associated with the given task id.
*
* @param callback The callback to receive the task key if it is found or null. This is always
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index ca73041..f4b8b62 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -94,13 +94,7 @@
mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
dp, mContext, TEMP_RECT,
mTaskViewSimulator.getOrientationState().getOrientationHandler());
-
- if (mDeviceState.isFullyGesturalNavMode()) {
- // We can drag all the way to the top of the screen.
- mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
- } else {
- mDragLengthFactor = 1 + AnimatorControllerWithResistance.TWO_BUTTON_EXTRA_DRAG_FACTOR;
- }
+ mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 64d05e1..cf71eae 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -21,11 +21,11 @@
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -35,37 +35,56 @@
import android.view.MotionEvent;
import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-import com.android.systemui.shared.recents.ISplitScreenListener;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteTransitionCompat;
+import com.android.wm.shell.onehanded.IOneHanded;
+import com.android.wm.shell.pip.IPip;
+import com.android.wm.shell.pip.IPipAnimationListener;
+import com.android.wm.shell.splitscreen.ISplitScreen;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.startingsurface.IStartingWindow;
+import com.android.wm.shell.startingsurface.IStartingWindowListener;
+import com.android.wm.shell.transition.IShellTransitions;
/**
* Holds the reference to SystemUI.
*/
-public class SystemUiProxy implements ISystemUiProxy {
+public class SystemUiProxy implements ISystemUiProxy,
+ SysUINavigationMode.NavigationModeChangeListener {
private static final String TAG = SystemUiProxy.class.getSimpleName();
public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
new MainThreadInitializedObject<>(SystemUiProxy::new);
private ISystemUiProxy mSystemUiProxy;
+ private IPip mPip;
+ private ISplitScreen mSplitScreen;
+ private IOneHanded mOneHanded;
+ private IShellTransitions mShellTransitions;
+ private IStartingWindow mStartingWindow;
private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
- MAIN_EXECUTOR.execute(() -> setProxy(null));
+ MAIN_EXECUTOR.execute(() -> clearProxy());
};
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
private boolean mLastShelfVisible;
- private float mLastBackButtonAlpha;
- private boolean mLastBackButtonAnimate;
+ private float mLastNavButtonAlpha;
+ private boolean mLastNavButtonAnimate;
+ private boolean mHasNavButtonAlphaBeenSet = false;
// TODO(141886704): Find a way to remove this
private int mLastSystemUiStateFlags;
public SystemUiProxy(Context context) {
- // Do nothing
+ SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+ }
+
+ @Override
+ public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+ // Whenever the nav mode changes, force reset the nav button alpha
+ setNavBarButtonAlpha(1f, false);
}
@Override
@@ -74,12 +93,23 @@
return null;
}
- public void setProxy(ISystemUiProxy proxy) {
+ public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
+ IOneHanded oneHanded, IShellTransitions shellTransitions,
+ IStartingWindow startingWindow) {
unlinkToDeath();
mSystemUiProxy = proxy;
+ mPip = pip;
+ mSplitScreen = splitScreen;
+ mOneHanded = oneHanded;
+ mShellTransitions = shellTransitions;
+ mStartingWindow = startingWindow;
linkToDeath();
}
+ public void clearProxy() {
+ setProxy(null, null, null, null, null, null);
+ }
+
// TODO(141886704): Find a way to remove this
public void setLastSystemUiStateFlags(int stateFlags) {
mLastSystemUiStateFlags = stateFlags;
@@ -148,28 +178,19 @@
return null;
}
- @Override
- public void setBackButtonAlpha(float alpha, boolean animate) {
- boolean changed = Float.compare(alpha, mLastBackButtonAlpha) != 0
- || animate != mLastBackButtonAnimate;
- if (mSystemUiProxy != null && changed) {
- mLastBackButtonAlpha = alpha;
- mLastBackButtonAnimate = animate;
- try {
- mSystemUiProxy.setBackButtonAlpha(alpha, animate);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setBackButtonAlpha", e);
- }
- }
- }
-
- public float getLastBackButtonAlpha() {
- return mLastBackButtonAlpha;
+ public float getLastNavButtonAlpha() {
+ return mLastNavButtonAlpha;
}
@Override
public void setNavBarButtonAlpha(float alpha, boolean animate) {
- if (mSystemUiProxy != null) {
+ boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
+ || animate != mLastNavButtonAnimate
+ || !mHasNavButtonAlphaBeenSet;
+ if (mSystemUiProxy != null && changed) {
+ mLastNavButtonAlpha = alpha;
+ mLastNavButtonAnimate = animate;
+ mHasNavButtonAlphaBeenSet = true;
try {
mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
} catch (RemoteException e) {
@@ -268,21 +289,6 @@
}
@Override
- public void setShelfHeight(boolean visible, int shelfHeight) {
- boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
- if (mSystemUiProxy != null && changed) {
- mLastShelfVisible = visible;
- mLastShelfHeight = shelfHeight;
- try {
- mSystemUiProxy.setShelfHeight(visible, shelfHeight);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setShelfHeight visible: " + visible
- + " height: " + shelfHeight, e);
- }
- }
- }
-
- @Override
public void handleImageAsScreenshot(Bitmap bitmap, Rect rect, Insets insets, int i) {
if (mSystemUiProxy != null) {
try {
@@ -318,20 +324,6 @@
}
}
- /**
- * Sets listener to get pinned stack animation callbacks.
- */
- @Override
- public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.setPinnedStackAnimationListener(listener);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
- }
- }
- }
-
@Override
public void onQuickSwitchToNewTask(int rotation) {
if (mSystemUiProxy != null) {
@@ -357,28 +349,6 @@
}
@Override
- public void startOneHandedMode() {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.startOneHandedMode();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call startOneHandedMode", e);
- }
- }
- }
-
- @Override
- public void stopOneHandedMode() {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.stopOneHandedMode();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call stopOneHandedMode", e);
- }
- }
- }
-
- @Override
public void expandNotificationPanel() {
if (mSystemUiProxy != null) {
try {
@@ -389,12 +359,45 @@
}
}
- @Override
+ //
+ // Pip
+ //
+
+ /**
+ * Sets the shelf height.
+ */
+ public void setShelfHeight(boolean visible, int shelfHeight) {
+ boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
+ if (mPip != null && changed) {
+ mLastShelfVisible = visible;
+ mLastShelfHeight = shelfHeight;
+ try {
+ mPip.setShelfHeight(visible, shelfHeight);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setShelfHeight visible: " + visible
+ + " height: " + shelfHeight, e);
+ }
+ }
+ }
+
+ /**
+ * Sets listener to get pinned stack animation callbacks.
+ */
+ public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
+ if (mPip != null) {
+ try {
+ mPip.setPinnedStackAnimationListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
+ }
+ }
+ }
+
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams, int launcherRotation, int shelfHeight) {
- if (mSystemUiProxy != null) {
+ if (mPip != null) {
try {
- return mSystemUiProxy.startSwipePipToHome(componentName, activityInfo,
+ return mPip.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams, launcherRotation, shelfHeight);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startSwipePipToHome", e);
@@ -403,111 +406,85 @@
return null;
}
- @Override
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- if (mSystemUiProxy != null) {
+ if (mPip != null) {
try {
- mSystemUiProxy.stopSwipePipToHome(componentName, destinationBounds);
+ mPip.stopSwipePipToHome(componentName, destinationBounds);
} catch (RemoteException e) {
Log.w(TAG, "Failed call stopSwipePipToHome");
}
}
}
- @Override
- public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.registerRemoteTransition(remoteTransition);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call registerRemoteTransition");
- }
- }
- }
+ //
+ // Splitscreen
+ //
- @Override
- public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.unregisterRemoteTransition(remoteTransition);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call registerRemoteTransition");
- }
- }
- }
-
- @Override
public void registerSplitScreenListener(ISplitScreenListener listener) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.registerSplitScreenListener(listener);
+ mSplitScreen.registerSplitScreenListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
}
}
- @Override
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.unregisterSplitScreenListener(listener);
+ mSplitScreen.unregisterSplitScreenListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call unregisterSplitScreenListener");
}
}
}
- @Override
public void setSideStageVisibility(boolean visible) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.setSideStageVisibility(visible);
+ mSplitScreen.setSideStageVisibility(visible);
} catch (RemoteException e) {
Log.w(TAG, "Failed call setSideStageVisibility");
}
}
}
- @Override
public void exitSplitScreen() {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.exitSplitScreen();
+ mSplitScreen.exitSplitScreen();
} catch (RemoteException e) {
Log.w(TAG, "Failed call exitSplitScreen");
}
}
}
- @Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ mSplitScreen.exitSplitScreenOnHide(exitSplitScreenOnHide);
} catch (RemoteException e) {
Log.w(TAG, "Failed call exitSplitScreen");
}
}
}
- @Override
public void startTask(int taskId, int stage, int position, Bundle options) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.startTask(taskId, stage, position, options);
+ mSplitScreen.startTask(taskId, stage, position, options);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startTask");
}
}
}
- @Override
public void startShortcut(String packageName, String shortcutId, int stage, int position,
Bundle options, UserHandle user) {
- if (mSystemUiProxy != null) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.startShortcut(packageName, shortcutId, stage, position, options,
+ mSplitScreen.startShortcut(packageName, shortcutId, stage, position, options,
user);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startShortcut");
@@ -515,15 +492,90 @@
}
}
- @Override
- public void startIntent(PendingIntent intent, int stage, int position, Bundle options) {
- if (mSystemUiProxy != null) {
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ Bundle options) {
+ if (mSplitScreen != null) {
try {
- mSystemUiProxy.startIntent(intent, stage, position, options);
+ mSplitScreen.startIntent(intent, fillInIntent, stage, position, options);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntent");
}
}
}
+ public void removeFromSideStage(int taskId) {
+ if (mSplitScreen != null) {
+ try {
+ mSplitScreen.removeFromSideStage(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeFromSideStage");
+ }
+ }
+ }
+
+ //
+ // One handed
+ //
+
+ public void startOneHandedMode() {
+ if (mOneHanded != null) {
+ try {
+ mOneHanded.startOneHanded();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startOneHandedMode", e);
+ }
+ }
+ }
+
+ public void stopOneHandedMode() {
+ if (mOneHanded != null) {
+ try {
+ mOneHanded.stopOneHanded();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopOneHandedMode", e);
+ }
+ }
+ }
+
+ //
+ // Remote transitions
+ //
+
+ public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) {
+ if (mShellTransitions != null) {
+ try {
+ mShellTransitions.registerRemote(remoteTransition.getFilter(),
+ remoteTransition.getTransition());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerRemoteTransition");
+ }
+ }
+ }
+
+ public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
+ if (mShellTransitions != null) {
+ try {
+ mShellTransitions.unregisterRemote(remoteTransition.getTransition());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerRemoteTransition");
+ }
+ }
+ }
+
+ //
+ // Starting window
+ //
+
+ /**
+ * Sets listener to get callbacks when launching a task.
+ */
+ public void setStartingWindowListener(IStartingWindowListener listener) {
+ if (mStartingWindow != null) {
+ try {
+ mStartingWindow.setStartingWindowListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setStartingWindowListener", e);
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 02c2763..9a454f2 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -50,6 +50,7 @@
private GestureState mLastGestureState;
private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
+ private Runnable mLiveTileCleanUpHandler;
private Context mCtx;
TaskAnimationManager(Context ctx) {
@@ -169,6 +170,10 @@
mLaunchOtherTaskHandler = handler;
}
+ public void setLiveTileCleanUpHandler(Runnable runnable) {
+ mLiveTileCleanUpHandler = runnable;
+ }
+
/**
* Finishes the running recents animation.
*/
@@ -206,6 +211,11 @@
* Cleans up the recents animation entirely.
*/
private void cleanUpRecentsAnimation() {
+ if (mLiveTileCleanUpHandler != null) {
+ mLiveTileCleanUpHandler.run();
+ mLiveTileCleanUpHandler = null;
+ }
+
// Release all the target leashes
if (mTargets != null) {
mTargets.release();
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 3d98b9c..1ad5f2c 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -18,7 +18,7 @@
import static android.view.Surface.ROTATION_0;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
@@ -36,11 +36,16 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.TaskShortcutFactory.SplitSelectSystemShortcut;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -57,11 +62,18 @@
*/
public class TaskOverlayFactory implements ResourceBasedOverride {
- public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
+ public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
+ DeviceProfile deviceProfile) {
final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
+ if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
+ FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+ continue;
+ }
+
if (shortcut != null) {
shortcuts.add(shortcut);
}
@@ -91,6 +103,18 @@
return shortcuts;
}
+
+ public static void addSplitOptions(List<SystemShortcut> outShortcuts,
+ BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
+ PagedOrientationHandler orientationHandler =
+ taskView.getRecentsView().getPagedOrientationHandler();
+ List<SplitPositionOption> positions =
+ orientationHandler.getSplitPositionOptions(deviceProfile);
+ for (SplitPositionOption option : positions) {
+ outShortcuts.add(new SplitSelectSystemShortcut(activity, taskView, option));
+ }
+ }
+
public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
return new TaskOverlay(thumbnailView);
}
@@ -163,7 +187,7 @@
* @param callback callback to run, after switching to screenshot
*/
public void endLiveTileMode(@NonNull Runnable callback) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
recentsView.switchToScreenshot(
() -> recentsView.finishRecentsAnimation(true /* toRecents */, callback));
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 65bb0f3..c06e9a9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -34,12 +34,13 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
-import com.android.launcher3.util.Executors;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -54,13 +55,11 @@
import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
/**
* Represents a system shortcut that can be shown for a recent task.
*/
public interface TaskShortcutFactory {
-
SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
@@ -95,6 +94,23 @@
}
}
+ class SplitSelectSystemShortcut extends SystemShortcut {
+ private final TaskView mTaskView;
+ private SplitPositionOption mSplitPositionOption;
+ public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
+ SplitPositionOption option) {
+ super(option.mIconResId, option.mTextResId, target, taskView.getItemInfo());
+ mTaskView = taskView;
+ mSplitPositionOption = option;
+ setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+ }
+
+ @Override
+ public void onClick(View view) {
+ mTaskView.initiateSplitSelect(mSplitPositionOption);
+ }
+ }
+
class MultiWindowSystemShortcut extends SystemShortcut {
private Handler mHandler;
@@ -213,6 +229,16 @@
}
@Override
+ public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
+ SystemShortcut shortcut = super.getShortcut(activity, taskView);
+ if (FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ // Disable if there's only one recent app for split screen
+ shortcut.setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+ }
+ return shortcut;
+ }
+
+ @Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
final ActivityCompat act = new ActivityCompat(activity);
final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
@@ -281,15 +307,9 @@
@Override
public void onClick(View view) {
- Consumer<Boolean> resultCallback = success -> {
- if (success) {
- SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
- mTaskView.getTask().key.id);
- } else {
- mTaskView.notifyTaskLaunchFailed(TAG);
- }
- };
- mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
+ if (mTaskView.launchTaskAnimated() != null) {
+ SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(mTaskView.getTask().key.id);
+ }
dismissTaskMenuView(mTarget);
mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
.log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 25c0928..79db842 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -18,24 +18,28 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.view.View;
@@ -138,7 +142,7 @@
boolean isRunningTask = v.isRunningTask();
TransformParams params = null;
TaskViewSimulator tsv = null;
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+ if (LIVE_TILE.get() && isRunningTask) {
params = v.getRecentsView().getLiveTileParams();
tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
}
@@ -158,8 +162,7 @@
boolean isQuickSwitch = v.isEndQuickswitchCuj();
v.setEndQuickswitchCuj(false);
- boolean inLiveTileMode =
- ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
+ boolean inLiveTileMode = LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
final RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets,
inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
@@ -180,6 +183,8 @@
boolean parallaxCenterAndAdjacentTask =
taskIndex != recentsView.getCurrentPage() && !(dp.isTablet
&& FeatureFlags.ENABLE_OVERVIEW_GRID.get());
+ float gridProgress = recentsView.getGridProgress();
+ float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
int startScroll = recentsView.getScrollOffset(taskIndex);
TaskViewSimulator topMostSimulator = null;
@@ -196,6 +201,7 @@
tsv.setPreview(targets.apps[targets.apps.length - 1]);
tsv.fullScreenProgress.value = 0;
tsv.recentsViewScale.value = 1;
+ tsv.taskSecondaryTranslation.value = gridTranslationSecondary;
tsv.setScroll(startScroll);
// Fade in the task during the initial 20% of the animation
@@ -208,6 +214,8 @@
AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
out.setFloat(tsv.recentsViewScale,
AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
+ TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
TaskViewSimulator finalTsv = tsv;
@@ -290,6 +298,93 @@
}
}
+ /**
+ * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
+ * if needed
+ *
+ * We could manually try to animate the just the bounds for the leashes we get back, but we try
+ * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
+ * us.
+ *
+ * First you have to call TVS#setPreview() to indicate which leash it will operate one
+ * Then operations happen in TVS#apply() on each frame callback.
+ *
+ * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
+ * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
+ * device is considered in multiWindowMode and things like insets and stuff change
+ * and calculations have to be adjusted in the animations for that
+ */
+ public static void composeRecentsSplitLaunchAnimator(@NonNull AnimatorSet anim,
+ @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
+ @NonNull StateManager stateManager, @NonNull DepthController depthController,
+ int targetStage) {
+ PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+ boolean isRunningTask = v.isRunningTask();
+ TransformParams params = null;
+ TaskViewSimulator tvs = null;
+ RecentsView recentsView = v.getRecentsView();
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+ params = recentsView.getLiveTileParams();
+ tvs = recentsView.getLiveTileTaskViewSimulator();
+ }
+
+ boolean inLiveTileMode =
+ ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
+ final RemoteAnimationTargets targets =
+ new RemoteAnimationTargets(appTargets, wallpaperTargets,
+ inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+
+ if (params == null) {
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+ targets.addReleaseCheck(applier);
+
+ params = new TransformParams()
+ .setSyncTransactionApplier(applier)
+ .setTargetSet(targets);
+ }
+
+ Rect crop = new Rect();
+ Context context = v.getContext();
+ DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+ if (tvs == null && targets.apps.length > 0) {
+ tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
+ tvs.setDp(dp);
+
+ // RecentsView never updates the display rotation until swipe-up so the value may
+ // be stale. Use the display value instead.
+ int displayRotation = DisplayController.getDefaultDisplay(recentsView.getContext())
+ .getInfo().rotation;
+ tvs.getOrientationState().update(displayRotation, displayRotation);
+
+ tvs.setPreview(targets.apps[targets.apps.length - 1]);
+ tvs.fullScreenProgress.value = 0;
+ tvs.recentsViewScale.value = 1;
+// tvs.setScroll(startScroll);
+
+ // Fade in the task during the initial 20% of the animation
+ out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
+ clampToProgress(LINEAR, 0, 0.2f));
+ }
+
+ TaskViewSimulator topMostSimulator = null;
+
+ if (tvs != null) {
+ out.setFloat(tvs.fullScreenProgress,
+ AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tvs.recentsViewScale,
+ AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+ out.setInt(tvs, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
+
+ TaskViewSimulator finalTsv = tvs;
+ TransformParams finalParams = params;
+ out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+ topMostSimulator = tvs;
+ }
+
+ anim.play(out.buildAnim());
+ }
+
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
@@ -307,7 +402,11 @@
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
- launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
+ Context context = v.getContext();
+ DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+ launcherAnim = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
+ ? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0)
+ : recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
@@ -336,7 +435,7 @@
};
}
pa.add(launcherAnim);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
+ if (LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
pa.addOnFrameCallback(recentsView::redrawLiveTile);
}
anim.play(pa.buildAnim());
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e243715..1fb9465 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,9 +21,15 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -37,18 +43,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;
@@ -91,10 +102,16 @@
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;
import com.android.systemui.shared.tracing.ProtoTraceable;
+import com.android.wm.shell.onehanded.IOneHanded;
+import com.android.wm.shell.pip.IPip;
+import com.android.wm.shell.splitscreen.ISplitScreen;
+import com.android.wm.shell.startingsurface.IStartingWindow;
+import com.android.wm.shell.transition.IShellTransitions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -121,6 +138,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;
@@ -131,8 +151,18 @@
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+ IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
+ ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
+ KEY_EXTRA_SHELL_SPLIT_SCREEN));
+ IOneHanded onehanded = IOneHanded.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
+ IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));
+ IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));
MAIN_EXECUTOR.execute(() -> {
- SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
+ SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
+ splitscreen, onehanded, shellTransitions, startingWindow);
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
mDeviceState.runOnUserUnlocked(() -> {
@@ -148,13 +178,23 @@
@BinderThread
public void onOverviewToggle() {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
- mOverviewCommandHelper.onOverviewToggle();
+ // If currently screen pinning, do not enter overview
+ if (mDeviceState.isScreenPinningActive()) {
+ return;
+ }
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
}
@BinderThread
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
- mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+ if (triggeredFromAltTab) {
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW_NEXT_FOCUS);
+ } else {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+ }
}
@BinderThread
@@ -162,7 +202,7 @@
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab hides the overview and ends at the target app
- mOverviewCommandHelper.onOverviewHidden();
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
}
}
@@ -248,6 +288,8 @@
private InputMonitorCompat mInputMonitorCompat;
private InputEventReceiver mInputEventReceiver;
+ private DisplayManager mDisplayManager;
+
@Override
public void onCreate() {
super.onCreate();
@@ -261,6 +303,7 @@
mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
ProtoTracer.INSTANCE.get(this).add(this);
+ mDisplayManager = getSystemService(DisplayManager.class);
sConnected = true;
}
@@ -278,7 +321,13 @@
private void initInputMonitor() {
disposeEventHandlers();
- if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
+
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.TIS_NO_EVENTS, "initInputMonitor: isButtonMode? "
+ + mDeviceState.isButtonNavMode());
+ }
+
+ if (mDeviceState.isButtonNavMode()) {
return;
}
@@ -308,8 +357,8 @@
public void onUserUnlocked() {
mTaskAnimationManager = new TaskAnimationManager(this);
mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
- mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
- mOverviewComponentObserver);
+ mOverviewCommandHelper = new OverviewCommandHelper(this,
+ mOverviewComponentObserver, mTaskAnimationManager);
mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
mInputConsumer.registerInputConsumer();
@@ -403,7 +452,7 @@
}
disposeEventHandlers();
mDeviceState.destroy();
- SystemUiProxy.INSTANCE.get(this).setProxy(null);
+ SystemUiProxy.INSTANCE.get(this).clearProxy();
ProtoTracer.INSTANCE.get(this).stop();
ProtoTracer.INSTANCE.get(this).remove(this);
@@ -426,6 +475,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);
@@ -528,7 +586,7 @@
ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
}
- private GestureState createGestureState(GestureState previousGestureState) {
+ public GestureState createGestureState(GestureState previousGestureState) {
GestureState gestureState = new GestureState(mOverviewComponentObserver,
ActiveGestureLog.INSTANCE.generateAndSetLogId());
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
@@ -660,8 +718,7 @@
runningComponent != null && runningComponent.equals(homeComponent);
}
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()
- && gestureState.getActivityInterface().isInLiveTileMode()) {
+ if (LIVE_TILE.get() && gestureState.getActivityInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(
previousGestureState, gestureState, event, forceOverviewInputConsumer);
} else if (gestureState.getRunningTask() == null) {
@@ -678,16 +735,15 @@
}
}
+ public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
+ return !mOverviewComponentObserver.isHomeAndOverviewSame()
+ ? mFallbackSwipeHandlerFactory : mLauncherSwipeHandlerFactory;
+ }
+
private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
MotionEvent event) {
- final AbsSwipeUpHandler.Factory factory;
- if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
- factory = mFallbackSwipeHandlerFactory;
- } else {
- factory = mLauncherSwipeHandlerFactory;
- }
-
+ final AbsSwipeUpHandler.Factory factory = getSwipeUpHandlerFactory();
final boolean shouldDefer = !mOverviewComponentObserver.isHomeAndOverviewSame()
|| gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event);
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
@@ -717,8 +773,7 @@
|| previousGestureState.isRunningAnimationToLauncher()
|| (ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
&& forceOverviewInputConsumer)
- || (ENABLE_QUICKSTEP_LIVE_TILE.get())
- && gestureState.getActivityInterface().isInLiveTileMode()) {
+ || (LIVE_TILE.get()) && gestureState.getActivityInterface().isInLiveTileMode()) {
return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
@@ -832,6 +887,9 @@
if (mGestureState != null) {
mGestureState.dump(pw);
}
+ pw.println("Input state:");
+ pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
+ pw.println(" mInputEventReceiver=" + mInputEventReceiver);
SysUINavigationMode.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
@@ -861,15 +919,17 @@
}
private AbsSwipeUpHandler createLauncherSwipeHandler(
- GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+ GestureState gestureState, long touchTimeMs) {
return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
- gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+ gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+ mInputConsumer);
}
private AbsSwipeUpHandler createFallbackSwipeHandler(
- GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+ GestureState gestureState, long touchTimeMs) {
return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
- gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+ gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+ mInputConsumer);
}
protected boolean shouldNotifyBackGesture() {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 24a7610..82bfa9b 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -25,9 +25,11 @@
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
@@ -69,7 +71,7 @@
return;
}
// While animating into recents, update the visible task data as needed
- setter.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+ setter.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
setProperties(toState, config, setter);
@@ -77,11 +79,12 @@
private void setProperties(RecentsState state, StateAnimationConfig config,
PropertySetter setter) {
- float buttonAlpha = state.hasButtons() ? 1 : 0;
+ float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
- buttonAlpha, LINEAR);
+ clearAllButtonAlpha, LINEAR);
+ float overviewButtonAlpha = state.hasOverviewActions(mActivity) ? 1 : 0;
setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
- MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+ MultiValueAlpha.VALUE, overviewButtonAlpha, LINEAR);
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
@@ -94,5 +97,7 @@
setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
+ setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
+ state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()) ? 1f : 0f, LINEAR);
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 8f2356c..e075045 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -24,14 +24,17 @@
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
+import android.util.Log;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.FallbackActivityInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -54,8 +57,8 @@
}
@Override
- public void init(OverviewActionsView actionsView) {
- super.init(actionsView);
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ super.init(actionsView, splitPlaceholderView);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
@@ -63,6 +66,7 @@
@Override
public void startHome() {
mActivity.startHome();
+ mActivity.getStateManager().goToState(RecentsState.HOME);
}
/**
@@ -120,6 +124,10 @@
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED,
+ "FallbackRecentsView.applyLoadPlan: running task is home");
+ }
// Check if the task list has running task
boolean found = false;
for (Task t : tasks) {
@@ -148,6 +156,11 @@
}
@Override
+ protected boolean isHomeTask(TaskView taskView) {
+ return mHomeTaskInfo != null && taskView.hasTaskId(mHomeTaskInfo.taskId);
+ }
+
+ @Override
public void setModalStateEnabled(boolean isModalState) {
super.setModalStateEnabled(isModalState);
if (isModalState) {
@@ -162,6 +175,8 @@
@Override
public void onStateTransitionStart(RecentsState toState) {
setOverviewStateEnabled(true);
+ setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ setOverviewFullscreenEnabled(toState.isFullScreen());
setFreezeViewVisibility(true);
}
@@ -176,7 +191,7 @@
super.setOverviewStateEnabled(enabled);
if (enabled) {
RecentsState state = mActivity.getStateManager().getState();
- setDisallowScrollToClearAll(!state.hasButtons());
+ setDisallowScrollToClearAll(!state.hasClearAllButton());
}
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index f15a9de..a9856d2 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -20,6 +20,8 @@
import android.content.Context;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.BaseState;
import com.android.quickstep.RecentsActivity;
@@ -29,14 +31,19 @@
public class RecentsState implements BaseState<RecentsState> {
private static final int FLAG_MODAL = BaseState.getFlag(0);
- private static final int FLAG_HAS_BUTTONS = BaseState.getFlag(1);
+ private static final int FLAG_CLEAR_ALL_BUTTON = BaseState.getFlag(1);
private static final int FLAG_FULL_SCREEN = BaseState.getFlag(2);
+ private static final int FLAG_OVERVIEW_ACTIONS = BaseState.getFlag(3);
+ private static final int FLAG_SHOW_AS_GRID = BaseState.getFlag(4);
- public static final RecentsState DEFAULT = new RecentsState(0, FLAG_HAS_BUTTONS);
+ public static final RecentsState DEFAULT = new RecentsState(0,
+ FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID);
public static final RecentsState MODAL_TASK = new ModalState(1,
- FLAG_DISABLE_RESTORE | FLAG_HAS_BUTTONS | FLAG_MODAL);
+ FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+ | FLAG_SHOW_AS_GRID);
public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+ public static final RecentsState HOME = new RecentsState(3, 0);
public final int ordinal;
private final int mFlags;
@@ -82,14 +89,35 @@
return hasFlag(FLAG_FULL_SCREEN);
}
- public boolean hasButtons() {
- return hasFlag(FLAG_HAS_BUTTONS);
+ /**
+ * For this state, whether clear all button should be shown.
+ */
+ public boolean hasClearAllButton() {
+ return hasFlag(FLAG_CLEAR_ALL_BUTTON);
+ }
+
+ /**
+ * For this state, whether overview actions should be shown.
+ */
+ public boolean hasOverviewActions(RecentsActivity activity) {
+ return hasFlag(FLAG_OVERVIEW_ACTIONS) && !showAsGrid(activity.getDeviceProfile());
}
public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
return new float[] { NO_SCALE, NO_OFFSET };
}
+ /**
+ * For this state, whether tasks should layout as a grid rather than a list.
+ */
+ public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+ return hasFlag(FLAG_SHOW_AS_GRID) && showAsGrid(deviceProfile);
+ }
+
+ private boolean showAsGrid(DeviceProfile deviceProfile) {
+ return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+ }
+
private static class ModalState extends RecentsState {
public ModalState(int id, int flags) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index b10bdde..cd69cf1 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -21,14 +21,18 @@
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
import static com.android.launcher3.Utilities.squaredHypot;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.PointF;
import android.view.MotionEvent;
import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DisplayController;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.SystemUiProxy;
@@ -45,11 +49,15 @@
private static final int ANGLE_MIN = 30;
private final Context mContext;
+ private final DisplayController.DisplayHolder mDisplayHolder;
+ private final Point mDisplaySize;
private final RecentsAnimationDeviceState mDeviceState;
private final float mDragDistThreshold;
private final float mSquaredSlop;
+ private final int mNavBarSize;
+
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
@@ -60,10 +68,14 @@
InputConsumer delegate, InputMonitorCompat inputMonitor) {
super(delegate, inputMonitor);
mContext = context;
+ mDisplayHolder = DisplayController.getDefaultDisplay(mContext);
mDeviceState = deviceState;
mDragDistThreshold = context.getResources().getDimensionPixelSize(
R.dimen.gestures_onehanded_drag_threshold);
mSquaredSlop = Utilities.squaredTouchSlop(context);
+ mDisplaySize = mDisplayHolder.getInfo().realSize;
+ mNavBarSize = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE,
+ mContext.getResources());
}
@Override
@@ -96,7 +108,8 @@
mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
|| (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
- mPassedSlop = true;
+ // To avoid mis-trigger when motion not touch system gesture region.
+ mPassedSlop = isInSystemGestureRegion(mLastPos);
setActive(ev);
} else {
mState = STATE_DELEGATE_ACTIVE;
@@ -154,6 +167,11 @@
SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
}
+ private boolean isInSystemGestureRegion(PointF lastPos) {
+ final int navBarUpperBound = mDisplaySize.y - mNavBarSize;
+ return mDeviceState.isGesturalNavMode() && lastPos.y > navBarUpperBound;
+ }
+
private boolean isValidStartAngle(float deltaX, float deltaY) {
final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index f2f9fb7..9878d45 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -31,17 +31,18 @@
import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.annotation.TargetApi;
import android.content.Context;
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;
@@ -63,7 +64,7 @@
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.CachedEventDispatcher;
import com.android.quickstep.util.MotionPauseDetector;
@@ -113,6 +114,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 +133,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 +177,7 @@
mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
+ mDisplayManager = getSystemService(DisplayManager.class);
}
@Override
@@ -197,6 +203,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()) {
@@ -362,9 +379,6 @@
// Once we detect the gesture, we can enable batching to reduce further updates
mInputEventReceiver.setBatchingEnabled(true);
- mActivityInterface.closeOverlay();
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-
// Notify the handler that the gesture has actually started
mInteractionHandler.onGestureStarted(isLikelyToStartNewTask);
}
@@ -372,8 +386,7 @@
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
- mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
- mTaskAnimationManager.isRecentsAnimationRunning());
+ mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs);
mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener());
Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index aad70c4..fa9e0ec 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -15,7 +15,7 @@
*/
package com.android.quickstep.inputconsumers;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.view.KeyEvent;
@@ -24,6 +24,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
@@ -38,7 +39,7 @@
/**
* Input consumer for handling touch on the recents/Launcher activity.
*/
-public class OverviewInputConsumer<T extends StatefulActivity<?>>
+public class OverviewInputConsumer<S extends BaseState<S>, T extends StatefulActivity<S>>
implements InputConsumer {
private final T mActivity;
@@ -99,7 +100,7 @@
@Override
public void onKeyEvent(KeyEvent ev) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
mActivity.dispatchKeyEvent(ev);
}
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index f9283a4..66c24c8 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,6 +16,9 @@
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;
@@ -28,8 +31,11 @@
import android.content.Context;
import android.util.Log;
+import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
+import androidx.slice.SliceItem;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
@@ -50,6 +56,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.LogConfig;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.Optional;
@@ -89,7 +96,7 @@
@Override
protected StatsLogger createLogger() {
- return new StatsCompatLogger();
+ return new StatsCompatLogger(mContext);
}
/**
@@ -131,6 +138,7 @@
private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
+ private Context mContext;
private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
private OptionalInt mRank = OptionalInt.empty();
@@ -140,6 +148,11 @@
private Optional<FromState> mFromState = Optional.empty();
private Optional<ToState> mToState = Optional.empty();
private Optional<String> mEditText = Optional.empty();
+ private SliceItem mSliceItem;
+
+ StatsCompatLogger(Context context) {
+ mContext = context;
+ }
@Override
public StatsLogger withItemInfo(ItemInfo itemInfo) {
@@ -177,10 +190,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;
}
@@ -204,12 +215,33 @@
}
@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)
@@ -230,6 +262,26 @@
}
}
+ @Override
+ public void sendToInteractionJankMonitor(EventEnum event, View view) {
+ if (!(event instanceof LauncherEvent)) {
+ return;
+ }
+ switch ((LauncherEvent) event) {
+ case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN:
+ InteractionJankMonitorWrapper.begin(
+ view,
+ InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+ break;
+ case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END:
+ InteractionJankMonitorWrapper.end(
+ InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+ break;
+ default:
+ break;
+ }
+ }
+
private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
LauncherAtom.ItemInfo.Builder itemInfoBuilder = atomInfo.toBuilder();
@@ -327,6 +379,8 @@
return info.getWidget().getPackageName();
case TASK:
return info.getTask().getPackageName();
+ case SEARCH_ACTION_ITEM:
+ return info.getSearchActionItem().getPackageName();
default:
return null;
}
@@ -342,6 +396,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/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index deb70e0..7f94839 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -38,7 +37,6 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.views.RecentsView;
/**
@@ -49,12 +47,6 @@
*/
public class AnimatorControllerWithResistance {
- /**
- * How much farther we can drag past overview in 2-button mode, as a factor of the distance
- * it takes to drag from an app to overview.
- */
- public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f;
-
private enum RecentsResistanceParams {
FROM_APP(0.75f, 0.5f, 1f),
FROM_OVERVIEW(1f, 0.75f, 0.5f);
@@ -161,12 +153,6 @@
LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
orientationHandler);
long distanceToCover = startRect.bottom;
- boolean isTwoButtonMode = SysUINavigationMode.getMode(params.context) == TWO_BUTTONS;
- if (isTwoButtonMode) {
- // We can only drag a small distance past overview, not to the top of the screen.
- distanceToCover = (long)
- ((params.dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR);
- }
PendingAnimation resistAnim = params.resistAnim != null
? params.resistAnim
: new PendingAnimation(distanceToCover * 2);
@@ -178,43 +164,35 @@
/ (params.dp.heightPx - startRect.bottom);
// This is what the scale would be at the end of the drag if we didn't apply resistance.
float endScale = params.startScale - prevScaleRate * distanceToCover;
- final TimeInterpolator scaleInterpolator;
- if (isTwoButtonMode) {
- // We are bounded by the distance of the drag, so we don't need to apply resistance.
- scaleInterpolator = LINEAR;
- } else {
- // Create an interpolator that resists the scale so the scale doesn't get smaller than
- // RECENTS_SCALE_MAX_RESIST.
- float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
- params.startScale, endScale);
- float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
- params.startScale, endScale);
- scaleInterpolator = t -> {
- if (t < startResist) {
- return t;
- }
- float resistProgress = Utilities.getProgress(t, startResist, 1);
- resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
- return startResist + resistProgress * (maxResist - startResist);
- };
- }
+ // Create an interpolator that resists the scale so the scale doesn't get smaller than
+ // RECENTS_SCALE_MAX_RESIST.
+ float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
+ params.startScale, endScale);
+ float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
+ params.startScale, endScale);
+ final TimeInterpolator scaleInterpolator = t -> {
+ if (t < startResist) {
+ return t;
+ }
+ float resistProgress = Utilities.getProgress(t, startResist, 1);
+ resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
+ return startResist + resistProgress * (maxResist - startResist);
+ };
resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
scaleInterpolator);
- if (!isTwoButtonMode) {
- // Compute where the task view would be based on the end scale, if we didn't translate.
- RectF endRectF = new RectF(startRect);
- Matrix temp = new Matrix();
- temp.setScale(params.resistanceParams.scaleMaxResist,
- params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
- temp.mapRect(endRectF);
- // Translate such that the task view touches the top of the screen when drag does.
- float endTranslation = endRectF.top
- * orientationHandler.getSecondaryTranslationDirectionFactor()
- * params.resistanceParams.translationFactor;
- resistAnim.addFloat(params.translationTarget, params.translationProperty,
- params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
- }
+ // Compute where the task view would be based on the end scale.
+ RectF endRectF = new RectF(startRect);
+ Matrix temp = new Matrix();
+ temp.setScale(params.resistanceParams.scaleMaxResist,
+ params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
+ temp.mapRect(endRectF);
+ // Translate such that the task view touches the top of the screen when drag does.
+ float endTranslation = endRectF.top
+ * orientationHandler.getSecondaryTranslationDirectionFactor()
+ * params.resistanceParams.translationFactor;
+ resistAnim.addFloat(params.translationTarget, params.translationProperty,
+ params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
return resistAnim;
}
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index d022085..0f2d778 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -99,7 +99,14 @@
.putExtra(Intent.EXTRA_STREAM, uri)
.putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId())
.setClipData(clipdata);
- context.startActivity(intent);
+
+ if (context.getUserId() != appTarget.getUser().getIdentifier()) {
+ intent.prepareToLeaveUser(context.getUserId());
+ intent.fixUris(context.getUserId());
+ context.startActivityAsUser(intent, appTarget.getUser());
+ } else {
+ context.startActivity(intent);
+ }
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
new file mode 100644
index 0000000..60c7add
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+
+import android.content.Context;
+
+import com.android.quickstep.SysUINavigationMode;
+
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/** A feature flag that listens to navigation mode changes. */
+public class NavigationModeFeatureFlag implements
+ SysUINavigationMode.NavigationModeChangeListener {
+
+ public static final NavigationModeFeatureFlag LIVE_TILE = new NavigationModeFeatureFlag(
+ ENABLE_QUICKSTEP_LIVE_TILE::get, mode -> mode.hasGestures);
+
+ private final Supplier<Boolean> mBasePredicate;
+ private final Predicate<SysUINavigationMode.Mode> mModePredicate;
+ private boolean mSupported;
+
+ private NavigationModeFeatureFlag(Supplier<Boolean> basePredicate,
+ Predicate<SysUINavigationMode.Mode> modePredicate) {
+ mBasePredicate = basePredicate;
+ mModePredicate = modePredicate;
+ }
+
+ public boolean get() {
+ return mBasePredicate.get() && mSupported;
+ }
+
+ public void initialize(Context context) {
+ onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context).getMode());
+ SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+ }
+
+ @Override
+ public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+ mSupported = mModePredicate.test(newMode);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 215f05a..188efad 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -23,9 +23,9 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -47,11 +47,12 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.views.TaskView;
import java.lang.annotation.Retention;
import java.util.function.IntConsumer;
@@ -367,8 +368,12 @@
*/
public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
Rect insets = dp.getInsets();
- float fullWidth = dp.widthPx - insets.left - insets.right;
- float fullHeight = dp.heightPx - insets.top - insets.bottom;
+ float fullWidth = dp.widthPx;
+ float fullHeight = dp.heightPx;
+ if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ fullWidth -= insets.left + insets.right;
+ fullHeight -= insets.top + insets.bottom;
+ }
if (dp.isMultiWindowMode) {
WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(mContext);
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 3adb459..5c6da16 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -21,7 +21,6 @@
import android.os.Handler;
import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.AnimationResult;
import com.android.launcher3.WrappedAnimationRunnerImpl;
import com.android.launcher3.WrappedLauncherAnimationRunner;
import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -36,23 +35,10 @@
RemoteAnimationTargetCompat[] wallpaperTargets);
ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
- mAnimationRunner = new WrappedAnimationRunnerImpl() {
- @Override
- public Handler getHandler() {
- return handler;
- }
-
- @Override
- public void onCreateAnimation(int transit,
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- RemoteAnimationTargetCompat[] nonApps,
- AnimationResult result) {
+ mAnimationRunner = (transit, appTargets, wallpaperTargets, nonApps, result) ->
result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
- }
- };
final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner(
- mAnimationRunner, false /* startAtFrontOfQueue */);
+ handler, mAnimationRunner, false /* startAtFrontOfQueue */);
return ActivityOptionsCompat.makeRemoteAnimation(
new RemoteAnimationAdapterCompat(wrapper, duration, 0));
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
new file mode 100644
index 0000000..d9154ed
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherAnimationRunner;
+import com.android.launcher3.WrappedAnimationRunnerImpl;
+import com.android.launcher3.WrappedLauncherAnimationRunner;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Represent data needed for the transient state when user has selected one app for split screen
+ * and is in the process of either a) selecting a second app or b) exiting intention to invoke split
+ */
+public class SplitSelectStateController {
+
+ private final SystemUiProxy mSystemUiProxy;
+ private TaskView mInitialTaskView;
+ private SplitPositionOption mInitialPosition;
+
+ public SplitSelectStateController(SystemUiProxy systemUiProxy) {
+ mSystemUiProxy = systemUiProxy;
+ }
+
+ /**
+ * To be called after first task selected
+ */
+ public void setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption) {
+ mInitialTaskView = taskView;
+ mInitialPosition = positionOption;
+ }
+
+ /**
+ * To be called after second task selected
+ */
+ public void setSecondTaskId(TaskView taskView) {
+ // Assume initial mInitialTaskId is for top/left part of screen
+ WrappedAnimationRunnerImpl initialSplitRunnerWrapped = new SplitLaunchAnimationRunner(
+ mInitialTaskView, 0);
+ WrappedAnimationRunnerImpl secondarySplitRunnerWrapped = new SplitLaunchAnimationRunner(
+ taskView, 1);
+ RemoteAnimationRunnerCompat initialSplitRunner = new WrappedLauncherAnimationRunner(
+ new Handler(Looper.getMainLooper()), initialSplitRunnerWrapped,
+ true /* startAtFrontOfQueue */);
+ RemoteAnimationRunnerCompat secondarySplitRunner = new WrappedLauncherAnimationRunner(
+ new Handler(Looper.getMainLooper()), secondarySplitRunnerWrapped,
+ true /* startAtFrontOfQueue */);
+ ActivityOptions initialOptions = ActivityOptionsCompat.makeRemoteAnimation(
+ new RemoteAnimationAdapterCompat(initialSplitRunner, 300, 150));
+ ActivityOptions secondaryOptions = ActivityOptionsCompat.makeRemoteAnimation(
+ new RemoteAnimationAdapterCompat(secondarySplitRunner, 300, 150));
+ mSystemUiProxy.startTask(mInitialTaskView.getTask().key.id, mInitialPosition.mStageType,
+ mInitialPosition.mStagePosition,
+ /*null*/ initialOptions.toBundle());
+ Pair<Integer, Integer> compliment = getComplimentaryStageAndPosition(mInitialPosition);
+ mSystemUiProxy.startTask(taskView.getTask().key.id, compliment.first,
+ compliment.second,
+ /*null*/ secondaryOptions.toBundle());
+ // After successful launch, call resetState
+ resetState();
+ }
+
+ @Nullable
+ public SplitPositionOption getActiveSplitPositionOption() {
+ return mInitialPosition;
+ }
+
+ /**
+ * @return the opposite stage and position from the {@param position} provided as first and
+ * second object, respectively
+ * Ex. If position is has stage = Main and position = Top/Left, this will return
+ * Pair(stage=Side, position=Bottom/Left)
+ */
+ private Pair<Integer, Integer> getComplimentaryStageAndPosition(SplitPositionOption position) {
+ // Right now this is as simple as flipping between 0 and 1
+ int complimentStageType = position.mStageType ^ 1;
+ int complimentStagePosition = position.mStagePosition ^ 1;
+ return new Pair<>(complimentStageType, complimentStagePosition);
+ }
+
+ /**
+ * Remote animation runner for animation to launch an app.
+ */
+ private class SplitLaunchAnimationRunner implements WrappedAnimationRunnerImpl {
+
+ private final TaskView mV;
+ private final int mTargetState;
+
+ SplitLaunchAnimationRunner(TaskView v, int targetState) {
+ mV = v;
+ mTargetState = targetState;
+ }
+
+ @Override
+ public void onCreateAnimation(int transit,
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ RemoteAnimationTargetCompat[] nonAppTargets,
+ LauncherAnimationRunner.AnimationResult result) {
+ AnimatorSet anim = new AnimatorSet();
+ BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext());
+ TaskViewUtils.composeRecentsSplitLaunchAnimator(anim, mV,
+ appTargets, wallpaperTargets, true, activity.getStateManager(),
+ activity.getDepthController(), mTargetState);
+ result.setAnimation(anim, activity);
+ }
+ }
+
+
+ /**
+ * To be called if split select was cancelled
+ */
+ public void resetState() {
+ mInitialTaskView = null;
+ mInitialPosition = null;
+ }
+
+ public boolean isSplitSelectActive() {
+ return mInitialTaskView != null;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 932ff27..d4ca31f 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -24,7 +24,6 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_TASKBAR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -37,6 +36,7 @@
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -82,53 +82,52 @@
DeviceProfile grid = launcher.getDeviceProfile();
Workspace workspace = launcher.getWorkspace();
- CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
- ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
- ViewGroup hotseat = launcher.getHotseat();
+ Hotseat hotseat = launcher.getHotseat();
+
+ // Hotseat and QSB takes up two additional rows.
+ int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
+
+ // Add animation for all the visible workspace pages
+ workspace.getVisiblePages()
+ .forEach(page -> addAnimationForPage((CellLayout) page, totalRows));
boolean workspaceClipChildren = workspace.getClipChildren();
boolean workspaceClipToPadding = workspace.getClipToPadding();
- boolean cellLayoutClipChildren = cellLayout.getClipChildren();
- boolean cellLayoutClipToPadding = cellLayout.getClipToPadding();
boolean hotseatClipChildren = hotseat.getClipChildren();
boolean hotseatClipToPadding = hotseat.getClipToPadding();
workspace.setClipChildren(false);
workspace.setClipToPadding(false);
- cellLayout.setClipChildren(false);
- cellLayout.setClipToPadding(false);
hotseat.setClipChildren(false);
hotseat.setClipToPadding(false);
- // Hotseat and QSB takes up two additional rows.
- int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
-
- // Set up springs on workspace items.
- for (int i = currentPage.getChildCount() - 1; i >= 0; i--) {
- View child = currentPage.getChildAt(i);
- CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
- }
-
// Set up springs for the hotseat and qsb.
- ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0);
+ ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
if (grid.isVerticalBarLayout()) {
- for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
- View child = hotseatChild.getChildAt(i);
+ for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+ View child = hotseatIcons.getChildAt(i);
CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
}
} else {
- for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
- View child = hotseatChild.getChildAt(i);
- addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
+ final int hotseatRow, qsbRow, taskbarRow;
+ if (grid.isTaskbarPresent) {
+ qsbRow = grid.inv.numRows + 1;
+ hotseatRow = grid.inv.numRows + 2;
+ } else {
+ hotseatRow = grid.inv.numRows + 1;
+ qsbRow = grid.inv.numRows + 2;
+ }
+ // Taskbar and hotseat overlap.
+ taskbarRow = hotseatRow;
+
+ for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+ View child = hotseatIcons.getChildAt(i);
+ addStaggeredAnimationForView(child, hotseatRow, totalRows);
}
- if (launcher.getAppsView().getSearchUiManager()
- .isQsbVisible(NORMAL.getVisibleElements(launcher))) {
- addStaggeredAnimationForView(launcher.getAppsView().getSearchView(),
- grid.inv.numRows + 2, totalRows);
- }
+ addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows);
+ addStaggeredAnimationForView(hotseat.getTaskbarView(), taskbarRow, totalRows);
}
if (animateOverviewScrim) {
@@ -146,21 +145,43 @@
public void onAnimationEnd(Animator animation) {
workspace.setClipChildren(workspaceClipChildren);
workspace.setClipToPadding(workspaceClipToPadding);
- cellLayout.setClipChildren(cellLayoutClipChildren);
- cellLayout.setClipToPadding(cellLayoutClipToPadding);
hotseat.setClipChildren(hotseatClipChildren);
hotseat.setClipToPadding(hotseatClipToPadding);
}
});
}
+ private void addAnimationForPage(CellLayout page, int totalRows) {
+ ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets();
+
+ boolean pageClipChildren = page.getClipChildren();
+ boolean pageClipToPadding = page.getClipToPadding();
+
+ page.setClipChildren(false);
+ page.setClipToPadding(false);
+
+ // Set up springs on workspace items.
+ for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = itemsContainer.getChildAt(i);
+ CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
+ addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
+ }
+
+ mAnimators.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ page.setClipChildren(pageClipChildren);
+ page.setClipToPadding(pageClipToPadding);
+ }
+ });
+ }
+
/**
* Setup workspace with 0 duration to prepare for our staggered animation.
*/
private void prepareToAnimate(Launcher launcher, boolean animateOverviewScrim) {
StateAnimationConfig config = new StateAnimationConfig();
- config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER
- | SKIP_TASKBAR;
+ config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER;
config.duration = 0;
// setRecentsAttachedToAppWindow() will animate recents out.
launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 0ce5072..0a1a6e8 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -134,8 +134,9 @@
@Override
public void onAnimationEnd(Animator animation) {
- if (!mHasAnimationEnded) super.onAnimationEnd(animation);
- SwipePipToHomeAnimator.this.onAnimationEnd();
+ if (mHasAnimationEnded) return;
+ super.onAnimationEnd(animation);
+ mHasAnimationEnded = true;
}
});
addUpdateListener(this);
@@ -223,14 +224,34 @@
return mDestinationBounds;
}
- private void onAnimationEnd() {
- if (mHasAnimationEnded) return;
+ /**
+ * @return {@link Rect} of the final window crop in destination orientation.
+ */
+ public Rect getFinishWindowCrop() {
+ final Rect windowCrop = new Rect(mAppBounds);
+ if (mSourceHintRectInsets != null) {
+ windowCrop.inset(mSourceHintRectInsets);
+ }
+ return windowCrop;
+ }
- final SurfaceControl.Transaction tx =
- PipSurfaceTransactionHelper.newSurfaceControlTransaction();
- mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBoundsTransformed, mFromRotation);
- tx.apply();
- mHasAnimationEnded = true;
+ /**
+ * @return Array of 9 floats represents the final transform in destination orientation.
+ */
+ public float[] getFinishTransform() {
+ final Matrix transform = new Matrix();
+ final float[] float9 = new float[9];
+ if (mSourceHintRectInsets == null) {
+ transform.setRectToRect(new RectF(mAppBounds), new RectF(mDestinationBounds),
+ Matrix.ScaleToFit.FILL);
+ } else {
+ final float scale = mAppBounds.width() <= mAppBounds.height()
+ ? (float) mDestinationBounds.width() / mAppBounds.width()
+ : (float) mDestinationBounds.height() / mAppBounds.height();
+ transform.setScale(scale, scale);
+ }
+ transform.getValues(float9);
+ return float9;
}
private RotatedPosition getRotatedPosition(float fraction) {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index edce194..6cfe302 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,15 +15,16 @@
*/
package com.android.quickstep.util;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.states.RotationHelper.deltaRotation;
import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
import android.animation.TimeInterpolator;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
@@ -73,6 +74,7 @@
@NonNull
private RecentsOrientedState mOrientationState;
+ private final boolean mIsRecentsRtl;
private final Rect mTaskRect = new Rect();
private boolean mDrawsBelowRecents;
@@ -98,6 +100,7 @@
public final AnimatedFloat recentsViewScale = new AnimatedFloat();
public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
+ public final AnimatedFloat recentsViewPrimaryTranslation = new AnimatedFloat();
private final ScrollState mScrollState = new ScrollState();
// Cached calculations
@@ -113,6 +116,8 @@
mOrientationState.setGestureActive(true);
mCurrentFullscreenParams = new FullscreenDrawParams(context);
mOrientationStateId = mOrientationState.getStateId();
+ Resources resources = context.getResources();
+ mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
}
/**
@@ -256,10 +261,12 @@
getFullScreenScale();
mThumbnailData.rotation = mOrientationState.getDisplayRotation();
+ // mIsRecentsRtl is the inverse of TaskView RTL.
+ boolean isRtlEnabled = !mIsRecentsRtl;
mPositionHelper.updateThumbnailMatrix(
mThumbnailPosition, mThumbnailData,
mTaskRect.width(), mTaskRect.height(),
- mDp, mOrientationState.getRecentsActivityRotation());
+ mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
mPositionHelper.getMatrix().invert(mInversePositionMatrix);
PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
@@ -277,9 +284,10 @@
mScrollState.updateInterpolation(mDp, start);
}
- float progress = Utilities.boundToRange(fullScreenProgress.value, 0, 1);
+ float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
mCurrentFullscreenParams.setProgress(
- progress, recentsViewScale.value, mTaskRect.width(), mDp, mPositionHelper);
+ fullScreenProgress, recentsViewScale.value, mTaskRect.width(), mDp,
+ mPositionHelper);
// Apply thumbnail matrix
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
@@ -304,6 +312,8 @@
mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
recentsViewSecondaryTranslation.value);
+ mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+ recentsViewPrimaryTranslation.value);
applyWindowToHomeRotation(mMatrix);
// Crop rect is the inverse of thumbnail matrix
@@ -323,7 +333,7 @@
.withCornerRadius(getCurrentCornerRadius())
.withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
+ if (LIVE_TILE.get() && params.getRecentsSurface() != null) {
// When relativeLayer = 0, it reverts the surfaces back to the original order.
builder.withRelativeLayerTo(params.getRecentsSurface(),
mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 0837300..12b59d0 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -21,6 +21,7 @@
import android.util.FloatProperty;
import android.widget.Button;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
@@ -40,27 +41,32 @@
}
};
+ private final StatefulActivity mActivity;
private float mScrollAlpha = 1;
private float mContentAlpha = 1;
private float mVisibilityAlpha = 1;
+ private float mGridProgress = 1;
private boolean mIsRtl;
- private final float mOriginalTranslationX, mOriginalTranslationY;
+ private float mNormalTranslationPrimary;
+ private float mGridTranslationPrimary;
+ private float mGridTranslationSecondary;
+ private float mGridScrollOffset;
+ private float mOffsetTranslationPrimary;
- private int mScrollOffset;
+ private int mSidePadding;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- mOriginalTranslationX = getTranslationX();
- mOriginalTranslationY = getTranslationY();
+ mActivity = StatefulActivity.fromContext(context);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
- mScrollOffset = orientationHandler.getClearAllScrollOffset(getRecentsView(), mIsRtl);
+ mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
}
private RecentsView getRecentsView() {
@@ -93,17 +99,27 @@
}
@Override
- public void onPageScroll(ScrollState scrollState) {
- PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+ public void onPageScroll(ScrollState scrollState, boolean gridEnabled) {
+ RecentsView recentsView = getRecentsView();
+ if (recentsView == null) {
+ return;
+ }
+
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
if (orientationSize == 0) {
return;
}
- float shift = Math.min(scrollState.scrollFromEdge, orientationSize);
- float translation = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift);
- orientationHandler.setPrimaryAndResetSecondaryTranslate(
- this, translation, mOriginalTranslationX, mOriginalTranslationY);
+ int leftEdgeScroll = recentsView.getLeftMostChildScroll();
+ float adjustedScrollFromEdge = scrollState.scrollFromEdge - leftEdgeScroll;
+ float shift = Math.min(adjustedScrollFromEdge, orientationSize);
+ mNormalTranslationPrimary = mIsRtl ? -shift : shift;
+ if (!gridEnabled) {
+ mNormalTranslationPrimary += mSidePadding;
+ }
+ applyPrimaryTranslation();
+ applySecondaryTranslation();
mScrollAlpha = 1 - shift / orientationSize;
updateAlpha();
}
@@ -111,6 +127,84 @@
private void updateAlpha() {
final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha;
setAlpha(alpha);
- setClickable(alpha == 1);
+ setClickable(Math.min(alpha, 1) == 1);
+ }
+
+ public void setGridTranslationPrimary(float gridTranslationPrimary) {
+ mGridTranslationPrimary = gridTranslationPrimary;
+ applyPrimaryTranslation();
+ }
+
+ public void setGridTranslationSecondary(float gridTranslationSecondary) {
+ mGridTranslationSecondary = gridTranslationSecondary;
+ applyPrimaryTranslation();
+ }
+
+ public void setGridScrollOffset(float gridScrollOffset) {
+ mGridScrollOffset = gridScrollOffset;
+ }
+
+ public void setOffsetTranslationPrimary(float offsetTranslationPrimary) {
+ mOffsetTranslationPrimary = offsetTranslationPrimary;
+ applyPrimaryTranslation();
+ }
+
+ public float getScrollAdjustment(boolean gridEnabled) {
+ float scrollAdjustment = 0;
+ if (gridEnabled) {
+ scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
+ }
+ scrollAdjustment += mOffsetTranslationPrimary;
+ return scrollAdjustment;
+ }
+
+ public float getOffsetAdjustment(boolean gridEnabled) {
+ return getScrollAdjustment(gridEnabled);
+ }
+
+ /**
+ * Moves ClearAllButton between carousel and 2 row grid.
+ *
+ * @param gridProgress 0 = carousel; 1 = 2 row grid.
+ */
+ public void setGridProgress(float gridProgress) {
+ mGridProgress = gridProgress;
+ applyPrimaryTranslation();
+ }
+
+ private void applyPrimaryTranslation() {
+ RecentsView recentsView = getRecentsView();
+ if (recentsView == null) {
+ return;
+ }
+
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ orientationHandler.getPrimaryViewTranslate().set(this,
+ orientationHandler.getPrimaryValue(0f, getOriginalTranslationY())
+ + mNormalTranslationPrimary + mOffsetTranslationPrimary + getGridTrans(
+ mGridTranslationPrimary));
+ }
+
+ private void applySecondaryTranslation() {
+ RecentsView recentsView = getRecentsView();
+ if (recentsView == null) {
+ return;
+ }
+
+ PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ orientationHandler.getSecondaryViewTranslate().set(this,
+ orientationHandler.getSecondaryValue(0f, getOriginalTranslationY())
+ + getGridTrans(mGridTranslationSecondary));
+ }
+
+ private float getGridTrans(float endTranslation) {
+ return mGridProgress > 0 ? endTranslation : 0;
+ }
+
+ /**
+ * Get the Y translation that is set in the original layout position, before scrolling.
+ */
+ private float getOriginalTranslationY() {
+ return mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx / 2.0f;
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 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/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 52a7466..a0af68a 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,18 +15,13 @@
*/
package com.android.quickstep.views;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
+import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
@@ -41,7 +36,6 @@
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.RecentsExtraCard;
@@ -86,8 +80,8 @@
}
@Override
- public void init(OverviewActionsView actionsView) {
- super.init(actionsView);
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ super.init(actionsView, splitPlaceholderView);
setContentAlpha(0);
}
@@ -95,7 +89,7 @@
public void startHome() {
Runnable onReachedHome = () -> mActivity.getStateManager().goToState(NORMAL, false);
OverviewToHomeAnim overviewToHomeAnim = new OverviewToHomeAnim(mActivity, onReachedHome);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
switchToScreenshot(null,
() -> finishRecentsAnimation(true /* toRecents */,
() -> overviewToHomeAnim.animateWithVelocity(0)));
@@ -104,35 +98,10 @@
}
}
- /**
- * Animates adjacent tasks and translate hotseat off screen as well.
- */
- @Override
- public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
- AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv);
-
- if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
- // Hotseat doesn't move when opening recents with the button,
- // so don't animate it here either.
- return anim;
- }
-
- float allAppsProgressOffscreen = ALL_APPS_PROGRESS_OFF_SCREEN;
- LauncherState state = mActivity.getStateManager().getState();
- if ((state.getVisibleElements(mActivity) & ALL_APPS_HEADER_EXTRA) != 0) {
- float maxShiftRange = mActivity.getDeviceProfile().heightPx;
- float currShiftRange = mActivity.getAllAppsController().getShiftRange();
- allAppsProgressOffscreen = 1f + (maxShiftRange - currShiftRange) / maxShiftRange;
- }
- anim.play(ObjectAnimator.ofFloat(
- mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen));
- return anim;
- }
-
@Override
protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
- mActivity.getStateManager().goToState(NORMAL, false /* animate */);
+ mActivity.getStateManager().moveToRestState();
} else {
LauncherState state = mActivity.getStateManager().getState();
mActivity.getAllAppsController().setState(state);
@@ -150,6 +119,8 @@
@Override
public void onStateTransitionStart(LauncherState toState) {
setOverviewStateEnabled(toState.overviewUi);
+ setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
setFreezeViewVisibility(true);
}
@@ -169,7 +140,7 @@
if (enabled) {
LauncherState state = mActivity.getStateManager().getState();
boolean hasClearAllButton = (state.getVisibleElements(mActivity)
- & OVERVIEW_BUTTONS) != 0;
+ & CLEAR_ALL_BUTTON) != 0;
setDisallowScrollToClearAll(!hasClearAllButton);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
deleted file mode 100644
index 8210ab0..0000000
--- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package com.android.quickstep.views;
-
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.util.FloatProperty;
-import android.view.ViewOverlay;
-
-import com.android.launcher3.anim.Interpolators;
-import com.android.quickstep.util.RecentsOrientedState.SurfaceRotation;
-
-public class LiveTileOverlay extends Drawable {
-
- private static final long ICON_ANIM_DURATION = 120;
-
- private static final FloatProperty<LiveTileOverlay> PROGRESS =
- new FloatProperty<LiveTileOverlay>("progress") {
- @Override
- public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
- liveTileOverlay.setIconAnimationProgress(progress);
- }
-
- @Override
- public Float get(LiveTileOverlay liveTileOverlay) {
- return liveTileOverlay.mIconAnimationProgress;
- }
- };
-
- public static final LiveTileOverlay INSTANCE = new LiveTileOverlay();
-
- private final Paint mPaint = new Paint();
- private final RectF mCurrentRect = new RectF();
- private final Rect mBoundsRect = new Rect();
-
- private @SurfaceRotation int mRotation = ROTATION_0;
-
- private float mCornerRadius;
- private Drawable mIcon;
- private Animator mIconAnimator;
-
- private float mIconAnimationProgress = 0f;
- private boolean mIsAttached;
-
- private LiveTileOverlay() {
- mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- }
-
- public void update(RectF currentRect, float cornerRadius) {
- invalidateSelf();
-
- mCurrentRect.set(currentRect);
- mCornerRadius = cornerRadius;
-
- mCurrentRect.roundOut(mBoundsRect);
- setBounds(mBoundsRect);
- invalidateSelf();
- }
-
- public void update(float left, float top, float right, float bottom) {
- mCurrentRect.set(left, top, right, bottom);
- }
-
- public void setRotation(@SurfaceRotation int rotation) {
- mRotation = rotation;
- }
-
- public void setIcon(Drawable icon) {
- mIcon = icon;
- }
-
- // TODO: consider cleaning this up and drawing icon in another way. Previously we place app
- // below launcher during the initial swipe up and render the icon in this live tile overlay.
- // However, this resulted in a bunch of touch input issues caused by Launcher getting the input
- // events during transition (to overview / to another app (quick switch). So now our new
- // solution places app on top in live tile until it fully settles in Overview.
- public void startIconAnimation() {
- if (mIconAnimator != null) {
- mIconAnimator.cancel();
- }
- // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
- mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
- mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
- mIconAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mIconAnimator = null;
- }
- });
- mIconAnimator.start();
- }
-
- public float cancelIconAnimation() {
- if (mIconAnimator != null) {
- mIconAnimator.cancel();
- }
- return mIconAnimationProgress;
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
- if (mIcon != null && mIconAnimationProgress > 0f) {
- canvas.save();
- float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
- 1f).getInterpolation(mIconAnimationProgress);
-
- int iconRadius = mIcon.getBounds().width() / 2;
- float dx = 0;
- float dy = 0;
-
- switch (mRotation) {
- case ROTATION_0:
- dx = mCurrentRect.centerX() - iconRadius * scale;
- dy = mCurrentRect.top - iconRadius * scale;
- break;
- case ROTATION_90:
- dx = mCurrentRect.right - iconRadius * scale;
- dy = mCurrentRect.centerY() - iconRadius * scale;
- break;
- case ROTATION_270:
- dx = mCurrentRect.left - iconRadius * scale;
- dy = mCurrentRect.centerY() - iconRadius * scale;
- break;
- case ROTATION_180:
- dx = mCurrentRect.centerX() - iconRadius * scale;
- dy = mCurrentRect.bottom - iconRadius * scale;
- break;
- }
-
- int rotationDegrees = mRotation * 90;
- if (mRotation == ROTATION_90 || mRotation == ROTATION_270) {
- canvas.rotate(rotationDegrees, dx + iconRadius, dy + iconRadius);
- }
- canvas.translate(dx, dy);
- canvas.scale(scale, scale);
- mIcon.draw(canvas);
- canvas.restore();
- }
- }
-
- @Override
- public void setAlpha(int i) { }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) { }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- public boolean attach(ViewOverlay overlay) {
- if (overlay != null && !mIsAttached) {
- overlay.add(this);
- mIsAttached = true;
- return true;
- }
-
- return false;
- }
-
- public void detach(ViewOverlay overlay) {
- if (overlay != null) {
- overlay.remove(this);
- mIsAttached = false;
- }
- }
-
- private void setIconAnimationProgress(float progress) {
- mIconAnimationProgress = progress;
- invalidateSelf();
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 8fb7e03..6fcd54c 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -29,6 +29,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.util.MultiValueAlpha;
@@ -144,6 +145,7 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+ updateHorizontalPadding();
}
public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
@@ -187,8 +189,27 @@
return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
}
+ private void updateHorizontalPadding() {
+ setPadding(mInsets.left, 0, mInsets.right, 0);
+ }
+
/** Updates vertical margins for different navigation mode or configuration changes. */
public void updateVerticalMargin(Mode mode) {
+ LayoutParams actionParams = (LayoutParams) findViewById(
+ R.id.action_buttons).getLayoutParams();
+ actionParams.setMargins(
+ actionParams.leftMargin, actionParams.topMargin, actionParams.rightMargin,
+ getBottomVerticalMargin(mode));
+ }
+
+ /**
+ * Set the device profile for this view to draw with.
+ */
+ public void setDp(DeviceProfile dp) {
+ requestLayout();
+ }
+
+ protected int getBottomVerticalMargin(Mode mode) {
int bottomMargin;
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -201,8 +222,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 5d492ac..d357ebe 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
@@ -35,18 +36,20 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
@@ -72,6 +75,7 @@
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -104,12 +108,15 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.OverScroller;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.ViewPool;
import com.android.quickstep.AnimatedFloat;
@@ -130,13 +137,13 @@
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.plugins.ResourceProvider;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.wm.shell.pip.IPipAnimationListener;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -207,11 +214,17 @@
}
};
+ /**
+ * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+ * are currently both used to apply secondary translation. Should their use cases change to be
+ * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+ * offsetX/Y property
+ */
public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
new FloatProperty<RecentsView>("taskSecondaryTranslation") {
@Override
public void setValue(RecentsView recentsView, float v) {
- recentsView.setTaskViewsSecondaryTranslation(v);
+ recentsView.setTaskViewsResistanceTranslation(v);
}
@Override
@@ -220,6 +233,25 @@
}
};
+ /**
+ * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+ * are currently both used to apply secondary translation. Should their use cases change to be
+ * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+ * offsetX/Y property
+ */
+ public static final FloatProperty<RecentsView> TASK_PRIMARY_TRANSLATION =
+ new FloatProperty<RecentsView>("taskPrimaryTranslation") {
+ @Override
+ public void setValue(RecentsView recentsView, float v) {
+ recentsView.setTaskViewsPrimaryTranslation(v);
+ }
+
+ @Override
+ public Float get(RecentsView recentsView) {
+ return recentsView.mTaskViewsPrimaryTranslation;
+ }
+ };
+
/** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
new FloatProperty<RecentsView>("recentsScale") {
@@ -230,7 +262,8 @@
view.mLastComputedTaskPushOutDistance = null;
view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
view.updatePageOffsets();
- view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
+ view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
+ view.setTaskViewsPrimaryTranslation(view.mTaskViewsPrimaryTranslation);
}
@Override
@@ -239,6 +272,19 @@
}
};
+ public static final FloatProperty<RecentsView> RECENTS_GRID_PROGRESS =
+ new FloatProperty<RecentsView>("recentsGrid") {
+ @Override
+ public void setValue(RecentsView view, float gridProgress) {
+ view.setGridProgress(gridProgress);
+ }
+
+ @Override
+ public Float get(RecentsView view) {
+ return view.mGridProgress;
+ }
+ };
+
protected final RecentsOrientedState mOrientationState;
protected final BaseActivityInterface mSizeStrategy;
protected RecentsAnimationController mRecentsAnimationController;
@@ -248,12 +294,14 @@
protected final TransformParams mLiveTileParams = new TransformParams();
protected final TaskViewSimulator mLiveTileTaskViewSimulator;
protected final Rect mLastComputedTaskSize = new Rect();
+ protected final Rect mLastComputedGridSize = new Rect();
// How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
protected Float mLastComputedTaskPushOutDistance = null;
protected boolean mEnableDrawingLiveTile = false;
protected final Rect mTempRect = new Rect();
protected final RectF mTempRectF = new RectF();
private final PointF mTempPointF = new PointF();
+ private float mFullscreenScale;
private static final int DISMISS_TASK_DURATION = 300;
private static final int ADDITION_TASK_DURATION = 200;
@@ -263,10 +311,14 @@
protected final T mActivity;
private final float mFastFlingVelocity;
private final RecentsModel mModel;
- private final int mTaskTopMargin;
+ private final int mRowSpacing;
private final ClearAllButton mClearAllButton;
private final Rect mClearAllButtonDeadZoneRect = new Rect();
private final Rect mTaskViewDeadZoneRect = new Rect();
+ /**
+ * Reflects if Recents is currently in the middle of a gesture
+ */
+ private boolean mGestureActive;
private final ScrollState mScrollState = new ScrollState();
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
@@ -282,9 +334,17 @@
protected boolean mDisallowScrollToClearAll;
private boolean mOverlayEnabled;
protected boolean mFreezeViewVisibility;
+ private boolean mOverviewGridEnabled;
+ private boolean mOverviewFullscreenEnabled;
private float mAdjacentPageOffset = 0;
private float mTaskViewsSecondaryTranslation = 0;
+ private float mTaskViewsPrimaryTranslation = 0;
+ // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
+ private float mGridProgress = 0;
+
+ // The GestureEndTarget that is still in progress.
+ private GestureState.GestureEndTarget mCurrentGestureEndTarget;
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -354,7 +414,7 @@
}
};
- private final PinnedStackAnimationListener mIPinnedStackAnimationListener =
+ private final PinnedStackAnimationListener mIPipAnimationListener =
new PinnedStackAnimationListener();
// Used to keep track of the last requested task list id, so that we do not request to load the
@@ -402,7 +462,28 @@
private boolean mShowEmptyMessage;
private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
private Layout mEmptyTextLayout;
- private boolean mLiveTileOverlayAttached;
+
+ /**
+ * Placeholder view indicating where the first split screen selected app will be placed
+ */
+ private SplitPlaceholderView mSplitPlaceholderView;
+ /**
+ * The first task that split screen selection was initiated with. When split select state is
+ * initialized, we create a
+ * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
+ * don't actually remove the task since the user might back out. As such, we also ensure this
+ * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
+ */
+ private TaskView mSplitHiddenTaskView;
+ /**
+ * Keeps track of the index of the TaskView that split screen was initialized with so we know
+ * where to insert it back into list of taskViews in case user backs out of entering split
+ * screen.
+ * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView,
+ * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/
+ * removed from recentsView
+ */
+ private int mSplitHiddenTaskViewIndex;
// Keeps track of the index where the first TaskView should be
private int mTaskViewStartIndex = 0;
@@ -448,8 +529,7 @@
mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
- mTaskTopMargin = getResources()
- .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ mRowSpacing = getResources().getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
mSquaredTouchSlop = squaredTouchSlop(context);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -537,9 +617,6 @@
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
- if (visibility == GONE && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- finishRecentsAnimation(true /* toRecents */, null);
- }
updateTaskStackListenerState();
}
@@ -549,13 +626,23 @@
return;
}
mModel.getIconCache().clear();
- unloadVisibleTaskData();
- loadVisibleTaskData();
+ unloadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
}
- public void init(OverviewActionsView actionsView) {
+ public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+ mSplitPlaceholderView = splitPlaceholderView;
+
+ }
+
+ public SplitPlaceholderView getSplitPlaceholder() {
+ return mSplitPlaceholderView;
+ }
+
+ public boolean isSplitSelectionActive() {
+ return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
}
@Override
@@ -569,9 +656,9 @@
mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIdp.addOnChangeListener(this);
- mIPinnedStackAnimationListener.setActivity(mActivity);
+ mIPipAnimationListener.setActivity(mActivity);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
- mIPinnedStackAnimationListener);
+ mIPipAnimationListener);
mOrientationState.initListeners();
SplitScreenBounds.INSTANCE.addOnChangeListener(this);
mTaskOverlayFactory.initListeners();
@@ -590,7 +677,7 @@
mIdp.removeOnChangeListener(this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
- mIPinnedStackAnimationListener.setActivity(null);
+ mIPipAnimationListener.setActivity(null);
mOrientationState.destroyListeners();
mTaskOverlayFactory.removeListeners();
}
@@ -599,8 +686,9 @@
public void onViewRemoved(View child) {
super.onViewRemoved(child);
- // Clear the task data for the removed child if it was visible
- if (child instanceof TaskView) {
+ // Clear the task data for the removed child if it was visible unless it's the initial
+ // taskview for entering split screen, we only pretend to dismiss the task
+ if (child instanceof TaskView && child != mSplitHiddenTaskView) {
TaskView taskView = (TaskView) child;
mHasVisibleTaskData.delete(taskView.getTask().key.id);
mTaskViewPool.recycle(taskView);
@@ -640,15 +728,31 @@
}
public boolean isTaskViewVisible(TaskView tv) {
- // For now, just check if it's the active task or an adjacent task
- return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
+ if (showAsGrid()) {
+ int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
+ return isTaskViewWithinBounds(tv, screenStart, screenEnd);
+ } else {
+ // For now, just check if it's the active task or an adjacent task
+ return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
+ }
+ }
+
+ private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
+ int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
+ mOverviewFullscreenEnabled, showAsGrid());
+ int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
+ mOverviewFullscreenEnabled));
+ int taskEnd = taskStart + taskSize;
+ return (taskStart >= start && taskStart <= end) || (taskEnd >= start
+ && taskEnd <= end);
}
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;
@@ -662,6 +766,9 @@
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
mTmpRunningTask = null;
+ if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
+ cancelSplitSelect(false);
+ }
}
}
@@ -703,10 +810,21 @@
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
- TaskView taskView = getCurrentPageTaskView();
- if (taskView != null && taskView.offerTouchToChildren(ev)) {
- // Keep consuming events to pass to delegate
- return true;
+ if (showAsGrid()) {
+ int taskCount = getTaskViewCount();
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) {
+ // Keep consuming events to pass to delegate
+ return true;
+ }
+ }
+ } else {
+ TaskView taskView = getCurrentPageTaskView();
+ if (taskView != null && taskView.offerTouchToChildren(ev)) {
+ // Keep consuming events to pass to delegate
+ return true;
+ }
}
final int x = (int) ev.getX();
@@ -758,6 +876,11 @@
}
@Override
+ protected boolean snapToPageInFreeScroll() {
+ return !showAsGrid();
+ }
+
+ @Override
protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
// Enables swiping to the left or right only if the task overlay is not modal.
if (!isModal()) {
@@ -769,6 +892,12 @@
}
protected void applyLoadPlan(ArrayList<Task> tasks) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "applyLoadPlan: taskCount=" + tasks.size());
+ for (Task t : tasks) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "\t" + t);
+ }
+ }
if (mPendingAnimation != null) {
mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks));
return;
@@ -781,7 +910,7 @@
}
// Unload existing visible task data
- unloadVisibleTaskData();
+ unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
TaskView ignoreResetTaskView =
mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
@@ -809,6 +938,7 @@
final TaskView taskView = (TaskView) getChildAt(pageIndex);
taskView.bind(task, mOrientationState);
}
+ updateTaskSize();
if (mNextPage == INVALID_PAGE) {
// Set the current page to the running task, but not if settling on new task.
@@ -829,12 +959,21 @@
resetTaskVisuals();
onTaskStackUpdated();
updateEnabledOverlays();
+
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "applyLoadPlan: taskViewCount="
+ + getTaskViewCount());
+ }
}
private boolean isModal() {
return mTaskModalness > 0;
}
+ public boolean isLoadingTasks() {
+ return mModel.isLoadingTasksInBackground();
+ }
+
private void removeTasksViewsAndClearAllButton() {
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
removeView(getTaskViewAt(i));
@@ -845,6 +984,12 @@
}
public int getTaskViewCount() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "getTaskViewCount:"
+ + " numChildren=" + getChildCount()
+ + " start=" + mTaskViewStartIndex
+ + " clearAll=" + indexOfChild(mClearAllButton));
+ }
int taskViewCount = getChildCount() - mTaskViewStartIndex;
if (indexOfChild(mClearAllButton) != -1) {
taskViewCount--;
@@ -867,7 +1012,7 @@
taskView.setModalness(mTaskModalness);
}
}
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
// Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
// to reset the params after it settles in Overview from swipe up so that we don't
// render with obsolete param values.
@@ -875,10 +1020,6 @@
mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-
- // Reset the live tile rect
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- LiveTileOverlay.INSTANCE.update(0, 0, deviceProfile.widthPx, deviceProfile.heightPx);
}
if (mRunningTaskTileHidden) {
setRunningTaskHidden(mRunningTaskTileHidden);
@@ -891,19 +1032,15 @@
updateCurveProperties();
// Update the set of visible task's data
- loadVisibleTaskData();
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
setTaskModalness(0);
}
public void setFullscreenProgress(float fullscreenProgress) {
mFullscreenProgress = fullscreenProgress;
int taskCount = getTaskViewCount();
- float accumulatedTranslationX = 0;
for (int i = 0; i < taskCount; i++) {
- TaskView taskView = getTaskViewAt(i);
- taskView.setFullscreenProgress(mFullscreenProgress);
- taskView.setAccumulatedTranslationX(accumulatedTranslationX);
- accumulatedTranslationX += taskView.getFullscreenTranslationX();
+ getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
}
// Fade out the actions view quickly (0.1 range)
@@ -926,7 +1063,9 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
resetPaddingFromTaskSize();
- mLiveTileTaskViewSimulator.setDp(mActivity.getDeviceProfile());
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ mLiveTileTaskViewSimulator.setDp(dp);
+ mActionsView.setDp(dp);
}
private void resetPaddingFromTaskSize() {
@@ -935,15 +1074,59 @@
mTaskWidth = mTempRect.width();
mTaskHeight = mTempRect.height();
- mTempRect.top -= mTaskTopMargin;
+ mTempRect.top -= dp.overviewTaskThumbnailTopMarginPx;
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
+
+ mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(),
+ mLastComputedGridSize);
+
// Force TaskView to update size from thumbnail
+ updateTaskSize();
+ }
+
+ /**
+ * Updates TaskView scaling and translation required to support variable width.
+ */
+ private void updateTaskSize() {
final int taskCount = getTaskViewCount();
- for (int i = 0; i < taskCount; i++) {
- getTaskViewAt(i).updateTaskSize();
+ if (taskCount == 0) {
+ return;
}
+
+ float accumulatedTranslationX = 0;
+ float[] fullscreenTranslations = new float[taskCount];
+ int firstNonHomeTaskIndex = 0;
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ if (isHomeTask(taskView)) {
+ if (firstNonHomeTaskIndex == i) {
+ firstNonHomeTaskIndex++;
+ }
+ continue;
+ }
+
+ taskView.updateTaskSize();
+ fullscreenTranslations[i] += accumulatedTranslationX;
+ // Compensate space caused by TaskView scaling.
+ float widthDiff =
+ taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
+ // Compensate page spacing widening caused by RecentsView scaling.
+ widthDiff += mPageSpacing * (1 - 1 / mFullscreenScale);
+ float fullscreenTranslationX = mIsRtl ? widthDiff : -widthDiff;
+ fullscreenTranslations[i] += fullscreenTranslationX;
+ accumulatedTranslationX += fullscreenTranslationX;
+ }
+
+ // We need to maintain first non-home task's full screen translation at 0, now shift
+ // translation of all the TaskViews to achieve that.
+ for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+ getTaskViewAt(i).setFullscreenTranslationX(
+ fullscreenTranslations[i] - fullscreenTranslations[firstNonHomeTaskIndex]);
+ }
+
+ updateGridProperties();
}
public void getTaskSize(Rect outRect) {
@@ -974,14 +1157,14 @@
}
// After scrolling, update the visible task's data
- loadVisibleTaskData();
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
}
// Update the high res thumbnail loader state
mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+ if (LIVE_TILE.get() && mEnableDrawingLiveTile
&& mLiveTileParams.getTargetSet() != null) {
redrawLiveTile();
}
@@ -1004,44 +1187,93 @@
View page = getPageAt(i);
mScrollState.updateInterpolation(mActivity.getDeviceProfile(),
mOrientationHandler.getChildStartWithTranslation(page));
- ((PageCallbacks) page).onPageScroll(mScrollState);
+ ((PageCallbacks) page).onPageScroll(mScrollState, mOverviewGridEnabled);
}
}
+ @Override
+ protected int getDestinationPage(int scaledScroll) {
+ if (!showAsGrid()) {
+ return super.getDestinationPage(scaledScroll);
+ }
+
+ final int childCount = getChildCount();
+ if (mPageScrolls == null || childCount != mPageScrolls.length) {
+ return -1;
+ }
+
+ // When in grid, return the page which scroll is closest to screenStart instead of page
+ // nearest to center of screen.
+ int minDistanceFromScreenStart = Integer.MAX_VALUE;
+ int minDistanceFromScreenStartIndex = -1;
+ for (int i = 0; i < childCount; ++i) {
+ int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll);
+ if (distanceFromScreenStart < minDistanceFromScreenStart) {
+ minDistanceFromScreenStart = distanceFromScreenStart;
+ minDistanceFromScreenStartIndex = i;
+ }
+ }
+ return minDistanceFromScreenStartIndex;
+ }
+
/**
* Iterates through all the tasks, and loads the associated task data for newly visible tasks,
* and unloads the associated task data for tasks that are no longer visible.
*/
- public void loadVisibleTaskData() {
+ public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
// Skip loading visible task data if we've already left the overview state, or if the
// task list hasn't been loaded yet (the task views will not reflect the task list)
return;
}
- int centerPageIndex = getPageNearestToCenterOfScreen();
- int numChildren = getChildCount();
- int lower = Math.max(0, centerPageIndex - 2);
- int upper = Math.min(centerPageIndex + 2, numChildren - 1);
+ int lower = 0;
+ int upper = 0;
+ int visibleStart = 0;
+ int visibleEnd = 0;
+ if (showAsGrid()) {
+ int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int pageOrientedSize = mOrientationHandler.getMeasuredSize(this);
+ int halfScreenSize = pageOrientedSize / 2;
+ // Use +/- 50% screen width as visible area.
+ visibleStart = screenStart - halfScreenSize;
+ visibleEnd = screenStart + pageOrientedSize + halfScreenSize;
+ } else {
+ int centerPageIndex = getPageNearestToCenterOfScreen();
+ int numChildren = getChildCount();
+ lower = Math.max(0, centerPageIndex - 2);
+ upper = Math.min(centerPageIndex + 2, numChildren - 1);
+ }
// Update the task data for the in/visible children
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = getTaskViewAt(i);
Task task = taskView.getTask();
int index = indexOfChild(taskView);
- boolean visible = lower <= index && index <= upper;
+ boolean visible;
+ if (showAsGrid()) {
+ visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd);
+ } else {
+ visible = lower <= index && index <= upper;
+ }
if (visible) {
if (task == mTmpRunningTask) {
// Skip loading if this is the task that we are animating into
continue;
}
if (!mHasVisibleTaskData.get(task.key.id)) {
- taskView.onTaskListVisibilityChanged(true /* visible */);
+ // Ignore thumbnail update if it's current running task during the gesture
+ // We snapshot at end of gesture, it will update then
+ int changes = dataChanges;
+ if (taskView == getRunningTaskView() && mGestureActive) {
+ changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
+ }
+ taskView.onTaskListVisibilityChanged(true /* visible */, changes);
}
mHasVisibleTaskData.put(task.key.id, visible);
} else {
if (mHasVisibleTaskData.get(task.key.id)) {
- taskView.onTaskListVisibilityChanged(false /* visible */);
+ taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
}
mHasVisibleTaskData.delete(task.key.id);
}
@@ -1051,12 +1283,12 @@
/**
* Unloads any associated data from the currently visible tasks
*/
- private void unloadVisibleTaskData() {
+ private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
if (mHasVisibleTaskData.valueAt(i)) {
TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
if (taskView != null) {
- taskView.onTaskListVisibilityChanged(false /* visible */);
+ taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
}
}
}
@@ -1094,7 +1326,7 @@
mRecentsAnimationController = null;
mLiveTileParams.setTargetSet(null);
- unloadVisibleTaskData();
+ unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
setCurrentPage(0);
mDwbToastShown = false;
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
@@ -1104,6 +1336,10 @@
}
}
+ public int getRunningTaskId() {
+ return mRunningTaskId;
+ }
+
public @Nullable TaskView getRunningTaskView() {
return getTaskView(mRunningTaskId);
}
@@ -1138,6 +1374,7 @@
* Called when a gesture from an app is starting.
*/
public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
+ mGestureActive = true;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
updateOrientationHandler();
@@ -1156,10 +1393,7 @@
*/
public void onSwipeUpAnimationSuccess() {
if (getRunningTaskView() != null) {
- float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached
- ? LiveTileOverlay.INSTANCE.cancelIconAnimation()
- : 0f;
- animateUpRunningTaskIconScale(startProgress);
+ animateUpRunningTaskIconScale(0f);
}
setSwipeDownShouldLaunchApp(true);
}
@@ -1204,13 +1438,14 @@
* Called when a gesture from an app has finished, and an end target has been determined.
*/
public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
-
+ mCurrentGestureEndTarget = endTarget;
}
/**
* Called when a gesture from an app has finished, and the animation to the target has ended.
*/
public void onGestureAnimationEnd() {
+ mGestureActive = false;
if (mOrientationState.setGestureActive(false)) {
updateOrientationHandler();
}
@@ -1218,12 +1453,18 @@
setOnScrollChangeListener(null);
setEnableFreeScroll(true);
setEnableDrawingLiveTile(true);
- if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (!LIVE_TILE.get()) {
setRunningTaskViewShowScreenshot(true);
}
setRunningTaskHidden(false);
animateUpRunningTaskIconScale();
- animateActionsViewIn();
+
+ // TODO: This should be tied to whether there is a focus app on overview.
+ if (!showAsGrid()) {
+ animateActionsViewIn();
+ }
+
+ mCurrentGestureEndTarget = null;
}
/**
@@ -1265,6 +1506,8 @@
setCurrentPage(getRunningTaskIndex());
setRunningTaskViewShowScreenshot(false);
setRunningTaskHidden(runningTaskTileHidden);
+ // Update task size after setting current task.
+ updateTaskSize();
// Reload the task list
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
@@ -1304,7 +1547,7 @@
}
private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
TaskView runningTaskView = getRunningTaskView();
if (runningTaskView != null) {
runningTaskView.setShowScreenshot(showScreenshot);
@@ -1312,29 +1555,6 @@
}
}
- public void showNextTask() {
- final TaskView runningTaskView = getRunningTaskView();
- final TaskView targetTask;
-
- if (runningTaskView == null) {
- // Launch the first task
- if (getTaskViewCount() > 0) {
- targetTask = getTaskViewAt(0);
- } else {
- return;
- }
- } else {
- final TaskView nextTask = getNextTaskView();
- if (nextTask != null) {
- targetTask = nextTask;
- } else {
- targetTask = runningTaskView;
- }
- }
- targetTask.setEndQuickswitchCuj(true);
- targetTask.launchTask(true);
- }
-
public void setRunningTaskIconScaledDown(boolean isScaledDown) {
if (mRunningTaskIconScaledDown != isScaledDown) {
mRunningTaskIconScaledDown = isScaledDown;
@@ -1373,6 +1593,159 @@
}
}
+ /**
+ * Updates TaskView and ClearAllButton scaling and translation required to turn into grid
+ * layout.
+ * This method only calculates the potential position and depends on {@link #setGridProgress} to
+ * apply the actual scaling and translation.
+ */
+ private void updateGridProperties() {
+ int taskCount = getTaskViewCount();
+ if (taskCount == 0) {
+ return;
+ }
+
+ final int boxLength = Math.max(mTaskWidth, mTaskHeight);
+ int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ int topRowWidth = 0;
+ int bottomRowWidth = 0;
+ float topAccumulatedTranslationX = 0;
+ float bottomAccumulatedTranslationX = 0;
+ IntSet topSet = new IntSet();
+ IntSet bottomSet = new IntSet();
+ float[] gridTranslations = new float[taskCount];
+ int firstNonHomeTaskIndex = 0;
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ if (isHomeTask(taskView)) {
+ if (firstNonHomeTaskIndex == i) {
+ firstNonHomeTaskIndex++;
+ }
+ continue;
+ }
+
+ if (topRowWidth <= bottomRowWidth) {
+ gridTranslations[i] += topAccumulatedTranslationX;
+ topRowWidth += taskView.getLayoutParams().width + mPageSpacing;
+ topSet.add(i);
+
+ taskView.setGridTranslationY(0);
+
+ // Move horizontally into empty space.
+ float widthOffset = 0;
+ for (int j = i - 1; bottomSet.contains(j); j--) {
+ widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+ }
+
+ float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
+ gridTranslations[i] += gridTranslationX;
+ topAccumulatedTranslationX += gridTranslationX;
+ } else {
+ gridTranslations[i] += bottomAccumulatedTranslationX;
+ bottomRowWidth += taskView.getLayoutParams().width + mPageSpacing;
+ bottomSet.add(i);
+
+ // Move into bottom row.
+ float heightOffset = (boxLength + taskTopMargin) + mRowSpacing;
+ taskView.setGridTranslationY(heightOffset);
+
+ // Move horizontally into empty space.
+ float widthOffset = 0;
+ for (int j = i - 1; topSet.contains(j); j--) {
+ widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+ }
+
+ float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
+ gridTranslations[i] += gridTranslationX;
+ bottomAccumulatedTranslationX += gridTranslationX;
+ }
+ }
+
+ // If the first non-home task does not take full width of task Rect, shift all tasks
+ // accordingly without affecting scrolls.
+ int firstTaskWidth = getTaskViewAt(firstNonHomeTaskIndex).getLayoutParams().width;
+ float firstNonHomeTaskOffset = firstTaskWidth == ViewGroup.LayoutParams.MATCH_PARENT ? 0
+ : mTaskWidth - firstTaskWidth;
+ float offsetTranslation = mIsRtl ? firstNonHomeTaskOffset : -firstNonHomeTaskOffset;
+
+ // We need to maintain first non-home task's grid translation at 0, now shift translation
+ // of all the TaskViews to achieve that.
+ for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ taskView.setGridTranslationX(
+ gridTranslations[i] - gridTranslations[firstNonHomeTaskIndex]);
+ taskView.setGridOffsetTranslationX(offsetTranslation);
+ }
+
+ // Use the accumulated translation of the longer row.
+ float clearAllAccumulatedTranslation = mIsRtl ? Math.max(topAccumulatedTranslationX,
+ bottomAccumulatedTranslationX) : Math.min(topAccumulatedTranslationX,
+ bottomAccumulatedTranslationX);
+
+ // If the last task is on the shorter row, ClearAllButton will embed into the shorter row
+ // which is not what we want. Compensate the width difference of the 2 rows in that case.
+ float shorterRowCompensation = 0;
+ if (topRowWidth <= bottomRowWidth) {
+ if (topSet.contains(taskCount - 1)) {
+ shorterRowCompensation = bottomRowWidth - topRowWidth;
+ }
+ } else {
+ if (bottomSet.contains(taskCount - 1)) {
+ shorterRowCompensation = topRowWidth - bottomRowWidth;
+ }
+ }
+ float clearAllShorterRowCompensation =
+ mIsRtl ? -shorterRowCompensation : shorterRowCompensation;
+
+ // If the total width is shorter than one grid's width, move ClearAllButton further away
+ // accordingly.
+ float clearAllShortTotalCompensation = 0;
+ float longRowWidth = Math.max(topRowWidth, bottomRowWidth);
+ if (longRowWidth < mLastComputedGridSize.width()) {
+ float shortTotalCompensation = mLastComputedGridSize.width() - longRowWidth;
+ clearAllShortTotalCompensation =
+ mIsRtl ? -shortTotalCompensation : shortTotalCompensation;
+ }
+
+ float clearAllTotalTranslationX =
+ clearAllAccumulatedTranslation + clearAllShorterRowCompensation
+ + clearAllShortTotalCompensation;
+
+ mClearAllButton.setGridTranslationPrimary(
+ clearAllTotalTranslationX - gridTranslations[firstNonHomeTaskIndex]);
+ mClearAllButton.setGridTranslationSecondary(
+ boxLength - mTaskHeight / 2f + mRowSpacing / 2f);
+ mClearAllButton.setGridScrollOffset(
+ mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left
+ : mLastComputedTaskSize.right - mLastComputedGridSize.right);
+ mClearAllButton.setOffsetTranslationPrimary(offsetTranslation);
+
+ setGridProgress(mGridProgress);
+ }
+
+ protected boolean isHomeTask(TaskView taskView) {
+ return false;
+ }
+
+ /**
+ * Moves TaskView and ClearAllButton between carousel and 2 row grid.
+ *
+ * @param gridProgress 0 = carousel; 1 = 2 row grid.
+ */
+ private void setGridProgress(float gridProgress) {
+ int taskCount = getTaskViewCount();
+ if (taskCount == 0) {
+ return;
+ }
+
+ mGridProgress = gridProgress;
+
+ for (int i = 0; i < taskCount; i++) {
+ getTaskViewAt(i).setGridProgress(gridProgress);
+ }
+ mClearAllButton.setGridProgress(gridProgress);
+ }
+
private void enableLayoutTransitions() {
if (mLayoutTransition == null) {
mLayoutTransition = new LayoutTransition();
@@ -1417,8 +1790,10 @@
/**
* Updates the page UI based on scroll params.
+ *
+ * @param gridEnabled whether Overveiw is currently showing as 2 rows grid
*/
- default void onPageScroll(ScrollState scrollState) {}
+ default void onPageScroll(ScrollState scrollState, boolean gridEnabled) {}
}
public static class ScrollState extends CurveProperties {
@@ -1465,7 +1840,7 @@
// alpha is set to 0 so that it can be recycled in the view pool properly
anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
FloatProperty<TaskView> secondaryViewTranslate =
- taskView.getDismissTaskTranslationProperty();
+ taskView.getSecondaryDissmissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
@@ -1508,7 +1883,8 @@
if (animateTaskView) {
addDismissedTaskAnimations(taskView, duration, anim);
}
- } else {
+ } else if (!showAsGrid()) {
+ // For grid layout, don't animate other tasks when dismissing in grid for now.
// If we just take newScroll - oldScroll, everything to the right of dragged task
// translates to the left. We need to offset this in some cases:
// - In RTL, add page offset to all pages, since we want pages to move to the right
@@ -1528,10 +1904,23 @@
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
}
+
+ // Additional offset for fake landscape, if the pinning happens to the right or
+ // left, we need to scroll all the tasks away from the direction of the splaceholder
+ // view
+ if (isSplitSelectionActive()) {
+ int splitPosition = getSplitPlaceholder().getSplitController()
+ .getActiveSplitPositionOption().mStagePosition;
+ int direction = mOrientationHandler
+ .getSplitTranslationDirectionFactor(splitPosition);
+ int splitOffset = mOrientationHandler.getSplitAnimationTranslation(
+ mSplitPlaceholderView.getHeight(), mActivity.getDeviceProfile());
+ offset += direction * splitOffset;
+ }
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
FloatProperty translationProperty = child instanceof TaskView
- ? ((TaskView) child).getFillDismissGapTranslationProperty()
+ ? ((TaskView) child).getPrimaryDismissTranslationProperty()
: mOrientationHandler.getPrimaryViewTranslate();
ResourceProvider rp = DynamicResource.provider(mActivity);
@@ -1550,7 +1939,7 @@
anim.addOnFrameCallback(this::updateCurveProperties);
}
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && getRunningTaskView() == taskView) {
+ if (LIVE_TILE.get() && getRunningTaskView() == taskView) {
anim.addOnFrameCallback(() -> {
mLiveTileTaskViewSimulator.taskSecondaryTranslation.value =
mOrientationHandler.getSecondaryValue(
@@ -1569,7 +1958,7 @@
mPendingAnimation.addEndListener(new Consumer<Boolean>() {
@Override
public void accept(Boolean success) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask() && success) {
+ if (LIVE_TILE.get() && taskView.isRunningTask() && success) {
finishRecentsAnimation(true /* toHome */, () -> onEnd(success));
} else {
onEnd(success);
@@ -1601,12 +1990,20 @@
startHome();
} else {
snapToPageImmediately(pageToSnapTo);
+ // Grid got messed up, reapply.
+ updateGridProperties();
}
// Update the layout synchronously so that the position of next view is
// immediately available.
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
}
resetTaskVisuals();
+ if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // We want to keep the tasks translations in this temporary state
+ // after resetting the rest above
+ setTaskViewsResistanceTranslation(mTaskViewsSecondaryTranslation);
+ setTaskViewsPrimaryTranslation(mTaskViewsPrimaryTranslation);
+ }
mPendingAnimation = null;
}
});
@@ -1745,7 +2142,7 @@
if (alpha > 0) {
setVisibility(VISIBLE);
} else if (!mFreezeViewVisibility) {
- setVisibility(GONE);
+ setVisibility(INVISIBLE);
}
}
@@ -1757,7 +2154,7 @@
if (mFreezeViewVisibility != freezeViewVisibility) {
mFreezeViewVisibility = freezeViewVisibility;
if (!mFreezeViewVisibility) {
- setVisibility(mContentAlpha > 0 ? VISIBLE : GONE);
+ setVisibility(mContentAlpha > 0 ? VISIBLE : INVISIBLE);
}
}
}
@@ -1891,7 +2288,7 @@
// Update the pivots such that when the task is scaled, it fills the full page
getTaskSize(mTempRect);
- getPagedViewOrientedState().getFullScreenScaleAndPivot(
+ mFullscreenScale = getPagedViewOrientedState().getFullScreenScaleAndPivot(
mTempRect, mActivity.getDeviceProfile(), mTempPointF);
setPivotX(mTempPointF.x);
setPivotY(mTempPointF.y);
@@ -1998,7 +2395,7 @@
return distanceToOffscreen * offsetProgress;
}
- private void setTaskViewsSecondaryTranslation(float translation) {
+ private void setTaskViewsResistanceTranslation(float translation) {
mTaskViewsSecondaryTranslation = translation;
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView task = getTaskViewAt(i);
@@ -2007,6 +2404,15 @@
mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
}
+ private void setTaskViewsPrimaryTranslation(float translation) {
+ mTaskViewsPrimaryTranslation = translation;
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ TaskView task = getTaskViewAt(i);
+ task.getPrimaryDismissTranslationProperty().set(task, translation / getScaleY());
+ }
+ mLiveTileTaskViewSimulator.recentsViewPrimaryTranslation.value = translation;
+ }
+
/**
* TODO: Do not assume motion across X axis for adjacent page
*/
@@ -2024,6 +2430,111 @@
}
}
+ public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
+ mSplitHiddenTaskView = taskView;
+ mSplitPlaceholderView.getSplitController().setInitialTaskSelect(taskView,
+ splitPositionOption);
+ mSplitHiddenTaskViewIndex = indexOfChild(taskView);
+ mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
+ }
+
+ public PendingAnimation createSplitSelectInitAnimation() {
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
+ }
+
+ public void confirmSplitSelect(TaskView taskView) {
+ mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
+ resetTaskVisuals();
+ setTranslationY(0);
+ }
+
+ public PendingAnimation cancelSplitSelect(boolean animate) {
+ mSplitPlaceholderView.getSplitController().resetState();
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ PendingAnimation pendingAnim = new PendingAnimation(duration);
+ if (!animate) {
+ resetFromSplitSelectionState();
+ return pendingAnim;
+ }
+
+ addViewInLayout(mSplitHiddenTaskView, mSplitHiddenTaskViewIndex,
+ mSplitHiddenTaskView.getLayoutParams());
+ mSplitHiddenTaskView.setAlpha(0);
+ int[] oldScroll = new int[getChildCount()];
+ getPageScrolls(oldScroll, false,
+ view -> view.getVisibility() != GONE && view != mSplitHiddenTaskView);
+
+ // x is correct, y is before tasks move up
+ int[] locationOnScreen = mSplitHiddenTaskView.getLocationOnScreen();
+ int[] newScroll = new int[getChildCount()];
+ getPageScrolls(newScroll, false, SIMPLE_SCROLL_LOGIC);
+
+ boolean needsCurveUpdates = false;
+ for (int i = mSplitHiddenTaskViewIndex; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (child == mSplitHiddenTaskView) {
+
+ int left = newScroll[i] + getPaddingStart();
+ int topMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ int top = -mSplitHiddenTaskView.getHeight() - locationOnScreen[1];
+ mSplitHiddenTaskView.layout(left, top,
+ left + mSplitHiddenTaskView.getWidth(),
+ top + mSplitHiddenTaskView.getHeight());
+ pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, TRANSLATION_Y,
+ -top + mSplitPlaceholderView.getHeight() - topMargin));
+ pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, ALPHA, 1));
+ } else {
+ // If insertion is on last index (furthest from clear all), we directly add the view
+ // else we translate all views to the right of insertion index further right,
+ // ignore views to left
+ int scrollDiff = newScroll[i] - oldScroll[i];
+ if (scrollDiff != 0) {
+ FloatProperty translationProperty = child instanceof TaskView
+ ? ((TaskView) child).getPrimaryDismissTranslationProperty()
+ : mOrientationHandler.getPrimaryViewTranslate();
+
+ ResourceProvider rp = DynamicResource.provider(mActivity);
+ SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
+ .setDampingRatio(
+ rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio))
+ .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness));
+ pendingAnim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff)
+ .setDuration(duration), ACCEL, sp);
+ needsCurveUpdates = true;
+ }
+ }
+ }
+
+ if (needsCurveUpdates) {
+ pendingAnim.addOnFrameCallback(this::updateCurveProperties);
+ }
+
+ pendingAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ resetFromSplitSelectionState();
+ }
+ });
+
+ return pendingAnim;
+ }
+
+ private void resetFromSplitSelectionState() {
+ mSplitHiddenTaskView.setTranslationY(0);
+ int pageToSnapTo = mCurrentPage;
+ if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
+ pageToSnapTo += 1;
+ } else {
+ pageToSnapTo = mSplitHiddenTaskViewIndex;
+ }
+ snapToPageImmediately(pageToSnapTo);
+ onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
+ resetTaskVisuals();
+ mSplitHiddenTaskView = null;
+ mSplitHiddenTaskViewIndex = -1;
+ }
+
private void updateDeadZoneRects() {
// Get the deadzone rect surrounding the clear all button to not dismiss overview to home
mClearAllButtonDeadZoneRect.setEmpty();
@@ -2114,7 +2625,7 @@
anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
int runningTaskIndex = recentsView.getRunningTaskIndex();
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && runningTaskIndex != -1
+ if (LIVE_TILE.get() && runningTaskIndex != -1
&& runningTaskIndex != taskIndex) {
anim.play(ObjectAnimator.ofFloat(
recentsView.getLiveTileTaskViewSimulator().taskPrimaryTranslation,
@@ -2191,23 +2702,17 @@
mPendingAnimation = new PendingAnimation(duration);
mPendingAnimation.add(anim);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
}
mPendingAnimation.addEndListener(isSuccess -> {
if (isSuccess) {
- Consumer<Boolean> onLaunchResult = (result) -> {
- onTaskLaunchAnimationEnd(result);
- if (!result) {
- tv.notifyTaskLaunchFailed(TAG);
- }
- };
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
finishRecentsAnimation(false /* toRecents */, null);
- onLaunchResult.accept(true /* success */);
+ onTaskLaunchAnimationEnd(true /* success */);
} else {
- tv.launchTask(false, onLaunchResult, getHandler());
+ tv.launchTask(this::onTaskLaunchAnimationEnd);
}
Task task = tv.getTask();
if (task != null) {
@@ -2231,7 +2736,7 @@
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
- loadVisibleTaskData();
+ loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
updateEnabledOverlays();
}
@@ -2313,16 +2818,6 @@
}
}
- public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
- mLiveTileOverlayAttached = liveTileOverlayAttached;
- }
-
- public void updateLiveTileIcon(Drawable icon) {
- if (mLiveTileOverlayAttached) {
- LiveTileOverlay.INSTANCE.setIcon(icon);
- }
- }
-
public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
if (mRecentsAnimationController == null) {
if (onFinishComplete != null) {
@@ -2354,39 +2849,59 @@
@Override
protected int computeMinScroll() {
if (getTaskViewCount() > 0) {
- if (mDisallowScrollToClearAll) {
+ if (mIsRtl && mDisallowScrollToClearAll) {
// We aren't showing the clear all button,
// so use the leftmost task as the min scroll.
- if (mIsRtl) {
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
- }
- return getScrollForPage(mTaskViewStartIndex);
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
}
- if (mIsRtl) {
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
- }
- return getScrollForPage(mTaskViewStartIndex);
+ return getLeftMostChildScroll();
}
return super.computeMinScroll();
}
+ /**
+ * Returns page scroll of the left most child.
+ */
+ public int getLeftMostChildScroll() {
+ if (mIsRtl) {
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
+ }
+ return getScrollForPage(mTaskViewStartIndex);
+ }
+
+ @Override
+ protected int computeMaxScroll() {
+ if (getTaskViewCount() > 0) {
+ if (!mIsRtl && mDisallowScrollToClearAll) {
+ // We aren't showing the clear all button,
+ // so use the rightmost task as the min scroll.
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
+ }
+ if (mIsRtl) {
+ return getScrollForPage(mTaskViewStartIndex);
+ }
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
+ }
+ return super.computeMaxScroll();
+ }
+
@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;
+ View child = getChildAt(i);
+ float scrollDiff = 0;
+ if (child instanceof TaskView) {
+ scrollDiff = ((TaskView) child).getScrollAdjustment(mOverviewFullscreenEnabled,
+ showAsGrid());
+ } else if (child instanceof ClearAllButton) {
+ scrollDiff = ((ClearAllButton) child).getScrollAdjustment(showAsGrid());
}
- final TaskView taskView = getTaskViewAt(
- Utilities.boundToRange(i, mTaskViewStartIndex, taskCount - 1));
- float scrollDiff =
- taskView.getFullscreenTranslationX() + taskView.getAccumulatedTranslationX();
if (scrollDiff != 0) {
outPageScrolls[i] += scrollDiff;
pageScrollChanged = true;
@@ -2397,14 +2912,15 @@
@Override
protected int getChildOffset(int index) {
- if (index < mTaskViewStartIndex) {
- return super.getChildOffset(index);
+ int childOffset = super.getChildOffset(index);
+ View child = getChildAt(index);
+ if (child instanceof TaskView) {
+ childOffset += ((TaskView) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
+ showAsGrid());
+ } else if (child instanceof ClearAllButton) {
+ childOffset += ((ClearAllButton) child).getOffsetAdjustment(showAsGrid());
}
-
- final TaskView taskView = getTaskViewAt(
- Utilities.boundToRange(index, mTaskViewStartIndex, getTaskViewCount() - 1));
- return super.getChildOffset(index) + (int) taskView.getFullscreenTranslationX()
- + (int) taskView.getAccumulatedTranslationX();
+ return childOffset;
}
@Override
@@ -2413,26 +2929,8 @@
if (taskView == null) {
return super.getChildVisibleSize(index);
}
- return super.getChildVisibleSize(index) - (int) taskView.getFullscreenTranslationX();
- }
-
- @Override
- protected int computeMaxScroll() {
- if (getTaskViewCount() > 0) {
- if (mDisallowScrollToClearAll) {
- // We aren't showing the clear all button,
- // so use the rightmost task as the min scroll.
- if (mIsRtl) {
- return getScrollForPage(mTaskViewStartIndex);
- }
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
- }
- if (mIsRtl) {
- return getScrollForPage(mTaskViewStartIndex);
- }
- return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
- }
- return super.computeMaxScroll();
+ return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
+ mOverviewFullscreenEnabled));
}
public ClearAllButton getClearAllButton() {
@@ -2454,7 +2952,7 @@
}
/**
- * @return How many pixels the page is offset on the currently laid out dominant axis.
+ * Returns how many pixels the page is offset on the currently laid out dominant axis.
*/
public int getScrollOffset(int pageIndex) {
if (pageIndex == -1) {
@@ -2470,6 +2968,29 @@
return getScrollForPage(pageIndex) - scroll;
}
+ /**
+ * Returns how many pixels the task is offset on the currently laid out secondary axis
+ * according to {@link #mGridProgress}.
+ */
+ public float getGridTranslationSecondary(int pageIndex) {
+ TaskView taskView = getTaskViewAtByAbsoluteIndex(pageIndex);
+ if (taskView == null) {
+ return 0;
+ }
+
+ return mOrientationHandler.getSecondaryValue(taskView.getGridTranslationX(),
+ taskView.getGridTranslationY());
+ }
+
+ /**
+ * Returns the progress of forming a grid from carousel.
+ *
+ * @return A float from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
+ */
+ public float getGridProgress() {
+ return mGridProgress;
+ }
+
public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
float degreesRotated;
if (navbarRotation == 0) {
@@ -2514,6 +3035,23 @@
}
}
+ public void setOverviewGridEnabled(boolean overviewGridEnabled) {
+ if (mOverviewGridEnabled != overviewGridEnabled) {
+ mOverviewGridEnabled = overviewGridEnabled;
+ // Request layout to ensure scroll position is recalculated with updated mGridProgress.
+ requestLayout();
+ }
+ }
+
+ public void setOverviewFullscreenEnabled(boolean overviewFullscreenEnabled) {
+ if (mOverviewFullscreenEnabled != overviewFullscreenEnabled) {
+ mOverviewFullscreenEnabled = overviewFullscreenEnabled;
+ // Request layout to ensure scroll position is recalculated with updated
+ // mFullscreenProgress.
+ requestLayout();
+ }
+ }
+
/**
* Switch the current running task view to static snapshot mode,
* capturing the snapshot at the same time.
@@ -2584,6 +3122,12 @@
return mSizeStrategy;
}
+ private boolean showAsGrid() {
+ return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
+ && mSizeStrategy.stateFromGestureEndTarget(
+ mCurrentGestureEndTarget).displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+ }
+
/**
* Used to register callbacks for when our empty message state changes.
*
@@ -2596,7 +3140,7 @@
}
private static class PinnedStackAnimationListener<T extends BaseActivity> extends
- IPinnedStackAnimationListener.Stub {
+ IPipAnimationListener.Stub {
private T mActivity;
public void setActivity(T activity) {
@@ -2604,10 +3148,14 @@
}
@Override
- public void onPinnedStackAnimationStarted() {
- // Needed for activities that auto-enter PiP, which will not trigger a remote
- // animation to be created
- mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ public void onPipAnimationStarted() {
+ MAIN_EXECUTOR.execute(() -> {
+ // Needed for activities that auto-enter PiP, which will not trigger a remote
+ // animation to be created
+ if (mActivity != null) {
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
new file mode 100644
index 0000000..fb9be81
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.View;
+
+import com.android.quickstep.util.SplitSelectStateController;
+
+public class SplitPlaceholderView extends View {
+
+ public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
+ new FloatProperty<SplitPlaceholderView>("SplitViewAlpha") {
+ @Override
+ public void setValue(SplitPlaceholderView splitPlaceholderView, float v) {
+ splitPlaceholderView.setVisibility(v != 0 ? VISIBLE : GONE);
+ splitPlaceholderView.setAlpha(v);
+ }
+
+ @Override
+ public Float get(SplitPlaceholderView splitPlaceholderView) {
+ return splitPlaceholderView.getAlpha();
+ }
+ };
+
+ private SplitSelectStateController mSplitController;
+
+ public SplitPlaceholderView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void init(SplitSelectStateController controller) {
+ this.mSplitController = controller;
+ }
+
+ public SplitSelectStateController getSplitController() {
+ return mSplitController;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index e21bf76..a46daf3 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,19 +16,21 @@
package com.android.quickstep.views;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -38,12 +40,13 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.TaskCornerRadius;
/**
* Contains options for a recent task when long-pressing its icon.
@@ -55,7 +58,6 @@
private static final int REVEAL_OPEN_DURATION = 150;
private static final int REVEAL_CLOSE_DURATION = 100;
- private final float mThumbnailTopMargin;
private BaseDraggingActivity mActivity;
private TextView mTaskName;
private AnimatorSet mOpenCloseAnimator;
@@ -70,7 +72,7 @@
super(context, attrs, defStyleAttr);
mActivity = BaseDraggingActivity.fromContext(context);
- mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+ setClipToOutline(true);
}
@Override
@@ -107,13 +109,30 @@
return (type & TYPE_TASK_MENU) != 0;
}
+ @Override
+ public ViewOutlineProvider getOutlineProvider() {
+ return new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
+ TaskCornerRadius.get(view.getContext()));
+ }
+ };
+ }
+
public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) {
- float adjustedY = y + mThumbnailTopMargin;
+ int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ float adjustedY = y + taskTopMargin;
// Changing pivot to make computations easier
// NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
// which would render the X and Y position set here incorrect
setPivotX(0);
- setPivotY(0);
+ if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+ // In tablet, set pivotY to original position without mThumbnailTopMargin adjustment.
+ setPivotY(-taskTopMargin);
+ } else {
+ setPivotY(0);
+ }
setRotation(pagedOrientationHandler.getDegreesRotated());
setX(pagedOrientationHandler.getTaskMenuX(x, mTaskView.getThumbnail()));
setY(pagedOrientationHandler.getTaskMenuY(adjustedY, mTaskView.getThumbnail()));
@@ -166,7 +185,8 @@
mTaskName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
mTaskName.setOnClickListener(v -> close(true));
- TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption);
+ TaskOverlayFactory.getEnabledShortcuts(taskView, mActivity.getDeviceProfile())
+ .forEach(this::addMenuOption);
}
private void addMenuOption(SystemShortcut menuOption) {
@@ -176,8 +196,10 @@
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
+ menuOptionView.setEnabled(menuOption.isEnabled());
+ menuOptionView.setAlpha(menuOption.isEnabled() ? 1 : 0.5f);
menuOptionView.setOnClickListener(view -> {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
RecentsView recentsView = mTaskView.getRecentsView();
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */,
@@ -254,7 +276,7 @@
}
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
- float radius = Themes.getDialogCornerRadius(getContext());
+ float radius = TaskCornerRadius.get(mContext);
Rect fromRect = new Rect(0, 0, getWidth(), 0);
Rect toRect = new Rect(0, 0, getWidth(), getHeight());
return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index f94acf8..af62582 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -19,7 +19,7 @@
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
import android.content.Context;
@@ -111,9 +111,6 @@
private boolean mOverlayEnabled;
private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
- // TODO(b/179466077): Remove when proper API is ready.
- private Float mThumbnailRatio = null;
-
public TaskThumbnailView(Context context) {
this(context, null);
}
@@ -215,13 +212,6 @@
return mDimAlpha;
}
- public Rect getInsets(Rect fallback) {
- if (mThumbnailData != null) {
- return mThumbnailData.insets;
- }
- return fallback;
- }
-
/**
* Get the scaled insets that are being used to draw the task view. This is a subsection of
* the full snapshot.
@@ -233,6 +223,10 @@
return Insets.NONE;
}
+ if (!TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ return Insets.NONE;
+ }
+
RectF bitmapRect = new RectF(
0, 0,
mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
@@ -322,7 +316,7 @@
public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
float cornerRadius) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (LIVE_TILE.get()) {
if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
@@ -385,9 +379,10 @@
mThumbnailData.thumbnail.getHeight());
int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
.getRecentsActivityRotation();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
- currentRotation);
+ currentRotation, isRtl);
mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
mPaint.setShader(mBitmapShader);
@@ -455,31 +450,6 @@
return mThumbnailData.isRealSnapshot && !mTask.isLocked;
}
- // TODO(b/179466077): Remove when proper API is ready.
- public float getThumbnailRatio() {
- // API is ready.
- if (mThumbnailRatio != null) {
- return mThumbnailRatio;
- }
-
- if (mThumbnailData == null || mThumbnailData.thumbnail == null) {
- final float[] thumbnailRatios =
- new float[]{0.8882452f, 1.2834098f, 0.5558415f, 2.15625f};
- // Use key's hash code to return a deterministic thumbnail ratio.
- mThumbnailRatio = thumbnailRatios[mTask.key.hashCode() % thumbnailRatios.length];
- return mThumbnailRatio;
- }
-
- float surfaceWidth = mThumbnailData.thumbnail.getWidth() / mThumbnailData.scale;
- float surfaceHeight = mThumbnailData.thumbnail.getHeight() / mThumbnailData.scale;
- float availableWidth = surfaceWidth
- - (mThumbnailData.insets.left + mThumbnailData.insets.right);
- float availableHeight = surfaceHeight
- - (mThumbnailData.insets.top + mThumbnailData.insets.bottom);
- mThumbnailRatio = availableWidth / availableHeight;
- return mThumbnailRatio;
- }
-
/**
* Utility class to position the thumbnail in the TaskView
*/
@@ -488,7 +458,6 @@
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
private final RectF mClippedInsets = new RectF();
private final Matrix mMatrix = new Matrix();
- private float mClipBottom = -1;
private boolean mIsOrientationChanged;
public Matrix getMatrix() {
@@ -499,13 +468,15 @@
* Updates the matrix based on the provided parameters
*/
public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
- int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
+ int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
+ boolean isRtl) {
boolean isRotated = false;
boolean isOrientationDifferent;
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
- RectF thumbnailClipHint = new RectF(thumbnailData.insets);
+ RectF thumbnailClipHint = TaskView.CLIP_STATUS_AND_NAV_BARS
+ ? new RectF(thumbnailData.insets) : new RectF();
float scale = thumbnailData.scale;
final float thumbnailScale;
@@ -532,6 +503,17 @@
float availableHeight = surfaceHeight
- (thumbnailClipHint.top + thumbnailClipHint.bottom);
+ if (isRotated) {
+ float canvasAspect = canvasWidth / (float) canvasHeight;
+ float availableAspect = availableHeight / availableWidth;
+ // Do not rotate thumbnail if it would not improve fit
+ if (Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+ availableAspect, 0.1f)) {
+ isRotated = false;
+ isOrientationDifferent = false;
+ }
+ }
+
final float targetW, targetH;
if (isOrientationDifferent) {
targetW = canvasHeight;
@@ -567,21 +549,21 @@
}
}
- // Update the clip hints
- float halfExtraW = (availableWidth - croppedWidth) / 2;
- thumbnailClipHint.left += halfExtraW;
- thumbnailClipHint.right += halfExtraW;
- if (thumbnailClipHint.left < 0) {
- thumbnailClipHint.right += thumbnailClipHint.left;
- thumbnailClipHint.left = 0;
- } else if (thumbnailClipHint.right < 0) {
- thumbnailClipHint.left += thumbnailClipHint.right;
- thumbnailClipHint.right = 0;
+ // Update the clip hints. Align to 0,0, crop the remaining.
+ if (isRtl) {
+ thumbnailClipHint.left += availableWidth - croppedWidth;
+ if (thumbnailClipHint.right < 0) {
+ thumbnailClipHint.left += thumbnailClipHint.right;
+ thumbnailClipHint.right = 0;
+ }
+ } else {
+ thumbnailClipHint.right += availableWidth - croppedWidth;
+ if (thumbnailClipHint.left < 0) {
+ thumbnailClipHint.right += thumbnailClipHint.left;
+ thumbnailClipHint.left = 0;
+ }
}
-
- float halfExtraH = (availableHeight - croppedHeight) / 2;
- thumbnailClipHint.top += halfExtraH;
- thumbnailClipHint.bottom += halfExtraH;
+ thumbnailClipHint.bottom += availableHeight - croppedHeight;
if (thumbnailClipHint.top < 0) {
thumbnailClipHint.bottom += thumbnailClipHint.top;
thumbnailClipHint.top = 0;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a1b5533..d497a96 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -27,7 +27,8 @@
import static android.view.Surface.ROTATION_90;
import static android.widget.Toast.LENGTH_SHORT;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -35,10 +36,13 @@
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -52,7 +56,6 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.os.Handler;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
@@ -66,6 +69,9 @@
import android.widget.FrameLayout;
import android.widget.Toast;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
@@ -79,7 +85,10 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
@@ -100,6 +109,7 @@
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
+import java.lang.annotation.Retention;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -111,12 +121,31 @@
private static final String TAG = TaskView.class.getSimpleName();
+ public static final int FLAG_UPDATE_ICON = 1;
+ public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
+
+ public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL;
+
+ /**
+ * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
+ * granularity on which components of this task require an update
+ */
+ @Retention(SOURCE)
+ @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
+ public @interface TaskDataChanges {}
+
/**
* The 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;
+ /**
+ * Should the TaskView display clip off the status and navigation bars in recents. When this
+ * is false the overview shows the whole screen scaled down instead.
+ */
+ public static final boolean CLIP_STATUS_AND_NAV_BARS = false;
+
private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
@@ -248,7 +277,7 @@
private IconView mIconView;
private final DigitalWellBeingToast mDigitalWellBeingToast;
private float mFullscreenProgress;
- private float mScaleAtFullscreen = 1;
+ private float mGridProgress;
private float mFullscreenScale = 1;
private final FullscreenDrawParams mCurrentFullscreenParams;
private final StatefulActivity mActivity;
@@ -262,8 +291,12 @@
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;
+ // The following grid translations scales with mGridProgress.
+ private float mGridTranslationX;
+ private float mGridTranslationY;
+ // Offset translation does not affect scroll calculation.
+ private float mGridOffsetTranslationX;
private ObjectAnimator mIconAndDimAnimator;
private float mIconScaleAnimStartProgress = 0;
@@ -280,7 +313,6 @@
private boolean mEndQuickswitchCuj;
private View mContextualChipWrapper;
- private View mContextualChip;
private final float[] mIconCenterCoords = new float[2];
private final float[] mChipCenterCoords = new float[2];
@@ -301,7 +333,7 @@
if (getTask() == null) {
return;
}
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+ if (LIVE_TILE.get() && isRunningTask()) {
if (!mIsClickableAsLiveTile) {
return;
}
@@ -328,7 +360,12 @@
});
anim.start();
} else {
- launchTask(true /* animate */);
+ if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // User tapped to select second split screen app
+ getRecentsView().confirmSplitSelect(this);
+ } else {
+ launchTaskAnimated();
+ }
}
mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
@@ -337,7 +374,8 @@
mCurrentFullscreenParams = new FullscreenDrawParams(context);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
- mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
+ mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams,
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
setOutlineProvider(mOutlineProvider);
}
@@ -424,11 +462,13 @@
}
mModalness = modalness;
mIconView.setAlpha(comp(modalness));
- if (mContextualChip != null) {
- mContextualChip.setScaleX(comp(modalness));
- mContextualChip.setScaleY(comp(modalness));
+ if (mContextualChipWrapper != null) {
+ mContextualChipWrapper.setScaleX(comp(modalness));
+ mContextualChipWrapper.setScaleY(comp(modalness));
}
- mDigitalWellBeingToast.updateBannerOffset(modalness);
+ mDigitalWellBeingToast.updateBannerOffset(modalness,
+ mCurrentFullscreenParams.mCurrentDrawnInsets.top
+ + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
}
public TaskMenuView getMenuView() {
@@ -449,7 +489,6 @@
cancelPendingLoadTasks();
mTask = task;
mSnapshotView.bind(task);
- updateTaskSize();
setOrientationState(orientedState);
}
@@ -457,6 +496,10 @@
return mTask;
}
+ public boolean hasTaskId(int taskId) {
+ return mTask != null && mTask.key != null && mTask.key.id == taskId;
+ }
+
public TaskThumbnailView getThumbnail() {
return mSnapshotView;
}
@@ -471,67 +514,78 @@
.createPlaybackController();
}
- public void launchTask(boolean animate) {
- launchTask(animate, false /* freezeTaskList */);
- }
-
- public void launchTask(boolean animate, boolean freezeTaskList) {
- launchTask(animate, freezeTaskList, (result) -> {
- if (!result) {
- notifyTaskLaunchFailed(TAG);
- }
- }, getHandler());
- }
-
- public void launchTask(boolean animate, Consumer<Boolean> resultCallback,
- Handler resultCallbackHandler) {
- launchTask(animate, false /* freezeTaskList */, resultCallback, resultCallbackHandler);
- }
-
- public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
- Handler resultCallbackHandler) {
+ /**
+ * Starts the task associated with this view and animates the startup.
+ * @return CompletionStage to indicate the animation completion or null if the launch failed.
+ */
+ public RunnableList launchTaskAnimated() {
if (mTask != null) {
- final ActivityOptions opts;
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
- if (animate) {
- opts = mActivity.getActivityLaunchOptions(this);
- if (freezeTaskList) {
- ActivityOptionsCompat.setFreezeRecentTasksList(opts);
- }
- ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
- opts, resultCallback, resultCallbackHandler);
+ ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this);
+ if (ActivityManagerWrapper.getInstance()
+ .startActivityFromRecents(mTask.key, opts.options)) {
+ return opts.onEndCallback;
} else {
- opts = ActivityOptionsCompat.makeCustomAnimation(getContext(), 0, 0, () -> {
- if (resultCallback != null) {
- // Only post the animation start after the system has indicated that the
- // transition has started
- resultCallbackHandler.post(() -> resultCallback.accept(true));
- }
- }, resultCallbackHandler);
- if (freezeTaskList) {
- ActivityOptionsCompat.setFreezeRecentTasksList(opts);
- }
- UI_HELPER_EXECUTOR.execute(
- () -> ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
- mTask.key,
- opts,
- (success) -> {
- if (resultCallback != null && !success) {
- // If the call to start activity failed, then post the
- // result
- // immediately, otherwise, wait for the animation start
- // callback
- // from the activity options above
- resultCallbackHandler.post(
- () -> resultCallback.accept(false));
- }
- }, resultCallbackHandler));
+ notifyTaskLaunchFailed(TAG);
+ return null;
}
+ } else {
+ return null;
}
}
+ /**
+ * Starts the task associated with this view without any animation
+ */
+ public void launchTask(@NonNull Consumer<Boolean> callback) {
+ launchTask(callback, false /* freezeTaskList */);
+ }
+
+ /**
+ * Starts the task associated with this view without any animation
+ */
+ public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+ if (mTask != null) {
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
+
+ // Indicate success once the system has indicated that the transition has started
+ ActivityOptions opts = ActivityOptionsCompat.makeCustomAnimation(
+ getContext(), 0, 0, () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
+ if (freezeTaskList) {
+ ActivityOptionsCompat.setFreezeRecentTasksList(opts);
+ }
+ Task.TaskKey key = mTask.key;
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
+ // If the call to start activity failed, then post the result immediately,
+ // otherwise, wait for the animation start callback from the activity options
+ // above
+ MAIN_EXECUTOR.post(() -> {
+ notifyTaskLaunchFailed(TAG);
+ callback.accept(false);
+ });
+ }
+ });
+ } else {
+ callback.accept(false);
+ }
+ }
+
+ /**
+ * See {@link TaskDataChanges}
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
public void onTaskListVisibilityChanged(boolean visible) {
+ onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL);
+ }
+
+ /**
+ * See {@link TaskDataChanges}
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
+ public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {
if (mTask == null) {
return;
}
@@ -542,25 +596,37 @@
RecentsModel model = RecentsModel.INSTANCE.get(getContext());
TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
TaskIconCache iconCache = model.getIconCache();
- mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
- mTask, thumbnail -> mSnapshotView.setThumbnail(mTask, thumbnail));
- mIconLoadRequest = iconCache.updateIconInBackground(mTask,
- (task) -> {
- setIcon(task.icon);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
- getRecentsView().updateLiveTileIcon(task.icon);
- }
- mDigitalWellBeingToast.initialize(mTask);
- });
+
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
+ mTask, thumbnail -> {
+ mSnapshotView.setThumbnail(mTask, thumbnail);
+ });
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ mIconLoadRequest = iconCache.updateIconInBackground(mTask,
+ (task) -> {
+ setIcon(task.icon);
+ mDigitalWellBeingToast.initialize(mTask);
+ });
+ }
} else {
- mSnapshotView.setThumbnail(null, null);
- setIcon(null);
- // Reset the task thumbnail reference as well (it will be fetched from the cache or
- // reloaded next time we need it)
- mTask.thumbnail = null;
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mSnapshotView.setThumbnail(null, null);
+ // Reset the task thumbnail reference as well (it will be fetched from the cache or
+ // reloaded next time we need it)
+ mTask.thumbnail = null;
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ setIcon(null);
+ }
}
}
+ private boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
+ return (dataChange & flag) == flag;
+ }
+
private void cancelPendingLoadTasks() {
if (mThumbnailLoadRequest != null) {
mThumbnailLoadRequest.cancel();
@@ -573,6 +639,11 @@
}
private boolean showTaskMenu() {
+ if (getRecentsView().mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ // Don't show menu when selecting second split screen app
+ return true;
+ }
+
if (!getRecentsView().isClearAllHidden()) {
getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
} else {
@@ -605,27 +676,27 @@
PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
- int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
- int taskIconMargin = (int) getResources().getDimension(R.dimen.task_icon_top_margin);
- int taskIconHeight = (int) getResources().getDimension(R.dimen.task_thumbnail_icon_size);
- int iconTopMargin = taskIconMargin - taskIconHeight + thumbnailPadding;
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ int taskIconMargin = deviceProfile.overviewTaskMarginPx;
+ int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
switch (orientationHandler.getRotation()) {
case ROTATION_90:
iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
- iconParams.rightMargin = -thumbnailPadding;
+ iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2;
iconParams.leftMargin = 0;
iconParams.topMargin = snapshotParams.topMargin / 2;
break;
case ROTATION_180:
iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
- iconParams.bottomMargin = -thumbnailPadding;
+ iconParams.bottomMargin = -snapshotParams.topMargin;
iconParams.leftMargin = iconParams.rightMargin = 0;
- iconParams.topMargin = iconTopMargin;
+ iconParams.topMargin = taskIconMargin;
break;
case ROTATION_270:
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
- iconParams.leftMargin = -thumbnailPadding;
+ iconParams.leftMargin = -taskIconHeight - taskIconMargin / 2;
iconParams.rightMargin = 0;
iconParams.topMargin = snapshotParams.topMargin / 2;
break;
@@ -633,11 +704,15 @@
default:
iconParams.gravity = TOP | CENTER_HORIZONTAL;
iconParams.leftMargin = iconParams.rightMargin = 0;
- iconParams.topMargin = iconTopMargin;
+ iconParams.topMargin = taskIconMargin;
break;
}
+ mSnapshotView.setLayoutParams(snapshotParams);
+ iconParams.width = iconParams.height = taskIconHeight;
mIconView.setLayoutParams(iconParams);
mIconView.setRotation(orientationHandler.getDegreesRotated());
+ snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ mSnapshotView.setLayoutParams(snapshotParams);
if (mMenuView != null) {
mMenuView.onRotationChanged();
@@ -657,12 +732,14 @@
.getInterpolation(progress);
mIconView.setScaleX(scale);
mIconView.setScaleY(scale);
- if (mContextualChip != null && mContextualChipWrapper != null) {
+ if (mContextualChipWrapper != null && mContextualChipWrapper != null) {
mContextualChipWrapper.setAlpha(scale);
- mContextualChip.setScaleX(scale);
- mContextualChip.setScaleY(scale);
+ mContextualChipWrapper.setScaleX(Math.min(scale, comp(mModalness)));
+ mContextualChipWrapper.setScaleY(Math.min(scale, comp(mModalness)));
}
- mDigitalWellBeingToast.updateBannerOffset(1f - scale);
+ mDigitalWellBeingToast.updateBannerOffset(1f - scale,
+ mCurrentFullscreenParams.mCurrentDrawnInsets.top
+ + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
}
public void setIconScaleAnimStartProgress(float startProgress) {
@@ -715,7 +792,8 @@
@Override
public void onRecycle() {
- mFullscreenTranslationX = mAccumulatedTranslationX = mBoxTranslationY = 0f;
+ mFullscreenTranslationX = mGridTranslationX =
+ mGridTranslationY = mGridOffsetTranslationX = mBoxTranslationY = 0f;
resetViewTransforms();
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
@@ -725,7 +803,7 @@
}
@Override
- public void onPageScroll(ScrollState scrollState) {
+ public void onPageScroll(ScrollState scrollState, boolean gridEnabled) {
// Don't do anything if it's modal.
if (mModalness > 0) {
return;
@@ -756,20 +834,18 @@
}
if (view != null) {
mContextualChipWrapper = view;
- LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams layoutParams = new LayoutParams(((View) getParent()).getMeasuredWidth(),
LayoutParams.WRAP_CONTENT);
layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
int expectedChipHeight = getExpectedViewHeight(view);
float chipOffset = getResources().getDimension(R.dimen.chip_hint_vertical_offset);
layoutParams.bottomMargin = -expectedChipHeight - (int) chipOffset;
- mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
- mContextualChip.setScaleX(0f);
- mContextualChip.setScaleY(0f);
+ mContextualChipWrapper.setScaleX(0f);
+ mContextualChipWrapper.setScaleY(0f);
addView(view, getChildCount(), layoutParams);
- if (mContextualChip != null) {
- mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
- }
if (mContextualChipWrapper != null) {
+ float scale = comp(mModalness);
+ mContextualChipWrapper.animate().scaleX(scale).scaleY(scale).setDuration(50);
mChipTouchDelegate = new TransformingTouchDelegate(mContextualChipWrapper);
}
}
@@ -790,7 +866,6 @@
}
View oldContextualChipWrapper = mContextualChipWrapper;
mContextualChipWrapper = null;
- mContextualChip = null;
mChipTouchDelegate = null;
return oldContextualChipWrapper;
}
@@ -800,7 +875,7 @@
super.onLayout(changed, left, top, right, bottom);
if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? (right - left) : 0);
- setPivotY(0);
+ setPivotY(mSnapshotView.getTop());
} else {
setPivotX((right - left) * 0.5f);
setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
@@ -827,9 +902,28 @@
applyScale();
}
+ public float getFullscreenScale() {
+ return mFullscreenScale;
+ }
+
+ /**
+ * Moves TaskView between carousel and 2 row grid.
+ *
+ * @param gridProgress 0 = carousel; 1 = 2 row grid.
+ */
+ public void setGridProgress(float gridProgress) {
+ mGridProgress = gridProgress;
+ applyTranslationX();
+ applyTranslationY();
+ applyScale();
+ }
+
private void applyScale() {
- setScaleX(mFullscreenScale);
- setScaleY(mFullscreenScale);
+ float scale = 1;
+ float fullScreenProgress = EXAGGERATED_EASE.getInterpolation(mFullscreenProgress);
+ scale *= Utilities.mapRange(fullScreenProgress, 1f, mFullscreenScale);
+ setScaleX(scale);
+ setScaleY(scale);
}
private void setDismissTranslationX(float x) {
@@ -862,22 +956,59 @@
applyTranslationY();
}
- private void setFullscreenTranslationX(float fullscreenTranslationX) {
+ public void setFullscreenTranslationX(float fullscreenTranslationX) {
mFullscreenTranslationX = fullscreenTranslationX;
applyTranslationX();
}
- public float getFullscreenTranslationX() {
- return mFullscreenTranslationX;
- }
-
- public void setAccumulatedTranslationX(float accumulatedTranslationX) {
- mAccumulatedTranslationX = accumulatedTranslationX;
+ public void setGridTranslationX(float gridTranslationX) {
+ mGridTranslationX = gridTranslationX;
applyTranslationX();
}
- public float getAccumulatedTranslationX() {
- return mAccumulatedTranslationX;
+ public float getGridTranslationX() {
+ return mGridTranslationX;
+ }
+
+ public void setGridTranslationY(float gridTranslationY) {
+ mGridTranslationY = gridTranslationY;
+ applyTranslationY();
+ }
+
+ public float getGridTranslationY() {
+ return mGridTranslationY;
+ }
+
+ public void setGridOffsetTranslationX(float gridOffsetTranslationX) {
+ mGridOffsetTranslationX = gridOffsetTranslationX;
+ applyTranslationX();
+ }
+
+ public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
+ float scrollAdjustment = 0;
+ if (fullscreenEnabled) {
+ scrollAdjustment += mFullscreenTranslationX;
+ }
+ if (gridEnabled) {
+ scrollAdjustment += mGridTranslationX;
+ }
+ return scrollAdjustment;
+ }
+
+ public float getOffsetAdjustment(boolean fullscreenEnabled,boolean gridEnabled) {
+ float offsetAdjustment = getScrollAdjustment(fullscreenEnabled, gridEnabled);
+ if (gridEnabled) {
+ offsetAdjustment += mGridOffsetTranslationX;
+ }
+ return offsetAdjustment;
+ }
+
+ public float getSizeAdjustment(boolean fullscreenEnabled) {
+ float sizeAdjustment = 1;
+ if (fullscreenEnabled) {
+ sizeAdjustment *= mFullscreenScale;
+ }
+ return sizeAdjustment;
}
private void setBoxTranslationY(float boxTranslationY) {
@@ -886,23 +1017,28 @@
}
private void applyTranslationX() {
- setTranslationX(
- mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
- + mFullscreenTranslationX + mAccumulatedTranslationX);
+ setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
+ + getFullscreenTrans(mFullscreenTranslationX)
+ + getGridTrans(mGridTranslationX + mGridOffsetTranslationX));
}
private void applyTranslationY() {
setTranslationY(
mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY
- + mBoxTranslationY);
+ + getGridTrans(mGridTranslationY) + mBoxTranslationY);
}
- public FloatProperty<TaskView> getFillDismissGapTranslationProperty() {
+ private float getGridTrans(float endTranslation) {
+ float progress = ACCEL_DEACCEL.getInterpolation(mGridProgress);
+ return Utilities.mapRange(progress, 0, endTranslation);
+ }
+
+ public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
- public FloatProperty<TaskView> getDismissTaskTranslationProperty() {
+ public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() {
return getPagedOrientationHandler().getSecondaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
@@ -933,17 +1069,17 @@
private static final class TaskOutlineProvider extends ViewOutlineProvider {
- private final int mMarginTop;
+ private int mMarginTop;
private FullscreenDrawParams mFullscreenParams;
- TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
- mMarginTop = context.getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_top_margin);
+ TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin) {
+ mMarginTop = topMargin;
mFullscreenParams = fullscreenParams;
}
- public void setFullscreenParams(FullscreenDrawParams params) {
+ public void updateParams(FullscreenDrawParams params, int topMargin) {
mFullscreenParams = params;
+ mMarginTop = topMargin;
}
@Override
@@ -980,7 +1116,8 @@
getContext().getText(R.string.accessibility_close)));
final Context context = getContext();
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile())) {
info.addAction(s.createAccessibilityAction(context));
}
@@ -1012,7 +1149,8 @@
return true;
}
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile())) {
if (s.hasHandlerForAction(action)) {
s.onClick(this);
return true;
@@ -1030,7 +1168,7 @@
return getRecentsView().mOrientationState.getOrientationHandler();
}
- public void notifyTaskLaunchFailed(String tag) {
+ private void notifyTaskLaunchFailed(String tag) {
String msg = "Failed to launch task";
if (mTask != null) {
msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
@@ -1050,7 +1188,9 @@
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
getThumbnail().getTaskOverlay().setFullscreenProgress(progress);
- updateTaskScaling();
+ applyTranslationX();
+ applyTranslationY();
+ applyScale();
TaskThumbnailView thumbnail = getThumbnail();
updateCurrentFullscreenParams(thumbnail.getPreviewPositionHelper());
@@ -1062,7 +1202,9 @@
}
thumbnail.setFullscreenParams(mCurrentFullscreenParams);
- mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
+ mOutlineProvider.updateParams(
+ mCurrentFullscreenParams,
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
invalidateOutline();
}
@@ -1077,40 +1219,50 @@
previewPositionHelper);
}
+ /**
+ * Updates TaskView scaling and translation required to support variable width if enabled, while
+ * ensuring TaskView fits into screen in fullscreen.
+ */
void updateTaskSize() {
ViewGroup.LayoutParams params = getLayoutParams();
if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
- final int thumbnailPadding = (int) getResources().getDimension(
- R.dimen.task_thumbnail_top_margin);
+ final int thumbnailPadding =
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
int taskWidth = lastComputedTaskSize.width();
int taskHeight = lastComputedTaskSize.height();
- int boxLength = Math.max(taskWidth, taskHeight);
- float thumbnailRatio = mSnapshotView.getThumbnailRatio();
int expectedWidth;
int expectedHeight;
- if (thumbnailRatio > 1) {
- expectedWidth = boxLength;
- expectedHeight = (int) (boxLength / thumbnailRatio) + thumbnailPadding;
+ float thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio(
+ TaskView.CLIP_STATUS_AND_NAV_BARS) : 0f;
+ if (isRunningTask() || thumbnailRatio == 0f) {
+ expectedWidth = taskWidth;
+ expectedHeight = taskHeight + thumbnailPadding;
} else {
- expectedWidth = (int) (boxLength * thumbnailRatio);
- expectedHeight = boxLength + thumbnailPadding;
+ int boxLength = Math.max(taskWidth, taskHeight);
+ 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);
+ float fullscreenScale = 1f;
if (expectedWidth > taskWidth) {
// In full screen, expectedWidth should not be larger than taskWidth.
- mScaleAtFullscreen = taskWidth / (float) expectedWidth;
+ fullscreenScale = 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;
+ fullscreenScale = taskHeight / (float) (expectedHeight - thumbnailPadding);
}
+ setFullscreenScale(fullscreenScale);
if (params.width != expectedWidth || params.height != expectedHeight) {
params.width = expectedWidth;
@@ -1119,35 +1271,15 @@
}
} else {
setBoxTranslationY(0);
+ setFullscreenScale(1);
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);
@@ -1176,6 +1308,12 @@
mSnapshotView.setOverlayEnabled(overlayEnabled);
}
+ public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
+ RecentsView rv = getRecentsView();
+ getMenuView().close(false);
+ rv.initiateSplitSelect(this, splitPositionOption);
+ }
+
/**
* We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
*/
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 2e7e6e0..713fd07 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -42,6 +42,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.RemoteException;
+import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +56,7 @@
import com.android.launcher3.tapl.OverviewTask;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.quickstep.views.RecentsView;
@@ -172,9 +174,15 @@
protected <T> T getFromRecents(Function<RecentsActivity, T> f) {
if (!TestHelpers.isInLauncherProcess()) return null;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "getFromRecents");
+ }
Object[] result = new Object[1];
Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> {
RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "activity=" + activity);
+ }
if (activity == null) {
return false;
}
@@ -200,8 +208,13 @@
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
BaseOverview overview = mLauncher.getBackground().switchToOverview();
- executeOnRecents(recents ->
- assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3));
+ executeOnRecents(recents -> {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.GET_RECENTS_FAILED, "isLoading=" +
+ recents.<RecentsView>getOverviewPanel().isLoadingTasks());
+ }
+ assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3);
+ });
// Test flinging forward and backward.
overview.flingForward();
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 35383d2..67840d1 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -200,8 +200,8 @@
() -> launcher.getNavigationModel() == expectedMode, WAIT_TIME_MS, launcher);
Wait.atMost(() -> "Switching nav mode: "
- + launcher.getNavigationModeMismatchError(),
- () -> launcher.getNavigationModeMismatchError() == null,
+ + launcher.getNavigationModeMismatchError(false),
+ () -> launcher.getNavigationModeMismatchError(false) == null,
WAIT_TIME_MS, launcher);
AbstractLauncherUiTest.checkDetectedLeaks(launcher);
return true;
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 0fe5432..5ffe315 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -120,6 +120,7 @@
getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
// Test opening a task.
+ startTestActivity(2);
OverviewTask task = mLauncher.pressHome().switchToOverview().getCurrentTask();
assertNotNull("overview.getCurrentTask() returned null (1)", task);
assertNotNull("OverviewTask.open returned null", task.open());
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 72116eb..4ca1f59 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -49,7 +49,6 @@
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.Background;
@@ -58,6 +57,7 @@
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.Before;
diff --git a/res/drawable-hdpi/widget_resize_frame.9.png b/res/drawable-hdpi/widget_resize_frame.9.png
deleted file mode 100644
index a710932..0000000
--- a/res/drawable-hdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_shadow.9.png b/res/drawable-hdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 7cb5214..0000000
--- a/res/drawable-hdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_frame.9.png b/res/drawable-mdpi/widget_resize_frame.9.png
deleted file mode 100644
index 252482f..0000000
--- a/res/drawable-mdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_shadow.9.png b/res/drawable-mdpi/widget_resize_shadow.9.png
deleted file mode 100644
index a2010e2..0000000
--- a/res/drawable-mdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_frame.9.png b/res/drawable-xhdpi/widget_resize_frame.9.png
deleted file mode 100644
index 563c75d..0000000
--- a/res/drawable-xhdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_shadow.9.png b/res/drawable-xhdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 2b1ac05..0000000
--- a/res/drawable-xhdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_frame.9.png b/res/drawable-xxhdpi/widget_resize_frame.9.png
deleted file mode 100644
index ea527f4..0000000
--- a/res/drawable-xxhdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_shadow.9.png b/res/drawable-xxhdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 5412168..0000000
--- a/res/drawable-xxhdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_resize_frame.9.png b/res/drawable-xxxhdpi/widget_resize_frame.9.png
deleted file mode 100644
index 4644e9a..0000000
--- a/res/drawable-xxxhdpi/widget_resize_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_resize_shadow.9.png b/res/drawable-xxxhdpi/widget_resize_shadow.9.png
deleted file mode 100644
index 63cea84..0000000
--- a/res/drawable-xxxhdpi/widget_resize_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
new file mode 100644
index 0000000..04bde8f
--- /dev/null
+++ b/res/drawable/add_item_dialog_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="?android:attr/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/add_item_dialog_button_background.xml b/res/drawable/add_item_dialog_button_background.xml
new file mode 100644
index 0000000..1b4591f
--- /dev/null
+++ b/res/drawable/add_item_dialog_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<inset
+ android:insetLeft="@dimen/pin_widget_button_inset_horizontal"
+ android:insetRight="@dimen/pin_widget_button_inset_horizontal"
+ android:insetTop="@dimen/pin_widget_button_inset_vertical"
+ android:insetBottom="@dimen/pin_widget_button_inset_vertical"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:tint="?android:attr/colorAccent" android:shape="rectangle">
+ <corners android:radius="18dp" />
+ <solid android:color="#FFFFFF" />
+ <padding
+ android:left="@dimen/pin_widget_button_padding_horizontal"
+ android:top="@dimen/pin_widget_button_padding_vertical"
+ android:right="@dimen/pin_widget_button_padding_horizontal"
+ android:bottom="@dimen/pin_widget_button_padding_vertical" />
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_searchbox.xml b/res/drawable/bg_widgets_searchbox.xml
new file mode 100644
index 0000000..2a50a51
--- /dev/null
+++ b/res/drawable/bg_widgets_searchbox.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <solid android:color="?android:attr/textColorPrimaryInverse" />
+ <corners android:radius="24dp" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/gm_edit_24.xml b/res/drawable/gm_edit_24.xml
new file mode 100644
index 0000000..59a0dc2
--- /dev/null
+++ b/res/drawable/gm_edit_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0L3,16.82L3,21h4.18L20.41,7.77c0.79,-0.78 0.79,-2.05 0,-2.83zM6.41,19.06L5,19v-1.36l9.82,-9.82 1.41,1.41 -9.82,9.83z"/>
+</vector>
diff --git a/res/drawable/ic_expand_less.xml b/res/drawable/ic_expand_less.xml
index 8360cee..cc16083 100644
--- a/res/drawable/ic_expand_less.xml
+++ b/res/drawable/ic_expand_less.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?android:attr/textColorHint">
+ android:tint="?android:attr/textColorSecondary">
<path
android:fillColor="#FF000000"
android:pathData="M18.59,16.41L20,15l-8,-8 -8,8 1.41,1.41L12,9.83"/>
diff --git a/res/drawable/ic_expand_more.xml b/res/drawable/ic_expand_more.xml
index 49e24f6..ecbce7f 100644
--- a/res/drawable/ic_expand_more.xml
+++ b/res/drawable/ic_expand_more.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?android:attr/textColorHint">
+ android:tint="?android:attr/textColorSecondary">
<path
android:fillColor="#FF000000"
android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17"/>
diff --git a/res/drawable/ic_gm_close_24.xml b/res/drawable/ic_gm_close_24.xml
new file mode 100644
index 0000000..2c9c932
--- /dev/null
+++ b/res/drawable/ic_gm_close_24.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/textColorTertiary"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_split_screen.xml b/res/drawable/ic_split_screen.xml
similarity index 100%
rename from quickstep/res/drawable/ic_split_screen.xml
rename to res/drawable/ic_split_screen.xml
diff --git a/res/drawable/middle_item_primary.xml b/res/drawable/middle_item_primary.xml
new file mode 100644
index 0000000..0c04ea1
--- /dev/null
+++ b/res/drawable/middle_item_primary.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/popupColorPrimary"/>
+ <corners android:radius="@dimen/popup_smaller_radius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/single_item_primary.xml b/res/drawable/single_item_primary.xml
new file mode 100644
index 0000000..1c0889b
--- /dev/null
+++ b/res/drawable/single_item_primary.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/popupColorPrimary"/>
+ <corners android:radius="@dimen/popup_single_item_radius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/single_item_secondary.xml b/res/drawable/single_item_secondary.xml
new file mode 100644
index 0000000..4edc481
--- /dev/null
+++ b/res/drawable/single_item_secondary.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/popupColorSecondary"/>
+ <corners android:radius="@dimen/popup_single_item_radius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/widget_reconfigure_button_frame.xml b/res/drawable/widget_reconfigure_button_frame.xml
new file mode 100644
index 0000000..37d93ad
--- /dev/null
+++ b/res/drawable/widget_reconfigure_button_frame.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:width="@dimen/widget_reconfigure_button_size"
+ android:height="@dimen/widget_reconfigure_button_size">
+ <shape
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorAccent" />
+ <corners android:radius="@dimen/widget_reconfigure_button_corner_radius" />
+ </shape>
+ </item>
+ <item
+ android:gravity="center"
+ android:padding="@dimen/widget_reconfigure_button_padding"
+ android:drawable="@drawable/gm_edit_24"
+ android:color="?android:attr/colorPrimary" />
+</layer-list>
diff --git a/res/drawable/widget_resize_frame.xml b/res/drawable/widget_resize_frame.xml
new file mode 100644
index 0000000..d157f5d
--- /dev/null
+++ b/res/drawable/widget_resize_frame.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@android:color/transparent" />
+ <corners android:radius="@android:dimen/system_app_widget_background_radius" />
+ <stroke android:width="2dp" android:color="?android:attr/colorAccent" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_bottom_ripple.xml b/res/drawable/widgets_list_bottom_ripple.xml
new file mode 100644
index 0000000..3a26091
--- /dev/null
+++ b/res/drawable/widgets_list_bottom_ripple.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_middle_ripple.xml b/res/drawable/widgets_list_middle_ripple.xml
new file mode 100644
index 0000000..da025d7
--- /dev/null
+++ b/res/drawable/widgets_list_middle_ripple.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:topRightRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_single_item_ripple.xml b/res/drawable/widgets_list_single_item_ripple.xml
new file mode 100644
index 0000000..b8b6f42
--- /dev/null
+++ b/res/drawable/widgets_list_single_item_ripple.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_top_ripple.xml b/res/drawable/widgets_list_top_ripple.xml
new file mode 100644
index 0000000..6efc3e1
--- /dev/null
+++ b/res/drawable/widgets_list_top_ripple.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackground" />
+ <corners
+ android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+ android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+ android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index 830255b..d5e7333 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -17,70 +17,51 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/add_item_confirmation"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:padding="24dp"
android:orientation="vertical">
- <ScrollView
+
+ <TextView
+ style="@style/TextHeadline"
+ android:id="@+id/widget_appName"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:maxLines="1" />
+
+ <include layout="@layout/widget_cell"
+ android:id="@+id/widget_cell"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:clipToPadding="false">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="20dp"
- android:paddingLeft="24dp"
- android:paddingRight="24dp"
- android:paddingTop="4dp"
- android:text="@string/add_item_request_drag_hint" />
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?android:attr/colorPrimaryDark"
- android:theme="?attr/widgetsTheme">
-
- <com.android.launcher3.dragndrop.LivePreviewWidgetCell
- android:id="@+id/widget_cell"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_weight="1"
- android:background="?android:attr/colorPrimaryDark"
- android:focusable="true"
- android:gravity="center_horizontal"
- android:orientation="vertical" >
-
- <include layout="@layout/widget_cell_content" />
-
- </com.android.launcher3.dragndrop.LivePreviewWidgetCell>
- </FrameLayout>
- </LinearLayout>
- </ScrollView>
+ android:layout_marginVertical="16dp" />
<LinearLayout
- style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
- android:paddingBottom="4dp"
- android:paddingEnd="12dp"
- android:paddingStart="12dp"
- android:paddingTop="4dp" >
+ android:padding="8dp"
+ android:orientation="horizontal">
<Button
- style="?android:attr/buttonBarButtonStyle"
+ style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onCancelClick"
android:text="@android:string/cancel" />
+
+ <Space
+ android:layout_width="4dp"
+ android:layout_height="wrap_content" />
+
<Button
- style="?android:attr/buttonBarButtonStyle"
+ style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onPlaceAutomaticallyClick"
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 0041c9a..24d764e 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -40,39 +40,10 @@
<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
- android:id="@id/search_container_all_apps"
layout="@layout/search_container_all_apps"/>
<include layout="@layout/all_apps_fast_scroller" />
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
index 12561b6..53db5ed 100644
--- a/res/layout/app_widget_resize_frame.xml
+++ b/res/layout/app_widget_resize_frame.xml
@@ -18,50 +18,72 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@drawable/widget_resize_shadow"
- android:foreground="@drawable/widget_resize_frame"
- android:foregroundTint="?attr/workspaceTextColor"
android:padding="0dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
+ <!-- Frame -->
+ <ImageView
+ android:id="@+id/widget_resize_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:layout_margin="@dimen/resize_frame_margin"
+ android:src="@drawable/widget_resize_frame" />
+
<!-- Left -->
<ImageView
+ android:id="@+id/widget_resize_left_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginLeft="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
- android:tint="?attr/workspaceTextColor" />
+ android:tint="?android:attr/colorAccent" />
<!-- Top -->
<ImageView
+ android:id="@+id/widget_resize_top_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
- android:tint="?attr/workspaceTextColor" />
+ android:tint="?android:attr/colorAccent" />
<!-- Right -->
<ImageView
+ android:id="@+id/widget_resize_right_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:layout_marginRight="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
- android:tint="?attr/workspaceTextColor" />
+ android:tint="?android:attr/colorAccent" />
<!-- Bottom -->
<ImageView
+ android:id="@+id/widget_resize_bottom_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
- android:tint="?attr/workspaceTextColor" />
+ android:tint="?android:attr/colorAccent" />
+
+ <ImageButton
+ android:id="@+id/widget_reconfigure_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/widget_reconfigure_button_padding"
+ android:layout_gravity="bottom|end"
+ android:layout_marginBottom="@dimen/widget_reconfigure_button_margin"
+ android:layout_marginEnd="@dimen/widget_reconfigure_button_margin"
+ android:src="@drawable/widget_reconfigure_button_frame"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone" />
</FrameLayout>
</com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 840a8b7..0d11b50 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -19,6 +19,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="@dimen/bg_popup_item_height"
+ android:background="@drawable/middle_item_primary"
android:theme="@style/PopupItem" >
<com.android.launcher3.shortcuts.DeepShortcutTextView
@@ -31,6 +32,8 @@
android:paddingEnd="@dimen/popup_padding_end"
android:drawableEnd="@drawable/ic_drag_handle"
android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
+ android:singleLine="true"
+ android:ellipsize="end"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
launcher:layoutHorizontal="true"
@@ -45,12 +48,4 @@
android:layout_gravity="start|center_vertical"
android:background="@drawable/ic_deepshortcut_placeholder"/>
- <View
- android:id="@+id/divider"
- android:layout_width="@dimen/deep_shortcuts_divider_width"
- android:layout_height="@dimen/popup_item_divider_height"
- android:layout_gravity="end|bottom"
- android:visibility="gone"
- android:background="?attr/popupColorTertiary" />
-
</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml
index 32a5419..1cdee08 100644
--- a/res/layout/folder_application.xml
+++ b/res/layout/folder_application.xml
@@ -21,4 +21,5 @@
android:textColor="?attr/folderTextColor"
android:includeFontPadding="false"
android:hapticFeedbackEnabled="false"
- launcher:iconDisplay="folder" />
+ launcher:iconDisplay="folder"
+ launcher:centerVertically="true" />
diff --git a/res/layout/longpress_options_menu.xml b/res/layout/longpress_options_menu.xml
index 20bb5b8..3898365 100644
--- a/res/layout/longpress_options_menu.xml
+++ b/res/layout/longpress_options_menu.xml
@@ -18,7 +18,6 @@
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="?attr/popupColorPrimary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml
index d01be01..147aa30 100644
--- a/res/layout/notification_content.xml
+++ b/res/layout/notification_content.xml
@@ -96,14 +96,6 @@
</com.android.launcher3.notification.NotificationMainView>
- <!-- Divider -->
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="@dimen/popup_item_divider_height"
- android:layout_below="@id/main_view"
- android:background="?attr/popupColorTertiary" />
-
<!-- Footer -->
<com.android.launcher3.notification.NotificationFooterLayout
android:id="@+id/footer"
diff --git a/res/layout/notification_gutter.xml b/res/layout/notification_gutter.xml
index 10e7f7d..9a3e55a 100644
--- a/res/layout/notification_gutter.xml
+++ b/res/layout/notification_gutter.xml
@@ -16,6 +16,5 @@
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="4dp"
- android:layout_marginTop="4dp"
- android:background="@drawable/bg_notification_content" />
\ No newline at end of file
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/popup_margin" />
\ No newline at end of file
diff --git a/res/layout/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/popup_container.xml b/res/layout/popup_container.xml
index c737407..04822fd 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -19,8 +19,15 @@
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="?attr/popupColorPrimary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical" />
\ No newline at end of file
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/notification_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:background="?attr/popupColorPrimary"
+ android:orientation="vertical"/>
+</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
diff --git a/res/layout/search_container_hotseat.xml b/res/layout/search_container_hotseat.xml
new file mode 100644
index 0000000..8f12ca0
--- /dev/null
+++ b/res/layout/search_container_hotseat.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="0dp" />
\ 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/system_shortcut.xml b/res/layout/system_shortcut.xml
index c620e2a..9f45f30 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -19,6 +19,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="@dimen/bg_popup_item_height"
+ android:background="@drawable/middle_item_primary"
android:theme="@style/PopupItem" >
<com.android.launcher3.BubbleTextView
@@ -30,7 +31,8 @@
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
android:paddingEnd="@dimen/popup_padding_end"
android:textSize="14sp"
- android:maxLines="1"
+ android:singleLine="true"
+ android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
launcher:iconDisplay="shortcut_popup"
launcher:layoutHorizontal="true"
@@ -44,12 +46,4 @@
android:layout_gravity="start|center_vertical"
android:backgroundTint="?android:attr/textColorTertiary"/>
- <View
- android:id="@+id/divider"
- android:layout_width="@dimen/deep_shortcuts_divider_width"
- android:layout_height="@dimen/popup_item_divider_height"
- android:layout_gravity="end|bottom"
- android:visibility="gone"
- android:background="?attr/popupColorTertiary" />
-
</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
index a340f4f..f992248 100644
--- a/res/layout/system_shortcut_icons.xml
+++ b/res/layout/system_shortcut_icons.xml
@@ -21,7 +21,7 @@
android:layout_height="@dimen/system_shortcut_header_height"
android:orientation="horizontal"
android:gravity="end|center_vertical"
- android:background="?attr/popupColorSecondary"
+ android:background="@drawable/single_item_secondary"
android:clipToPadding="true">
<Space android:layout_width="0dp"
diff --git a/res/layout/taskbar_view.xml b/res/layout/taskbar_view.xml
new file mode 100644
index 0000000..96ae43d
--- /dev/null
+++ b/res/layout/taskbar_view.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<Space
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:visibility="gone" />
\ No newline at end of file
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index c230dad..8b18857 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -19,7 +19,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/round_rect_folder"
- android:elevation="5dp"
android:orientation="vertical" >
<com.android.launcher3.folder.FolderPagedView
@@ -27,15 +26,12 @@
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:paddingTop="16dp"
launcher:pageIndicator="@+id/folder_page_indicator" />
<LinearLayout
android:id="@+id/folder_footer"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="48dp"
android:clipChildren="false"
android:orientation="horizontal"
android:paddingLeft="12dp"
@@ -53,13 +49,10 @@
android:gravity="center_horizontal"
android:hint="@string/folder_hint_text"
android:imeOptions="flagNoExtractUi"
- android:paddingBottom="@dimen/folder_label_padding_bottom"
- android:paddingTop="@dimen/folder_label_padding_top"
android:singleLine="true"
android:textColor="?attr/folderTextColor"
android:textColorHighlight="?android:attr/colorControlHighlight"
- android:textColorHint="?attr/folderHintColor"
- android:textSize="@dimen/folder_label_text_size" />
+ android:textColorHint="?attr/folderHintColor"/>
<com.android.launcher3.pageindicators.PageIndicatorDots
android:id="@+id/folder_page_indicator"
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 148a99b..55dd1de 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -15,12 +15,13 @@
-->
<com.android.launcher3.widget.WidgetCell
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
+ android:paddingVertical="@dimen/widget_cell_vertical_padding"
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" />
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 64f2362..30bd8b1 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -17,47 +17,50 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/widget_preview_label_vertical_padding"
- android:paddingBottom="@dimen/widget_preview_label_vertical_padding"
- android:paddingLeft="@dimen/widget_preview_label_horizontal_padding"
- android:paddingRight="@dimen/widget_preview_label_horizontal_padding"
- android:orientation="horizontal">
-
- <!-- The name of the widget. -->
- <TextView
- android:id="@+id/widget_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:gravity="start"
- android:singleLine="true"
- android:maxLines="1"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="14sp" />
-
- <!-- The original dimensions of the widget (can't be the same text as above due to different
- style. -->
- <TextView
- android:id="@+id/widget_dims"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="5dp"
- android:layout_marginLeft="5dp"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="14sp"
- android:alpha="0.8" />
- </LinearLayout>
-
<!-- The image of the widget. This view does not support padding. Any placement adjustment
- should be done using margins. -->
+ should be done using margins. Width & height are set at runtime after scaling the preview
+ image. -->
<com.android.launcher3.widget.WidgetImageView
android:id="@+id/widget_preview"
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:importantForAccessibility="no"
+ android:layout_marginVertical="8dp" />
+
+ <!-- The name of the widget. -->
+ <TextView
+ android:id="@+id/widget_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:gravity="center_horizontal"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/widget_cell_font_size" />
+
+ <!-- The original dimensions of the widget -->
+ <TextView
+ android:id="@+id/widget_dims"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="@dimen/widget_cell_font_size"
+ android:alpha="0.7" />
+
+ <TextView
+ android:id="@+id/widget_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textSize="@dimen/widget_cell_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="2"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:alpha="0.7" />
+
</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index 3fdfc96..8002d1d 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -19,36 +19,12 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingTop="28dp"
+ android:paddingTop="16dp"
android:background="@drawable/top_round_rect_primary"
android:elevation="@dimen/deep_shortcuts_elevation"
android:layout_gravity="bottom"
android:theme="?attr/widgetsTheme">
- <TextView
- style="@style/TextHeadline"
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"/>
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:paddingTop="4dp"
- android:fontFamily="sans-serif"
- android:textColor="?android:attr/textColorTertiary"
- android:textSize="14sp"
- android:text="@string/long_press_widget_to_add"/>
-
- <include layout="@layout/widgets_scroll_container"
- android:id="@+id/widgets"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="45dp"
- android:layout_marginBottom="40dp"/>
+ <include layout="@layout/widgets_bottom_sheet_content" />
</com.android.launcher3.widget.WidgetsBottomSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
new file mode 100644
index 0000000..a9d523a
--- /dev/null
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <View
+ android:layout_width="48dp"
+ android:layout_height="2dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="16dp"
+ android:background="?android:attr/textColorSecondary"/>
+ <TextView
+ style="@style/TextHeadline"
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"/>
+
+ <ScrollView
+ android:id="@+id/widgets_table_scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fadeScrollbars="false"
+ android:layout_marginVertical="16dp">
+ <include layout="@layout/widgets_table_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal" />
+ </ScrollView>
+</merge>
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 6e7cf0f..172284b 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -15,6 +15,7 @@
-->
<com.android.launcher3.widget.picker.WidgetsFullSheet
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
@@ -24,14 +25,16 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?android:attr/colorPrimary"
+ android:background="?android:attr/colorBackgroundFloating"
android:elevation="4dp">
- <com.android.launcher3.widget.picker.WidgetsRecyclerView
- android:id="@+id/widgets_list_view"
+ <TextView
+ android:id="@+id/no_widgets_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipToPadding="false" />
+ android:gravity="center"
+ android:visibility="gone"
+ tools:text="No widgets available" />
<!-- Fast scroller popup -->
<TextView
@@ -48,5 +51,13 @@
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/fastscroll_end_margin" />
+
+ <com.android.launcher3.widget.picker.WidgetsRecyclerView
+ android:id="@+id/search_widgets_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:clipToPadding="false" />
+
</com.android.launcher3.views.TopRoundedCornerView>
</com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
new file mode 100644
index 0000000..ae877d4
--- /dev/null
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -0,0 +1,45 @@
+<?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">
+
+ <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>
+
+ <include layout="@layout/personal_work_tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp" />
+</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
new file mode 100644
index 0000000..fbe559c
--- /dev/null
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -0,0 +1,21 @@
+<?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.WidgetsRecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ 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..e5df175
--- /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:paddingHorizontal="16dp"
+ android:layout_marginBottom="16dp"
+ android:orientation="vertical">
+ <View
+ android:id="@+id/collapse_handle"
+ android:layout_width="48dp"
+ android:layout_height="2dp"
+ android:layout_marginTop="16dp"
+ android:elevation="2dp"
+ android:layout_gravity="center_horizontal"
+ android:background="?android:attr/textColorSecondary"/>
+ <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:textColor="?android:attr/textColorSecondary"
+ android:text="@string/widget_button_text"/>
+ <include layout="@layout/widgets_search_bar"/>
+
+ <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+ android:id="@+id/recommended_widget_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:visibility="gone" />
+</LinearLayout>
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index faff10c..598041c 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -18,8 +18,10 @@
android:id="@+id/widgets_list_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/selectableItemBackground"
- android:paddingVertical="20dp"
+ android:layout_marginHorizontal="8dp"
+ android:background="@drawable/widgets_list_middle_ripple"
+ android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"
+ android:paddingVertical="@dimen/widget_list_header_view_vertical_padding"
android:orientation="horizontal">
<ImageView
@@ -52,7 +54,11 @@
android:id="@+id/app_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="n widgets" />
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:alpha="0.7"
+ tools:text="m widgets, n shortcuts" />
</LinearLayout>
@@ -66,6 +72,7 @@
android:layout_gravity="center_vertical"
android:layout_alignParentEnd="true"
android:clickable="false"
+ android:importantForAccessibility="no"
android:button="@drawable/widgets_tray_expand_button"/>
</com.android.launcher3.widget.picker.WidgetsListHeader>
\ No newline at end of file
diff --git a/res/layout/widgets_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/res/layout/widgets_scroll_container.xml b/res/layout/widgets_scroll_container.xml
deleted file mode 100644
index fc509d1..0000000
--- a/res/layout/widgets_scroll_container.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<HorizontalScrollView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/widgets_scroll_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?android:attr/colorPrimaryDark"
- android:scrollbars="none">
- <LinearLayout
- android:id="@+id/widgets_cell_list"
- style="@style/TextTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingStart="0dp"
- android:paddingEnd="0dp"
- android:orientation="horizontal"
- android:showDividers="none"/>
-</HorizontalScrollView>
\ No newline at end of file
diff --git a/res/layout/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
new file mode 100644
index 0000000..e3836df
--- /dev/null
+++ b/res/layout/widgets_search_bar.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widgets_search_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="16dp"
+ android:background="@drawable/bg_widgets_searchbox"
+ android:elevation="2dp">
+
+ <com.android.launcher3.ExtendedEditText
+ android:id="@+id/widgets_search_bar_edit_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="12dp"
+ android:drawablePadding="8dp"
+ android:drawableStart="@drawable/ic_allapps_search"
+ android:background="@null"
+ android:hint="@string/widgets_full_sheet_search_bar_hint"
+ android:maxLines="1"
+ android:layout_weight="1"
+ android:inputType="text"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textColorHint="?android:attr/textColorTertiary"/>
+
+ <ImageButton
+ android:id="@+id/widgets_search_cancel_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:padding="8dp"
+ android:src="@drawable/ic_gm_close_24"
+ android:background="?android:selectableItemBackground"
+ android:layout_gravity="center"
+ android:contentDescription="@string/widgets_full_sheet_cancel_button_description"
+ android:visibility="gone"/>
+</com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar>
\ No newline at end of file
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
new file mode 100644
index 0000000..0b5f0b9
--- /dev/null
+++ b/res/layout/widgets_table_container.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<TableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widgets_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="8dp"
+ android:background="@drawable/widgets_list_middle_ripple"
+ android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"/>
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index 510e1f4..07a5096 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -21,6 +21,8 @@
<style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
+ <item name="android:windowBackground">@drawable/add_item_dialog_background</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
\ No newline at end of file
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
index 1f401c4..ec07591 100644
--- a/res/values-sw720dp/config.xml
+++ b/res/values-sw720dp/config.xml
@@ -1,5 +1,4 @@
<resources>
- <bool name="config_largeHeap">true</bool>
<!-- All Apps & Widgets -->
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
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
index 6baf39e..24aac10 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -17,17 +17,17 @@
*/
-->
<resources>
- <color name="popup_color_primary_light">@android:color/system_primary_50</color>
- <color name="popup_color_secondary_light">@android:color/system_primary_100</color>
- <color name="popup_color_tertiary_light">@android:color/system_primary_300</color>
- <color name="popup_color_primary_dark">@android:color/system_primary_800</color>
- <color name="popup_color_secondary_dark">@android:color/system_primary_900</color>
- <color name="popup_color_tertiary_dark">@android:color/system_primary_700</color>
+ <color name="popup_color_primary_light">@android:color/system_neutral1_50</color>
+ <color name="popup_color_secondary_light">@android:color/system_neutral2_100</color>
+ <color name="popup_color_tertiary_light">@android:color/system_neutral2_300</color>
+ <color name="popup_color_primary_dark">@android:color/system_neutral1_800</color>
+ <color name="popup_color_secondary_dark">@android:color/system_neutral1_900</color>
+ <color name="popup_color_tertiary_dark">@android:color/system_neutral2_700</color>
- <color name="workspace_text_color_light">@android:color/system_primary_50</color>
- <color name="workspace_text_color_dark">@android:color/system_primary_900</color>
+ <color name="workspace_text_color_light">@android:color/system_neutral1_50</color>
+ <color name="workspace_text_color_dark">@android:color/system_neutral1_900</color>
- <color name="text_color_primary_dark">@android:color/system_primary_50</color>
- <color name="text_color_secondary_dark">@android:color/system_primary_200</color>
- <color name="text_color_tertiary_dark">@android:color/system_primary_400</color>
+ <color name="text_color_primary_dark">@android:color/system_neutral1_50</color>
+ <color name="text_color_secondary_dark">@android:color/system_neutral2_200</color>
+ <color name="text_color_tertiary_dark">@android:color/system_neutral2_400</color>
</resources>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 587df6d..f4a729f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -133,6 +133,9 @@
<attr name="numAllAppsColumns" format="integer" />
<attr name="defaultLayoutId" format="reference" />
<attr name="demoModeLayoutId" format="reference" />
+ <attr name="isScalable" format="boolean" />
+ <attr name="devicePaddingId" format="reference" />
+
</declare-styleable>
<declare-styleable name="DevicePadding">
@@ -150,6 +153,12 @@
<attr name="minWidthDps" format="float" />
<attr name="minHeightDps" format="float" />
+ <!-- These min cell values are only used if GridDisplayOption#isScalable is true-->
+ <attr name="minCellHeightDps" format="float" />
+ <attr name="minCellWidthDps" format="float" />
+
+ <attr name="borderSpacingDps" format="float" />
+
<attr name="iconImageSize" format="float" />
<!-- landscapeIconSize defaults to iconSize, if not specified -->
<attr name="landscapeIconSize" format="float" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 41d1a12..65e2ab3 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -24,7 +24,7 @@
<!-- AllApps & Launcher transitions -->
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
- <integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
+ <integer name="config_workspaceSpringLoadShrinkPercentage">85</integer>
<!-- The duration of the animation from search hint to text entry -->
<integer name="config_searchHintAnimationDuration">50</integer>
@@ -61,7 +61,6 @@
<!-- Various classes overriden by projects/build flavors. -->
<string name="folder_name_provider_class" translatable="false"></string>
<string name="stats_log_manager_class" translatable="false"></string>
- <string name="app_transition_manager_class" translatable="false"></string>
<string name="instant_app_resolver_class" translatable="false"></string>
<string name="main_process_initializer_class" translatable="false"></string>
<string name="app_launch_tracker_class" translatable="false"></string>
@@ -90,6 +89,7 @@
<string name="wallpaper_picker_package" translatable="false"></string>
<string name="calendar_component_name" translatable="false"></string>
<string name="clock_component_name" translatable="false"></string>
+ <string name="local_colors_extraction_class" translatable="false"></string>
<!-- Accessibility actions -->
<item type="id" name="action_remove" />
@@ -188,4 +188,8 @@
</string-array>
<string-array name="filtered_components" ></string-array>
+
+ <!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
+ <string name="color_generator_class" translatable="false"/>
+
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 84dfca5..1fccdf3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,6 +20,7 @@
<!-- Dynamic Grid -->
<dimen name="dynamic_grid_edge_margin">8dp</dimen>
+ <dimen name="dynamic_grid_left_right_margin">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>
@@ -27,7 +28,8 @@
<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>
+
+ <dimen name="two_panel_home_side_padding">18dp</dimen>
<!-- Hotseat -->
<dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
@@ -37,6 +39,9 @@
<dimen name="dynamic_grid_hotseat_extra_vertical_size">34dp</dimen>
<dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
+ <!-- Scalable Grid -->
+ <dimen name="scalable_grid_left_right_margin">22dp</dimen>
+
<!-- Workspace page indicator -->
<dimen name="workspace_page_indicator_height">24dp</dimen>
<dimen name="workspace_page_indicator_line_height">1dp</dimen>
@@ -49,6 +54,13 @@
<!-- App Widget resize frame -->
<dimen name="widget_handle_margin">13dp</dimen>
<dimen name="resize_frame_background_padding">24dp</dimen>
+ <dimen name="resize_frame_margin">22dp</dimen>
+
+ <!-- App widget reconfigure button -->
+ <dimen name="widget_reconfigure_button_corner_radius">14dp</dimen>
+ <dimen name="widget_reconfigure_button_padding">6dp</dimen>
+ <dimen name="widget_reconfigure_button_margin">32dp</dimen>
+ <dimen name="widget_reconfigure_button_size">36dp</dimen>
<!-- Fast scroll -->
<dimen name="fastscroll_track_min_width">6dp</dimen>
@@ -90,7 +102,6 @@
<!-- The size of corner radius of the arrow in the arrow toast. -->
<dimen name="arrow_toast_corner_radius">2dp</dimen>
-
<!-- Search bar in All Apps -->
<dimen name="all_apps_header_max_elevation">3dp</dimen>
<dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
@@ -102,8 +113,16 @@
<dimen name="work_profile_footer_text_size">16sp</dimen>
<!-- Widget tray -->
- <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
- <dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
+ <dimen name="widget_cell_vertical_padding">8dp</dimen>
+ <dimen name="widget_cell_horizontal_padding">16dp</dimen>
+ <dimen name="widget_cell_font_size">14sp</dimen>
+
+
+ <dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
+ <dimen name="widget_list_content_corner_radius">4dp</dimen>
+
+ <dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
+ <dimen name="widget_list_entry_bottom_margin">2dp</dimen>
<dimen name="widget_preview_shadow_blur">0.5dp</dimen>
<dimen name="widget_preview_key_shadow_distance">1dp</dimen>
@@ -125,6 +144,12 @@
<dimen name="shortcut_preview_padding_right">0dp</dimen>
<dimen name="shortcut_preview_padding_top">0dp</dimen>
+<!-- Pin widget dialog -->
+ <dimen name="pin_widget_button_padding_horizontal">8dp</dimen>
+ <dimen name="pin_widget_button_padding_vertical">4dp</dimen>
+ <dimen name="pin_widget_button_inset_horizontal">4dp</dimen>
+ <dimen name="pin_widget_button_inset_vertical">6dp</dimen>
+
<!-- Dragging -->
<!-- Drag padding to add to the bottom of drop targets -->
<dimen name="drop_target_drag_padding">14dp</dimen>
@@ -148,10 +173,12 @@
<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">12dp</dimen>
- <dimen name="folder_label_padding_bottom">12dp</dimen>
- <dimen name="folder_label_text_size">16sp</dimen>
+ <!-- label text size = workspace text size multiplied by this scale -->
+ <dimen name="folder_label_text_scale">1.14</dimen>
+ <dimen name="folder_label_height">48dp</dimen>
+
+ <dimen name="folder_content_padding_left_right">8dp</dimen>
+ <dimen name="folder_content_padding_top">16dp</dimen>
<!-- Sizes for managed profile badges -->
<dimen name="profile_badge_size">24dp</dimen>
@@ -169,35 +196,33 @@
<dimen name="pending_widget_elevation">2dp</dimen>
<!-- Deep shortcuts -->
- <dimen name="deep_shortcuts_elevation">9dp</dimen>
- <dimen name="bg_popup_item_width">260dp</dimen>
+ <dimen name="deep_shortcuts_elevation">0dp</dimen>
+ <dimen name="bg_popup_item_width">216dp</dimen>
<dimen name="bg_popup_item_height">56dp</dimen>
- <dimen name="bg_popup_item_condensed_height">48dp</dimen>
<dimen name="pre_drag_view_scale">6dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
- <dimen name="deep_shortcut_icon_size">36dp</dimen>
- <dimen name="deep_shortcut_drawable_padding">8dp</dimen>
+ <dimen name="deep_shortcut_icon_size">32dp</dimen>
+ <dimen name="popup_margin">2dp</dimen>
+ <dimen name="popup_single_item_radius">100dp</dimen>
+ <dimen name="popup_smaller_radius">4dp</dimen>
+ <dimen name="deep_shortcut_drawable_padding">12dp</dimen>
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
<dimen name="popup_padding_start">10dp</dimen>
<dimen name="popup_padding_end">16dp</dimen>
<dimen name="popup_vertical_padding">4dp</dimen>
- <dimen name="popup_arrow_width">10dp</dimen>
- <dimen name="popup_arrow_height">8dp</dimen>
- <dimen name="popup_arrow_vertical_offset">-2dp</dimen>
+ <dimen name="popup_arrow_width">12dp</dimen>
+ <dimen name="popup_arrow_height">10dp</dimen>
+ <dimen name="popup_arrow_vertical_offset">-1dp</dimen>
<!-- popup_padding_start + deep_shortcut_icon_size / 2 -->
- <dimen name="popup_arrow_horizontal_center_start">28dp</dimen>
- <!-- popup_padding_end + deep_shortcut_drag_handle_size / 2 -->
- <dimen name="popup_arrow_horizontal_center_end">24dp</dimen>
+ <dimen name="popup_arrow_horizontal_center_offset">26dp</dimen>
<dimen name="popup_arrow_corner_radius">2dp</dimen>
<!-- popup_padding_start + icon_size + 10dp -->
- <dimen name="deep_shortcuts_text_padding_start">56dp</dimen>
- <!-- popup_item_width - deep_shortcuts_text_padding_start -->
- <dimen name="deep_shortcuts_divider_width">164dp</dimen>
+ <dimen name="deep_shortcuts_text_padding_start">52dp</dimen>
<dimen name="system_shortcut_icon_size">24dp</dimen>
- <!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
+ <!-- popup_arrow_horizontal_center_offset - system_shortcut_icon_size / 2 -->
<dimen name="system_shortcut_margin_start">16dp</dimen>
- <dimen name="system_shortcut_header_height">48dp</dimen>
+ <dimen name="system_shortcut_header_height">56dp</dimen>
<dimen name="system_shortcut_header_icon_touch_size">48dp</dimen>
<!-- (touch_size - icon_size) / 2 -->
<dimen name="system_shortcut_header_icon_padding">12dp</dimen>
@@ -253,10 +278,23 @@
<!-- 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>
+
+ <!-- Size of the maximum radius for the enforced rounded rectangles. -->
+ <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
+
+<!-- Overview placeholder to compile in Launcer3 without Quickstep -->
+ <dimen name="task_thumbnail_icon_size">0dp</dimen>
+ <dimen name="task_thumbnail_icon_size_grid">0dp</dimen>
+ <dimen name="overview_task_margin">0dp</dimen>
</resources>
diff --git a/res/values/id.xml b/res/values/id.xml
new file mode 100644
index 0000000..39c49bd
--- /dev/null
+++ b/res/values/id.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <item type="id" name="view_type_widgets_list" />
+ <item type="id" name="view_type_widgets_header" />
+ <item type="id" name="view_type_widgets_search_header" />
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5a9def7..e5e5db3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,11 +38,18 @@
<!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
<string name="home_screen">Home</string>
+ <!-- Options for recent tasks -->
+ <!-- Title for an option to enter split screen mode for a given app -->
+ <string name="recent_task_option_split_screen">Split screen</string>
+ <string translatable="false" name="split_screen_position_top">Pin to top</string>
+ <string translatable="false" name="split_screen_position_left">Pin to left</string>
+ <string translatable="false" name="split_screen_position_right">Pin to right</string>
+
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
- <string name="long_press_widget_to_add">Touch & hold to pick up a widget.</string>
+ <string name="long_press_widget_to_add">Touch & hold to move a widget.</string>
<!-- Accessibility spoken hint message in widget picker, which allows user to add a widget. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
- <string name="long_accessible_way_to_add">Double-tap & hold to pick up a widget or use custom actions.</string>
+ <string name="long_accessible_way_to_add">Double-tap & hold to move a widget or use custom actions.</string>
<!-- The format string for the dimensions of a widget in the drawer -->
<!-- There is a special version of this format string for Farsi -->
<string name="widget_dims_format">%1$d \u00d7 %2$d</string>
@@ -52,17 +59,41 @@
<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>
+ <!-- Label for showing the number of widgets an app has in the full widgets picker.
+ [CHAR_LIMIT=25] -->
+ <plurals name="widgets_count">
+ <item quantity="one"><xliff:g id="widgets_count" example="1">%1$d</xliff:g> widget</item>
+ <item quantity="other"><xliff:g id="widgets_count" example="2">%1$d</xliff:g> widgets</item>
</plurals>
+ <!-- Label for showing the number of shortcut an app has in the full widgets picker.
+ [CHAR_LIMIT=25] -->
+ <plurals name="shortcuts_count">
+ <item quantity="one"><xliff:g id="shortcuts_count" example="1">%1$d</xliff:g> shortcut</item>
+ <item quantity="other"><xliff:g id="shortcuts_count" example="2">%1$d</xliff:g> shortcuts</item>
+ </plurals>
+ <!-- Label for showing both the number of widgets and shortcuts an app has in the full widgets
+ picker. [CHAR_LIMIT=50] -->
+ <string name="widgets_and_shortcuts_count"><xliff:g id="widgets_count" example="5 widgets">%1$s</xliff:g>, <xliff:g id="shortcuts_count" example="1 shortcut">%2$s</xliff:g></string>
+
+ <!-- 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>
+ <!-- Spoken text for screen readers. This text lets a user know that the button is used to clear
+ the text that the user entered in the search box. [CHAR_LIMIT=none] -->
+ <string name="widgets_full_sheet_cancel_button_description">Clear text from search box</string>
+ <!-- Text shown when there is no widgets shown in the popup view showing all available widgets
+ installed on the device. [CHAR_LIMIT=none] -->
+ <string name="no_widgets_available">No widgets available</string>
+ <!-- Text shown when there are no matching widget search results for user's search query.
+ [CHAR_LIMIT=none] -->
+ <string name="no_search_results">No search results</string>
<!-- All Apps -->
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
<string name="all_apps_search_bar_hint">Search apps</string>
- <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
- <string name="all_apps_on_device_search_bar_hint">Search this phone and more…</string>
<!-- Loading apps text. [CHAR_LIMIT=50] -->
<string name="all_apps_loading_message">Loading apps…</string>
<!-- No-search-results text. [CHAR_LIMIT=50] -->
@@ -84,9 +115,9 @@
<!-- Drag and drop -->
<!-- Message to tell the user to press and hold on a shortcut to add it [CHAR_LIMIT=50] -->
- <string name="long_press_shortcut_to_add">Touch & hold to pick up a shortcut.</string>
+ <string name="long_press_shortcut_to_add">Touch & hold to move a shortcut.</string>
<!-- Accessibility spoken hint message in deep shortcut menu, which allows user to add a shortcut. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=200] -->
- <string name="long_accessible_way_to_add_shortcut">Double-tap & hold to pick up a shortcut or use custom actions.</string>
+ <string name="long_accessible_way_to_add_shortcut">Double-tap & hold to move a shortcut or use custom actions.</string>
<skip />
<!-- Error message when user has filled a home screen -->
@@ -185,8 +216,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]-->
@@ -243,8 +272,6 @@
<string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
<!-- Strings for widgets & more in the popup container/bottom sheet -->
- <!-- Title for a bottom sheet that shows widgets for a particular app -->
- <string name="widgets_bottom_sheet_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> widgets</string>
<!-- Accessibility title for the popup containing a list of widgets. [CHAR_LIMIT=50] -->
<string name="widgets_list">Widgets list</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index adc2238..a27cdac 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -145,6 +145,8 @@
<style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
+ <item name="android:windowBackground">@drawable/add_item_dialog_background</item>
+ <item name="android:windowNoTitle">true</item>
</style>
<style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
@@ -281,4 +283,8 @@
<item name="android:colorControlHighlight">#DFE1E5</item>
<item name="android:colorForeground">@color/all_apps_bg_hand_fill_dark</item>
</style>
+
+ <style name="Widget.DeviceDefault.Button.Rounded.Colored" parent="@android:style/Widget.DeviceDefault.Button.Colored">
+ <item name="android:background">@drawable/add_item_dialog_button_background</item>
+ </style>
</resources>
diff --git a/res/xml/size_limits.xml b/res/xml/size_limits_80x104.xml
similarity index 98%
rename from res/xml/size_limits.xml
rename to res/xml/size_limits_80x104.xml
index ba57014..e11bc5e 100644
--- a/res/xml/size_limits.xml
+++ b/res/xml/size_limits_80x104.xml
@@ -38,7 +38,7 @@
<workspaceBottomPadding
launcher:a="0.50"
launcher:b="0"
- launcher:c="-16dp"/>
+ launcher:c="16dp"/>
<hotseatBottomPadding
launcher:a="0.50"
launcher:b="0"
diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp
new file mode 100644
index 0000000..bf32362
--- /dev/null
+++ b/robolectric_tests/Android.bp
@@ -0,0 +1,58 @@
+// 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.
+
+//
+// Launcher Robolectric test target.
+//
+// "robolectric_android-all-stub", not needed, we write our own stubs
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
+filegroup {
+ name: "launcher3-robolectric-resources",
+ path: "resources",
+ srcs: ["resources/*"],
+}
+
+filegroup {
+ name: "launcher3-robolectric-src",
+ srcs: ["src/**/*.java"],
+}
+
+android_robolectric_test {
+ name: "LauncherRoboTests",
+ srcs: [
+ ":launcher3-robolectric-src",
+ ":launcher3-test-src-common",
+ ],
+ java_resources: [":launcher3-robolectric-resources"],
+ static_libs: [
+ "truth-prebuilt",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "mockito-robolectric-prebuilt",
+ ],
+ robolectric_prebuilt_version: "4.5.1",
+ instrumentation_for: "Launcher3",
+
+ test_options: {
+ timeout: 36000,
+ },
+}
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
deleted file mode 100644
index 836ded5..0000000
--- a/robolectric_tests/Android.mk
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#############################################
-# Launcher Robolectric test target. #
-#############################################
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := LauncherRoboTests
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-
-LOCAL_SDK_VERSION := system_current
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, ../tests/src_common)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.runner \
- androidx.test.rules \
- mockito-robolectric-prebuilt \
- truth-prebuilt
-LOCAL_JAVA_LIBRARIES := \
- platform-robolectric-4.3.1-prebuilt
-
-LOCAL_JAVA_RESOURCE_DIRS := resources config
-
-LOCAL_INSTRUMENTATION_FOR := Launcher3
-LOCAL_MODULE_TAGS := optional
-
-# Generate test_config.properties
-include external/robolectric-shadows/gen_test_config.mk
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-############################################
-# Target to run the previous target. #
-############################################
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := RunLauncherRoboTests
-LOCAL_SDK_VERSION := system_current
-LOCAL_JAVA_LIBRARIES := LauncherRoboTests
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_TEST_PACKAGE := Launcher3
-LOCAL_INSTRUMENT_SOURCE_DIRS := packages/apps/Launcher3/src
-
-LOCAL_ROBOTEST_TIMEOUT := 36000
-
-include prebuilts/misc/common/robolectric/4.4/run_robotests.mk
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/resources/robolectric.properties
similarity index 98%
rename from robolectric_tests/config/robolectric.properties
rename to robolectric_tests/resources/robolectric.properties
index 4e811f3..abb6968 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/resources/robolectric.properties
@@ -1,4 +1,5 @@
-sdk=29
+sdk=30
+
shadows= \
com.android.launcher3.shadows.LShadowAppPredictionManager \
com.android.launcher3.shadows.LShadowAppWidgetManager \
@@ -11,4 +12,4 @@
com.android.launcher3.shadows.ShadowOverrides \
com.android.launcher3.shadows.ShadowSurfaceTransactionApplier \
-application=com.android.launcher3.util.LauncherTestApplication
\ No newline at end of file
+application=com.android.launcher3.util.LauncherTestApplication
diff --git a/robolectric_tests/resources/widgets_predication_update_task_data.txt b/robolectric_tests/resources/widgets_predication_update_task_data.txt
new file mode 100644
index 0000000..941d195
--- /dev/null
+++ b/robolectric_tests/resources/widgets_predication_update_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by WidgetsPredictionUpdateTasksTest
+
+classMap s com.android.launcher3.model.data.WorkspaceItemInfo
+classMap w com.android.launcher3.model.data.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
index 34cb2ad..4d151f1 100644
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -54,6 +54,7 @@
*/
@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.PAUSED)
+@org.junit.Ignore
public class LauncherUIScrollTest {
private Context mTargetContext;
diff --git a/robolectric_tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java b/robolectric_tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
new file mode 100644
index 0000000..92f77f2
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Point;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public final class LauncherAppWidgetProviderInfoTest {
+
+ private static final int CELL_SIZE = 50;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ }
+
+ @Test
+ public void initSpans_minWidthSmallerThanCellWidth_shouldInitializeSpansToOne() {
+ LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
+ info.minWidth = 20;
+ info.minHeight = 20;
+ InvariantDeviceProfile idp = createIDP();
+
+ info.initSpans(mContext, idp);
+
+ assertThat(info.spanX).isEqualTo(1);
+ assertThat(info.spanY).isEqualTo(1);
+ }
+
+ @Test
+ public void initSpans_minWidthLargerThanCellWidth_shouldInitializeSpans() {
+ LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
+ info.minWidth = 80;
+ info.minHeight = 80;
+ InvariantDeviceProfile idp = createIDP();
+
+ info.initSpans(mContext, idp);
+
+ assertThat(info.spanX).isEqualTo(2);
+ assertThat(info.spanY).isEqualTo(2);
+ }
+
+ @Test
+ public void initSpans_minResizeWidthUnspecified_shouldInitializeMinSpansToOne() {
+ LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
+ InvariantDeviceProfile idp = createIDP();
+
+ info.initSpans(mContext, idp);
+
+ assertThat(info.minSpanX).isEqualTo(1);
+ assertThat(info.minSpanY).isEqualTo(1);
+ }
+
+ @Test
+ public void initSpans_minResizeWidthSmallerThanCellWidth_shouldInitializeMinSpansToOne() {
+ LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
+ info.minResizeWidth = 20;
+ info.minResizeHeight = 20;
+ InvariantDeviceProfile idp = createIDP();
+
+ info.initSpans(mContext, idp);
+
+ assertThat(info.minSpanX).isEqualTo(1);
+ assertThat(info.minSpanY).isEqualTo(1);
+ }
+
+ @Test
+ public void initSpans_minResizeWidthLargerThanCellWidth_shouldInitializeMinSpans() {
+ LauncherAppWidgetProviderInfo info = new LauncherAppWidgetProviderInfo();
+ info.minResizeWidth = 80;
+ info.minResizeHeight = 80;
+ InvariantDeviceProfile idp = createIDP();
+
+ info.initSpans(mContext, idp);
+
+ assertThat(info.minSpanX).isEqualTo(2);
+ assertThat(info.minSpanY).isEqualTo(2);
+ }
+
+ private InvariantDeviceProfile createIDP() {
+ DeviceProfile profile = Mockito.mock(DeviceProfile.class);
+ Mockito.when(profile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE));
+
+ InvariantDeviceProfile idp = new InvariantDeviceProfile();
+ idp.landscapeProfile = idp.portraitProfile = profile;
+ return idp;
+ }
+
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
index 04797a6..cc36f63 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
@@ -32,12 +32,12 @@
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.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -221,6 +221,27 @@
assertThat(currentList).containsExactlyElementsIn(newList);
}
+ @Test
+ public void headersContentsMix_headerWidgetsModified_shouldInvokeCorrectCallbacks() {
+ // GIVEN the current list has app headers [A, B, E content].
+ ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
+ List.of(mHeaderA, mHeaderB, mContentE));
+ // GIVEN the new list has one of the headers widgets list modified.
+ List<WidgetsListBaseEntry> newList = List.of(
+ new WidgetsListHeaderEntry(
+ mHeaderA.mPkgItem, mHeaderA.mTitleSectionName,
+ mHeaderA.mWidgets.subList(0, 1)),
+ mHeaderB, mContentE);
+
+ // WHEN computing the list difference.
+ mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+ // THEN notify "A" has been changed.
+ verify(mAdapter).notifyItemChanged(/* position= */ 0);
+ // THEN the current list contains all elements from the new list.
+ assertThat(currentList).containsExactlyElementsIn(newList);
+ }
+
private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
int numOfWidgets) {
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index e94b253..6b5678c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -26,18 +26,21 @@
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
+import android.os.Process;
+import android.os.UserHandle;
import android.view.LayoutInflater;
import androidx.recyclerview.widget.RecyclerView;
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.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -67,6 +70,7 @@
private WidgetsListAdapter mAdapter;
private InvariantDeviceProfile mTestProfile;
+ private UserHandle mUserHandle;
private Context mContext;
@Before
@@ -76,8 +80,9 @@
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
+ mUserHandle = Process.myUserHandle();
mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
- mIconCache, null, null);
+ mIconCache, null, null, null);
mAdapter.registerAdapterDataObserver(mListener);
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
@@ -126,7 +131,8 @@
mAdapter.setWidgets(generateSampleMap(3));
// WHEN com.google.test.1 header is expanded.
- mAdapter.onHeaderClicked(/* isExpanded= */ true, TEST_PACKAGE_PLACEHOLDER + 1);
+ mAdapter.onHeaderClicked(/* showWidgets= */ true,
+ new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
// THEN the visible entries list becomes:
// [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
@@ -143,7 +149,8 @@
// GIVEN test com.google.test1 is expanded.
// Visible entries in the adapter are:
// [com.google.test0, com.google.test1, com.google.test1 content]
- mAdapter.onHeaderClicked(/* isExpanded= */ true, TEST_PACKAGE_PLACEHOLDER + 1);
+ mAdapter.onHeaderClicked(/* showWidgets= */ true,
+ new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
Mockito.reset(mListener);
// WHEN the adapter is updated with the same list of apps but com.google.test1 has 2 widgets
@@ -200,6 +207,30 @@
verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
}
+ @Test
+ public void setWidgetsOnSearch_expandedApp_shouldResetExpandedApp() {
+ // GIVEN a list of widgets entries:
+ // [com.google.test0, com.google.test0 content,
+ // com.google.test1, com.google.test1 content,
+ // com.google.test2, com.google.test2 content]
+ // The visible widgets entries: [com.google.test0, com.google.test1, com.google.test2].
+ ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(2);
+ mAdapter.setWidgetsOnSearch(allEntries);
+ // GIVEN com.google.test.1 header is expanded. The visible entries list becomes:
+ // [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
+ mAdapter.onHeaderClicked(/* showWidgets= */ true,
+ new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
+ Mockito.reset(mListener);
+
+ // WHEN same widget entries are set again.
+ mAdapter.setWidgetsOnSearch(allEntries);
+
+ // THEN expanded app is reset and the visible entries list becomes:
+ // [com.google.test0, com.google.test1, com.google.test2]
+ verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
+ verify(mListener).onItemRangeRemoved(/* positionStart= */ 2, /* itemCount= */ 1);
+ }
+
/**
* Generates a list of sample widget entries.
*
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index ae5b9a5..12a092d 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -18,7 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
@@ -26,25 +28,22 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
-import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.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.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListHeaderViewHolderBinder.OnHeaderClickListener;
import org.junit.After;
import org.junit.Before;
@@ -74,12 +73,15 @@
// testing.
private ActivityController<TestActivity> mActivityController;
private TestActivity mTestActivity;
- private FakeOnHeaderClickListener mFakeOnHeaderClickListener = new FakeOnHeaderClickListener();
@Mock
private IconCache mIconCache;
@Mock
private DeviceProfile mDeviceProfile;
+ @Mock
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+ @Mock
+ private OnHeaderClickListener mOnHeaderClickListener;
@Before
public void setUp() {
@@ -98,9 +100,15 @@
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
+ WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
LayoutInflater.from(mTestActivity),
- mFakeOnHeaderClickListener);
+ mWidgetPreviewLoader,
+ mIconCache,
+ /* iconClickListener= */ view -> {},
+ /* iconLongClickListener= */ view -> false,
+ /* searchBarUIHelper= */ null);
+ mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
+ LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
}
@After
@@ -117,7 +125,7 @@
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -125,6 +133,23 @@
assertThat(appSubtitle.getText()).isEqualTo("3 widgets");
}
+ @Test
+ public void bindViewHolder_shouldAttachOnHeaderClickListener() {
+ WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+ new FrameLayout(mTestActivity));
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ WidgetsListHeaderEntry entry = generateSampleAppHeader(
+ APP_NAME,
+ TEST_PACKAGE,
+ /* numOfWidgets= */ 3);
+
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ widgetsListHeader.callOnClick();
+
+ verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
+ eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
+ }
+
private WidgetsListHeaderEntry generateSampleAppHeader(String appName, String packageName,
int numOfWidgets) {
PackageItemInfo appInfo = new PackageItemInfo(packageName);
@@ -152,22 +177,4 @@
}
return widgetItems;
}
-
- private void assertWidgetCellWithLabel(View view, String label) {
- assertThat(view).isInstanceOf(WidgetCell.class);
- TextView widgetLabel = (TextView) view.findViewById(R.id.widget_name);
- assertThat(widgetLabel.getText()).isEqualTo(label);
- }
-
- private final class FakeOnHeaderClickListener implements OnHeaderClickListener {
-
- boolean mShowWidgets = false;
- @Nullable String mHeaderClickedPackage = null;
-
- @Override
- public void onHeaderClicked(boolean showWidgets, String packageName) {
- mShowWidgets = showWidgets;
- mHeaderClickedPackage = packageName;
- }
- }
}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
similarity index 65%
copy from robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java
copy to robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index ec9fde3..e090341 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -15,12 +15,12 @@
*/
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.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
@@ -28,15 +28,11 @@
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.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;
@@ -45,8 +41,9 @@
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 com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
import org.junit.After;
import org.junit.Before;
@@ -65,12 +62,12 @@
import java.util.List;
@RunWith(RobolectricTestRunner.class)
-public final class WidgetsListRowViewHolderBinderTest {
+public final class WidgetsListSearchHeaderViewHolderBinderTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
private Context mContext;
- private WidgetsListRowViewHolderBinder mViewHolderBinder;
+ private WidgetsListSearchHeaderViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
// Replace ActivityController with ActivityScenario, which is the recommended way for activity
// testing.
@@ -78,15 +75,13 @@
private TestActivity mTestActivity;
@Mock
- private OnLongClickListener mOnLongClickListener;
- @Mock
- private OnClickListener mOnIconClickListener;
- @Mock
private IconCache mIconCache;
@Mock
+ private DeviceProfile mDeviceProfile;
+ @Mock
private WidgetPreviewLoader mWidgetPreviewLoader;
@Mock
- private DeviceProfile mDeviceProfile;
+ private OnHeaderClickListener mOnHeaderClickListener;
@Before
public void setUp() {
@@ -105,12 +100,15 @@
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- mViewHolderBinder = new WidgetsListRowViewHolderBinder(
- mContext,
+ WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
LayoutInflater.from(mTestActivity),
- mOnIconClickListener,
- mOnLongClickListener,
- mWidgetPreviewLoader);
+ mWidgetPreviewLoader,
+ mIconCache,
+ /* iconClickListener= */ view -> {},
+ /* iconLongClickListener= */ view -> false,
+ /* searchBarUIHelper= */ null);
+ mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
+ LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
}
@After
@@ -119,35 +117,47 @@
}
@Test
- public void bindViewHolder_appWith3Widgets_shouldHave3Widgets() {
- WidgetsRowViewHolder viewHolder = mViewHolderBinder.newViewHolder(
+ public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
+ WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
new FrameLayout(mTestActivity));
- WidgetsListContentEntry entry = generateSampleAppWithWidgets(
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry);
- shadowOf(getMainLooper()).idle();
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
- // THEN the cell container has 5 children: 3 widgets + 2 separators
- // Index: 0 1 2 3 4
- // View: .SampleWidget0 | .SampleWidget1 | .SampleWidget2
- assertThat(viewHolder.cellContainer.getChildCount()).isEqualTo(5);
- // Widget 0 label is .SampleWidget0.
- assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(0), ".SampleWidget0");
- // Widget 1 label is .SampleWidget1.
- assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(2), ".SampleWidget1");
- // Widget 2 label is .SampleWidget2.
- assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(4), ".SampleWidget2");
+ TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
+ TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
+ assertThat(appTitle.getText()).isEqualTo(APP_NAME);
+ assertThat(appSubtitle.getText())
+ .isEqualTo(".SampleWidget0, .SampleWidget1, .SampleWidget2");
}
- private WidgetsListContentEntry generateSampleAppWithWidgets(String appName, String packageName,
- int numOfWidgets) {
+ @Test
+ public void bindViewHolder_shouldAttachOnHeaderClickListener() {
+ WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+ new FrameLayout(mTestActivity));
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
+ APP_NAME,
+ TEST_PACKAGE,
+ /* numOfWidgets= */ 3);
+
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ widgetsListHeader.callOnClick();
+
+ verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
+ eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
+ }
+
+ private WidgetsListSearchHeaderEntry generateSampleSearchHeader(String appName,
+ String packageName, int numOfWidgets) {
PackageItemInfo appInfo = new PackageItemInfo(packageName);
appInfo.title = appName;
appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
- return new WidgetsListContentEntry(appInfo,
+ return new WidgetsListSearchHeaderEntry(appInfo,
/* titleSectionName= */ "",
generateWidgetItems(packageName, numOfWidgets));
}
@@ -168,10 +178,4 @@
}
return widgetItems;
}
-
- private void assertWidgetCellWithLabel(View view, String label) {
- assertThat(view).isInstanceOf(WidgetCell.class);
- TextView widgetLabel = (TextView) view.findViewById(R.id.widget_name);
- assertThat(widgetLabel.getText()).isEqualTo(label);
- }
}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
similarity index 82%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java
rename to robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index ec9fde3..0935d1c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -32,11 +32,11 @@
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;
@@ -45,6 +45,7 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -65,12 +66,12 @@
import java.util.List;
@RunWith(RobolectricTestRunner.class)
-public final class WidgetsListRowViewHolderBinderTest {
+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 WidgetsListRowViewHolderBinder mViewHolderBinder;
+ private WidgetsListTableViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
// Replace ActivityController with ActivityScenario, which is the recommended way for activity
// testing.
@@ -105,12 +106,20 @@
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- mViewHolderBinder = new WidgetsListRowViewHolderBinder(
+ WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
+ LayoutInflater.from(mTestActivity),
+ mWidgetPreviewLoader,
+ mIconCache,
+ /* iconClickListener= */ view -> {},
+ /* iconLongClickListener= */ view -> false,
+ /* searchBarUIHelper= */ null);
+ mViewHolderBinder = new WidgetsListTableViewHolderBinder(
mContext,
LayoutInflater.from(mTestActivity),
mOnIconClickListener,
mOnLongClickListener,
- mWidgetPreviewLoader);
+ mWidgetPreviewLoader,
+ widgetsListAdapter);
}
@After
@@ -126,19 +135,20 @@
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
shadowOf(getMainLooper()).idle();
- // THEN the cell container has 5 children: 3 widgets + 2 separators
- // Index: 0 1 2 3 4
+ // THEN the table container has one row, which contains 3 widgets.
// View: .SampleWidget0 | .SampleWidget1 | .SampleWidget2
- assertThat(viewHolder.cellContainer.getChildCount()).isEqualTo(5);
+ assertThat(viewHolder.mTableContainer.getChildCount()).isEqualTo(1);
+ TableRow row = (TableRow) viewHolder.mTableContainer.getChildAt(0);
+ assertThat(row.getChildCount()).isEqualTo(3);
// Widget 0 label is .SampleWidget0.
- assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(0), ".SampleWidget0");
+ assertWidgetCellWithLabel(row.getChildAt(0), ".SampleWidget0");
// Widget 1 label is .SampleWidget1.
- assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(2), ".SampleWidget1");
+ assertWidgetCellWithLabel(row.getChildAt(1), ".SampleWidget1");
// Widget 2 label is .SampleWidget2.
- assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(4), ".SampleWidget2");
+ assertWidgetCellWithLabel(row.getChildAt(2), ".SampleWidget2");
}
private WidgetsListContentEntry generateSampleAppWithWidgets(String appName, String packageName,
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
index 86df3f8..2d22c45 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
@@ -26,11 +26,11 @@
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.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import org.junit.Before;
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
new file mode 100644
index 0000000..c2bf1ae
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(RobolectricTestRunner.class)
+public class SimpleWidgetsSearchAlgorithmTest {
+
+ private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm;
+ @Mock
+ private WidgetsPickerSearchPipeline mSearchPipeline;
+ @Mock
+ private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
+ @Captor
+ private ArgumentCaptor<Consumer<List<WidgetsListBaseEntry>>> mConsumerCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mSimpleWidgetsSearchAlgorithm = new SimpleWidgetsSearchAlgorithm(mSearchPipeline);
+ }
+
+ @Test
+ public void doSearch_shouldQueryPipeline() {
+ mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
+
+ verify(mSearchPipeline).query(eq("abc"), any());
+ }
+
+ @Test
+ public void doSearch_shouldInformSearchCallbackOnQueryResult() {
+ ArrayList<WidgetsListBaseEntry> baseEntries = new ArrayList<>();
+
+ mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
+
+ verify(mSearchPipeline).query(eq("abc"), mConsumerCaptor.capture());
+ mConsumerCaptor.getValue().accept(baseEntries);
+ shadowOf(getMainLooper()).idle();
+ // Verify SearchCallback#onSearchResult receives a query token along with the search
+ // results. The query token is the original query string concatenated with the query
+ // timestamp.
+ verify(mSearchCallback).onSearchResult(matches("abc\t\\d*"), eq(baseEntries));
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java
new file mode 100644
index 0000000..17ededd
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.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.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class SimpleWidgetsSearchPipelineTest {
+ @Mock private IconCache mIconCache;
+
+ private InvariantDeviceProfile mTestProfile;
+ private WidgetsListHeaderEntry mCalendarHeaderEntry;
+ private WidgetsListContentEntry mCalendarContentEntry;
+ private WidgetsListHeaderEntry mCameraHeaderEntry;
+ private WidgetsListContentEntry mCameraContentEntry;
+ private WidgetsListHeaderEntry mClockHeaderEntry;
+ private WidgetsListContentEntry mClockContentEntry;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ return componentWithLabel.getComponent().getShortClassName();
+ }).when(mIconCache).getTitleNoCache(any());
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+ mContext = RuntimeEnvironment.application;
+
+ mCalendarHeaderEntry =
+ createWidgetsHeaderEntry("com.example.android.Calendar", "Calendar", 2);
+ mCalendarContentEntry =
+ createWidgetsContentEntry("com.example.android.Calendar", "Calendar", 2);
+ mCameraHeaderEntry = createWidgetsHeaderEntry("com.example.android.Camera", "Camera", 11);
+ mCameraContentEntry = createWidgetsContentEntry("com.example.android.Camera", "Camera", 11);
+ mClockHeaderEntry = createWidgetsHeaderEntry("com.example.android.Clock", "Clock", 3);
+ mClockContentEntry = createWidgetsContentEntry("com.example.android.Clock", "Clock", 3);
+ }
+
+ @Test
+ public void query_shouldMatchOnAppName() {
+ SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
+ List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+ mCameraContentEntry, mClockHeaderEntry, mClockContentEntry));
+
+ pipeline.query("Ca", results ->
+ assertEquals(results,
+ List.of(
+ new WidgetsListSearchHeaderEntry(
+ mCalendarHeaderEntry.mPkgItem,
+ mCalendarHeaderEntry.mTitleSectionName,
+ mCalendarHeaderEntry.mWidgets),
+ mCalendarContentEntry,
+ new WidgetsListSearchHeaderEntry(
+ mCameraHeaderEntry.mPkgItem,
+ mCameraHeaderEntry.mTitleSectionName,
+ mCameraHeaderEntry.mWidgets),
+ mCameraContentEntry)));
+ shadowOf(getMainLooper()).idle();
+ }
+
+ @Test
+ public void query_shouldMatchOnWidgetLabel() {
+ SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
+ List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+ mCameraContentEntry));
+
+ pipeline.query("Widget1", results ->
+ assertEquals(results,
+ List.of(
+ new WidgetsListSearchHeaderEntry(
+ mCalendarHeaderEntry.mPkgItem,
+ mCalendarHeaderEntry.mTitleSectionName,
+ mCalendarHeaderEntry.mWidgets.subList(1, 2)),
+ new WidgetsListContentEntry(
+ mCalendarHeaderEntry.mPkgItem,
+ mCalendarHeaderEntry.mTitleSectionName,
+ mCalendarHeaderEntry.mWidgets.subList(1, 2)),
+ new WidgetsListSearchHeaderEntry(
+ mCameraHeaderEntry.mPkgItem,
+ mCameraHeaderEntry.mTitleSectionName,
+ mCameraHeaderEntry.mWidgets.subList(1, 3)),
+ new WidgetsListContentEntry(
+ mCameraHeaderEntry.mPkgItem,
+ mCameraHeaderEntry.mTitleSectionName,
+ mCameraHeaderEntry.mWidgets.subList(1, 3)))));
+ shadowOf(getMainLooper()).idle();
+ }
+
+ private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
+ int numOfWidgets) {
+ List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+ PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+ widgetItems.get(0).user);
+
+ return new WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+ }
+
+ private WidgetsListContentEntry createWidgetsContentEntry(String packageName, String appName,
+ int numOfWidgets) {
+ List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+ PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+ widgetItems.get(0).user);
+
+ return new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+ }
+
+ private PackageItemInfo createPackageItemInfo(String packageName, String appName,
+ UserHandle userHandle) {
+ PackageItemInfo pInfo = new PackageItemInfo(packageName);
+ pInfo.title = appName;
+ pInfo.user = userHandle;
+ pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+ return pInfo;
+ }
+
+ private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
+ ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+ ArrayList<WidgetItem> widgetItems = new ArrayList<>();
+ for (int i = 0; i < numOfWidgets; i++) {
+ ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
+ AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+ widgetInfo.provider = cn;
+ ReflectionHelpers.setField(widgetInfo, "providerInfo",
+ packageManager.addReceiverIfNotPresent(cn));
+
+ WidgetItem widgetItem = new WidgetItem(
+ LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
+ mTestProfile, mIconCache);
+ widgetItems.add(widgetItem);
+ }
+ return widgetItems;
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
new file mode 100644
index 0000000..4e6f17c
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class WidgetsSearchBarControllerTest {
+
+ private WidgetsSearchBarController mController;
+ private Context mContext;
+ private ExtendedEditText mEditText;
+ private ImageButton mCancelButton;
+ @Mock
+ private SearchModeListener mSearchModeListener;
+ @Mock
+ private SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mEditText = new ExtendedEditText(mContext);
+ mCancelButton = new ImageButton(mContext);
+ mController = new WidgetsSearchBarController(
+ mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener);
+ }
+
+ @Test
+ public void onSearchResult_shouldInformSearchModeListener() {
+ ArrayList<WidgetsListBaseEntry> entries = new ArrayList<>();
+ mController.onSearchResult("abc", entries);
+
+ verify(mSearchModeListener).onSearchResults(entries);
+ }
+
+ @Test
+ public void afterTextChanged_shouldInformSearchModeListenerToEnterSearch() {
+ mEditText.setText("abc");
+
+ verify(mSearchModeListener).enterSearchMode();
+ verifyNoMoreInteractions(mSearchModeListener);
+ }
+
+ @Test
+ public void afterTextChanged_shouldDoSearch() {
+ mEditText.setText("abc");
+
+ verify(mSearchAlgorithm).doSearch(eq("abc"), any());
+ }
+
+ @Test
+ public void afterTextChanged_shouldShowCancelButton() {
+ mEditText.setText("abc");
+
+ assertEquals(mCancelButton.getVisibility(), View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_empty_shouldInformSearchModeListenerToExitSearch() {
+ mEditText.setText("");
+
+ verify(mSearchModeListener).exitSearchMode();
+ verifyNoMoreInteractions(mSearchModeListener);
+ }
+
+ @Test
+ public void afterTextChanged_empty_shouldCancelSearch() {
+ mEditText.setText("");
+
+ verify(mSearchAlgorithm).cancel(true);
+ verifyNoMoreInteractions(mSearchAlgorithm);
+ }
+
+ @Test
+ public void afterTextChanged_empty_shouldHideCancelButton() {
+ mEditText.setText("");
+
+ assertEquals(mCancelButton.getVisibility(), View.GONE);
+ }
+
+ @Test
+ public void cancelSearch_shouldInformSearchModeListenerToClearResultsAndExitSearch() {
+ mCancelButton.performClick();
+
+ verify(mSearchModeListener).exitSearchMode();
+ }
+
+ @Test
+ public void cancelSearch_shouldCancelSearch() {
+ mCancelButton.performClick();
+
+ verify(mSearchAlgorithm).cancel(true);
+ verifyNoMoreInteractions(mSearchAlgorithm);
+ }
+
+ @Test
+ public void cancelSearch_shouldClearSearchBar() {
+ mCancelButton.performClick();
+
+ assertEquals(mEditText.getText().toString(), "");
+ }
+}
diff --git a/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..e68edd3
--- /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.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.LauncherAppWidgetProviderInfo;
+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/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java b/robolectric_tests/unstaged/SettingsCacheTest.java
similarity index 100%
rename from robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
rename to robolectric_tests/unstaged/SettingsCacheTest.java
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 1330ed4..5d41bb5 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -2,6 +2,7 @@
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.views.BaseDragLayer.LAYOUT_X;
import static com.android.launcher3.views.BaseDragLayer.LAYOUT_Y;
@@ -13,17 +14,24 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
import android.util.AttributeSet;
+import android.util.SizeF;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.util.FocusLogic;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import java.util.ArrayList;
import java.util.List;
@@ -43,6 +51,14 @@
inv.portraitProfile.getCellSize()};
});
+ // Represents the border spacing size on the grid in the two orientations.
+ public static final MainThreadInitializedObject<int[]> BORDER_SPACING_SIZE =
+ new MainThreadInitializedObject<>(c -> {
+ InvariantDeviceProfile inv = LauncherAppState.getIDP(c);
+ return new int[] {inv.landscapeProfile.cellLayoutBorderSpacingPx,
+ inv.portraitProfile.cellLayoutBorderSpacingPx};
+ });
+
private static final int HANDLE_COUNT = 4;
private static final int INDEX_LEFT = 0;
private static final int INDEX_TOP = 1;
@@ -59,6 +75,7 @@
private LauncherAppWidgetHostView mWidgetView;
private CellLayout mCellLayout;
private DragLayer mDragLayer;
+ private ImageButton mReconfigureButton;
private Rect mWidgetPadding;
@@ -88,6 +105,8 @@
private int mRunningVInc;
private int mMinHSpan;
private int mMinVSpan;
+ private int mMaxHSpan;
+ private int mMaxVSpan;
private int mDeltaX;
private int mDeltaY;
private int mDeltaXAddOn;
@@ -126,10 +145,10 @@
protected void onFinishInflate() {
super.onFinishInflate();
- ViewGroup content = (ViewGroup) getChildAt(0);
- for (int i = 0; i < HANDLE_COUNT; i ++) {
- mDragHandles[i] = content.getChildAt(i);
- }
+ mDragHandles[INDEX_LEFT] = findViewById(R.id.widget_resize_left_handle);
+ mDragHandles[INDEX_TOP] = findViewById(R.id.widget_resize_top_handle);
+ mDragHandles[INDEX_RIGHT] = findViewById(R.id.widget_resize_right_handle);
+ mDragHandles[INDEX_BOTTOM] = findViewById(R.id.widget_resize_bottom_handle);
}
@Override
@@ -152,6 +171,15 @@
DragLayer dl = launcher.getDragLayer();
AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
.inflate(R.layout.app_widget_resize_frame, dl, false);
+ if (widget.hasEnforcedCornerRadius()) {
+ float enforcedCornerRadius = widget.getEnforcedCornerRadius();
+ ImageView imageView = frame.findViewById(R.id.widget_resize_frame);
+ Drawable d = imageView.getDrawable();
+ if (d instanceof GradientDrawable) {
+ GradientDrawable gd = (GradientDrawable) d.mutate();
+ gd.setCornerRadius(enforcedCornerRadius);
+ }
+ }
frame.setupForWidget(widget, cellLayout, dl);
((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
@@ -171,6 +199,8 @@
mMinHSpan = info.minSpanX;
mMinVSpan = info.minSpanY;
+ mMaxHSpan = info.maxSpanX;
+ mMaxVSpan = info.maxSpanY;
mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
widgetView.getAppWidgetInfo().provider, null);
@@ -183,6 +213,17 @@
mDragHandles[INDEX_RIGHT].setVisibility(GONE);
}
+ mReconfigureButton = (ImageButton) findViewById(R.id.widget_reconfigure_button);
+ if (info.isReconfigurable()) {
+ mReconfigureButton.setVisibility(VISIBLE);
+ mReconfigureButton.setOnClickListener(view -> mLauncher
+ .getAppWidgetHost()
+ .startConfigActivity(
+ mLauncher,
+ mWidgetView.getAppWidgetId(),
+ Launcher.REQUEST_RECONFIGURE_APPWIDGET));
+ }
+
// When we create the resize frame, we first mark all cells as unoccupied. The appropriate
// cells (same if not resized, or different) will be marked as occupied when the resize
// frame is dismissed.
@@ -302,7 +343,7 @@
// expandability.
mTempRange1.set(cellX, spanX + cellX);
int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
- hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
+ hSpanInc, mMinHSpan, mMaxHSpan, mCellLayout.getCountX(), mTempRange2);
cellX = mTempRange2.start;
spanX = mTempRange2.size();
if (hSpanDelta != 0) {
@@ -311,7 +352,7 @@
mTempRange1.set(cellY, spanY + cellY);
int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
- vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
+ vSpanInc, mMinVSpan, mMaxVSpan, mCellLayout.getCountY(), mTempRange2);
cellY = mTempRange2.start;
spanY = mTempRange2.size();
if (vSpanDelta != 0) {
@@ -352,33 +393,80 @@
}
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<SizeF> 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 SizeF getWidgetSize(Context context, Point cellSize, int spanX, int spanY,
+ int borderSpacing) {
final float density = context.getResources().getDisplayMetrics().density;
- final Point[] cellSize = CELL_SIZE.get(context);
-
- final int borderSpacing = context.getResources()
- .getDimensionPixelSize(R.dimen.dynamic_grid_cell_border_spacing);
final float hBorderSpacing = (spanX - 1) * borderSpacing;
final float vBorderSpacing = (spanY - 1) * borderSpacing;
- // Compute landscape size
- int landWidth = (int) (((spanX * cellSize[0].x) + hBorderSpacing) / density);
- int landHeight = (int) (((spanY * cellSize[0].y) + vBorderSpacing) / density);
+ return new SizeF(((spanX * cellSize.x) + hBorderSpacing) / density,
+ ((spanY * cellSize.y) + vBorderSpacing) / density);
+ }
- // Compute portrait size
- int portWidth = (int) (((spanX * cellSize[1].x) + hBorderSpacing) / density);
- int portHeight = (int) (((spanY * cellSize[1].y) + vBorderSpacing) / density);
- rect.set(portWidth, landHeight, landWidth, portHeight);
- return rect;
+ /** Returns the list of sizes for a widget of given span, in dp. */
+ public static ArrayList<SizeF> getWidgetSizes(Context context, int spanX, int spanY) {
+ final Point[] cellSize = CELL_SIZE.get(context);
+ final int[] borderSpacing = BORDER_SPACING_SIZE.get(context);
+
+ SizeF landSize = getWidgetSize(context, cellSize[0], spanX, spanY, borderSpacing[0]);
+ SizeF portSize = getWidgetSize(context, cellSize[1], spanX, spanY, borderSpacing[1]);
+
+ ArrayList<SizeF> 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<SizeF> sizes, @Nullable Rect outRect) {
+ if (outRect == null) {
+ outRect = new Rect();
+ }
+ if (sizes.isEmpty()) {
+ outRect.set(0, 0, 0, 0);
+ } else {
+ SizeF first = sizes.get(0);
+ outRect.set((int) first.getWidth(), (int) first.getHeight(), (int) first.getWidth(),
+ (int) first.getHeight());
+ for (int i = 1; i < sizes.size(); i++) {
+ outRect.union((int) sizes.get(i).getWidth(), (int) sizes.get(i).getHeight());
+ }
+ }
+ 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
@@ -483,7 +571,7 @@
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// Clear the frame and give focus to the widget host view when a directional key is pressed.
- if (FocusLogic.shouldConsume(keyCode)) {
+ if (shouldConsume(keyCode)) {
close(false);
mWidgetView.requestFocus();
return true;
@@ -507,6 +595,13 @@
return false;
}
+ private boolean isTouchOnReconfigureButton(MotionEvent ev) {
+ int xFrame = (int) ev.getX() - getLeft();
+ int yFrame = (int) ev.getY() - getTop();
+ mReconfigureButton.getHitRect(sTmpRect);
+ return sTmpRect.contains(xFrame, yFrame);
+ }
+
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
int action = ev.getAction();
@@ -534,6 +629,11 @@
if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
return true;
}
+ // Keep the resize frame open but let a click on the reconfigure button fall through to the
+ // button's OnClickListener.
+ if (isTouchOnReconfigureButton(ev)) {
+ return false;
+ }
close(false);
return false;
}
@@ -583,12 +683,15 @@
* @param minSize minimum size after with the moving edge should not be shifted any further.
* For eg, if delta = -3 when moving the endEdge brings the size to less than
* minSize, only delta = -2 will applied
+ * @param maxSize maximum size after with the moving edge should not be shifted any further.
+ * For eg, if delta = -3 when moving the endEdge brings the size to greater
+ * than maxSize, only delta = -2 will applied
* @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
* @return the amount of increase when endEdge was moves and the amount of decrease when
* the start edge was moved.
*/
public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
- int minSize, int maxEnd, IntRange out) {
+ int minSize, int maxSize, int maxEnd, IntRange out) {
applyDelta(moveStart, moveEnd, delta, out);
if (out.start < 0) {
out.start = 0;
@@ -603,7 +706,24 @@
out.end = out.start + minSize;
}
}
+ if (out.size() > maxSize) {
+ if (moveStart) {
+ out.start = out.end - maxSize;
+ } else if (moveEnd) {
+ out.end = out.start + maxSize;
+ }
+ }
return moveEnd ? out.size() - size() : size() - out.size();
}
}
+
+ /**
+ * Returns true only if this utility class handles the key code.
+ */
+ public static boolean shouldConsume(int keyCode) {
+ return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+ || keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
+ || keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END
+ || keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
+ }
}
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index b85c648..75e89b2 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -21,6 +21,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 062ab71..f77f7e8 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -279,7 +279,7 @@
/**
* Used to set the override visibility state, used only to handle the transition home with the
* recents animation.
- * @see QuickstepAppTransitionManagerImpl#createWallpaperOpenRunner
+ * @see QuickstepTransitionManager#createWallpaperOpenRunner
*/
public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
mForceInvisible |= flag;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 5bfde15..e38ab74 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -27,6 +27,7 @@
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Process;
import android.os.StrictMode;
@@ -40,6 +41,7 @@
import android.view.WindowMetrics;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -52,10 +54,12 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.WindowBounds;
@@ -76,6 +80,7 @@
protected boolean mIsSafeModeEnabled;
private Runnable mOnStartCallback;
+ private RunnableList mOnResumeCallbacks = new RunnableList();
private int mThemeRes = R.style.AppTheme;
@@ -98,6 +103,16 @@
}
@Override
+ protected void onResume() {
+ super.onResume();
+ mOnResumeCallbacks.executeAllAndClear();
+ }
+
+ public void addOnResumeCallback(Runnable callback) {
+ mOnResumeCallbacks.add(callback);
+ }
+
+ @Override
public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
updateTheme();
}
@@ -149,20 +164,35 @@
return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
}
- public final Bundle getActivityLaunchOptionsAsBundle(View v) {
- ActivityOptions activityOptions = getActivityLaunchOptions(v);
- return activityOptions == null ? null : activityOptions.toBundle();
+ @NonNull
+ public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+ int left = 0, top = 0;
+ int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
+ if (v instanceof BubbleTextView) {
+ // Launch from center of icon, not entire view
+ Drawable icon = ((BubbleTextView) v).getIcon();
+ if (icon != null) {
+ Rect bounds = icon.getBounds();
+ left = (width - bounds.width()) / 2;
+ top = v.getPaddingTop();
+ width = bounds.width();
+ height = bounds.height();
+ }
+ }
+ ActivityOptions options =
+ ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+ RunnableList callback = new RunnableList();
+ addOnResumeCallback(callback::executeAllAndDestroy);
+ return new ActivityOptionsWrapper(options, callback);
}
- public abstract ActivityOptions getActivityLaunchOptions(View v);
-
public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {
if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return false;
}
- Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null;
+ Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v).toBundle() : null;
UserHandle user = item == null ? null : item.user;
// Prepare intent
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 e5a4335..2b58fb6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -17,7 +17,6 @@
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;
@@ -51,9 +50,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-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;
@@ -83,8 +80,8 @@
* because we want to make the bubble taller than the text and TextView's clip is
* too aggressive.
*/
-public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
- IconLabelDotView, DraggableView, Reorderable, AllAppsSectionDecorator.SelfDecoratingView {
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
+ IconLabelDotView, DraggableView, Reorderable {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -195,7 +192,7 @@
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
defaultIconSize = grid.iconSizePx;
- setCenterVertically(ENABLE_FOUR_COLUMNS.get());
+ setCenterVertically(grid.isScalableGrid);
} else if (mDisplay == DISPLAY_ALL_APPS) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
@@ -433,13 +430,6 @@
}
}
- @Override
- public void onLauncherResume() {
- // Reset the pressed state of icon that was locked in the press state while activity
- // was launching
- setStayPressed(false);
- }
-
void clearPressedBackground() {
setPressed(false);
setStayPressed(false);
@@ -954,16 +944,4 @@
setCompoundDrawables(null, newIcon, null, null);
}
}
-
- @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 459b9a8..4740079 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -21,14 +21,9 @@
import static com.android.launcher3.LauncherState.NORMAL;
import android.animation.AnimatorSet;
-import android.animation.FloatArrayEvaluator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -45,10 +40,7 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.Thunk;
/**
* Implements a DropTarget.
@@ -72,6 +64,7 @@
private static final int[] sTempCords = new int[2];
private static final int DRAG_VIEW_DROP_DURATION = 285;
+ private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f;
public static final int TOOLTIP_DEFAULT = 0;
public static final int TOOLTIP_LEFT = 1;
@@ -89,9 +82,6 @@
/** An item must be dragged at least this many pixels before this drop target is enabled. */
private final int mDragDistanceThreshold;
- /** The paint applied to the drag view on hover */
- protected int mHoverColor = 0;
-
protected CharSequence mText;
protected ColorStateList mOriginalTextColor;
protected Drawable mDrawable;
@@ -101,7 +91,6 @@
private int mToolTipLocation;
private AnimatorSet mCurrentColorAnim;
- @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter;
public ButtonDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -177,8 +166,7 @@
mToolTip.showAsDropDown(this, x, y);
}
- d.dragView.setColor(mHoverColor);
- animateTextColor(mHoverColor);
+ d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
if (d.stateAnnouncer != null) {
d.stateAnnouncer.cancel();
}
@@ -190,51 +178,15 @@
// Do nothing
}
- protected void resetHoverColor() {
- animateTextColor(mOriginalTextColor.getDefaultColor());
- }
-
- private void animateTextColor(int targetColor) {
- if (mCurrentColorAnim != null) {
- mCurrentColorAnim.cancel();
- }
-
- mCurrentColorAnim = new AnimatorSet();
- mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION);
-
- if (mSrcFilter == null) {
- mSrcFilter = new ColorMatrix();
- mDstFilter = new ColorMatrix();
- mCurrentFilter = new ColorMatrix();
- }
-
- int defaultTextColor = mOriginalTextColor.getDefaultColor();
- Themes.setColorChangeOnMatrix(defaultTextColor, getTextColor(), mSrcFilter);
- Themes.setColorChangeOnMatrix(defaultTextColor, targetColor, mDstFilter);
-
- ValueAnimator anim1 = ValueAnimator.ofObject(
- new FloatArrayEvaluator(mCurrentFilter.getArray()),
- mSrcFilter.getArray(), mDstFilter.getArray());
- anim1.addUpdateListener((anim) -> {
- mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
- invalidate();
- });
-
- mCurrentColorAnim.play(anim1);
- mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, TEXT_COLOR, targetColor));
- mCurrentColorAnim.start();
- }
-
@Override
public final void onDragExit(DragObject d) {
hideTooltip();
if (!d.dragComplete) {
d.dragView.setColor(0);
- resetHoverColor();
+ d.dragView.setAlpha(1f);
} else {
- // Restore the hover color
- d.dragView.setColor(mHoverColor);
+ d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
}
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 947388b..9df8d44 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -53,12 +53,14 @@
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -179,6 +181,7 @@
private final ArrayList<View> mIntersectingViews = new ArrayList<>();
private final Rect mOccupiedRect = new Rect();
private final int[] mDirectionVector = new int[2];
+
final int[] mPreviousReorderDirection = new int[2];
private static final int INVALID_DIRECTION = -100;
@@ -208,15 +211,14 @@
setWillNotDraw(false);
setClipToPadding(false);
mActivity = ActivityContext.lookupContext(context);
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- DeviceProfile grid = mActivity.getDeviceProfile();
-
- mBorderSpacing = grid.cellLayoutBorderSpacingPx;
+ mBorderSpacing = deviceProfile.cellLayoutBorderSpacingPx;
mCellWidth = mCellHeight = -1;
mFixedCellWidth = mFixedCellHeight = -1;
- mCountX = grid.inv.numColumns;
- mCountY = grid.inv.numRows;
+ mCountX = deviceProfile.inv.numColumns;
+ mCountY = deviceProfile.inv.numRows;
mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
@@ -233,7 +235,7 @@
mBackground.setCallback(this);
mBackground.setAlpha(0);
- mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
+ mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
// Initialize the data structures used for the drag visualization.
mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
@@ -960,15 +962,18 @@
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
- if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
- return;
- }
-
- Bitmap dragOutline = outlineProvider.generatedDragOutline;
if (cellX != oldDragCellX || cellY != oldDragCellY) {
mDragCell[0] = cellX;
mDragCell[1] = cellY;
+ applyColorExtraction(dragObject, mDragCell, spanX, spanY);
+
+ if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
+ return;
+ }
+
+ Bitmap dragOutline = outlineProvider.generatedDragOutline;
+
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
@@ -1010,6 +1015,22 @@
}
}
+ /** Applies the local color extraction to a dragging widget object. */
+ private void applyColorExtraction(DropTarget.DragObject dragObject, int[] targetCell, int spanX,
+ int spanY) {
+ // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
+ Drawable drawable = dragObject.dragView.getDrawable();
+ if (drawable instanceof AppWidgetHostViewDrawable) {
+ Workspace workspace =
+ Launcher.getLauncher(dragObject.dragView.getContext()).getWorkspace();
+ int screenId = workspace.getIdForScreen(this);
+ int pageId = workspace.getPageIndexForScreenId(screenId);
+ AppWidgetHostViewDrawable hostViewDrawable = ((AppWidgetHostViewDrawable) drawable);
+ cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
+ hostViewDrawable.getAppWidgetHostView().handleDrag(mTempRect, pageId);
+ }
+ }
+
@SuppressLint("StringFormatMatches")
public String getItemMoveDescription(int cellX, int cellY) {
if (mContainerType == HOTSEAT) {
@@ -2663,20 +2684,21 @@
}
public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
- int rowCount, int borderSpacing) {
+ int rowCount, int borderSpacing, @Nullable Rect inset) {
setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
- borderSpacing);
+ borderSpacing, inset);
}
/**
- * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, int)},
+ * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, int, Rect)},
* 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,
- int rowCount, float cellScaleX, float cellScaleY, int borderSpacing) {
+ int rowCount, float cellScaleX, float cellScaleY, int borderSpacing,
+ @Nullable Rect inset) {
if (isLockedToGrid) {
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
@@ -2697,6 +2719,13 @@
height = Math.round(myCellHeight) - topMargin - bottomMargin;
x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpacing);
y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpacing);
+
+ if (inset != null) {
+ x -= inset.left;
+ y -= inset.top;
+ width += inset.left + inset.right;
+ height += inset.top + inset.bottom;
+ }
}
}
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index cc119c9..e46aad2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -53,9 +53,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- // Get the hover color
- mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
-
setDrawable(R.drawable.ic_remove_shadow);
}
diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java
index 4827f36..7c387b1 100644
--- a/src/com/android/launcher3/DevicePaddings.java
+++ b/src/com/android/launcher3/DevicePaddings.java
@@ -52,8 +52,8 @@
ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
- public DevicePaddings(Context context) {
- try (XmlResourceParser parser = context.getResources().getXml(R.xml.size_limits)) {
+ public DevicePaddings(Context context, int devicePaddingId) {
+ try (XmlResourceParser parser = context.getResources().getXml(devicePaddingId)) {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
@@ -94,16 +94,27 @@
if (workspaceTopPadding == null
|| workspaceBottomPadding == null
|| hotseatBottomPadding == null) {
- throw new RuntimeException("DevicePadding missing padding.");
+ if (Utilities.IS_DEBUG_DEVICE) {
+ throw new RuntimeException("DevicePadding missing padding.");
+ }
}
- mDevicePaddings.add(new DevicePadding(maxWidthPx, workspaceTopPadding,
- workspaceBottomPadding, hotseatBottomPadding));
+ DevicePadding dp = new DevicePadding(maxWidthPx, workspaceTopPadding,
+ workspaceBottomPadding, hotseatBottomPadding);
+ if (dp.isValid()) {
+ mDevicePaddings.add(dp);
+ } else {
+ Log.e(TAG, "Invalid device padding found.");
+ if (Utilities.IS_DEBUG_DEVICE) {
+ throw new RuntimeException("DevicePadding is invalid");
+ }
+ }
}
}
}
}
} catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failure parsing device padding layout.", e);
throw new RuntimeException(e);
}
@@ -128,6 +139,9 @@
*/
public static final class DevicePadding {
+ // One for each padding since they can each be off by 1 due to rounding errors.
+ private static final int ROUNDING_THRESHOLD_PX = 3;
+
private final int maxEmptySpacePx;
private final PaddingFormula workspaceTopPadding;
private final PaddingFormula workspaceBottomPadding;
@@ -143,6 +157,10 @@
this.hotseatBottomPadding = hotseatBottomPadding;
}
+ public int getMaxEmptySpacePx() {
+ return maxEmptySpacePx;
+ }
+
public int getWorkspaceTopPadding(int extraSpacePx) {
return workspaceTopPadding.calculate(extraSpacePx);
}
@@ -154,6 +172,22 @@
public int getHotseatBottomPadding(int extraSpacePx) {
return hotseatBottomPadding.calculate(extraSpacePx);
}
+
+ public boolean isValid() {
+ int workspaceTopPadding = getWorkspaceTopPadding(maxEmptySpacePx);
+ int workspaceBottomPadding = getWorkspaceBottomPadding(maxEmptySpacePx);
+ int hotseatBottomPadding = getHotseatBottomPadding(maxEmptySpacePx);
+ int sum = workspaceTopPadding + workspaceBottomPadding + hotseatBottomPadding;
+ int diff = Math.abs(sum - maxEmptySpacePx);
+ if (DEBUG) {
+ Log.d(TAG, "isValid: workspaceTopPadding=" + workspaceTopPadding
+ + ", workspaceBottomPadding=" + workspaceBottomPadding
+ + ", hotseatBottomPadding=" + hotseatBottomPadding
+ + ", sum=" + sum
+ + ", diff=" + diff);
+ }
+ return diff <= ROUNDING_THRESHOLD_PX;
+ }
}
/**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 83a7d77..1ce5f4d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,7 +16,8 @@
package com.android.launcher3;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
+import static com.android.launcher3.ResourceUtils.pxFromDp;
+import static com.android.launcher3.Utilities.dpiFromPx;
import android.content.Context;
import android.content.res.Configuration;
@@ -25,9 +26,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;
@@ -35,6 +39,8 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.WindowBounds;
+import java.io.PrintWriter;
+
public class DeviceProfile {
private static final float TABLET_MIN_DPS = 600;
@@ -63,6 +69,8 @@
public final float aspectRatio;
+ public final boolean isScalableGrid;
+
/**
* The maximum amount of left/right workspace padding as a percentage of the screen width.
* To be clear, this means that up to 7% of the screen width can be used as left padding, and
@@ -76,22 +84,27 @@
private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
// Workspace
- public final int desiredWorkspaceLeftRightMarginPx;
- public final int cellLayoutBorderSpacingPx;
+ public final int desiredWorkspaceLeftRightOriginalPx;
+ public int desiredWorkspaceLeftRightMarginPx;
+ public final int cellLayoutBorderSpacingOriginalPx;
+ public int cellLayoutBorderSpacingPx;
public final int cellLayoutPaddingLeftRightPx;
public final int cellLayoutBottomPaddingPx;
public final int edgeMarginPx;
public float workspaceSpringLoadShrinkFactor;
public final int workspaceSpringLoadedBottomSpace;
+ private final int extraSpace;
public int workspaceTopPadding;
public int workspaceBottomPadding;
+ public int extraHotseatBottomPadding;
// Workspace page indicator
public final int workspacePageIndicatorHeight;
private final int mWorkspacePageIndicatorOverlapWorkspace;
// Workspace icons
+ public float iconScale;
public int iconSizePx;
public int iconTextSizePx;
public int iconDrawablePaddingPx;
@@ -99,13 +112,20 @@
public int cellWidthPx;
public int cellHeightPx;
- public int cellYPaddingPx;
public int workspaceCellPaddingXPx;
+ public int cellYPaddingPx;
+
// Folder
+ public float folderLabelTextScale;
+ public int folderLabelTextSizePx;
public int folderIconSizePx;
public int folderIconOffsetYPx;
+ // Folder content
+ public int folderContentPaddingLeftRight;
+ public int folderContentPaddingTop;
+
// Folder cell
public int folderCellWidthPx;
public int folderCellHeightPx;
@@ -132,6 +152,11 @@
public int allAppsIconDrawablePaddingPx;
public float allAppsIconTextSizePx;
+ // Overview
+ public int overviewTaskMarginPx;
+ public int overviewTaskIconSizePx;
+ public int overviewTaskThumbnailTopMarginPx;
+
// Widgets
public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
@@ -149,6 +174,12 @@
public DotRenderer mDotRendererWorkSpace;
public DotRenderer mDotRendererAllApps;
+ // Taskbar
+ public boolean isTaskbarPresent;
+ public int taskbarSize;
+ // How much of the bottom inset is due to Taskbar rather than other system elements.
+ public int nonOverlappingTaskbarInset;
+
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
Point minSize, Point maxSize, int width, int height, boolean isLandscape,
boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
@@ -157,25 +188,28 @@
this.inv = inv;
this.isLandscape = isLandscape;
this.isMultiWindowMode = isMultiWindowMode;
+ this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
windowX = windowPosition.x;
windowY = windowPosition.y;
+ isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
+
// 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;
// Constants from resources
- float swDPs = Utilities.dpiFromPx(
- Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
+ float swDPs = dpiFromPx(Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
boolean allowRotation = context.getResources().getBoolean(R.bool.allow_rotation);
// Tablet UI is built with assumption that simulated landscape is disabled.
isTablet = allowRotation && swDPs >= TABLET_MIN_DPS;
@@ -185,25 +219,53 @@
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
// Some more constants
- this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
-
context = getContext(context, info, isVerticalBarLayout()
? Configuration.ORIENTATION_LANDSCAPE
: Configuration.ORIENTATION_PORTRAIT);
final Resources res = context.getResources();
- edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
- desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
+ 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();
+ nonOverlappingTaskbarInset =
+ taskbarSize - windowInsets.getSystemWindowInsetBottom();
+ if (nonOverlappingTaskbarInset > 0) {
+ nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
+ }
+ }
+ availableHeightPx = nonFinalAvailableHeightPx;
- 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);
+ edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+
+ desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : isScalableGrid
+ ? res.getDimensionPixelSize(R.dimen.scalable_grid_left_right_margin)
+ : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
+ desiredWorkspaceLeftRightOriginalPx = desiredWorkspaceLeftRightMarginPx;
+
+ folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
+ folderContentPaddingLeftRight =
+ res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
+ folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_content_padding_top);
+
+ setCellLayoutBorderSpacing(pxFromDp(inv.borderSpacing, mInfo.metrics, 1f));
+ cellLayoutBorderSpacingOriginalPx = cellLayoutBorderSpacingPx;
+
int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
- int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
- if (isLandscape) {
+ int cellLayoutPadding = isScalableGrid
+ ? 0
+ : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
+
+ if (FeatureFlags.ENABLE_TWO_PANEL_HOME.get() && isTablet) {
+ cellLayoutPaddingLeftRightPx =
+ res.getDimensionPixelSize(R.dimen.two_panel_home_side_padding);
+ cellLayoutBottomPaddingPx = 0;
+ } else if (isLandscape) {
cellLayoutPaddingLeftRightPx = 0;
cellLayoutBottomPaddingPx = cellLayoutPadding;
} else {
@@ -235,29 +297,43 @@
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
int hotseatExtraVerticalSize =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
- hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics)
+ hotseatBarSizePx = pxFromDp(inv.iconSize, mInfo.metrics, 1f)
+ (isVerticalBarLayout()
? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
: (hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx
- + (ENABLE_FOUR_COLUMNS.get() ? 0 : hotseatExtraVerticalSize)));
+ + (isScalableGrid ? 0 : hotseatExtraVerticalSize)));
+
+ overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
+ overviewTaskIconSizePx =
+ isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get() ? res.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_size_grid) : res.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_size);
+ overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
// Calculate all of the remaining variables.
- int extraSpace = updateAvailableDimensions(res);
+ extraSpace = updateAvailableDimensions(res);
// Now that we have all of the variables calculated, we can tune certain sizes.
- if (ENABLE_FOUR_COLUMNS.get()) {
- DevicePadding padding = inv.devicePaddings.getDevicePadding(extraSpace);
- workspaceTopPadding = padding.getWorkspaceTopPadding(extraSpace);
- workspaceBottomPadding = padding.getWorkspaceBottomPadding(extraSpace);
+ if (isScalableGrid && inv.devicePaddings != null) {
+ // Paddings were created assuming no scaling, so we first unscale the extra space.
+ int unscaledExtraSpace = (int) (extraSpace / iconScale);
+ DevicePadding padding = inv.devicePaddings.getDevicePadding(unscaledExtraSpace);
- float hotseatBarBottomPadding = padding.getHotseatBottomPadding(extraSpace);
- hotseatBarSizePx += hotseatBarBottomPadding;
- hotseatBarBottomPaddingPx += hotseatBarBottomPadding;
+ int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
+ int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
+ int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
+
+ workspaceTopPadding = Math.round(paddingWorkspaceTop * iconScale);
+ workspaceBottomPadding = Math.round(paddingWorkspaceBottom * iconScale);
+ extraHotseatBottomPadding = Math.round(paddingHotseatBottom * iconScale);
+
+ hotseatBarSizePx += extraHotseatBottomPadding;
+ hotseatBarBottomPaddingPx += extraHotseatBottomPadding;
} else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
// We increase the hotseat size when there is extra space.
// ie. For a display with a large aspect ratio, we can keep the icons on the workspace
// in portrait mode closer together by adding more height to the hotseat.
// Note: This calculation was created after noticing a pattern in the design spec.
- extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
+ int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
- workspacePageIndicatorHeight;
hotseatBarSizePx += extraSpace;
hotseatBarBottomPaddingPx += extraSpace;
@@ -275,6 +351,30 @@
IconShape.DEFAULT_PATH_SIZE);
}
+ private void setCellLayoutBorderSpacing(int borderSpacing) {
+ if (isScalableGrid) {
+ cellLayoutBorderSpacingPx = borderSpacing;
+ folderContentPaddingLeftRight = borderSpacing;
+ folderContentPaddingTop = borderSpacing;
+ } else {
+ cellLayoutBorderSpacingPx = 0;
+ }
+ }
+
+ /**
+ * We inset the widget padding added by the system and instead rely on the border spacing
+ * between cells to create reliable consistency between widgets
+ */
+ public boolean shouldInsetWidgets() {
+ Rect widgetPadding = inv.defaultWidgetPadding;
+
+ // Check all sides to ensure that the widget won't overlap into another cell.
+ return cellLayoutBorderSpacingPx > widgetPadding.left
+ && cellLayoutBorderSpacingPx > widgetPadding.top
+ && cellLayoutBorderSpacingPx > widgetPadding.right
+ && cellLayoutBorderSpacingPx > widgetPadding.bottom;
+ }
+
public Builder toBuilder(Context context) {
Point size = new Point(availableWidthPx, availableHeightPx);
return new Builder(context, inv, mInfo)
@@ -356,40 +456,68 @@
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)
- + (cellLayoutBorderSpacingPx * (inv.numRows - 1));
- int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
+ Point workspacePadding = getTotalWorkspacePadding();
+
+ // Check to see if the icons fit within the available height.
+ float usedHeight = getCellLayoutHeight();
+ final int maxHeight = availableHeightPx - workspacePadding.y;
float extraHeight = Math.max(0, maxHeight - usedHeight);
- if (usedHeight > maxHeight) {
- float scale = maxHeight / usedHeight;
- updateIconSize(scale, res);
- extraHeight = 0;
+ float scaleY = maxHeight / usedHeight;
+ boolean shouldScale = scaleY < 1f;
+
+ float scaleX = 1f;
+ if (isScalableGrid) {
+ // We scale to fit the cellWidth and cellHeight in the available space.
+ // The benefit of scalable grids is that we can get consistent aspect ratios between
+ // devices.
+ float usedWidth = (cellWidthPx * inv.numColumns)
+ + (cellLayoutBorderSpacingPx * (inv.numColumns - 1))
+ + (desiredWorkspaceLeftRightMarginPx * 2);
+ // We do not subtract padding here, as we also scale the workspace padding if needed.
+ scaleX = availableWidthPx / usedWidth;
+ shouldScale = true;
}
+
+ if (shouldScale) {
+ float scale = Math.min(scaleX, scaleY);
+ updateIconSize(scale, res);
+ extraHeight = Math.max(0, maxHeight - getCellLayoutHeight());
+ }
+
updateAvailableFolderCellDimensions(res);
return Math.round(extraHeight);
}
+ private int getCellLayoutHeight() {
+ return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacingPx * (inv.numRows - 1));
+ }
+
/**
* Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
*/
public void updateIconSize(float scale, Resources res) {
+ iconScale = scale;
+
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
- iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, mInfo.metrics)
- * scale));
+ iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mInfo.metrics, scale));
iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
- if (ENABLE_FOUR_COLUMNS.get()) {
- cellHeightPx = iconSizePx + iconDrawablePaddingPx
- + Utilities.calculateTextHeight(iconTextSizePx)
- + (cellYPaddingPx * 2);
+ setCellLayoutBorderSpacing((int) (cellLayoutBorderSpacingOriginalPx * scale));
+
+ if (isScalableGrid) {
+ cellWidthPx = pxFromDp(inv.minCellWidth, mInfo.metrics, scale);
+ cellHeightPx = pxFromDp(inv.minCellHeight, mInfo.metrics, scale);
+ int cellContentHeight = iconSizePx + iconDrawablePaddingPx
+ + Utilities.calculateTextHeight(iconTextSizePx);
+ cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
+ desiredWorkspaceLeftRightMarginPx = (int) (desiredWorkspaceLeftRightOriginalPx * scale);
} else {
- cellYPaddingPx = 0;
+ cellWidthPx = iconSizePx + iconDrawablePaddingPx;
cellHeightPx = iconSizePx + iconDrawablePaddingPx
+ Utilities.calculateTextHeight(iconTextSizePx);
int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
@@ -402,11 +530,10 @@
iconDrawablePaddingPx = cellPaddingY;
}
}
- cellWidthPx = iconSizePx + iconDrawablePaddingPx;
// All apps
if (allAppsHasDifferentNumColumns()) {
- allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics);
+ allAppsIconSizePx = pxFromDp(inv.allAppsIconSize, mInfo.metrics);
allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics);
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
// We use 4 below to ensure labels are closer to their corresponding icon.
@@ -450,12 +577,10 @@
}
private void updateAvailableFolderCellDimensions(Resources res) {
- int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
- + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
- + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
-
updateFolderCellSize(1f, res);
+ final int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_height);
+
// Don't let the folder get too close to the edges of the screen.
int folderMargin = edgeMarginPx * 2;
Point totalWorkspacePadding = getTotalWorkspacePadding();
@@ -464,13 +589,14 @@
float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
+ ((inv.numFolderRows - 1) * cellLayoutBorderSpacingPx);
int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
- - folderMargin;
+ - folderMargin - folderContentPaddingTop;
float scaleY = contentMaxHeight / contentUsedHeight;
// Check if the icons fit within the available width.
float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
+ ((inv.numFolderColumns - 1) * cellLayoutBorderSpacingPx);
- int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
+ int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin
+ - folderContentPaddingLeftRight * 2;
float scaleX = contentMaxWidth / contentUsedWidth;
float scale = Math.min(scaleX, scaleY);
@@ -480,9 +606,10 @@
}
private void updateFolderCellSize(float scale, Resources res) {
- folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) * scale);
- folderChildTextSizePx =
- (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
+ float invIconSizeDp = isVerticalBarLayout() ? inv.landscapeIconSize : inv.iconSize;
+ folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mInfo.metrics, scale));
+ folderChildTextSizePx = pxFromDp(inv.iconTextSize, mInfo.metrics, scale);
+ folderLabelTextSizePx = (int) (folderChildTextSizePx * folderLabelTextScale);
int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
@@ -546,7 +673,8 @@
padding.right = hotseatBarSizePx;
}
} else {
- int paddingBottom = hotseatBarSizePx + workspacePageIndicatorHeight
+ int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
+ int paddingBottom = hotseatTop + workspacePageIndicatorHeight
+ workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
if (isTablet) {
// Pad the left and right of the workspace to ensure consistent spacing
@@ -556,15 +684,20 @@
((inv.numColumns - 1) * cellWidthPx)));
availablePaddingX = (int) Math.min(availablePaddingX,
widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
+ int hotseatVerticalPadding = isTaskbarPresent ? 0
+ : hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx;
int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom
- - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
- - hotseatBarBottomPaddingPx);
+ - (2 * inv.numRows * cellHeightPx) - hotseatVerticalPadding);
padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
+
+ if (FeatureFlags.ENABLE_TWO_PANEL_HOME.get()) {
+ padding.set(0, padding.top, 0, padding.bottom);
+ }
} else {
// Pad the top and bottom of the workspace with search/hotseat bar sizes
padding.set(desiredWorkspaceLeftRightMarginPx,
- workspaceTopPadding + edgeMarginPx,
+ workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx),
desiredWorkspaceLeftRightMarginPx,
paddingBottom);
}
@@ -611,10 +744,11 @@
mInsets.top + availableHeightPx);
} else {
// Folders should only appear below the drop target bar and above the hotseat
+ int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
return new Rect(mInsets.left + edgeMarginPx,
mInsets.top + dropTargetBarSizePx + edgeMarginPx,
mInsets.left + availableWidthPx - edgeMarginPx,
- mInsets.top + availableHeightPx - hotseatBarSizePx
+ mInsets.top + availableHeightPx - hotseatTop
- workspacePageIndicatorHeight - edgeMarginPx);
}
}
@@ -648,8 +782,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;
@@ -680,6 +819,100 @@
}
}
+ private String pxToDpStr(String name, float value) {
+ return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mInfo.metrics) + "dp)";
+ }
+
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "DeviceProfile:");
+ writer.println(prefix + "\t1 dp = " + mInfo.metrics.density + " px");
+
+ writer.println(prefix + "\tisTablet:" + isTablet);
+ writer.println(prefix + "\tisLargeTablet:" + isLargeTablet);
+ writer.println(prefix + "\tisPhone:" + isPhone);
+ writer.println(prefix + "\ttransposeLayoutWithOrientation:"
+ + transposeLayoutWithOrientation);
+
+ writer.println(prefix + "\tisLandscape:" + isLandscape);
+ writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
+
+ writer.println(prefix + pxToDpStr("windowX", windowX));
+ writer.println(prefix + pxToDpStr("windowY", windowY));
+ writer.println(prefix + pxToDpStr("widthPx", widthPx));
+ writer.println(prefix + pxToDpStr("heightPx", heightPx));
+
+ writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx));
+ writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx));
+
+ writer.println(prefix + "\taspectRatio:" + aspectRatio);
+
+ writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
+
+ writer.println(prefix + "\tinv.minCellWidth:" + inv.minCellWidth + "dp");
+ writer.println(prefix + "\tinv.minCellHeight:" + inv.minCellHeight + "dp");
+
+ writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
+ writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
+
+ writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x));
+ writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y));
+
+ writer.println(prefix + "\tinv.iconSize:" + inv.iconSize + "dp");
+ writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
+ writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
+ writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx));
+
+ writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx));
+ writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx));
+ writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx));
+ writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
+ writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
+ folderChildDrawablePaddingPx));
+
+ writer.println(prefix + pxToDpStr("cellLayoutBorderSpacingPx",
+ cellLayoutBorderSpacingPx));
+ writer.println(prefix + pxToDpStr("desiredWorkspaceLeftRightMarginPx",
+ desiredWorkspaceLeftRightMarginPx));
+
+ writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
+ writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
+ writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
+ allAppsIconDrawablePaddingPx));
+ writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
+
+ writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
+ writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
+ writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
+ writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
+ writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
+ hotseatBarSidePaddingStartPx));
+ writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
+ hotseatBarSidePaddingEndPx));
+
+ writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
+
+ writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize));
+ writer.println(prefix + pxToDpStr("nonOverlappingTaskbarInset",
+ nonOverlappingTaskbarInset));
+
+ writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
+ writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
+ writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
+ writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
+
+ writer.println(prefix + pxToDpStr("scaleToFit", iconScale));
+ writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
+
+ if (inv.devicePaddings != null) {
+ int unscaledExtraSpace = (int) (extraSpace / iconScale);
+ writer.println(prefix + pxToDpStr("maxEmptySpace",
+ inv.devicePaddings.getDevicePadding(unscaledExtraSpace).getMaxEmptySpacePx()));
+ }
+ writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
+ writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
+ writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding));
+ }
+
private static Context getContext(Context c, Info info, int orientation) {
Configuration config = new Configuration(c.getResources().getConfiguration());
config.orientation = orientation;
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 02c6162..c79dabe 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -131,10 +131,9 @@
public void reset() {
if (!TextUtils.isEmpty(getText())) {
setText("");
- } else {
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- return;
- }
+ }
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ return;
}
if (isFocused()) {
View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
deleted file mode 100644
index e5aecf7..0000000
--- a/src/com/android/launcher3/FocusHelper.java
+++ /dev/null
@@ -1,567 +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;
-
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.SoundEffectConstants;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderPagedView;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.FocusLogic;
-import com.android.launcher3.util.Thunk;
-
-/**
- * A keyboard listener we set on all the workspace icons.
- */
-class IconKeyEventListener implements View.OnKeyListener {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- return FocusHelper.handleIconKeyEvent(v, keyCode, event);
- }
-}
-
-/**
- * A keyboard listener we set on all the hotseat buttons.
- */
-class HotseatIconKeyEventListener implements View.OnKeyListener {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
- }
-}
-
-/**
- * A keyboard listener we set on full screen pages (e.g. custom content).
- */
-class FullscreenKeyEventListener implements View.OnKeyListener {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
- || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
- // Handle the key event just like a workspace icon would in these cases. In this case,
- // it will basically act as if there is a single icon in the top left (so you could
- // think of the fullscreen page as a focusable fullscreen widget).
- return FocusHelper.handleIconKeyEvent(v, keyCode, event);
- }
- return false;
- }
-}
-
-/**
- * TODO: Reevaluate if this is still required
- */
-public class FocusHelper {
-
- private static final String TAG = "FocusHelper";
- private static final boolean DEBUG = false;
-
- /**
- * Handles key events in paged folder.
- */
- public static class PagedFolderKeyEventListener implements View.OnKeyListener {
-
- private final Folder mFolder;
-
- public PagedFolderKeyEventListener(Folder folder) {
- mFolder = folder;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent e) {
- boolean consume = FocusLogic.shouldConsume(keyCode);
- if (e.getAction() == KeyEvent.ACTION_UP) {
- return consume;
- }
- if (DEBUG) {
- Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
- KeyEvent.keyCodeToString(keyCode)));
- }
-
- if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
- if (FeatureFlags.IS_STUDIO_BUILD) {
- throw new IllegalStateException("Parent of the focused item is not supported.");
- } else {
- return false;
- }
- }
-
- // Initialize variables.
- final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
- final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
-
- final int iconIndex = itemContainer.indexOfChild(v);
- final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
-
- final int pageIndex = pagedView.indexOfChild(cellLayout);
- final int pageCount = pagedView.getPageCount();
- final boolean isLayoutRtl = Utilities.isRtl(v.getResources());
-
- int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
- // Process focus.
- int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
- pageCount, isLayoutRtl);
- if (newIconIndex == FocusLogic.NOOP) {
- handleNoopKey(keyCode, v);
- return consume;
- }
- ShortcutAndWidgetContainer newParent = null;
- View child = null;
-
- switch (newIconIndex) {
- case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
- case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
- newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
- if (newParent != null) {
- int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
- pagedView.snapToPage(pageIndex - 1);
- child = newParent.getChildAt(
- ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
- ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
- row);
- }
- break;
- case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
- newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
- if (newParent != null) {
- pagedView.snapToPage(pageIndex - 1);
- child = newParent.getChildAt(0, 0);
- }
- break;
- case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
- newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
- if (newParent != null) {
- pagedView.snapToPage(pageIndex - 1);
- child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1);
- }
- break;
- case FocusLogic.NEXT_PAGE_FIRST_ITEM:
- newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
- if (newParent != null) {
- pagedView.snapToPage(pageIndex + 1);
- child = newParent.getChildAt(0, 0);
- }
- break;
- case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
- case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
- newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
- if (newParent != null) {
- pagedView.snapToPage(pageIndex + 1);
- child = FocusLogic.getAdjacentChildInNextFolderPage(
- newParent, v, newIconIndex);
- }
- break;
- case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
- child = cellLayout.getChildAt(0, 0);
- break;
- case FocusLogic.CURRENT_PAGE_LAST_ITEM:
- child = pagedView.getLastItem();
- break;
- default: // Go to some item on the current page.
- child = itemContainer.getChildAt(newIconIndex);
- break;
- }
- if (child != null) {
- child.requestFocus();
- playSoundEffect(keyCode, v);
- } else {
- handleNoopKey(keyCode, v);
- }
- return consume;
- }
-
- public void handleNoopKey(int keyCode, View v) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
- mFolder.mFolderName.requestFocus();
- playSoundEffect(keyCode, v);
- }
- }
- }
-
- /**
- * Handles key events in the workspace hotseat (bottom of the screen).
- * <p>Currently we don't special case for the phone UI in different orientations, even though
- * the hotseat is on the side in landscape mode. This is to ensure that accessibility
- * consistency is maintained across rotations.
- */
- static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
- boolean consume = FocusLogic.shouldConsume(keyCode);
- if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
- return consume;
- }
-
- final Launcher launcher = Launcher.getLauncher(v.getContext());
- final DeviceProfile profile = launcher.getDeviceProfile();
-
- if (DEBUG) {
- Log.v(TAG, String.format(
- "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
- KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
- }
-
- // Initialize the variables.
- final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
- final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
- final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
-
- final ItemInfo itemInfo = (ItemInfo) v.getTag();
- int pageIndex = workspace.getNextPage();
- int pageCount = workspace.getChildCount();
- int iconIndex = hotseatParent.indexOfChild(v);
- int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
- .getChildAt(iconIndex).getLayoutParams()).cellX;
-
- final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
- if (iconLayout == null) {
- // This check is to guard against cases where key strokes rushes in when workspace
- // child creation/deletion is still in flux. (e.g., during drop or fling
- // animation.)
- return consume;
- }
- final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
-
- ViewGroup parent = null;
- int[][] matrix = null;
-
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
- !profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
- iconIndex += iconParent.getChildCount();
- parent = iconParent;
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
- profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
- iconIndex += iconParent.getChildCount();
- parent = iconParent;
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
- profile.isVerticalBarLayout()) {
- keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
- } else {
- // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
- // matrix extended with hotseat.
- matrix = FocusLogic.createSparseMatrix(hotseatLayout);
- parent = hotseatParent;
- }
-
- // Process the focus.
- int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
- pageCount, Utilities.isRtl(v.getResources()));
-
- View newIcon = null;
- switch (newIconIndex) {
- case FocusLogic.NEXT_PAGE_FIRST_ITEM:
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
- newIcon = parent.getChildAt(0);
- // TODO(hyunyoungs): handle cases where the child is not an icon but
- // a folder or a widget.
- workspace.snapToPage(pageIndex + 1);
- break;
- case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
- newIcon = parent.getChildAt(0);
- // TODO(hyunyoungs): handle cases where the child is not an icon but
- // a folder or a widget.
- workspace.snapToPage(pageIndex - 1);
- break;
- case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
- newIcon = parent.getChildAt(parent.getChildCount() - 1);
- // TODO(hyunyoungs): handle cases where the child is not an icon but
- // a folder or a widget.
- workspace.snapToPage(pageIndex - 1);
- break;
- case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
- case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
- // Go to the previous page but keep the focus on the same hotseat icon.
- workspace.snapToPage(pageIndex - 1);
- break;
- case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
- case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
- // Go to the next page but keep the focus on the same hotseat icon.
- workspace.snapToPage(pageIndex + 1);
- break;
- }
- if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
- newIconIndex -= iconParent.getChildCount();
- }
- if (parent != null) {
- if (newIcon == null && newIconIndex >= 0) {
- newIcon = parent.getChildAt(newIconIndex);
- }
- if (newIcon != null) {
- newIcon.requestFocus();
- playSoundEffect(keyCode, v);
- }
- }
- return consume;
- }
-
- /**
- * Handles key events in a workspace containing icons.
- */
- static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
- boolean consume = FocusLogic.shouldConsume(keyCode);
- if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
- return consume;
- }
-
- Launcher launcher = Launcher.getLauncher(v.getContext());
- DeviceProfile profile = launcher.getDeviceProfile();
-
- if (DEBUG) {
- Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
- KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
- }
-
- // Initialize the variables.
- ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
- CellLayout iconLayout = (CellLayout) parent.getParent();
- final Workspace workspace = (Workspace) iconLayout.getParent();
- final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
- final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar);
- final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
-
- final ItemInfo itemInfo = (ItemInfo) v.getTag();
- final int iconIndex = parent.indexOfChild(v);
- final int pageIndex = workspace.indexOfChild(iconLayout);
- final int pageCount = workspace.getChildCount();
-
- CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
- ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
- int[][] matrix;
-
- // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
- // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
- // with the hotseat.
- if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
- profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
- } else {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- }
-
- // Process the focus.
- int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
- pageCount, Utilities.isRtl(v.getResources()));
- boolean isRtl = Utilities.isRtl(v.getResources());
- View newIcon = null;
- CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
- switch (newIconIndex) {
- case FocusLogic.NOOP:
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
- newIcon = tabs;
- }
- break;
- case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
- case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
- int newPageIndex = pageIndex - 1;
- if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
- newPageIndex = pageIndex + 1;
- }
- int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
- parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
- if (parent != null) {
- iconLayout = (CellLayout) parent.getParent();
- matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
- iconLayout.getCountX(), row);
- newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
- newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
- if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
- newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
- isRtl);
- } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
- newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
- isRtl);
- } else {
- newIcon = parent.getChildAt(newIconIndex);
- }
- }
- break;
- case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
- workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
- newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
- if (newIcon == null) {
- // Check the hotseat if no focusable item was found on the workspace.
- newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
- workspace.snapToPage(pageIndex - 1);
- }
- break;
- case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
- newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
- break;
- case FocusLogic.NEXT_PAGE_FIRST_ITEM:
- newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
- break;
- case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
- case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
- newPageIndex = pageIndex + 1;
- if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
- newPageIndex = pageIndex - 1;
- }
- row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
- parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
- if (parent != null) {
- iconLayout = (CellLayout) parent.getParent();
- matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
- newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
- newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
- if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
- newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
- isRtl);
- } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
- newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
- isRtl);
- } else {
- newIcon = parent.getChildAt(newIconIndex);
- }
- }
- break;
- case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
- newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
- if (newIcon == null) {
- // Check the hotseat if no focusable item was found on the workspace.
- newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
- }
- break;
- case FocusLogic.CURRENT_PAGE_LAST_ITEM:
- newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
- if (newIcon == null) {
- // Check the hotseat if no focusable item was found on the workspace.
- newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
- }
- break;
- default:
- // current page, some item.
- if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
- newIcon = parent.getChildAt(newIconIndex);
- } else if (parent.getChildCount() <= newIconIndex &&
- newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
- newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
- }
- break;
- }
- if (newIcon != null) {
- newIcon.requestFocus();
- playSoundEffect(keyCode, v);
- }
- return consume;
- }
-
- //
- // Helper methods.
- //
-
- /**
- * Private helper method to get the CellLayoutChildren given a CellLayout index.
- */
- @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
- ViewGroup container, int i) {
- CellLayout parent = (CellLayout) container.getChildAt(i);
- return parent.getShortcutsAndWidgets();
- }
-
- /**
- * Helper method to be used for playing sound effects.
- */
- @Thunk static void playSoundEffect(int keyCode, View v) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_PAGE_DOWN:
- case KeyEvent.KEYCODE_MOVE_END:
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_PAGE_UP:
- case KeyEvent.KEYCODE_MOVE_HOME:
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- break;
- default:
- break;
- }
- }
-
- private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
- int pageIndex, boolean isRtl) {
- if (pageIndex - 1 < 0) {
- return null;
- }
- CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
- View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
- if (newIcon == null) {
- // Check the hotseat if no focusable item was found on the workspace.
- newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
- workspace.snapToPage(pageIndex - 1);
- }
- return newIcon;
- }
-
- private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
- int pageIndex, boolean isRtl) {
- if (pageIndex + 1 >= workspace.getPageCount()) {
- return null;
- }
- CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
- View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
- if (newIcon == null) {
- // Check the hotseat if no focusable item was found on the workspace.
- newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
- workspace.snapToPage(pageIndex + 1);
- }
- return newIcon;
- }
-
- private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
- View icon;
- int countX = cellLayout.getCountX();
- for (int y = 0; y < cellLayout.getCountY(); y++) {
- int increment = isRtl ? -1 : 1;
- for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
- if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
- return icon;
- }
- }
- }
- return null;
- }
-
- private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
- boolean isRtl) {
- View icon;
- int countX = cellLayout.getCountX();
- for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
- int increment = isRtl ? 1 : -1;
- for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
- if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
- return icon;
- }
- }
- }
- return null;
- }
-}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 4f4f2a7..4049ed6 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -20,15 +20,15 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
-import com.android.launcher3.config.FeatureFlags;
-
import java.util.function.Consumer;
/**
@@ -36,6 +36,9 @@
*/
public class Hotseat extends CellLayout implements Insettable {
+ // Ratio of empty space, qsb should take up to appear visually centered.
+ public static final float QSB_CENTER_FACTOR = .325f;
+
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mHasVerticalHotseat;
private Workspace mWorkspace;
@@ -43,6 +46,12 @@
@Nullable
private Consumer<Boolean> mOnVisibilityAggregatedCallback;
+ private final View mQsb;
+ private final int mQsbHeight;
+
+ private final View mTaskbarView;
+ private final int mTaskbarViewHeight;
+
public Hotseat(Context context) {
this(context, null);
}
@@ -53,6 +62,15 @@
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+ mQsbHeight = mQsb.getLayoutParams().height;
+ addView(mQsb);
+
+ mTaskbarView = LayoutInflater.from(context).inflate(R.layout.taskbar_view, this, false);
+ mTaskbarViewHeight = mTaskbarView.getLayoutParams().height;
+ // We want taskbar in the back so its background applies to Hotseat as well.
+ addView(mTaskbarView, 0);
}
/**
@@ -76,7 +94,7 @@
if (hasVerticalHotseat) {
setGridSize(1, idp.numHotseatIcons);
} else {
- setGridSize(idp.numHotseatIcons, FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 2 : 1);
+ setGridSize(idp.numHotseatIcons, 1);
}
showInlineQsb();
}
@@ -87,6 +105,7 @@
DeviceProfile grid = mActivity.getDeviceProfile();
if (grid.isVerticalBarLayout()) {
+ mQsb.setVisibility(View.GONE);
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
if (grid.isSeascape()) {
lp.gravity = Gravity.LEFT;
@@ -96,16 +115,21 @@
lp.width = grid.hotseatBarSizePx + insets.right;
}
} else {
+ mQsb.setVisibility(View.VISIBLE);
lp.gravity = Gravity.BOTTOM;
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
- lp.height = grid.hotseatBarSizePx + insets.bottom;
+ lp.height = (grid.isTaskbarPresent
+ ? grid.workspacePadding.bottom
+ : grid.hotseatBarSizePx)
+ + (grid.isTaskbarPresent ? grid.taskbarSize : insets.bottom);
}
- Rect padding = grid.getHotseatLayoutPadding();
- int paddingBottom = padding.bottom;
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !grid.isVerticalBarLayout()) {
- paddingBottom -= grid.hotseatBarBottomPaddingPx;
+
+ if (!grid.isTaskbarPresent) {
+ // When taskbar is present, we set the padding separately to ensure a seamless visual
+ // handoff between taskbar and hotseat during drag and drop.
+ Rect padding = grid.getHotseatLayoutPadding();
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
- setPadding(padding.left, padding.top, padding.right, paddingBottom);
setLayoutParams(lp);
InsettableFrameLayout.dispatchInsets(this, insets);
@@ -160,4 +184,69 @@
protected void showInlineQsb() {
//Does nothing
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = getShortcutsAndWidgets().getMeasuredWidth();
+ mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
+ mTaskbarView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mTaskbarViewHeight, MeasureSpec.EXACTLY));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ int qsbWidth = mQsb.getMeasuredWidth();
+ int left = (r - l - qsbWidth) / 2;
+ int right = left + qsbWidth;
+
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ int freeSpace = dp.isTaskbarPresent
+ ? dp.workspacePadding.bottom
+ : dp.hotseatBarSizePx - dp.hotseatCellHeightPx - mQsbHeight;
+ int bottom = b - t
+ - (int) (freeSpace * QSB_CENTER_FACTOR)
+ - (dp.isTaskbarPresent ? dp.taskbarSize : dp.getInsets().bottom);
+ int top = bottom - mQsbHeight;
+ mQsb.layout(left, top, right, bottom);
+
+ int taskbarWidth = mTaskbarView.getMeasuredWidth();
+ left = (r - l - taskbarWidth) / 2;
+ right = left + taskbarWidth;
+ bottom = b - t;
+ top = bottom - mTaskbarViewHeight;
+ mTaskbarView.layout(left, top, right, bottom);
+ }
+
+ /**
+ * Returns the first View for which the given itemOperator returns true, or null.
+ */
+ public View getFirstItemMatch(Workspace.ItemOperator itemOperator) {
+ return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
+ }
+
+ /**
+ * Sets the alpha value of just our ShortcutAndWidgetContainer.
+ */
+ public void setIconsAlpha(float alpha) {
+ getShortcutsAndWidgets().setAlpha(alpha);
+ }
+
+ /**
+ * Returns the QSB inside hotseat
+ */
+ public View getQsb() {
+ return mQsb;
+ }
+
+ /**
+ * Returns the Taskbar inside hotseat
+ */
+ public View getTaskbarView() {
+ return mTaskbarView;
+ }
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2a08c50..3dbd479 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
@@ -111,6 +112,10 @@
public float allAppsIconSize;
public float allAppsIconTextSize;
+ public float minCellHeight;
+ public float minCellWidth;
+ public float borderSpacing;
+
private SparseArray<TypedValue> mExtraAttrs;
/**
@@ -123,6 +128,12 @@
*/
public int numAllAppsColumns;
+ /**
+ * Do not query directly. see {@link DeviceProfile#isScalableGrid}.
+ */
+ protected boolean isScalable;
+ public int devicePaddingId;
+
public String dbFile;
public int defaultLayoutId;
int demoModeLayoutId;
@@ -130,7 +141,7 @@
public DeviceProfile landscapeProfile;
public DeviceProfile portraitProfile;
- public DevicePaddings devicePaddings;
+ @Nullable public DevicePaddings devicePaddings;
public Point defaultWallpaperSize;
public Rect defaultWidgetPadding;
@@ -154,6 +165,11 @@
iconTextSize = p.iconTextSize;
numHotseatIcons = p.numHotseatIcons;
numAllAppsColumns = p.numAllAppsColumns;
+ isScalable = p.isScalable;
+ devicePaddingId = p.devicePaddingId;
+ minCellHeight = p.minCellHeight;
+ minCellWidth = p.minCellWidth;
+ borderSpacing = p.borderSpacing;
dbFile = p.dbFile;
allAppsIconSize = p.allAppsIconSize;
allAppsIconTextSize = p.allAppsIconTextSize;
@@ -213,12 +229,17 @@
result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
result.allAppsIconSize = Math.min(
defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize);
+ result.minCellHeight = defaultDisplayOption.minCellHeight;
+ result.minCellWidth = defaultDisplayOption.minCellWidth;
+ result.borderSpacing = defaultDisplayOption.borderSpacing;
- devicePaddings = new DevicePaddings(context);
initGrid(context, myInfo, result);
}
public static String getCurrentGridName(Context context) {
+ if (ENABLE_TWO_PANEL_HOME.get()) {
+ return ENABLE_TWO_PANEL_HOME.key;
+ }
if (ENABLE_FOUR_COLUMNS.get()) {
return ENABLE_FOUR_COLUMNS.key;
}
@@ -242,7 +263,6 @@
ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
- devicePaddings = new DevicePaddings(context);
initGrid(context, displayInfo, displayOption);
return displayOption.grid.name;
}
@@ -259,6 +279,8 @@
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
numAllAppsColumns = closestProfile.numAllAppsColumns;
+ isScalable = closestProfile.isScalable;
+ devicePaddingId = closestProfile.devicePaddingId;
mExtraAttrs = closestProfile.extraAttrs;
@@ -269,6 +291,10 @@
iconTextSize = displayOption.iconTextSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
+ minCellHeight = displayOption.minCellHeight;
+ minCellWidth = displayOption.minCellWidth;
+ borderSpacing = displayOption.borderSpacing;
+
if (Utilities.isGridOptionsEnabled(context)) {
allAppsIconSize = displayOption.allAppsIconSize;
allAppsIconTextSize = displayOption.allAppsIconTextSize;
@@ -277,6 +303,10 @@
allAppsIconTextSize = iconTextSize;
}
+ if (devicePaddingId != 0) {
+ devicePaddings = new DevicePaddings(context, devicePaddingId);
+ }
+
// If the partner customization apk contains any grid overrides, apply them
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, displayInfo.metrics);
@@ -589,6 +619,9 @@
private final int defaultLayoutId;
private final int demoModeLayoutId;
+ private final boolean isScalable;
+ private final int devicePaddingId;
+
private final SparseArray<TypedValue> extraAttrs;
public GridOption(Context context, AttributeSet attrs) {
@@ -612,6 +645,11 @@
numAllAppsColumns = a.getInt(
R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
+ isScalable = a.getBoolean(
+ R.styleable.GridDisplayOption_isScalable, false);
+ devicePaddingId = a.getResourceId(
+ R.styleable.GridDisplayOption_devicePaddingId, 0);
+
a.recycle();
extraAttrs = Themes.createValueMap(context, attrs,
@@ -626,6 +664,10 @@
private final float minHeightDps;
private final boolean canBeDefault;
+ private float minCellHeight;
+ private float minCellWidth;
+ private float borderSpacing;
+
private float iconSize;
private float iconTextSize;
private float landscapeIconSize;
@@ -643,6 +685,10 @@
canBeDefault = a.getBoolean(
R.styleable.ProfileDisplayOption_canBeDefault, false);
+ minCellHeight = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightDps, 0);
+ minCellWidth = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthDps, 0);
+ borderSpacing = a.getFloat(R.styleable.ProfileDisplayOption_borderSpacingDps, 0);
+
iconSize = a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
iconSize);
@@ -664,6 +710,9 @@
minWidthDps = 0;
minHeightDps = 0;
canBeDefault = false;
+ minCellHeight = 0;
+ minCellWidth = 0;
+ borderSpacing = 0;
}
private DisplayOption multiply(float w) {
@@ -672,6 +721,9 @@
allAppsIconSize *= w;
iconTextSize *= w;
allAppsIconTextSize *= w;
+ minCellHeight *= w;
+ minCellWidth *= w;
+ borderSpacing *= w;
return this;
}
@@ -681,6 +733,9 @@
allAppsIconSize += p.allAppsIconSize;
iconTextSize += p.iconTextSize;
allAppsIconTextSize += p.allAppsIconTextSize;
+ minCellHeight += p.minCellHeight;
+ minCellWidth += p.minCellWidth;
+ borderSpacing += p.borderSpacing;
return this;
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 49adf1f..89c0f66 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -42,6 +42,7 @@
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;
@@ -60,7 +61,6 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
-import android.app.ActivityOptions;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.ActivityNotFoundException;
@@ -113,13 +113,13 @@
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.allapps.search.LiveSearchManager;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
@@ -180,7 +180,9 @@
import com.android.launcher3.views.FloatingSurfaceView;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.views.ScrimView;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -272,11 +274,8 @@
private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
- private LauncherAppTransitionManager mAppTransitionManager;
private Configuration mOldConfig;
- private LiveSearchManager mLiveSearchManager;
-
@Thunk
Workspace mWorkspace;
@Thunk
@@ -308,8 +307,6 @@
@Thunk
boolean mWorkspaceLoading = true;
- private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
-
// Used to notify when an activity launch has been deferred because launcher is not yet resumed
// TODO: See if we can remove this later
private Runnable mOnDeferredActivityLaunchCallback;
@@ -401,8 +398,6 @@
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new StateManager<>(this, NORMAL);
- mLiveSearchManager = new LiveSearchManager(this);
-
mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
mAppWidgetManager = new WidgetManagerHelper(this);
@@ -415,9 +410,6 @@
crossFadeWithPreviousAppearance();
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
- mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
- mAppTransitionManager.registerRemoteAnimations();
-
boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
if (internalStateHandled) {
if (savedInstanceState != null) {
@@ -493,10 +485,6 @@
}
}
- public LiveSearchManager getLiveSearchManager() {
- return mLiveSearchManager;
- }
-
protected LauncherOverlayManager getDefaultOverlay() {
return new LauncherOverlayManager() { };
}
@@ -1067,7 +1055,11 @@
if (ALL_APPS.equals(state)) {
// creates new instance ID since new all apps session is started.
mAllAppsSessionLogId = new InstanceIdSequence().newInstanceId();
- getStatsLogManager().logger().log(LAUNCHER_ALLAPPS_ENTRY);
+ 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) {
@@ -1082,15 +1074,6 @@
TraceHelper.FLAG_UI_EVENT);
super.onResume();
- if (!mOnResumeCallbacks.isEmpty()) {
- final ArrayList<OnResumeCallback> resumeCallbacks = new ArrayList<>(mOnResumeCallbacks);
- mOnResumeCallbacks.clear();
- for (int i = resumeCallbacks.size() - 1; i >= 0; i--) {
- resumeCallbacks.get(i).onLauncherResume();
- }
- resumeCallbacks.clear();
- }
-
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
} else {
@@ -1601,10 +1584,7 @@
LauncherAppState.getIDP(this).removeOnChangeListener(this);
mOverlayManager.onActivityDestroyed(this);
- mAppTransitionManager.unregisterRemoteAnimations();
- mAppTransitionManager.unregisterRemoteTransitions();
mUserChangedCallbackCloseable.close();
- mLiveSearchManager.stop();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1928,16 +1908,6 @@
@TargetApi(Build.VERSION_CODES.M)
@Override
- public ActivityOptions getActivityLaunchOptions(View v) {
- return mAppTransitionManager.getActivityLaunchOptions(this, v);
- }
-
- public LauncherAppTransitionManager getAppTransitionManager() {
- return mAppTransitionManager;
- }
-
- @TargetApi(Build.VERSION_CODES.M)
- @Override
protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
// Due to legacy reasons, direct call shortcuts require Launchers to have the
// corresponding permission. Show the appropriate permission prompt if that
@@ -1986,7 +1956,7 @@
// state when we return to launcher.
BubbleTextView btv = (BubbleTextView) v;
btv.setStayPressed(true);
- addOnResumeCallback(btv);
+ addOnResumeCallback(() -> btv.setStayPressed(false));
}
return success;
}
@@ -2030,10 +2000,6 @@
return result;
}
- public void addOnResumeCallback(OnResumeCallback callback) {
- mOnResumeCallbacks.add(callback);
- }
-
/**
* Persistant callback which notifies when an activity launch is deferred because the activity
* was not yet resumed.
@@ -2668,6 +2634,7 @@
mDragLayer.dump(prefix, writer);
mStateManager.dump(prefix, writer);
mPopupDataProvider.dump(prefix, writer);
+ mDeviceProfile.dump(prefix, writer);
try {
FileLog.flushAll(writer);
@@ -2753,8 +2720,10 @@
return super.onKeyUp(keyCode, event);
}
- protected StateHandler<LauncherState>[] createStateHandlers() {
- return new StateHandler[] { getAllAppsController(), getWorkspace() };
+ @Override
+ protected void collectStateHandlers(List<StateHandler> out) {
+ out.add(getAllAppsController());
+ out.add(getWorkspace());
}
public TouchController[] createTouchControllers() {
@@ -2805,15 +2774,6 @@
return (T) activityContext;
}
-
- /**
- * Callback for listening for onResume
- */
- public interface OnResumeCallback {
-
- void onLauncherResume();
- }
-
/**
* Cross-fades the launcher's updated appearance with its previous appearance.
*
@@ -2856,6 +2816,14 @@
return false;
}
+ public boolean supportsAdaptiveIconAnimation(View clickedView) {
+ return false;
+ }
+
+ public DragOptions getDefaultWorkspaceDragOptions() {
+ return new DragOptions();
+ }
+
private static class NonConfigInstance {
public Configuration config;
public Bitmap snapshot;
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
deleted file mode 100644
index 0fa441a..0000000
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Manages the opening and closing app transitions from Launcher.
- */
-public class LauncherAppTransitionManager implements ResourceBasedOverride {
-
- public static LauncherAppTransitionManager newInstance(Context context) {
- return Overrides.getObject(LauncherAppTransitionManager.class,
- context, R.string.app_transition_manager_class);
- }
-
- public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
- int left = 0, top = 0;
- int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
- if (v instanceof BubbleTextView) {
- // Launch from center of icon, not entire view
- Drawable icon = ((BubbleTextView) v).getIcon();
- if (icon != null) {
- Rect bounds = icon.getBounds();
- left = (width - bounds.width()) / 2;
- top = v.getPaddingTop();
- width = bounds.width();
- height = bounds.height();
- }
- }
- return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
- }
-
- public boolean supportsAdaptiveIconAnimation(View clickedView) {
- return false;
- }
-
- /**
- * Registers remote animations for certain system transitions.
- */
- public void registerRemoteAnimations() {
- // Do nothing
- }
-
- /**
- * Unregisters all remote animations.
- */
- public void unregisterRemoteAnimations() {
- // Do nothing
- }
-
- /**
- * Registers remote transitions for certain system transitions.
- */
- public void registerRemoteTransitions() {
- // Do nothing
- }
-
- /**
- * Unregisters all remote transitions.
- */
- public void unregisterRemoteTransitions() {
- // Do nothing
- }
-}
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
deleted file mode 100644
index 618b5de..0000000
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package com.android.launcher3;
-
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.UserHandle;
-
-import com.android.launcher3.icons.ComponentWithLabelAndIcon;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-
-/**
- * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
- * a common object for describing both framework provided AppWidgets as well as custom widgets
- * (who's implementation is owned by the launcher). This object represents a widget type / class,
- * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo}
- */
-public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
- implements ComponentWithLabelAndIcon {
-
- public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
-
- public int spanX;
- public int spanY;
- public int minSpanX;
- public int minSpanY;
-
- public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
- AppWidgetProviderInfo info) {
- final LauncherAppWidgetProviderInfo launcherInfo;
- if (info instanceof LauncherAppWidgetProviderInfo) {
- launcherInfo = (LauncherAppWidgetProviderInfo) info;
- } else {
-
- // In lieu of a public super copy constructor, we first write the AppWidgetProviderInfo
- // into a parcel, and then construct a new LauncherAppWidgetProvider info from the
- // associated super parcel constructor. This allows us to copy non-public members without
- // using reflection.
- Parcel p = Parcel.obtain();
- info.writeToParcel(p, 0);
- p.setDataPosition(0);
- launcherInfo = new LauncherAppWidgetProviderInfo(p);
- p.recycle();
- }
- launcherInfo.initSpans(context);
- return launcherInfo;
- }
-
- protected LauncherAppWidgetProviderInfo() {}
-
- protected LauncherAppWidgetProviderInfo(Parcel in) {
- super(in);
- }
-
- public void initSpans(Context context) {
- InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
-
- Point landCellSize = idp.landscapeProfile.getCellSize();
- Point portCellSize = idp.portraitProfile.getCellSize();
-
- // Always assume we're working with the smallest span to make sure we
- // reserve enough space in both orientations.
- float smallestCellWidth = Math.min(landCellSize.x, portCellSize.x);
- float smallestCellHeight = Math.min(landCellSize.y, portCellSize.y);
-
- // We want to account for the extra amount of padding that we are adding to the widget
- // to ensure that it gets the full amount of space that it has requested.
- Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
- context, provider, null);
- spanX = Math.max(1, (int) Math.ceil(
- (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
- spanY = Math.max(1, (int) Math.ceil(
- (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
-
- minSpanX = Math.max(1, (int) Math.ceil(
- (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
- minSpanY = Math.max(1, (int) Math.ceil(
- (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
- }
-
- public String getLabel(PackageManager packageManager) {
- return super.loadLabel(packageManager);
- }
-
- public Point getMinSpans() {
- return new Point((resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
- (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
- }
-
- public boolean isCustomWidget() {
- return provider.getClassName().startsWith(CLS_CUSTOM_WIDGET_PREFIX);
- }
-
- public int getWidgetFeatures() {
- if (Utilities.ATLEAST_P) {
- return widgetFeatures;
- } else {
- return 0;
- }
- }
-
- @Override
- public final ComponentName getComponent() {
- return provider;
- }
-
- @Override
- public final UserHandle getUser() {
- return getProfile();
- }
-
- @Override
- public Drawable getFullResIcon(IconCache cache) {
- return cache.getFullResIcon(provider.getPackageName(), icon);
- }
-}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e89b9b0..699495c 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -615,7 +615,9 @@
+ "\" bitmapIcon=" + info.bitmap.icon
+ " componentName=" + info.componentName.getPackageName());
}
+ writer.println();
}
+ mModelDelegate.dump(prefix, fd, writer, args);
mBgDataModel.dump(prefix, fd, writer, args);
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e151777..2ec5e90 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -71,6 +71,7 @@
import com.android.launcher3.util.NoLocaleSQLiteHelper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
import org.xmlpull.v1.XmlPullParser;
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 76c4518..83ddf64 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -41,8 +41,15 @@
}
private void handleSystemWindowInsets(Rect insets) {
- // Update device profile before notifying th children.
- mActivity.getDeviceProfile().updateInsets(insets);
+ DeviceProfile dp = mActivity.getDeviceProfile();
+
+ // Taskbar provides insets, but we don't want that for most Launcher elements so remove it.
+ mTempRect.set(insets);
+ insets = mTempRect;
+ insets.bottom = Math.max(0, insets.bottom - dp.nonOverlappingTaskbarInset);
+
+ // Update device profile before notifying the children.
+ dp.updateInsets(insets);
boolean resetState = !insets.equals(mInsets);
setInsets(insets);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index dfdc53c..22c257a 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -193,6 +193,7 @@
public static final int CONTAINER_DESKTOP = -100;
public static final int CONTAINER_HOTSEAT = -101;
public static final int CONTAINER_PREDICTION = -102;
+ public static final int CONTAINER_WIDGETS_PREDICTION = -111;
public static final int CONTAINER_HOTSEAT_PREDICTION = -103;
public static final int CONTAINER_ALL_APPS = -104;
public static final int CONTAINER_WIDGETS_TRAY = -105;
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index f9a1ded..06bc438 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
@@ -29,7 +30,6 @@
import android.content.Context;
import android.view.animation.Interpolator;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.HintState;
@@ -51,17 +51,13 @@
*/
public static final int NONE = 0;
public static final int HOTSEAT_ICONS = 1 << 0;
- public static final int HOTSEAT_SEARCH_BOX = 1 << 1;
- public static final int ALL_APPS_HEADER = 1 << 2;
- public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
- public static final int ALL_APPS_CONTENT = 1 << 4;
- public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
- public static final int OVERVIEW_BUTTONS = 1 << 6;
- public static final int TASKBAR = 1 << 7;
-
- /** Mask of all the items that are contained in the apps view. */
- public static final int APPS_VIEW_ITEM_MASK =
- HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+ public static final int ALL_APPS_CONTENT = 1 << 1;
+ public static final int VERTICAL_SWIPE_INDICATOR = 1 << 2;
+ public static final int OVERVIEW_ACTIONS = 1 << 3;
+ public static final int TASKBAR = 1 << 4;
+ public static final int CLEAR_ALL_BUTTON = 1 << 5;
+ public static final int WORKSPACE_PAGE_INDICATOR = 1 << 6;
+ public static final int SPLIT_PLACHOLDER_VIEW = 1 << 7;
// Flag indicating workspace has multiple pages visible.
public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
@@ -124,6 +120,8 @@
OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
public static final LauncherState BACKGROUND_APP =
OverviewState.newBackgroundState(BACKGROUND_APP_STATE_ORDINAL);
+ public static final LauncherState OVERVIEW_SPLIT_SELECT =
+ OverviewState.newSplitSelectState(OVERVIEW_SPLIT_SELECT_ORDINAL);
public final int ordinal;
@@ -178,21 +176,20 @@
return launcher.getNormalOverviewScaleAndOffset();
}
- public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
- }
-
public float getOverviewFullscreenProgress() {
return 0;
}
public int getVisibleElements(Launcher launcher) {
- int flags = HOTSEAT_ICONS | VERTICAL_SWIPE_INDICATOR | TASKBAR;
- if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && !launcher.getDeviceProfile().isVerticalBarLayout()) {
- flags |= HOTSEAT_SEARCH_BOX;
- }
- return flags;
+ return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
+ }
+
+ /**
+ * A shorthand for checking getVisibleElements() & elements == elements.
+ * @return Whether all of the given elements are visible.
+ */
+ public boolean areElementsVisible(Launcher launcher, int elements) {
+ return (getVisibleElements(launcher) & elements) == elements;
}
/**
@@ -221,6 +218,14 @@
}
/**
+ * For this state, how much additional vertical translation there should be for each of the
+ * child TaskViews.
+ */
+ public float getOverviewSecondaryTranslation(Launcher launcher) {
+ return 0;
+ }
+
+ /**
* The amount of blur and wallpaper zoom to apply to the background of either the app
* or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
*
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index ada297f..76885cc 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -63,6 +63,7 @@
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
+import java.util.List;
/**
* An abstraction of the original Workspace which supports browsing through a
@@ -217,7 +218,7 @@
/**
* Returns the index of the currently displayed page. When in free scroll mode, this is the page
* that the user was on before entering free scroll mode (e.g. the home screen page they
- * long-pressed on to enter the overview). Try using {@link #getPageNearestToCenterOfScreen()}
+ * long-pressed on to enter the overview). Try using {@link #getDestinationPage()}
* to get the page the user is currently scrolling over.
*/
public int getCurrentPage() {
@@ -282,7 +283,47 @@
private int validateNewPage(int newPage) {
newPage = ensureWithinScrollBounds(newPage);
// Ensure that it is clamped by the actual set of children in all cases
- return Utilities.boundToRange(newPage, 0, getPageCount() - 1);
+ newPage = Utilities.boundToRange(newPage, 0, getPageCount() - 1);
+
+ if (getPanelCount() > 1) {
+ // Always return left panel as new page
+ newPage = getLeftmostVisiblePageForIndex(newPage);
+ }
+ return newPage;
+ }
+
+ private int getLeftmostVisiblePageForIndex(int pageIndex) {
+ int panelCount = getPanelCount();
+ return (pageIndex / panelCount) * panelCount;
+ }
+
+ /**
+ * Returns the number of pages that are shown at the same time.
+ */
+ protected int getPanelCount() {
+ return 1;
+ }
+
+ /**
+ * Returns the currently visible pages.
+ */
+ public Iterable<View> getVisiblePages() {
+ int panelCount = getPanelCount();
+ List<View> visiblePages = new ArrayList<>(panelCount);
+ for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) {
+ View page = getPageAt(i);
+ if (page != null) {
+ visiblePages.add(page);
+ }
+ }
+ return visiblePages;
+ }
+
+ /**
+ * Returns true if the view is on one of the current pages, false otherwise.
+ */
+ public boolean isVisible(View child) {
+ return getLeftmostVisiblePageForIndex(indexOfChild(child)) == mCurrentPage;
}
/**
@@ -548,6 +589,10 @@
super.forceLayout();
}
+ private int getPageWidthSize(int widthSize) {
+ return (widthSize - mInsets.left - mInsets.right) / getPanelCount();
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getChildCount() == 0) {
@@ -578,7 +623,7 @@
if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
int myWidthSpec = MeasureSpec.makeMeasureSpec(
- widthSize - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
+ getPageWidthSize(widthSize), MeasureSpec.EXACTLY);
int myHeightSpec = MeasureSpec.makeMeasureSpec(
heightSize - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
@@ -672,9 +717,11 @@
// In case the pages are of different width, align the page to left or right edge
// based on the orientation.
+ // In case we have multiple panels on the screen, scrollOffsetEnd is the scroll
+ // needed for the whole visible area, so we have to divide it by panelCount.
final int pageScroll = mIsRtl
- ? (childStart - scrollOffsetStart)
- : Math.max(0, childPrimaryEnd - scrollOffsetEnd);
+ ? (childStart - scrollOffsetStart)
+ : Math.max(0, childPrimaryEnd - scrollOffsetEnd / getPanelCount());
if (outPageScrolls[i] != pageScroll) {
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
@@ -682,6 +729,19 @@
childStart += primaryDimension + mPageSpacing + getChildGap();
}
}
+
+ int panelCount = getPanelCount();
+ if (panelCount > 1) {
+ for (int i = 0; i < childCount; i++) {
+ // In case we have multiple panels, always use left panel's page scroll for all
+ // panels on the screen.
+ int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
+ if (outPageScrolls[i] != adjustedScroll) {
+ outPageScrolls[i] = adjustedScroll;
+ pageScrollChanged = true;
+ }
+ }
+ }
return pageScrollChanged;
}
@@ -794,14 +854,16 @@
}
if (direction == View.FOCUS_LEFT) {
if (getCurrentPage() > 0) {
- snapToPage(getCurrentPage() - 1);
- getChildAt(getCurrentPage() - 1).requestFocus(direction);
+ int nextPage = validateNewPage(getCurrentPage() - 1);
+ snapToPage(nextPage);
+ getChildAt(nextPage).requestFocus(direction);
return true;
}
} else if (direction == View.FOCUS_RIGHT) {
if (getCurrentPage() < getPageCount() - 1) {
- snapToPage(getCurrentPage() + 1);
- getChildAt(getCurrentPage() + 1).requestFocus(direction);
+ int nextPage = validateNewPage(getCurrentPage() + 1);
+ snapToPage(nextPage);
+ getChildAt(nextPage).requestFocus(direction);
return true;
}
}
@@ -820,11 +882,13 @@
}
if (direction == View.FOCUS_LEFT) {
if (mCurrentPage > 0) {
- getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
+ int nextPage = validateNewPage(mCurrentPage - 1);
+ getPageAt(nextPage).addFocusables(views, direction, focusableMode);
}
- } else if (direction == View.FOCUS_RIGHT){
+ } else if (direction == View.FOCUS_RIGHT) {
if (mCurrentPage < getPageCount() - 1) {
- getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
+ int nextPage = validateNewPage(mCurrentPage + 1);
+ getPageAt(nextPage).addFocusables(views, direction, focusableMode);
}
}
}
@@ -1004,10 +1068,7 @@
// Try canceling the long press. It could also have been scheduled
// by a distant descendant, so use the mAllowLongPress flag to block
// everything
- final View currentPage = getPageAt(mCurrentPage);
- if (currentPage != null) {
- currentPage.cancelLongPress();
- }
+ getVisiblePages().forEach(View::cancelLongPress);
}
protected float getScrollProgress(int screenCenter, View v, int page) {
@@ -1255,12 +1316,14 @@
if (((isSignificantMove && !isDeltaLeft && !isFling) ||
(isFling && !isVelocityLeft)) && mCurrentPage > 0) {
- finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+ finalPage = returnToOriginalPage
+ ? mCurrentPage : mCurrentPage - getPanelCount();
snapToPageWithVelocity(finalPage, velocity);
} else if (((isSignificantMove && isDeltaLeft && !isFling) ||
(isFling && isVelocityLeft)) &&
mCurrentPage < getChildCount() - 1) {
- finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
+ finalPage = returnToOriginalPage
+ ? mCurrentPage : mCurrentPage + getPanelCount();
snapToPageWithVelocity(finalPage, velocity);
} else {
snapToDestination();
@@ -1277,7 +1340,7 @@
if (((initialScroll >= maxScroll) && (isVelocityLeft || !isFling)) ||
((initialScroll <= minScroll) && (!isVelocityLeft || !isFling))) {
mScroller.springBack(initialScroll, minScroll, maxScroll);
- mNextPage = getPageNearestToCenterOfScreen();
+ mNextPage = getDestinationPage();
} else {
mScroller.setInterpolator(mDefaultInterpolator);
mScroller.fling(initialScroll, -velocity,
@@ -1285,11 +1348,12 @@
Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR));
int finalPos = mScroller.getFinalPos();
- mNextPage = getPageNearestToCenterOfScreen(finalPos);
+ mNextPage = getDestinationPage(finalPos);
int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
- if (finalPos > minScroll && finalPos < maxScroll) {
+ if (snapToPageInFreeScroll() && finalPos > minScroll
+ && finalPos < maxScroll) {
// If scrolling ends in the half of the added space that is closer to
// the end, settle to the end. Otherwise snap to the nearest page.
// If flinging past one of the ends, don't change the velocity as it
@@ -1335,6 +1399,10 @@
return true;
}
+ protected boolean snapToPageInFreeScroll() {
+ return true;
+ }
+
protected boolean shouldFlingForVelocity(int velocity) {
float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
return Math.abs(velocity) > threshold;
@@ -1440,13 +1508,20 @@
}
}
+ public int getDestinationPage() {
+ return getDestinationPage(mOrientationHandler.getPrimaryScroll(this));
+ }
+
+ protected int getDestinationPage(int primaryScroll) {
+ return getPageNearestToCenterOfScreen(primaryScroll);
+ }
+
public int getPageNearestToCenterOfScreen() {
return getPageNearestToCenterOfScreen(mOrientationHandler.getPrimaryScroll(this));
}
- private int getPageNearestToCenterOfScreen(int scaledScroll) {
- int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
- int screenCenter = scaledScroll + (pageOrientationSize / 2);
+ private int getPageNearestToCenterOfScreen(int primaryScroll) {
+ int screenCenter = getScreenCenter(primaryScroll);
int minDistanceFromScreenCenter = Integer.MAX_VALUE;
int minDistanceFromScreenCenterIndex = -1;
final int childCount = getChildCount();
@@ -1462,20 +1537,28 @@
}
private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) {
- int childSize = getChildVisibleSize(childIndex);
+ int childSize = Math.round(getChildVisibleSize(childIndex));
int halfChildSize = (childSize / 2);
int childCenter = getChildOffset(childIndex) + halfChildSize;
return childCenter - screenCenter;
}
protected int getDisplacementFromScreenCenter(int childIndex) {
- int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
- int screenCenter = mOrientationHandler.getPrimaryScroll(this) + (pageOrientationSize / 2);
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int screenCenter = getScreenCenter(primaryScroll);
return getDisplacementFromScreenCenter(childIndex, screenCenter);
}
+ private int getScreenCenter(int primaryScroll) {
+ float primaryScale = mOrientationHandler.getPrimaryScale(this);
+ float primaryPivot = mOrientationHandler.getPrimaryValue(getPivotX(), getPivotY());
+ int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
+ return Math.round(primaryScroll + (pageOrientationSize / 2f - primaryPivot) / primaryScale
+ + primaryPivot);
+ }
+
protected void snapToDestination() {
- snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
+ snapToPage(getDestinationPage(), getPageSnapDuration());
}
protected boolean isInOverScroll() {
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index f60e1f8..ece123d 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -60,7 +60,11 @@
}
public static int pxFromDp(float size, DisplayMetrics metrics) {
- return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+ return pxFromDp(size, metrics, 1f);
+ }
+
+ public static int pxFromDp(float size, DisplayMetrics metrics, float scale) {
+ return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(scale
+ * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
}
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 7276887..858b72e 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -33,7 +33,6 @@
import android.view.View;
import android.widget.Toast;
-import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
@@ -45,7 +44,7 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import java.net.URISyntaxException;
@@ -109,15 +108,12 @@
mCurrentAccessibilityAction = action;
if (action == UNINSTALL) {
- mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
setDrawable(R.drawable.ic_uninstall_shadow);
updateText(R.string.uninstall_drop_target_label);
} else if (action == DISMISS_PREDICTION) {
- mHoverColor = Themes.getColorAccent(getContext());
setDrawable(R.drawable.ic_block_shadow);
updateText(R.string.dismiss_prediction_label);
} else if (action == RECONFIGURE) {
- mHoverColor = Themes.getColorAccent(getContext());
setDrawable(R.drawable.ic_setup_shadow);
updateText(R.string.gadget_setup_text);
}
@@ -227,7 +223,7 @@
DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
if (target != null) {
deferred.mPackageName = target.getPackageName();
- mLauncher.addOnResumeCallback(deferred);
+ mLauncher.addOnResumeCallback(deferred::onLauncherResume);
} else {
deferred.sendFailure();
}
@@ -310,7 +306,7 @@
* A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until
* {@link #onLauncherResume}
*/
- private class DeferredOnComplete implements DragSource, OnResumeCallback {
+ private class DeferredOnComplete implements DragSource {
private final DragSource mOriginal;
private final Context mContext;
@@ -329,7 +325,6 @@
mDragObject = d;
}
- @Override
public void onLauncherResume() {
// We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index eab8272..2c24c3a 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -37,6 +37,8 @@
// return an (x, y) value from helper functions. Do NOT use them to maintain other state.
private final int[] mTmpCellXY = new int[2];
+ private final Rect mTempRect = new Rect();
+
@ContainerType
private final int mContainerType;
private final WallpaperManager mWallpaperManager;
@@ -101,11 +103,12 @@
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (child instanceof LauncherAppWidgetHostView) {
DeviceProfile profile = mActivity.getDeviceProfile();
+ ((LauncherAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
- profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing);
+ profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing, mTempRect);
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
- mBorderSpacing);
+ mBorderSpacing, null);
}
}
@@ -124,12 +127,12 @@
final DeviceProfile profile = mActivity.getDeviceProfile();
if (child instanceof LauncherAppWidgetHostView) {
+ ((LauncherAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
- profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing);
- // Widgets have their own padding
+ profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing, mTempRect);
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
- mBorderSpacing);
+ mBorderSpacing, null);
// Center the icon/folder
int cHeight = getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 51d8e66..e57844d 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);
@@ -691,6 +690,47 @@
};
}
+ /**
+ * Compares the ratio of two quantities and returns whether that ratio is greater than the
+ * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+ * of a percentage.
+ */
+ public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+ float bound) {
+ return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+ }
+
+ /**
+ * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
+ * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
+ * the final bounds.
+ */
+ public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
+ int delta) {
+ int rdelta = ((delta % 4) + 4) % 4;
+ int origLeft = inOutBounds.left;
+ switch (rdelta) {
+ case 0:
+ return;
+ case 1:
+ inOutBounds.left = inOutBounds.top;
+ inOutBounds.top = parentWidth - inOutBounds.right;
+ inOutBounds.right = inOutBounds.bottom;
+ inOutBounds.bottom = parentWidth - origLeft;
+ return;
+ case 2:
+ inOutBounds.left = parentWidth - inOutBounds.right;
+ inOutBounds.right = parentWidth - origLeft;
+ return;
+ case 3:
+ inOutBounds.left = parentHeight - inOutBounds.bottom;
+ inOutBounds.bottom = inOutBounds.right;
+ inOutBounds.right = parentHeight - inOutBounds.top;
+ inOutBounds.top = origLeft;
+ return;
+ }
+ }
+
private static class FixedSizeEmptyDrawable extends ColorDrawable {
private final int mSize;
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 2c45c77..446c873 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -50,6 +50,7 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SQLiteCacheHelper;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetManagerHelper;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 2528c03..6a16da9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -63,13 +63,13 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
-import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -104,11 +104,14 @@
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetManagerHelper;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
import java.util.ArrayList;
@@ -185,7 +188,6 @@
@Thunk final Launcher mLauncher;
@Thunk DragController mDragController;
- private final Rect mTempRect = new Rect();
private final int[] mTempXY = new int[2];
private final float[] mTempFXY = new float[2];
@Thunk float[] mDragViewVisualCenter = new float[2];
@@ -309,8 +311,12 @@
Rect padding = grid.workspacePadding;
setPadding(padding.left, padding.top, padding.right, padding.bottom);
mInsets.set(insets);
+ // Increase our bottom insets so we don't overlap with the taskbar.
+ mInsets.bottom += grid.nonOverlappingTaskbarInset;
- if (mWorkspaceFadeInAdjacentScreens) {
+ if (isTwoPanelEnabled()) {
+ setPageSpacing(0); // we have two pages and we don't want any spacing
+ } else if (mWorkspaceFadeInAdjacentScreens) {
// In landscape mode the page spacing is set to the default.
setPageSpacing(grid.edgeMarginPx);
} else {
@@ -322,12 +328,30 @@
setPageSpacing(Math.max(maxInsets, maxPadding));
}
-
int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
int paddingBottom = grid.cellLayoutBottomPaddingPx;
+ int twoPanelLandscapeSidePadding = paddingLeftRight * 2;
+ int twoPanelPortraitSidePadding = paddingLeftRight / 2;
+
+ int panelCount = getPanelCount();
for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
- mWorkspaceScreens.valueAt(i)
- .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
+ int paddingLeft = paddingLeftRight;
+ int paddingRight = paddingLeftRight;
+ if (panelCount > 1) {
+ if (i % panelCount == 0) { // left side panel
+ paddingLeft = grid.isLandscape ? twoPanelLandscapeSidePadding
+ : twoPanelPortraitSidePadding;
+ paddingRight = 0;
+ } else if (i % panelCount == panelCount - 1) { // right side panel
+ paddingLeft = 0;
+ paddingRight = grid.isLandscape ? twoPanelLandscapeSidePadding
+ : twoPanelPortraitSidePadding;
+ } else { // middle panel
+ paddingLeft = 0;
+ paddingRight = 0;
+ }
+ }
+ mWorkspaceScreens.valueAt(i).setPadding(paddingLeft, 0, paddingRight, paddingBottom);
}
}
@@ -366,10 +390,19 @@
}
public float getWallpaperOffsetForCenterPage() {
- int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen());
+ return getWallpaperOffsetForPage(getPageNearestToCenterOfScreen());
+ }
+
+ private float getWallpaperOffsetForPage(int page) {
+ int pageScroll = getScrollForPage(page);
return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
}
+ /** Returns the number of pages used for the wallpaper parallax. */
+ public int getNumPagesForWallpaperParallax() {
+ return mWallpaperOffset.getNumPagesForWallpaperParallax();
+ }
+
public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
Rect r = new Rect();
cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
@@ -387,15 +420,6 @@
layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
}
- if (mOutlineProvider != null) {
- if (dragObject.dragView != null) {
- Bitmap preview = dragObject.dragView.getPreviewBitmap();
-
- // The outline is used to visualize where the item will land if dropped
- mOutlineProvider.generateDragOutline(preview);
- }
- }
-
updateChildrenLayersEnabled();
// Do not add a new page if it is a accessible drag which was not started by the workspace.
@@ -416,7 +440,7 @@
// widgets as they cannot be placed inside a folder.
// Start at the current page and search right (on LTR) until finding a page with
// enough space. Since an empty screen is the furthest right, a page must be found.
- int currentPage = getPageNearestToCenterOfScreen();
+ int currentPage = getDestinationPage();
for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
CellLayout page = (CellLayout) getPageAt(pageIndex);
if (page.hasReorderSolution(dragObject.dragInfo)) {
@@ -434,6 +458,15 @@
.log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
}
+ private boolean isTwoPanelEnabled() {
+ return mLauncher.mDeviceProfile.isTablet && FeatureFlags.ENABLE_TWO_PANEL_HOME.get();
+ }
+
+ @Override
+ protected int getPanelCount() {
+ return isTwoPanelEnabled() ? 2 : super.getPanelCount();
+ }
+
public void deferRemoveExtraEmptyScreen() {
mDeferRemoveExtraEmptyScreen = true;
}
@@ -445,6 +478,19 @@
}
updateChildrenLayersEnabled();
+ StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+ stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == NORMAL) {
+ if (!mDeferRemoveExtraEmptyScreen) {
+ removeExtraEmptyScreen(true /* stripEmptyScreens */);
+ }
+ stateManager.removeStateListener(this);
+ }
+ }
+ });
+
mDragInfo = null;
mOutlineProvider = null;
mDragSourceInternal = null;
@@ -808,7 +854,7 @@
private boolean shouldConsumeTouch(View v) {
return !workspaceIconsCanBeDragged()
- || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
+ || (!workspaceInModalState() && !isVisible(v));
}
public boolean isSwitchingState() {
@@ -1409,7 +1455,7 @@
// TAPL can work only if UIDevice is set up as setCompressedLayoutHeirarchy(false).
// Hiding workspace from the tests when it's
// IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS.
- return null;
+ return AccessibilityNodeInfo.obtain();
}
return super.createAccessibilityNodeInfo();
}
@@ -1482,10 +1528,10 @@
draggableView = (DraggableView) child;
}
- // The drag bitmap follows the touch point around on the screen
- final Bitmap b = previewProvider.createDragBitmap();
+ // The draggable drawable follows the touch point around on the screen
+ final Drawable drawable = previewProvider.createDrawable();
int halfPadding = previewProvider.previewPadding / 2;
- float scale = previewProvider.getScaleAndPosition(b, mTempXY);
+ float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
int dragLayerX = mTempXY[0];
int dragLayerY = mTempXY[1];
@@ -1511,9 +1557,21 @@
}
}
- DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
- dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
- scale, dragOptions);
+ if (drawable instanceof AppWidgetHostViewDrawable) {
+ mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
+ }
+ DragView dv = mDragController.startDrag(
+ drawable,
+ draggableView,
+ dragLayerX,
+ dragLayerY,
+ source,
+ dragObject,
+ dragVisualizeOffset,
+ dragRect,
+ scale * iconScale,
+ scale,
+ dragOptions);
dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
return dv;
}
@@ -1876,19 +1934,6 @@
};
}
}
- StateManager<LauncherState> stateManager = mLauncher.getStateManager();
- stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState == NORMAL) {
- if (!mDeferRemoveExtraEmptyScreen) {
- removeExtraEmptyScreen(true /* stripEmptyScreens */);
- }
- stateManager.removeStateListener(this);
- }
- }
- });
-
mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
lp.cellX, lp.cellY, item.spanX, item.spanY);
} else {
@@ -2248,19 +2293,27 @@
int nextPage = getNextPage();
if (layout == null && !isPageInTransition()) {
- // Check if the item is dragged over left page
+ // Check if the item is dragged over currentPage - 1 page
mTempTouchCoordinates[0] = Math.min(centerX, d.x);
mTempTouchCoordinates[1] = d.y;
layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates);
}
if (layout == null && !isPageInTransition()) {
- // Check if the item is dragged over right page
+ // Check if the item is dragged over currentPage + 1 page
mTempTouchCoordinates[0] = Math.max(centerX, d.x);
mTempTouchCoordinates[1] = d.y;
layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates);
}
+ // If two panel is enabled, users can also drag items to currentPage + 2
+ if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) {
+ // Check if the item is dragged over currentPage + 2 page
+ mTempTouchCoordinates[0] = Math.max(centerX, d.x);
+ mTempTouchCoordinates[1] = d.y;
+ layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), mTempTouchCoordinates);
+ }
+
// Always pick the current page.
if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
layout = (CellLayout) getChildAt(nextPage);
@@ -2574,7 +2627,11 @@
}
- public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+ private Drawable createWidgetDrawable(ItemInfo widgetInfo, View layout) {
+ if (layout instanceof LauncherAppWidgetHostView) {
+ return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) layout);
+ }
+
int[] unScaledSize = estimateItemSize(widgetInfo);
int visibility = layout.getVisibility();
layout.setVisibility(VISIBLE);
@@ -2586,7 +2643,7 @@
Bitmap b = BitmapRenderer.createHardwareBitmap(
unScaledSize[0], unScaledSize[1], layout::draw);
layout.setVisibility(visibility);
- return b;
+ return new FastBitmapDrawable(b);
}
private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
@@ -2656,8 +2713,8 @@
boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
- Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
- dragView.setCrossFadeBitmap(crossFadeBitmap);
+ Drawable crossFadeDrawable = createWidgetDrawable(info, finalView);
+ dragView.setCrossFadeDrawable(crossFadeDrawable);
dragView.crossFade((int) (duration * 0.8f));
} else if (isWidget && external) {
scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
@@ -2972,7 +3029,7 @@
* @param operators List of operators, in order starting from best matching operator.
* @return
*/
- private View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
+ View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
// This array is filled with the first match for each operator.
final View[] matches = new View[operators.length];
// For efficiency, the outer loop should be CellLayout.
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 0e0ddfb..16e022c 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.WORKSPACE_PAGE_INDICATOR;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -45,7 +46,6 @@
import com.android.launcher3.LauncherState.PageAlphaProvider;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringAnimationBuilder;
@@ -94,7 +94,6 @@
ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
mLauncher);
- ScaleAndTranslation qsbScaleAndTranslation = state.getQsbScaleAndTranslation(mLauncher);
mNewScale = scaleAndTranslation.scale;
PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
final int childCount = mWorkspace.getChildCount();
@@ -108,9 +107,6 @@
pageAlphaProvider.interpolator);
boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
Hotseat hotseat = mWorkspace.getHotseat();
- // Since we set the pivot relative to mWorkspace, we need to scale a sibling of Workspace.
- AllAppsContainerView qsbScaleView = mLauncher.getAppsView();
- View qsbView = qsbScaleView.getSearchView();
if (playAtomicComponent) {
Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
LauncherState fromState = mLauncher.getStateManager().getState();
@@ -124,26 +120,22 @@
}
setPivotToScaleWithWorkspace(hotseat);
- setPivotToScaleWithWorkspace(qsbScaleView);
float hotseatScale = hotseatScaleAndTranslation.scale;
if (shouldSpring) {
PendingAnimation pa = (PendingAnimation) propertySetter;
pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale));
- pa.add(getSpringScaleAnimator(mLauncher, qsbScaleView,
- qsbScaleAndTranslation.scale));
} else {
Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
scaleInterpolator);
propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
hotseatScaleInterpolator);
- propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
- hotseatScaleInterpolator);
}
float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
+ float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
- hotseatIconsAlpha, fadeInterpolator);
+ workspacePageIndicatorAlpha, fadeInterpolator);
}
if (config.onlyPlayAtomicComponent()) {
@@ -165,8 +157,6 @@
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
propertySetter.setFloat(mWorkspace.getPageIndicator(), VIEW_TRANSLATE_Y,
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
- propertySetter.setFloat(qsbView, VIEW_TRANSLATE_Y,
- qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator);
setScrim(propertySetter, state);
}
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index cd4616a..db7fd3f 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -357,10 +357,8 @@
}
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));
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 505e6d8..591de04 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,7 +15,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;
@@ -32,6 +33,7 @@
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -60,19 +62,20 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
+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.
@@ -194,9 +197,11 @@
break;
}
}
- rebindAdapters(hasWorkApps);
- if (hasWorkApps) {
- resetWorkProfile();
+ if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) {
+ rebindAdapters(hasWorkApps);
+ if (hasWorkApps) {
+ resetWorkProfile();
+ }
}
}
@@ -239,11 +244,7 @@
hideInput();
return false;
}
- boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
- if (shouldScroll) {
- hideInput();
- }
- return shouldScroll;
+ return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
}
@Override
@@ -340,7 +341,7 @@
mSearchContainer = findViewById(R.id.search_container_all_apps);
mSearchUiManager = (SearchUiManager) mSearchContainer;
- mSearchUiManager.initialize(this);
+ mSearchUiManager.initializeSearch(this);
}
public SearchUiManager getSearchUiManager() {
@@ -388,7 +389,8 @@
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
if (Utilities.ATLEAST_Q) {
- mNavBarScrimHeight = insets.getTappableElementInsets().bottom;
+ mNavBarScrimHeight = insets.getTappableElementInsets().bottom
+ - mLauncher.getDeviceProfile().nonOverlappingTaskbarInset;
} else {
mNavBarScrimHeight = insets.getStableInsetBottom();
}
@@ -431,10 +433,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;
@@ -480,10 +492,14 @@
int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
View newView = getLayoutInflater().inflate(layout, this, false);
addView(newView, index);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.WORK_PROFILE_REMOVED, "should show tabs:" + showTabs,
+ new Exception());
+ }
if (showTabs) {
mViewPager = (AllAppsPagedView) newView;
mViewPager.initParentViews(this);
- mViewPager.getPageIndicator().setContainerView(this);
+ mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
} else {
mViewPager = null;
}
@@ -493,14 +509,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));
}
@@ -545,37 +562,9 @@
/**
* Handles selection on focused view and returns success
*/
- public boolean selectFocusedView(View v) {
- ItemInfo headerItem = getHighlightedItemFromHeader();
- if (headerItem != null) {
- return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
- }
- AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
- if (focusedItem != null) {
- View focusedView = getActiveRecyclerView().getLayoutManager()
- .findViewByPosition(focusedItem.position);
- if (focusedView != null && mSearchAdapterProvider.onAdapterItemSelected(focusedItem,
- focusedView)) {
- return true;
- }
- }
- if (focusedItem != null && focusedItem.appInfo != null) {
- ItemInfo itemInfo = focusedItem.appInfo;
- return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
- }
- return false;
- }
-
- /**
- * Returns the ItemInfo of a focused view inside {@link FloatingHeaderView}
- */
- public ItemInfo getHighlightedItemFromHeader() {
- View view = getFloatingHeaderView().getFocusedChild();
- if (view != null && view.getTag() instanceof ItemInfo) {
- return ((ItemInfo) view.getTag());
- }
-
- return null;
+ public boolean launchHighlightedItem() {
+ if (mSearchAdapterProvider == null) return false;
+ return mSearchAdapterProvider.launchHighlightedItem();
}
public SearchAdapterProvider getSearchAdapterProvider() {
@@ -755,6 +744,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..bb175ea 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
@@ -241,20 +240,18 @@
@Override
public int getSpanSize(int position) {
int viewType = mApps.getAdapterItems().get(position).viewType;
+ int totalSpans = mGridLayoutMgr.getSpanCount();
if (isIconViewType(viewType)) {
- return 1 * SPAN_MULTIPLIER;
+ return totalSpans / mAppsPerRow;
} else if (mSearchAdapterProvider.isSearchView(viewType)) {
- return mSearchAdapterProvider.getGridSpanSize(viewType, mAppsPerRow);
+ return totalSpans / mSearchAdapterProvider.getItemsPerRow(viewType, mAppsPerRow);
} else {
// Section breaks span the full width
- return mAppsPerRow * SPAN_MULTIPLIER;
+ return totalSpans;
}
}
}
- // multiplier to support adapter item column count that is not mAppsPerRow.
- public static final int SPAN_MULTIPLIER = 3;
-
private final BaseDraggingActivity mLauncher;
private final LayoutInflater mLayoutInflater;
private final AlphabeticalAppsList mApps;
@@ -286,14 +283,19 @@
mOnIconClickListener = launcher.getItemOnClickListener();
- setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
-
mSearchAdapterProvider = searchAdapterProvider;
+ setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
}
public void setAppsPerRow(int appsPerRow) {
mAppsPerRow = appsPerRow;
- mGridLayoutMgr.setSpanCount(mAppsPerRow * SPAN_MULTIPLIER);
+ int totalSpans = mAppsPerRow;
+ for (int itemPerRow : mSearchAdapterProvider.getSupportedItemsPerRowArray()) {
+ if (totalSpans % itemPerRow != 0) {
+ totalSpans *= itemPerRow;
+ }
+ }
+ mGridLayoutMgr.setSpanCount(totalSpans);
}
/**
@@ -372,10 +374,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 +407,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
deleted file mode 100644
index b34c8b8..0000000
--- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import android.annotation.TargetApi;
-import android.graphics.Insets;
-import android.os.Build;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowInsetsAnimationControlListener;
-import android.view.WindowInsetsAnimationController;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.UiThreadHelper;
-
-/**
- * Handles IME over all apps to be synchronously transitioning along with the passed in
- * root inset.
- */
-public class AllAppsInsetTransitionController {
-
- private static final boolean DEBUG = true;
- private static final String TAG = "AllAppsInsetTransitionController";
- private static final Interpolator LINEAR = new LinearInterpolator();
-
- private WindowInsetsAnimationController mAnimationController;
- private WindowInsetsAnimationControlListener mCurrentRequest;
-
- private Runnable mSearchEduRunnable;
-
- private float mAllAppsHeight;
-
- private int mDownInsetBottom;
- private boolean mShownAtDown;
-
- private int mHiddenInsetBottom;
- private int mShownInsetBottom;
-
- private float mDown, mCurrent;
- private View mApps;
-
- /**
- *
- */
- public boolean showSearchEduIfNecessary() {
- if (mSearchEduRunnable == null) {
- return false;
- }
- mSearchEduRunnable.run();
- return true;
- }
-
- public void setSearchEduRunnable(Runnable eduRunnable) {
- mSearchEduRunnable = eduRunnable;
- }
-
- // Only purpose of these states is to keep track of fast fling transition
- enum State {
- RESET, DRAG_START_BOTTOM, DRAG_START_BOTTOM_IME_CANCELLED,
- FLING_END_TOP, FLING_END_TOP_IME_CANCELLED,
- DRAG_START_TOP, FLING_END_BOTTOM
- }
-
- private State mState;
-
- public AllAppsInsetTransitionController(float allAppsHeight, View appsView) {
- mAllAppsHeight = allAppsHeight;
- mApps = appsView;
- }
-
- public void show() {
- mApps.getWindowInsetsController().show(WindowInsets.Type.ime());
- }
-
- public void hide() {
- if (!Utilities.ATLEAST_R) return;
-
- WindowInsets insets = mApps.getRootWindowInsets();
- if (insets == null) return;
-
- boolean imeVisible = insets.isVisible(WindowInsets.Type.ime());
-
- if (DEBUG) {
- Log.d(TAG, "\nhide imeVisible=" + imeVisible);
- }
- if (insets.isVisible(WindowInsets.Type.ime())) {
- mApps.getWindowInsetsController().hide(WindowInsets.Type.ime());
- }
- }
-
- /**
- * Initializes member variables and requests for the {@link WindowInsetsAnimationController}
- * object.
- *
- * @param progress value between 0..1
- */
- @TargetApi(Build.VERSION_CODES.R)
- public void onDragStart(float progress) {
- if (!Utilities.ATLEAST_R) return;
-
- // Until getRootWindowInsets().isVisible(...) method returns correct value,
- // only support InsetController based IME transition during swipe up and
- // NOT swipe down
- if (Float.compare(progress, 0f) == 0) return;
-
- setState(true, false, progress);
- mDown = progress * mAllAppsHeight;
-
- // Below two values are sometimes incorrect. Possibly a platform bug
- // mDownInsetBottom = mApps.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
- // mShownAtDown = mApps.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
-
- if (DEBUG) {
- Log.d(TAG, "\nonDragStart progress=" + progress
- + " mDownInsets=" + mDownInsetBottom
- + " mShownAtDown=" + mShownAtDown);
- }
-
- mApps.getWindowInsetsController().controlWindowInsetsAnimation(
- WindowInsets.Type.ime(), -1 /* no predetermined duration */, LINEAR, null,
- mCurrentRequest = new WindowInsetsAnimationControlListener() {
-
- @Override
- public void onReady(WindowInsetsAnimationController controller, int types) {
- if (DEBUG) {
- Log.d(TAG, "Listener.onReady " + (mCurrentRequest == this));
- }
- if (controller != null) {
- if (mCurrentRequest == this && !handleFinishOnFling(controller)) {
- mAnimationController = controller;
- } else {
- controller.finish(false /* just don't show */);
- }
- }
- }
-
- @Override
- public void onFinished(WindowInsetsAnimationController controller) {
- // when screen lock happens, then this method get called
- if (DEBUG) {
- Log.d(TAG, "Listener.onFinished ctrl=" + controller
- + " mAnimationController=" + mAnimationController);
- }
- if (mAnimationController != null) {
- mAnimationController.finish(true);
- mAnimationController = null;
- }
- }
-
- @Override
- public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
- if (DEBUG) {
- // Keep the verbose logging to chase down IME not showing up issue.
- // b/178904132
- Log.e(TAG, "Listener.onCancelled ctrl=" + controller
- + " mAnimationController=" + mAnimationController,
- new Exception());
- }
- if (mState == State.DRAG_START_BOTTOM) {
- mState = State.DRAG_START_BOTTOM_IME_CANCELLED;
- }
- mAnimationController = null;
- if (controller != null) {
- controller.finish(true);
- }
- }
- });
- }
-
- /**
- * If IME bounds after touch sequence finishes, call finish.
- */
- private boolean handleFinishOnFling(WindowInsetsAnimationController controller) {
- if (!Utilities.ATLEAST_R) return false;
-
- if (mState == State.FLING_END_TOP) {
- controller.finish(true);
- return true;
- } else if (mState == State.FLING_END_BOTTOM) {
- controller.finish(false);
- return true;
- }
- return false;
- }
-
- /**
- * Handles the translation using the progress.
- *
- * @param progress value between 0..1
- */
- @TargetApi(Build.VERSION_CODES.R)
- public void setProgress(float progress) {
- if (!Utilities.ATLEAST_R) return;
- // progress that equals to 0 or 1 is error prone. Do not use them.
- // Instead use onDragStart and onAnimationEnd
- if (mAnimationController == null || progress <= 0f || progress >= 1f) return;
-
- mCurrent = progress * mAllAppsHeight;
- mHiddenInsetBottom = mAnimationController.getHiddenStateInsets().bottom; // 0
- mShownInsetBottom = mAnimationController.getShownStateInsets().bottom; // 1155
-
- int shift = mShownAtDown ? 0 : (int) (mAllAppsHeight - mShownInsetBottom);
-
- int inset = (int) (mDownInsetBottom + (mDown - mCurrent) - shift);
-
- final int start = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
- final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom;
- inset = Math.max(inset, mHiddenInsetBottom);
- inset = Math.min(inset, mShownInsetBottom);
- if (DEBUG && false) {
- Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
- + mDown + " hidden=" + mHiddenInsetBottom
- + " shown=" + mShownInsetBottom
- + " mDownInsets.bottom=" + mDownInsetBottom + " inset=" + inset
- + " shift= " + shift);
- }
-
- mAnimationController.setInsetsAndAlpha(
- Insets.of(0, 0, 0, inset),
- 1f, (inset - start) / (float) (end - start));
- }
-
- /**
- * Report to the animation controller that we no longer plan to translate anymore.
- *
- * @param progress value between 0..1
- */
- @TargetApi(Build.VERSION_CODES.R)
- public void onAnimationEnd(float progress) {
- if (DEBUG) {
- Log.d(TAG, "onAnimationEnd progress=" + progress
- + " mAnimationController=" + mAnimationController);
- }
- if (mState == null) {
- // only called when launcher restarting.
- UiThreadHelper.hideKeyboardAsync(mApps.getContext(), mApps.getWindowToken());
- }
-
- setState(false, true, progress);
-
-
- if (mAnimationController == null) {
- if (mState == State.FLING_END_TOP_IME_CANCELLED) {
- mApps.getWindowInsetsController().show(WindowInsets.Type.ime());
- }
- return;
- }
-
- /* handle finish */
- if (mState == State.FLING_END_TOP) {
- mAnimationController.finish(true /* show */);
- } else {
- if (Float.compare(progress, 1f) == 0 /* bottom */) {
- mAnimationController.finish(false /* gone */);
- } else {
- mAnimationController.finish(mShownAtDown);
- }
- }
- /* handle finish */
-
- if (DEBUG) {
- Log.d(TAG, "endTranslation progress=" + progress
- + " mAnimationController=" + mAnimationController);
- }
- mAnimationController = null;
- mCurrentRequest = null;
- setState(false, false, progress);
- }
-
- private void setState(boolean start, boolean end, float progress) {
- State state = State.RESET;
- if (start && end) {
- throw new IllegalStateException("drag start and end cannot happen in same call");
- }
- if (start) {
- if (Float.compare(progress, 1f) == 0) {
- state = State.DRAG_START_BOTTOM;
- } else if (Float.compare(progress, 0f) == 0) {
- state = State.DRAG_START_TOP;
- }
- } else if (end) {
- if (Float.compare(progress, 1f) == 0 && mState == State.DRAG_START_TOP) {
- state = State.FLING_END_BOTTOM;
- } else if (Float.compare(progress, 0f) == 0) {
- if (mState == State.DRAG_START_BOTTOM) {
- state = State.FLING_END_TOP;
- } else if (mState == State.DRAG_START_BOTTOM_IME_CANCELLED) {
- state = State.FLING_END_TOP_IME_CANCELLED;
- }
- }
- }
- if (DEBUG) {
- Log.d(TAG, "setState " + mState + " -> " + state);
- }
- mState = state;
- }
-}
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..66575eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -19,6 +19,9 @@
import static android.view.View.MeasureSpec.UNSPECIFIED;
import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -28,6 +31,7 @@
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowInsets;
import androidx.recyclerview.widget.RecyclerView;
@@ -36,7 +40,7 @@
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.logging.StatsLogManager;
import com.android.launcher3.views.RecyclerViewFastScroller;
import java.util.ArrayList;
@@ -109,23 +113,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) {
@@ -194,6 +181,25 @@
}
@Override
+ public void onScrollStateChanged(int state) {
+ super.onScrollStateChanged(state);
+
+ StatsLogManager mgr = BaseDraggingActivity.fromContext(getContext()).getStatsLogManager();
+ switch (state) {
+ case SCROLL_STATE_DRAGGING:
+ mgr.logger().sendToInteractionJankMonitor(
+ LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
+ requestFocus();
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
+ break;
+ case SCROLL_STATE_IDLE:
+ mgr.logger().sendToInteractionJankMonitor(
+ LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END, this);
+ break;
+ }
+ }
+
+ @Override
public boolean onInterceptTouchEvent(MotionEvent e) {
boolean result = super.onInterceptTouchEvent(e);
if (!result && e.getAction() == MotionEvent.ACTION_DOWN
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index 6c95992..0bd2f44 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.RectF;
import android.view.View;
@@ -26,8 +27,8 @@
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.SearchAdapterProvider;
+import com.android.launcher3.allapps.search.SectionDecorationInfo;
import com.android.launcher3.util.Themes;
import java.util.List;
@@ -45,111 +46,75 @@
@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) {
+ SearchAdapterProvider adapterProvider = mAppsView.getSearchAdapterProvider();
+ 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 (view.equals(adapterProvider.getHighlightedItem())) {
+ decorationHandler.onFocusDraw(c, view);
+ } else {
+ decorationHandler.onGroupDraw(c, view);
+ }
}
}
- i++;
}
- if (lastDecorationHandler != null) {
- drawDecoration(c, lastDecorationHandler, parent);
- }
- }
-
- private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler,
- RecyclerView parent) {
- if (decorationHandler == null) return;
- 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();
- AppsGridLayoutManager layoutManager = (AppsGridLayoutManager)
- mAppsView.getActiveRecyclerView().getLayoutManager();
- if (layoutManager.findFirstVisibleItemPosition() <= index
- && index < parent.getChildCount()) {
- RecyclerView.ViewHolder vh = parent.findViewHolderForAdapterPosition(index);
- if (vh != null) decorationHandler.onFocusDraw(c, vh.itemView);
- }
- }
- decorationHandler.reset();
}
/**
* Handles grouping and drawing of items in the same all apps sections.
*/
public static class SectionDecorationHandler {
- private static final int FILL_ALPHA = 0;
-
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.getColorBackground(context);
- mFillcolor = ColorUtils.setAlphaComponent(endScrim, FILL_ALPHA);
+ mFillcolor = ColorUtils.setAlphaComponent(endScrim, fillAlpha);
mFocusColor = endScrim;
- mRadius = Themes.getDialogCornerRadius(context);
- }
- /**
- * Extends current bounds to include the view.
- */
- public void extendBounds(View view) {
- if (mBounds.isEmpty()) {
- mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
- } else {
- mBounds.set(
- Math.min(mBounds.left, view.getLeft()),
- Math.min(mBounds.top, view.getTop()),
- Math.max(mBounds.right, view.getRight()),
- Math.max(mBounds.bottom, view.getBottom())
- );
- }
+ 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
+ };
+
}
/**
* Draw bounds onto canvas.
*/
- public void onDraw(Canvas canvas) {
+ public void onGroupDraw(Canvas canvas, View view) {
+ if (view == null) return;
mPaint.setColor(mFillcolor);
- canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint);
+ mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+ onDraw(canvas);
}
/**
@@ -159,13 +124,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);
}
/**
@@ -175,19 +147,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/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a4e1f27..16ecd58 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -16,16 +16,11 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
@@ -36,14 +31,12 @@
import android.util.FloatProperty;
import android.view.View;
import android.view.animation.Interpolator;
-import android.widget.EditText;
-
-import androidx.core.os.BuildCompat;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
@@ -62,8 +55,8 @@
* If release velocity < THRES1, snap according to either top or bottom depending on whether it's
* closer to top or closer to the page indicator.
*/
-public class AllAppsTransitionController implements StateHandler<LauncherState>,
- OnDeviceProfileChangeListener {
+public class AllAppsTransitionController
+ implements StateHandler<LauncherState>, OnDeviceProfileChangeListener {
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -97,7 +90,6 @@
private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent
private float mScrollRangeDelta = 0;
- private AllAppsInsetTransitionController mInsetController;
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
@@ -112,10 +104,6 @@
return mShiftRange;
}
- public AllAppsInsetTransitionController getInsetController() {
- return mInsetController;
- }
-
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mIsVerticalLayout = dp.isVerticalBarLayout();
@@ -138,14 +126,9 @@
*/
public void setProgress(float progress) {
mProgress = progress;
- mScrimView.setProgress(progress);
- float shiftCurrent = progress * mShiftRange;
- mAppsView.setTranslationY(shiftCurrent);
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get()) {
- mInsetController.setProgress(progress);
- }
+ mScrimView.setProgress(progress);
+ mAppsView.setTranslationY(progress * mShiftRange);
}
public float getProgress() {
@@ -206,25 +189,10 @@
*/
public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) {
int visibleElements = state.getVisibleElements(mLauncher);
- boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
- boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
-
Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
- Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
-
-
- setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
- setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
- mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
- hasAllAppsContent, setter, headerFade, allAppsFade);
-
- mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
-
- // Set visibility of the container at the very beginning or end of the transition.
- setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
- hasAnyVisibleItem ? INSTANT : FINAL_FRAME);
+ setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade);
}
public AnimatorListenerAdapter getProgressAnimatorListener() {
@@ -234,10 +202,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()) {
- mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && Utilities.ATLEAST_R) {
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
@@ -261,19 +226,9 @@
* TODO: This logic should go in {@link LauncherState}
*/
private void onProgressAnimationEnd() {
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
}
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
- && !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get() && BuildCompat.isAtLeastR()) {
- mInsetController.onAnimationEnd(mProgress);
- if (Float.compare(mProgress, 0f) == 0) {
- EditText editText = mAppsView.getSearchUiManager().getEditText();
- if (editText != null && !mInsetController.showSearchEduIfNecessary()) {
- editText.requestFocus();
- }
- }
- // TODO: should make the controller hide synchronously
- }
}
}
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..9bf6043 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -17,10 +17,8 @@
import android.graphics.Rect;
import android.view.View;
-import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.PropertySetter;
/**
* A abstract representation of a row in all-apps view
@@ -47,9 +45,6 @@
*/
boolean hasVisibleContent();
- void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade);
-
/**
* Scrolls the content vertically.
*/
@@ -61,4 +56,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..86f330c 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
@@ -26,7 +24,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
@@ -37,7 +34,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.AllAppsRow;
@@ -88,7 +84,6 @@
private int mSnappedScrolledY;
private int mTranslationY;
- private boolean mAllowTouchForwarding;
private boolean mForwardToRecyclerView;
protected boolean mTabsHidden;
@@ -200,7 +195,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();
}
}
@@ -350,10 +345,6 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (!mAllowTouchForwarding) {
- mForwardToRecyclerView = false;
- return super.onInterceptTouchEvent(ev);
- }
calcOffset(mTempOffset);
ev.offsetLocation(mTempOffset.x, mTempOffset.y);
mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
@@ -382,20 +373,6 @@
p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
}
- public void setContentVisibility(boolean hasHeader, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- for (FloatingHeaderRow row : mAllRows) {
- row.setContentVisibility(hasHeader, hasAllAppsContent, setter, headerFade, allAppsFade);
- }
-
- allowTouchForwarding(hasAllAppsContent);
- setter.setFloat(mTabLayout, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
- }
-
- protected void allowTouchForwarding(boolean allow) {
- mAllowTouchForwarding = allow;
- }
-
public boolean hasVisibleContent() {
for (FloatingHeaderRow row : mAllRows) {
if (row.hasVisibleContent()) {
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 4876298..13ddc12 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -16,8 +16,6 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SWITCHED_TO_MAIN_TAB;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SWITCHED_TO_WORK_TAB;
import android.content.Context;
import android.graphics.Rect;
@@ -87,17 +85,10 @@
}
@Override
- public void onTabChanged(int pos) {
- super.onTabChanged(pos);
+ public void onActivePageChanged(int currentActivePage) {
+ super.onActivePageChanged(currentActivePage);
if (mUsingTabs) {
- // Log tab switches only when the launcher is in AllApps state
- if (mLauncher.getStateManager().getCurrentStableState() == LauncherState.ALL_APPS) {
- mLauncher.getStatsLogManager().logger()
- .log(pos == AdapterHolder.WORK ? LAUNCHER_ALLAPPS_SWITCHED_TO_WORK_TAB
- : LAUNCHER_ALLAPPS_SWITCHED_TO_MAIN_TAB);
- }
-
- if (pos == AdapterHolder.WORK) {
+ if (currentActivePage == AdapterHolder.WORK) {
WorkEduView.showWorkEduIfNeeded(mLauncher);
} else {
mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index cf7142c..5b5fbb7 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -18,14 +18,10 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
import android.graphics.Rect;
import android.view.View;
-import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.PropertySetter;
import com.android.systemui.plugins.AllAppsRow;
/**
@@ -65,13 +61,6 @@
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
- PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
- // Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(mView, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
- }
-
- @Override
public void setVerticalScroll(int scroll, boolean isScrolledOut) {
mView.setVisibility(isScrolledOut ? INVISIBLE : VISIBLE);
if (!isScrolledOut) {
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index f926086..0a2dea9 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -15,16 +15,12 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
-
import android.graphics.Rect;
import android.view.KeyEvent;
-import android.view.animation.Interpolator;
-import android.widget.EditText;
import androidx.annotation.Nullable;
-import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.ExtendedEditText;
/**
* Interface for controlling the Apps search UI.
@@ -34,7 +30,7 @@
/**
* Initializes the search manager.
*/
- void initialize(AllAppsContainerView containerView);
+ void initializeSearch(AllAppsContainerView containerView);
/**
* Notifies the search manager to close any active search session.
@@ -45,7 +41,7 @@
* Called before dispatching a key event, in case the search manager wants to initialize
* some UI beforehand.
*/
- void preDispatchKeyEvent(KeyEvent keyEvent);
+ default void preDispatchKeyEvent(KeyEvent keyEvent) { };
/**
* Returns the vertical shift for the all-apps view, so that it aligns with the hotseat.
@@ -53,26 +49,18 @@
float getScrollRangeDelta(Rect insets);
/**
- * Called as part of state transition to update the content UI
- */
- void setContentVisibility(int visibleElements, PropertySetter setter,
- Interpolator interpolator);
-
- /**
* Called when activity is destroyed. Used to close search system services
*/
- default void destroy(){}
-
- /**
- * Returns true if the QSB should be visible for the given set of visible elements
- */
- default boolean isQsbVisible(int visibleElements) {
- return (visibleElements & ALL_APPS_HEADER) != 0;
- }
+ default void destroySearch() { }
/**
* @return the edit text object
*/
@Nullable
- EditText getEditText();
+ ExtendedEditText getEditText();
+
+ /**
+ * sets highlight result's title
+ */
+ default void setFocusedResultTitle(@Nullable CharSequence title) { }
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 198c4b2..79718fb 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,9 +33,8 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.PackageManagerHelper;
-
-import java.util.ArrayList;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
/**
* An interface to a search box that AllApps can command.
@@ -43,11 +44,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 +58,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 +86,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,33 +99,19 @@
}
// 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) {
- // selectFocusedView should return SearchTargetEvent that is passed onto onClick
- if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
- return true;
- }
- }
- }
- // Skip if it's not the right action
- if (actionId != EditorInfo.IME_ACTION_SEARCH) {
- return false;
+ if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
+ mLauncher.getStatsLogManager().logger()
+ .log(LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME);
+ // selectFocusedView should return SearchTargetEvent that is passed onto onClick
+ return Launcher.getLauncher(mLauncher).getAppsView().launchHighlightedItem();
}
-
- // Skip if the query is empty
- String query = v.getText().toString();
- if (query.isEmpty()) {
- return false;
- }
- return mLauncher.startActivitySafely(v,
- PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null
- );
+ return false;
}
@Override
@@ -140,7 +127,7 @@
@Override
public void onFocusChange(View view, boolean hasFocus) {
- if (!hasFocus) {
+ if (!hasFocus && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mInput.hideKeyboard();
}
}
@@ -149,7 +136,7 @@
* Resets the search bar state.
*/
public void reset() {
- mCb.clearSearchResult();
+ mCallback.clearSearchResult();
mInput.reset();
mQuery = null;
}
@@ -167,31 +154,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 aef32d7..bfcc1c7 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -31,8 +31,6 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
-import android.view.animation.Interpolator;
-import android.widget.EditText;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
@@ -45,8 +43,8 @@
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
-import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.search.SearchCallback;
import java.util.ArrayList;
@@ -54,7 +52,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;
@@ -134,7 +132,7 @@
}
@Override
- public void initialize(AllAppsContainerView appsView) {
+ public void initializeSearch(AllAppsContainerView appsView) {
mApps = appsView.getApps();
mAppsView = appsView;
mSearchBarController.initialize(
@@ -223,13 +221,7 @@
}
@Override
- public void setContentVisibility(int visibleElements, PropertySetter setter,
- Interpolator interpolator) {
- setter.setViewAlpha(this, isQsbVisible(visibleElements) ? 1 : 0, interpolator);
- }
-
- @Override
- public EditText getEditText() {
+ public ExtendedEditText getEditText() {
return this;
}
}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
index 84688e1..34895ed 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -25,6 +25,7 @@
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.search.StringMatcherUtility;
import java.util.ArrayList;
import java.util.List;
@@ -37,14 +38,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
@@ -67,10 +68,10 @@
// apps that don't match all of the words in the query.
final String queryTextLower = query.toLowerCase();
final ArrayList<AppInfo> result = new ArrayList<>();
- DefaultAppSearchAlgorithm.StringMatcher matcher =
- DefaultAppSearchAlgorithm.StringMatcher.getInstance();
+ StringMatcherUtility.StringMatcher matcher =
+ StringMatcherUtility.StringMatcher.getInstance();
for (AppInfo info : apps) {
- if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) {
+ if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
result.add(info);
}
}
@@ -81,7 +82,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..a386ef8 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -19,14 +19,14 @@
import android.os.Handler;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.model.data.AppInfo;
-
-import java.text.Collator;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
/**
* The default search implementation.
*/
-public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
+public class DefaultAppSearchAlgorithm implements SearchAlgorithm<AdapterItem> {
protected final Handler mResultHandler;
private final AppsSearchPipeline mAppsSearchPipeline;
@@ -45,138 +45,10 @@
@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)),
null);
}
-
- public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
- int queryLength = query.length();
-
- String title = info.title.toString();
- int titleLength = title.length();
-
- if (titleLength < queryLength || queryLength <= 0) {
- return false;
- }
-
- if (requestSimpleFuzzySearch(query)) {
- return title.toLowerCase().contains(query);
- }
-
- int lastType;
- int thisType = Character.UNASSIGNED;
- int nextType = Character.getType(title.codePointAt(0));
-
- int end = titleLength - queryLength;
- for (int i = 0; i <= end; i++) {
- lastType = thisType;
- thisType = nextType;
- nextType = i < (titleLength - 1) ?
- Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED;
- if (isBreak(thisType, lastType, nextType) &&
- matcher.matches(query, title.substring(i, i + queryLength))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if the current point should be a break point. Following cases
- * are considered as break points:
- * 1) Any non space character after a space character
- * 2) Any digit after a non-digit character
- * 3) Any capital character after a digit or small character
- * 4) Any capital character before a small character
- */
- private static boolean isBreak(int thisType, int prevType, int nextType) {
- switch (prevType) {
- case Character.UNASSIGNED:
- case Character.SPACE_SEPARATOR:
- case Character.LINE_SEPARATOR:
- case Character.PARAGRAPH_SEPARATOR:
- return true;
- }
- switch (thisType) {
- case Character.UPPERCASE_LETTER:
- if (nextType == Character.UPPERCASE_LETTER) {
- return true;
- }
- // Follow through
- case Character.TITLECASE_LETTER:
- // Break point if previous was not a upper case
- return prevType != Character.UPPERCASE_LETTER;
- case Character.LOWERCASE_LETTER:
- // Break point if previous was not a letter.
- return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
- case Character.DECIMAL_DIGIT_NUMBER:
- case Character.LETTER_NUMBER:
- case Character.OTHER_NUMBER:
- // Break point if previous was not a number
- return !(prevType == Character.DECIMAL_DIGIT_NUMBER
- || prevType == Character.LETTER_NUMBER
- || prevType == Character.OTHER_NUMBER);
- case Character.MATH_SYMBOL:
- case Character.CURRENCY_SYMBOL:
- case Character.OTHER_PUNCTUATION:
- case Character.DASH_PUNCTUATION:
- // Always a break point for a symbol
- return true;
- default:
- return false;
- }
- }
-
- public static class StringMatcher {
-
- private static final char MAX_UNICODE = '\uFFFF';
-
- private final Collator mCollator;
-
- StringMatcher() {
- // On android N and above, Collator uses ICU implementation which has a much better
- // support for non-latin locales.
- mCollator = Collator.getInstance();
- mCollator.setStrength(Collator.PRIMARY);
- mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
- }
-
- /**
- * Returns true if {@param query} is a prefix of {@param target}
- */
- public boolean matches(String query, String target) {
- switch (mCollator.compare(query, target)) {
- case 0:
- return true;
- case -1:
- // The target string can contain a modifier which would make it larger than
- // the query string (even though the length is same). If the query becomes
- // larger after appending a unicode character, it was originally a prefix of
- // the target string and hence should match.
- return mCollator.compare(query + MAX_UNICODE, target) > -1;
- default:
- return false;
- }
- }
-
- public static StringMatcher getInstance() {
- return new StringMatcher();
- }
- }
-
- private static boolean requestSimpleFuzzySearch(String s) {
- for (int i = 0; i < s.length(); ) {
- int codepoint = s.codePointAt(i);
- i += Character.charCount(codepoint);
- switch (Character.UnicodeScript.of(codepoint)) {
- case HAN:
- //Character.UnicodeScript.HAN: use String.contains to match
- return true;
- }
- }
- return false;
- }
}
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index 330ec68..ba895ed 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -20,20 +20,26 @@
import android.view.ViewGroup;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.model.data.ItemInfo;
/**
* Provides views for local search results
*/
public class DefaultSearchAdapterProvider extends SearchAdapterProvider {
+ private View mHighlightedView;
+
public DefaultSearchAdapterProvider(BaseDraggingActivity launcher) {
super(launcher);
}
@Override
public void onBindView(AllAppsGridAdapter.ViewHolder holder, int position) {
-
+ if (position == 0) {
+ mHighlightedView = holder.itemView;
+ }
}
@Override
@@ -48,7 +54,17 @@
}
@Override
- public boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem adapterItem, View view) {
+ public boolean launchHighlightedItem() {
+ if (mHighlightedView instanceof BubbleTextView
+ && mHighlightedView.getTag() instanceof ItemInfo) {
+ ItemInfo itemInfo = (ItemInfo) mHighlightedView.getTag();
+ return mLauncher.startActivitySafely(mHighlightedView, itemInfo.getIntent(), itemInfo);
+ }
return false;
}
+
+ @Override
+ public View getHighlightedItem() {
+ return mHighlightedView;
+ }
}
diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
deleted file mode 100644
index 4ef154e..0000000
--- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
-
-import android.app.Activity;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.lifecycle.Observer;
-import androidx.slice.Slice;
-import androidx.slice.SliceViewManager;
-import androidx.slice.SliceViewManager.SliceCallback;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Manages Lifecycle for Live search results
- */
-public class LiveSearchManager implements StateListener<LauncherState> {
-
- private static final String TAG = "LiveSearchManager";
-
- public static final int SEARCH_APPWIDGET_HOST_ID = 2048;
-
- private final Launcher mLauncher;
- private final HashMap<Uri, SliceLifeCycle> mUriSliceMap = new HashMap<>();
-
- private final HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders =
- new HashMap<>();
- private SearchWidgetHost mSearchWidgetHost;
-
- public LiveSearchManager(Launcher launcher) {
- mLauncher = launcher;
- mLauncher.getStateManager().addStateListener(this);
- }
-
- /**
- * Creates new {@link AppWidgetHostView} from {@link AppWidgetProviderInfo}. Caches views for
- * quicker result within the same search session
- */
- public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) {
- if (mSearchWidgetHost == null) {
- mSearchWidgetHost = new SearchWidgetHost(mLauncher);
- mSearchWidgetHost.startListening();
- }
-
- ComponentName provider = providerInfo.provider;
- UserHandle userHandle = providerInfo.getProfile();
-
- ComponentKey key = new ComponentKey(provider, userHandle);
- if (mWidgetPlaceholders.containsKey(key)) {
- return mWidgetPlaceholders.get(key);
- }
-
- LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
- mLauncher, providerInfo);
- PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo);
-
- Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo);
- int appWidgetId = mSearchWidgetHost.allocateAppWidgetId();
- boolean success = AppWidgetManager.getInstance(mLauncher)
- .bindAppWidgetIdIfAllowed(appWidgetId, userHandle, provider, options);
- if (!success) {
- mSearchWidgetHost.deleteAppWidgetId(appWidgetId);
- mWidgetPlaceholders.put(key, null);
- return null;
- }
-
- SearchWidgetInfoContainer view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(
- mLauncher, appWidgetId, providerInfo);
- view.setTag(pendingAddWidgetInfo);
- mWidgetPlaceholders.put(key, view);
- return view;
- }
-
- /**
- * Stop search session
- */
- public void stop() {
- clearWidgetHost();
- }
-
- private void clearWidgetHost() {
- if (mSearchWidgetHost != null) {
- mSearchWidgetHost.stopListening();
- mSearchWidgetHost.clearViews();
- mSearchWidgetHost.deleteHost();
- mWidgetPlaceholders.clear();
- mSearchWidgetHost = null;
- }
- }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState != ALL_APPS) {
- // Clear all search session related objects
- mUriSliceMap.values().forEach(SliceLifeCycle::destroy);
- mUriSliceMap.clear();
-
- clearWidgetHost();
- }
- }
-
- /**
- * Adds a new observer for the provided uri and returns a callback to cancel this observer
- */
- public SafeCloseable addObserver(Uri uri, Observer<Slice> listener) {
- SliceLifeCycle slc = mUriSliceMap.get(uri);
- if (slc == null) {
- slc = new SliceLifeCycle(uri, mLauncher);
- mUriSliceMap.put(uri, slc);
- }
- slc.addListener(listener);
-
- final SliceLifeCycle sliceLifeCycle = slc;
- return () -> sliceLifeCycle.removeListener(listener);
- }
-
- static class SearchWidgetHost extends AppWidgetHost {
- SearchWidgetHost(Context context) {
- super(context, SEARCH_APPWIDGET_HOST_ID);
- }
-
- @Override
- protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
- AppWidgetProviderInfo appWidget) {
- return new SearchWidgetInfoContainer(context);
- }
-
- @Override
- public void clearViews() {
- super.clearViews();
- }
- }
-
- private static class SliceLifeCycle
- implements ActivityLifecycleCallbacks, SliceCallback {
-
- private final Uri mUri;
- private final Launcher mLauncher;
- private final SliceViewManager mSliceViewManager;
- private final ArrayList<Observer<Slice>> mListeners = new ArrayList<>();
-
- private boolean mDestroyed = false;
- private boolean mWasListening = false;
-
- SliceLifeCycle(Uri uri, Launcher launcher) {
- mUri = uri;
- mLauncher = launcher;
- mSliceViewManager = SliceViewManager.getInstance(launcher);
- launcher.registerActivityLifecycleCallbacks(this);
-
- if (launcher.isDestroyed()) {
- onActivityDestroyed(launcher);
- } else if (launcher.isStarted()) {
- onActivityStarted(launcher);
- }
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- destroy();
- }
-
- @Override
- public void onActivityStarted(Activity activity) {
- updateListening();
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- updateListening();
- }
-
- private void updateListening() {
- boolean isListening = mDestroyed
- ? false
- : (mLauncher.isStarted() && !mListeners.isEmpty());
- UI_HELPER_EXECUTOR.execute(() -> uploadListeningBg(isListening));
- }
-
- @WorkerThread
- private void uploadListeningBg(boolean isListening) {
- if (mWasListening != isListening) {
- mWasListening = isListening;
- if (isListening) {
- mSliceViewManager.registerSliceCallback(mUri, MAIN_EXECUTOR, this);
- // Update slice one-time on the different thread so that we can display
- // multiple slices in parallel
- THREAD_POOL_EXECUTOR.execute(this::updateSlice);
- } else {
- mSliceViewManager.unregisterSliceCallback(mUri, this);
- }
- }
- }
-
- @UiThread
- private void addListener(Observer<Slice> listener) {
- mListeners.add(listener);
- updateListening();
- }
-
- @UiThread
- private void removeListener(Observer<Slice> listener) {
- mListeners.remove(listener);
- updateListening();
- }
-
- @WorkerThread
- private void updateSlice() {
- try {
- Slice s = mSliceViewManager.bindSlice(mUri);
- MAIN_EXECUTOR.execute(() -> onSliceUpdated(s));
- } catch (Exception e) {
- Log.d(TAG, "Error fetching slice", e);
- }
- }
-
- @UiThread
- @Override
- public void onSliceUpdated(@Nullable Slice s) {
- mListeners.forEach(l -> l.onChanged(s));
- }
-
- private void destroy() {
- if (mDestroyed) {
- return;
- }
- mDestroyed = true;
- mLauncher.unregisterActivityLifecycleCallbacks(this);
- mListeners.clear();
- }
-
- @Override
- public void onActivityCreated(Activity activity, Bundle bundle) { }
-
- @Override
- public void onActivityPaused(Activity activity) { }
-
- @Override
- public void onActivityResumed(Activity activity) { }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
- }
-}
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index a79ec43..a650a7d 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -16,6 +16,7 @@
package com.android.launcher3.allapps.search;
+import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -40,6 +41,12 @@
public abstract void onBindView(AllAppsGridAdapter.ViewHolder holder, int position);
/**
+ * Called from LiveSearchManager to notify slice status updates.
+ */
+ public void onSliceStatusUpdate(Uri sliceUri) {
+ }
+
+ /**
* Returns whether or not viewType can be handled by searchProvider
*/
public abstract boolean isSearchView(int viewType);
@@ -51,16 +58,29 @@
ViewGroup parent, int viewType);
/**
+ * Returns supported item per row combinations supported
+ */
+ public int[] getSupportedItemsPerRowArray() {
+ return new int[]{};
+ }
+
+ /**
* Returns how many cells a view should span
*/
- public int getGridSpanSize(int viewType, int appsPerRow) {
- return appsPerRow * AllAppsGridAdapter.SPAN_MULTIPLIER;
+ public int getItemsPerRow(int viewType, int appsPerRow) {
+ return appsPerRow;
}
/**
* handles selection event on search adapter item. Returns false if provider can not handle
* event
*/
- public abstract boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem adapterItem,
- View view);
+ public abstract boolean launchHighlightedItem();
+
+ /**
+ * Returns the current highlighted view
+ */
+ public abstract View getHighlightedItem();
+
+
}
diff --git a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java
deleted file mode 100644
index 8e5f8cb..0000000
--- a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-import android.widget.RemoteViews;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A placeholder {@link AppWidgetHostView} used for managing widget search results
- */
-public class SearchWidgetInfoContainer extends AppWidgetHostView {
- private int mAppWidgetId;
- private AppWidgetProviderInfo mProviderInfo;
- private RemoteViews mViews;
- private List<AppWidgetHostView> mListeners = new ArrayList<>();
-
- public SearchWidgetInfoContainer(Context context) {
- super(context);
- }
-
- @Override
- public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
- mAppWidgetId = appWidgetId;
- mProviderInfo = info;
- for (AppWidgetHostView listener : mListeners) {
- listener.setAppWidget(mAppWidgetId, mProviderInfo);
- }
- }
-
- @Override
- public void updateAppWidget(RemoteViews remoteViews) {
- mViews = remoteViews;
- for (AppWidgetHostView listener : mListeners) {
- listener.updateAppWidget(remoteViews);
- }
- }
-
- /**
- * Create a live {@link AppWidgetHostView} from placeholder
- */
- public void attachWidget(AppWidgetHostView hv) {
- hv.setTag(getTag());
- hv.setAppWidget(mAppWidgetId, mProviderInfo);
- hv.updateAppWidget(mViews);
- mListeners.add(hv);
- }
-
- /**
- * stops AppWidgetHostView from getting updates
- */
- public void detachWidget(AppWidgetHostView hostView) {
- mListeners.remove(hostView);
- }
-
-}
diff --git a/src/com/android/launcher3/allapps/search/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..56dd63c 100644
--- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
+++ b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
@@ -18,37 +18,19 @@
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 GROUPING = 1 << 1;
private String mSectionId;
private SectionDecorationHandler mDecorationHandler;
- public int getPosStart() {
- return mPosStart;
- }
-
- public void setPosStart(int posStart) {
- mPosStart = posStart;
- }
-
- 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 +38,6 @@
mDecorationHandler = sectionDecorationHandler;
}
-
public SectionDecorationHandler getDecorationHandler() {
return mDecorationHandler;
}
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 8016b2d..7980138 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,9 +16,6 @@
package com.android.launcher3.anim;
-import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-
-import android.content.Context;
import android.graphics.Path;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
@@ -67,9 +64,6 @@
*/
public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1;
- private static final int MIN_SETTLE_DURATION = 200;
- private static final float OVERSHOOT_FACTOR = 0.9f;
-
static {
Path exaggeratedEase = new Path();
exaggeratedEase.moveTo(0, 0);
@@ -83,6 +77,9 @@
public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+ public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL =
+ v -> ACCEL_DEACCEL.getInterpolation(TOUCH_RESPONSE_INTERPOLATOR.getInterpolation(v));
+
/**
* Inversion of ZOOM_OUT, compounded with an ease-out.
@@ -175,76 +172,4 @@
float upperBound) {
return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound);
}
-
- /**
- * Computes parameters necessary for an overshoot effect.
- */
- public static class OvershootParams {
- public Interpolator interpolator;
- public float start;
- public float end;
- public long duration;
-
- /**
- * Given the input params, sets OvershootParams variables to be used by the caller.
- * @param startProgress The progress from 0 to 1 that the overshoot starts from.
- * @param overshootPastProgress The progress from 0 to 1 where we overshoot past (should
- * either be equal to startProgress or endProgress, depending on if we want to
- * overshoot immediately or only once we reach the end).
- * @param endProgress The final progress from 0 to 1 that we will settle to.
- * @param velocityPxPerMs The initial velocity that causes this overshoot.
- * @param totalDistancePx The distance against which progress is calculated.
- */
- public OvershootParams(float startProgress, float overshootPastProgress,
- float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) {
- velocityPxPerMs = Math.abs(velocityPxPerMs);
- overshootPastProgress = Math.max(overshootPastProgress, startProgress);
- start = startProgress;
- int startPx = (int) (start * totalDistancePx);
- // Overshoot by about half a frame.
- float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
- getSingleFrameMs(context) / totalDistancePx / 2;
- overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
- end = overshootPastProgress + overshootBy;
- int endPx = (int) (end * totalDistancePx);
- int overshootDistance = endPx - startPx;
- // Calculate deceleration necessary to reach overshoot distance.
- // Formula: velocityFinal^2 = velocityInitial^2 + 2 * acceleration * distance
- // 0 = v^2 + 2ad (velocityFinal == 0)
- // a = v^2 / -2d
- float decelerationPxPerMs = velocityPxPerMs * velocityPxPerMs / (2 * overshootDistance);
- // Calculate time necessary to reach peak of overshoot.
- // Formula: acceleration = velocity / time
- // time = velocity / acceleration
- duration = (long) (velocityPxPerMs / decelerationPxPerMs);
-
- // Now that we're at the top of the overshoot, need to settle back to endProgress.
- float settleDistance = end - endProgress;
- int settleDistancePx = (int) (settleDistance * totalDistancePx);
- // Calculate time necessary for the settle.
- // Formula: distance = velocityInitial * time + 1/2 * acceleration * time^2
- // d = 1/2at^2 (velocityInitial = 0, since we just stopped at the top)
- // t = sqrt(2d/a)
- // Above formula assumes constant acceleration. Since we use ACCEL_DEACCEL, we actually
- // have acceleration to halfway then deceleration the rest. So the formula becomes:
- // t = sqrt(d/a) * 2 (half the distance for accel, half for deaccel)
- long settleDuration = (long) Math.sqrt(settleDistancePx / decelerationPxPerMs) * 4;
-
- settleDuration = Math.max(MIN_SETTLE_DURATION, settleDuration);
- // How much of the animation to devote to playing the overshoot (the rest is for settle).
- float overshootFraction = (float) duration / (duration + settleDuration);
- duration += settleDuration;
- // Finally, create the interpolator, composed of two interpolators: an overshoot, which
- // reaches end > 1, and then a settle to endProgress.
- Interpolator overshoot = Interpolators.clampToProgress(DEACCEL, 0, overshootFraction);
- // The settle starts at 1, where 1 is the top of the overshoot, and maps to a fraction
- // such that final progress is endProgress. For example, if we overshot to 1.1 but want
- // to end at 1, we need to map to 1/1.1.
- Interpolator settle = Interpolators.clampToProgress(Interpolators.mapToProgress(
- ACCEL_DEACCEL, 1, (endProgress - start) / (end - start)), overshootFraction, 1);
- interpolator = t -> t <= overshootFraction
- ? overshoot.getInterpolation(t)
- : settle.getInterpolation(t);
- }
- }
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 556aff6..7f76d27 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -95,9 +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 FOLDER_NAME_SUGGEST = new DeviceFlag(
"FOLDER_NAME_SUGGEST", true,
"Suggests folder names instead of blank text.");
@@ -143,6 +140,9 @@
public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
"ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
+ public static final BooleanFlag ENABLE_WIDGETS_PICKER_AIAI_SEARCH = new DeviceFlag(
+ "ENABLE_WIDGETS_PICKER_AIAI_SEARCH", false, "Enable AiAi search in the widgets picker");
+
public static final BooleanFlag ENABLE_OVERVIEW_SHARE = getDebugFlag(
"ENABLE_OVERVIEW_SHARE", false, "Show Share button in Overview Actions");
@@ -161,7 +161,7 @@
"ENABLE_SMARTSPACE_UNIVERSAL", false,
"Replace Smartspace with a version rendered by System UI.");
- public static final BooleanFlag ENABLE_SMARTSPACE_ENHANCED = getDebugFlag(
+ public static final BooleanFlag ENABLE_SMARTSPACE_ENHANCED = new DeviceFlag(
"ENABLE_SMARTSPACE_ENHANCED", false,
"Replace Smartspace with the enhanced version. "
+ "Ignored if ENABLE_SMARTSPACE_UNIVERSAL is enabled.");
@@ -201,13 +201,23 @@
"ENABLE_APP_PREDICTIONS_WHILE_VISIBLE", true, "Allows app "
+ "predictions to be updated while they are visible to the user.");
- public static final BooleanFlag ENABLE_TASKBAR = new DeviceFlag(
+ public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
"ENABLE_TASKBAR", false, "Allows a system Taskbar to be shown on larger devices.");
- public static final BooleanFlag ENABLE_OVERVIEW_GRID = new DeviceFlag(
+ public static final BooleanFlag ENABLE_OVERVIEW_GRID = getDebugFlag(
"ENABLE_OVERVIEW_GRID", false, "Uses grid overview layout. "
+ "Only applicable on large screen devices.");
+ public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(
+ "ENABLE_TWO_PANEL_HOME", false,
+ "Uses two panel on home screen. Only applicable on large screen devices.");
+
+ public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
+ "ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
+
+ public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
+ "ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
@@ -266,6 +276,8 @@
}
public void addChangeListener(Context context, Runnable r) { }
+
+ public void removeChangeListener(Runnable r) {}
}
public static class DebugFlag extends BooleanFlag {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 77d2b85..b7a7366 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -37,18 +37,19 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
+import android.view.WindowManager;
+import android.widget.TextView;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetHost;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.ItemInstallQueue;
@@ -56,8 +57,11 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetImageView;
import com.android.launcher3.widget.WidgetManagerHelper;
@@ -78,7 +82,7 @@
private LauncherAppState mApp;
private InvariantDeviceProfile mIdp;
- private LivePreviewWidgetCell mWidgetCell;
+ private WidgetCell mWidgetCell;
// Widget request specific options.
private LauncherAppWidgetHost mAppWidgetHost;
@@ -117,14 +121,18 @@
}
}
- mWidgetCell.setOnTouchListener(this);
- mWidgetCell.setOnLongClickListener(this);
+ WidgetImageView preview = mWidgetCell.findViewById(R.id.widget_preview);
+ preview.setOnTouchListener(this);
+ preview.setOnLongClickListener(this);
// savedInstanceState is null when the activity is created the first time (i.e., avoids
// duplicate logging during rotation)
if (savedInstanceState == null) {
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_START);
}
+
+ TextView widgetAppName = findViewById(R.id.widget_appName);
+ widgetAppName.setText(getApplicationInfo().labelRes);
}
@Override
@@ -140,7 +148,7 @@
// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
// we abort the drag.
- if (img.getBitmap() == null) {
+ if (img.getDrawable() == null) {
return false;
}
@@ -149,7 +157,7 @@
// Start home and pass the draw request params
PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
- img.getBitmap().getWidth(), img.getWidth());
+ img.getDrawable().getIntrinsicWidth(), img.getWidth());
// Start a system drag and drop. We use a transparent bitmap as preview for system drag
@@ -324,4 +332,15 @@
.withItemInfo((ItemInfo) mWidgetCell.getWidgetView().getTag())
.log(command);
}
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ View view = getWindow().getDecorView();
+ WindowManager.LayoutParams layoutParams =
+ (WindowManager.LayoutParams) view.getLayoutParams();
+ layoutParams.gravity = Gravity.BOTTOM;
+ layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
+ getWindowManager().updateViewLayout(view, layoutParams);
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
new file mode 100644
index 0000000..5cd95dc
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.dragndrop;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/** A drawable which renders {@link LauncherAppWidgetHostView} to a canvas. */
+public final class AppWidgetHostViewDrawable extends Drawable {
+
+ private final LauncherAppWidgetHostView mAppWidgetHostView;
+ private Paint mPaint = new Paint();
+ private final Path mClipPath;
+
+ public AppWidgetHostViewDrawable(LauncherAppWidgetHostView appWidgetHostView) {
+ mAppWidgetHostView = appWidgetHostView;
+ Path clipPath = null;
+ if (appWidgetHostView.getClipToOutline()) {
+ Outline outline = new Outline();
+ mAppWidgetHostView.getOutlineProvider().getOutline(mAppWidgetHostView, outline);
+ Rect rect = new Rect();
+ if (outline.getRect(rect)) {
+ float radius = outline.getRadius();
+ clipPath = new Path();
+ clipPath.addRoundRect(new RectF(rect), radius, radius, Path.Direction.CCW);
+ }
+ }
+ mClipPath = clipPath;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ int saveCount = canvas.saveLayer(0, 0, getIntrinsicWidth(), getIntrinsicHeight(), mPaint);
+ if (mClipPath != null) {
+ canvas.clipPath(mClipPath);
+ }
+ mAppWidgetHostView.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mAppWidgetHostView.getMeasuredWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mAppWidgetHostView.getMeasuredHeight();
+ }
+
+ @Override
+ public int getOpacity() {
+ // This is up to app widget provider. We don't know if the host view will cover anything
+ // behind the drawable.
+ return PixelFormat.UNKNOWN;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public int getAlpha() {
+ return mPaint.getAlpha();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ return mPaint.getColorFilter();
+ }
+
+ /** Returns the {@link LauncherAppWidgetHostView}. */
+ public LauncherAppWidgetHostView getAppWidgetHostView() {
+ return mAppWidgetHostView;
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 93df599..b7a70cb 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -25,9 +25,9 @@
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -129,7 +129,7 @@
* drop, it is the responsibility of the {@link DropTarget} to exit out of the spring loaded
* mode. If the drop was cancelled for some reason, the UI will automatically exit out of this mode.
*
- * @param b The bitmap to display as the drag image. It will be re-scaled to the
+ * @param drawable The drawable to be displayed in the drag view. It will be re-scaled to the
* enlarged size.
* @param originalView The source view (ie. icon, widget etc.) that is being dragged
* and which the DragView represents
@@ -140,9 +140,18 @@
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
- public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
- DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
- float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
+ public DragView startDrag(
+ Drawable drawable,
+ DraggableView originalView,
+ int dragLayerX,
+ int dragLayerY,
+ DragSource source,
+ ItemInfo dragInfo,
+ Point dragOffset,
+ Rect dragRegion,
+ float initialDragViewScale,
+ float dragViewScaleOnDrop,
+ DragOptions options) {
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
@@ -173,8 +182,14 @@
final Resources res = mLauncher.getResources();
final float scaleDps = mIsInPreDrag
? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
- final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
- registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
+ final DragView dragView = mDragObject.dragView = new DragView(
+ mLauncher,
+ drawable,
+ registrationX,
+ registrationY,
+ initialDragViewScale,
+ dragViewScaleOnDrop,
+ scaleDps);
dragView.setItemInfo(dragInfo);
mDragObject.dragComplete = false;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 7a6b4f9..419c3f1 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -110,6 +110,8 @@
mDragController = dragController;
recreateControllers();
+ mOverviewScrim.setup();
+
mWorkspaceDragScrim = new WorkspaceDragScrim((this));
mWorkspaceDragScrim.setWorkspace(workspace);
@@ -118,8 +120,6 @@
mRootView = (LauncherRootView) getParent();
mSysUiScrim = new SysUiScrim(mRootView);
mRootView.setSysUiScrim(mSysUiScrim);
-
-
}
@Override
@@ -555,7 +555,6 @@
public void setInsets(Rect insets) {
super.setInsets(insets);
mSysUiScrim.onInsetsChanged(insets, mAllowSysuiScrims);
- mOverviewScrim.onInsetsChanged(insets);
}
public WorkspaceDragScrim getWorkspaceDragScrim() {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 86b93d0..e2816f4 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -67,9 +67,9 @@
public static final int COLOR_CHANGE_DURATION = 120;
public static final int VIEW_ZOOM_DURATION = 150;
- private boolean mDrawBitmap = true;
- private Bitmap mBitmap;
- private Bitmap mCrossFadeBitmap;
+ private boolean mShouldDraw = true;
+ private Drawable mDrawable;
+ private Drawable mCrossFadeDrawable;
@Thunk Paint mPaint;
private final int mBlurSizeOutline;
private final int mRegistrationX;
@@ -114,19 +114,21 @@
* The registration point is the point inside our view that the touch events should
* be centered upon.
* @param launcher The Launcher instance
- * @param bitmap The view that we're dragging around. We scale it up when we draw it.
+ * @param drawable The view that we're dragging around. We scale it up when we draw it.
* @param registrationX The x coordinate of the registration point.
* @param registrationY The y coordinate of the registration point.
*/
- public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
- final float initialScale, final float scaleOnDrop, final float finalScaleDps) {
+ public DragView(Launcher launcher, Drawable drawable, int registrationX,
+ int registrationY, final float initialScale, final float scaleOnDrop,
+ final float finalScaleDps) {
super(launcher);
mLauncher = launcher;
mDragLayer = launcher.getDragLayer();
mDragController = launcher.getDragController();
mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
- final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
+ final float scale = (drawable.getIntrinsicWidth() + finalScaleDps)
+ / drawable.getIntrinsicWidth();
// Set the initial scale to avoid any jumps
setScaleX(initialScale);
@@ -144,8 +146,9 @@
}
});
- mBitmap = bitmap;
- setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
+ mDrawable = drawable;
+ setDragRegion(new Rect(0, 0, drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight()));
// The point in our scaled bitmap that the touch events are located
mRegistrationX = registrationX;
@@ -197,8 +200,8 @@
@Override
public void run() {
Object[] outObj = new Object[1];
- int w = mBitmap.getWidth();
- int h = mBitmap.getHeight();
+ int w = mDrawable.getIntrinsicWidth();
+ int h = mDrawable.getIntrinsicHeight();
Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
if (dr instanceof AdaptiveIconDrawable) {
@@ -214,11 +217,11 @@
mBadge.setBounds(badgeBounds);
// Do not draw the background in case of folder as its translucent
- mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
+ mShouldDraw = !(dr instanceof FolderAdaptiveIcon);
try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
Drawable nDr; // drawable to be normalized
- if (mDrawBitmap) {
+ if (mShouldDraw) {
nDr = dr;
} else {
// Since we just want the scale, avoid heavy drawing operations
@@ -308,7 +311,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
+ setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
}
/** Sets the scale of the view over the normal workspace icon size. */
@@ -352,29 +355,37 @@
return mDragRegion;
}
- public Bitmap getPreviewBitmap() {
- return mBitmap;
- }
-
@Override
protected void onDraw(Canvas canvas) {
mHasDrawn = true;
- if (mDrawBitmap) {
+ if (mShouldDraw) {
// Always draw the bitmap to mask anti aliasing due to clipPath
- boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+ boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeDrawable != null;
if (crossFade) {
int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
mPaint.setAlpha(alpha);
}
- canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+ mDrawable.setColorFilter(mPaint.getColorFilter());
+ mDrawable.setAlpha(mPaint.getAlpha());
+ mDrawable.setBounds(
+ new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+ mDrawable.getIntrinsicHeight()));
+ mDrawable.draw(canvas);
if (crossFade) {
mPaint.setAlpha((int) (255 * mCrossFadeProgress));
final int saveCount = canvas.save();
- float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
- float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+ float sX = ((float) mDrawable.getIntrinsicWidth())
+ / mCrossFadeDrawable.getIntrinsicWidth();
+ float sY = ((float) mDrawable.getIntrinsicHeight())
+ / mCrossFadeDrawable.getIntrinsicHeight();
canvas.scale(sX, sY);
- canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+ mCrossFadeDrawable.setColorFilter(mPaint.getColorFilter());
+ mCrossFadeDrawable.setAlpha(mPaint.getAlpha());
+ mDrawable.setBounds(
+ new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+ mDrawable.getIntrinsicHeight()));
+ mCrossFadeDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
@@ -390,8 +401,8 @@
}
}
- public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
- mCrossFadeBitmap = crossFadeBitmap;
+ public void setCrossFadeDrawable(Drawable crossFadeDrawable) {
+ mCrossFadeDrawable = crossFadeDrawable;
}
public void crossFade(int duration) {
@@ -469,8 +480,8 @@
// Start the pick-up animation
DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
- lp.width = mBitmap.getWidth();
- lp.height = mBitmap.getHeight();
+ lp.width = mDrawable.getIntrinsicWidth();
+ lp.height = mDrawable.getIntrinsicHeight();
lp.customPosition = true;
setLayoutParams(lp);
move(touchX, touchY);
@@ -550,6 +561,11 @@
return mInitialScale;
}
+ /** Returns the current {@link Drawable} that is rendered in this view. */
+ public Drawable getDrawable() {
+ return mDrawable;
+ }
+
private static class SpringFloatValue {
private static final FloatPropertyCompat<SpringFloatValue> VALUE =
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index 7788f93..0a1aba1 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -65,6 +65,9 @@
}
public Runnable getFlingAnimation(DropTarget.DragObject dragObject, DragOptions options) {
+ if (options == null) {
+ return null;
+ }
PointF vel = isFlingingToDelete();
options.isFlingToDelete = vel != null;
if (!options.isFlingToDelete) {
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
deleted file mode 100644
index a9389bc..0000000
--- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package com.android.launcher3.dragndrop;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.RemoteViews;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.widget.WidgetCell;
-
-/**
- * Extension of {@link WidgetCell} which supports generating previews from {@link RemoteViews}
- */
-public class LivePreviewWidgetCell extends WidgetCell {
-
- private RemoteViews mPreview;
-
- public LivePreviewWidgetCell(Context context) {
- this(context, null);
- }
-
- public LivePreviewWidgetCell(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public LivePreviewWidgetCell(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void setPreview(RemoteViews view) {
- mPreview = view;
- }
-
- @Override
- public void ensurePreview() {
- if (mPreview != null && mActiveRequest == null) {
- Bitmap preview = generateFromRemoteViews(
- mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
- if (preview != null) {
- applyPreview(preview);
- return;
- }
- }
- super.ensurePreview();
- }
-
- /**
- * Generates a bitmap by inflating {@param views}.
- * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
- *
- * TODO: Consider moving this to the background thread.
- */
- public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
- LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
-
- DeviceProfile dp = activity.getDeviceProfile();
- int viewWidth = dp.cellWidthPx * info.spanX;
- int viewHeight = dp.cellHeightPx * info.spanY;
-
- final View v;
- try {
- v = views.apply(activity, new FrameLayout(activity));
- v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
-
- viewWidth = v.getMeasuredWidth();
- viewHeight = v.getMeasuredHeight();
- v.layout(0, 0, viewWidth, viewHeight);
- } catch (Exception e) {
- return null;
- }
-
- preScaledWidthOut[0] = viewWidth;
- final int bitmapWidth, bitmapHeight;
- final float scale;
- if (viewWidth > previewSize) {
- scale = ((float) previewSize) / viewWidth;
- bitmapWidth = previewSize;
- bitmapHeight = (int) (viewHeight * scale);
- } else {
- scale = 1;
- bitmapWidth = viewWidth;
- bitmapHeight = viewHeight;
- }
-
- return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
- c.scale(scale, scale);
- v.draw(c);
- });
- }
-}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 6104d80..9f12e6e 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -30,8 +30,8 @@
import com.android.launcher3.DragSource;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingItemDragHelper;
@@ -96,7 +96,7 @@
PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
- dragHelper.setPreview(getPreview(mRequest));
+ dragHelper.setRemoteViewsPreview(getPreview(mRequest));
}
return dragHelper;
}
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index 37200a6..6325877 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -56,9 +56,8 @@
if (mScreen != null) {
// Snap to the screen that we are hovering over now
Workspace w = mLauncher.getWorkspace();
- int page = w.indexOfChild(mScreen);
- if (page != w.getCurrentPage()) {
- w.snapToPage(page);
+ if (!w.isVisible(mScreen)) {
+ w.snapToPage(w.indexOfChild(mScreen));
}
} else {
mLauncher.getDragController().cancelDrag();
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 5954efa..c67efef 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -97,7 +97,7 @@
double thetaShift = 0;
if (curNumItems == 3) {
- thetaShift = Math.PI / 6;
+ thetaShift = Math.PI / 2;
} else if (curNumItems == 4) {
thetaShift = Math.PI / 4;
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 504b29e..ec7155c 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -44,6 +45,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
+import android.util.TypedValue;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -246,11 +248,16 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ final DeviceProfile dp = mLauncher.getDeviceProfile();
+ final int paddingLeftRight = dp.folderContentPaddingLeftRight;
+
mContent = findViewById(R.id.folder_content);
+ mContent.setPadding(paddingLeftRight, dp.folderContentPaddingTop, paddingLeftRight, 0);
mContent.setFolder(this);
mPageIndicator = findViewById(R.id.folder_page_indicator);
mFolderName = findViewById(R.id.folder_name);
+ mFolderName.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.folderLabelTextSizePx);
mFolderName.setOnBackKeyListener(this);
mFolderName.setOnFocusChangeListener(this);
mFolderName.setOnEditorActionListener(this);
@@ -262,12 +269,7 @@
mFolderName.forceDisableSuggestions(true);
mFooter = findViewById(R.id.folder_footer);
-
- // We find out how tall footer wants to be (it is set to wrap_content), so that
- // we can allocate the appropriate amount of space for it.
- int measureSpec = MeasureSpec.UNSPECIFIED;
- mFooter.measure(measureSpec, measureSpec);
- mFooterHeight = mFooter.getMeasuredHeight();
+ mFooterHeight = getResources().getDimensionPixelSize(R.dimen.folder_label_height);
if (Utilities.ATLEAST_R) {
mFolderWindowInsetsAnimationCallback =
@@ -694,6 +696,9 @@
mPageIndicator.stopAllAnimations();
startAnimation(anim);
+ // Because t=0 has the folder match the folder icon, we can skip the
+ // first frame and have the same movement one frame earlier.
+ anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration()));
// Make sure the folder picks up the last drag move even if the finger doesn't move.
if (mDragController.isDragging()) {
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 1cac31e..ee6ea99 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -40,7 +40,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
@@ -177,7 +176,7 @@
Math.round((totalOffsetX + initialSize)),
Math.round((paddingOffsetY + initialSize)));
Rect endRect = new Rect(0, 0, lp.width, lp.height);
- float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics());
+ float finalRadius = mFolderBackground.getCornerRadius();
// Create the animators.
AnimatorSet a = new AnimatorSet();
@@ -324,7 +323,9 @@
final int previewPosX =
(int) ((mTmpParams.transX - iconOffsetX + previewItemOffsetX) / folderScale);
- final int previewPosY = (int) ((mTmpParams.transY + previewItemOffsetY) / folderScale);
+ final float paddingTop = btv.getPaddingTop() * iconScale;
+ final int previewPosY = (int) ((mTmpParams.transY + previewItemOffsetY - paddingTop)
+ / folderScale);
final float xDistance = previewPosX - btvLp.x;
final float yDistance = previewPosY - btvLp.y;
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 21822a3..9bc5444 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -30,9 +30,11 @@
import android.view.View;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.SafeCloseable;
@@ -88,10 +90,14 @@
}
/**
- * Returns a new bitmap to show when the {@link #mView} is being dragged around.
- * Responsibility for the bitmap is transferred to the caller.
+ * Returns a new drawable to show when the {@link #mView} is being dragged around.
+ * Responsibility for the drawable is transferred to the caller.
*/
- public Bitmap createDragBitmap() {
+ public Drawable createDrawable() {
+ if (mView instanceof LauncherAppWidgetHostView) {
+ return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) mView);
+ }
+
int width = 0;
int height = 0;
// Assume scaleX == scaleY, which is always the case for workspace items.
@@ -105,8 +111,9 @@
height = mView.getHeight();
}
- return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, scale));
+ return new FastBitmapDrawable(
+ BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
+ height + blurSizeOutline, (c) -> drawDragView(c, scale)));
}
public final void generateDragOutline(Bitmap preview) {
@@ -129,7 +136,7 @@
return bounds;
}
- public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+ public float getScaleAndPosition(Drawable preview, int[] outPos) {
float scale = Launcher.getLauncher(mView.getContext())
.getDragLayer().getLocationInDragLayer(mView, outPos);
if (mView instanceof LauncherAppWidgetHostView) {
@@ -139,8 +146,8 @@
}
outPos[0] = Math.round(outPos[0] -
- (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
- outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
+ (preview.getIntrinsicWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
+ outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getIntrinsicHeight() / 2
- previewPadding / 2);
return scale;
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3a9986e..5e9b179 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -58,7 +58,6 @@
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -91,6 +90,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
index c0c3e5e..53303db 100644
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -18,10 +18,6 @@
import static android.view.View.VISIBLE;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.graphics.Rect;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
@@ -55,15 +51,15 @@
public OverviewScrim(View view) {
super(view);
- mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
onExtractedColorsChanged(mWallpaperColorInfo);
}
- public void onInsetsChanged(Rect insets) {
- mStableScrimmedView = (OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0
- ? mLauncher.getHotseat()
- : mLauncher.getOverviewPanel();
+ /**
+ * Initializes once view hierarchy is established.
+ */
+ public void setup() {
+ mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
}
public void updateCurrentScrimmedView(ViewGroup root) {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 61f2c2a..988794c 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -131,6 +131,7 @@
/**
* Fetches high-res icon for the provided ItemInfo and updates the caller when done.
+ *
* @return a request ID that can be used to cancel the request.
*/
public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller,
@@ -139,7 +140,7 @@
if (mPendingIconRequestCount <= 0) {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
}
- mPendingIconRequestCount ++;
+ mPendingIconRequestCount++;
HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler,
() -> {
@@ -158,7 +159,7 @@
}
private void onIconRequestEnd() {
- mPendingIconRequestCount --;
+ mPendingIconRequestCount--;
if (mPendingIconRequestCount <= 0) {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
@@ -289,7 +290,8 @@
@NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
boolean usePkgIcon, boolean useLowResIcon) {
CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
- activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon);
+ activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
+ useLowResIcon);
applyCacheEntry(entry, infoInOut);
}
@@ -315,7 +317,8 @@
}
public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
- cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel());
+ cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(),
+ info.getAppLabel());
}
@Override
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 1266bb4..c5ffff9 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -21,8 +21,10 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
import android.content.Context;
+import android.view.View;
import androidx.annotation.Nullable;
+import androidx.slice.SliceItem;
import com.android.launcher3.R;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
@@ -31,8 +33,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ResourceBasedOverride;
-import java.util.Optional;
-
/**
* Handles the user event logging in R+.
*
@@ -363,11 +363,57 @@
@UiEvent(doc = "User closed the AllApps keyboard.")
LAUNCHER_ALLAPPS_KEYBOARD_CLOSED(694),
- @UiEvent(doc = "User switched to Main tab in AllApps screen.")
- LAUNCHER_ALLAPPS_SWITCHED_TO_MAIN_TAB(695),
+ @UiEvent(doc = "User switched to AllApps Main/Personal tab by swiping left.")
+ LAUNCHER_ALLAPPS_SWIPE_TO_PERSONAL_TAB(695),
- @UiEvent(doc = "User switched to Work tab in AllApps screen.")
- LAUNCHER_ALLAPPS_SWITCHED_TO_WORK_TAB(696),
+ @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 = "User long-pressed on an AllApps item.")
+ LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED(719),
+
+ @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),
+
+ @UiEvent(doc = "All apps vertical fling started.")
+ LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN(724),
+
+ @UiEvent(doc = "All apps vertical fling ended.")
+ LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END(725)
;
// ADD MORE
@@ -475,10 +521,24 @@
}
/**
+ * 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) {
}
+
+ /**
+ * Builds the final message and logs it to two different atoms, one for
+ * event tracking and the other for jank tracking.
+ */
+ default void sendToInteractionJankMonitor(EventEnum event, View v) {
+ }
}
/**
@@ -486,7 +546,9 @@
*/
public StatsLogger logger() {
StatsLogger logger = createLogger();
- Optional.ofNullable(mInstanceId).ifPresent(logger::withInstanceId);
+ if (mInstanceId != null) {
+ return logger.withInstanceId(mInstanceId);
+ }
return logger;
}
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 543ee2a..ad553d5 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -124,7 +124,7 @@
public void bindUpdatedWidgets(BgDataModel dataModel) {
final ArrayList<WidgetsListBaseEntry> widgets =
- dataModel.widgetsModel.getWidgetsList(mApp.getContext());
+ dataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index e8a52bd..804e72e 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -25,7 +25,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
@@ -40,6 +39,7 @@
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 79467d3..c6c0791 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -39,7 +39,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
@@ -48,6 +47,7 @@
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index d09bf81..df8367f 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -42,7 +42,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -52,6 +51,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PersistedItemArray;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import java.util.HashSet;
import java.util.List;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8438622..f6c7c06 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -45,12 +45,12 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.TimingLogger;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
@@ -87,6 +87,7 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.ArrayList;
@@ -126,7 +127,7 @@
private final UserManagerState mUserManagerState = new UserManagerState();
- protected Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
+ protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>();
private boolean mStopped;
@@ -191,6 +192,16 @@
loadWorkspace(allShortcuts);
logger.addSplit("loadWorkspace");
+ // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
+ // sanitizeData should not be invoked if the workspace is loaded from a db different
+ // from the main db as defined in the invariant device profile.
+ // (e.g. both grid preview and minimal device mode uses a different db)
+ if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
+ verifyNotStopped();
+ sanitizeData();
+ logger.addSplit("sanitizeData");
+ }
+
verifyNotStopped();
mResults.bindWorkspace();
logger.addSplit("bindWorkspace");
@@ -273,14 +284,6 @@
loadFolderNames();
}
- // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
- // sanitizeData should not be invoked if the workspace is loaded from a db different
- // from the main db as defined in the invariant device profile.
- // (e.g. both grid preview and minimal device mode uses a different db)
- if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
- sanitizeData();
- }
-
verifyNotStopped();
updateHandler.finish();
logger.addSplit("finish icon update");
@@ -664,12 +667,13 @@
final boolean wasProviderReady = !c.hasRestoreFlag(
LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
- if (mWidgetProvidersMap == null) {
- mWidgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(
- context);
+ ComponentKey providerKey = new ComponentKey(component, c.user);
+ if (!mWidgetProvidersMap.containsKey(providerKey)) {
+ mWidgetProvidersMap.put(providerKey,
+ widgetHelper.findProvider(component, c.user));
}
- final AppWidgetProviderInfo provider = mWidgetProvidersMap.get(
- new ComponentKey(component, c.user));
+ final AppWidgetProviderInfo provider =
+ mWidgetProvidersMap.get(providerKey);
final boolean isProviderReady = isValidProvider(provider);
if (!isSafeMode && !customWidget &&
@@ -874,10 +878,10 @@
}
}
- // Remove any ghost widgets
- LauncherSettings.Settings.call(contentResolver,
- LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
}
+ // Remove any ghost widgets
+ LauncherSettings.Settings.call(contentResolver,
+ LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
// Update pinned state of model shortcuts
mBgDataModel.updateShortcutPinnedState(context);
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 92bea5b..13ec1ec 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -27,6 +27,8 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ResourceBasedOverride;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Map;
/**
@@ -89,4 +91,11 @@
@WorkerThread
public void destroy() { }
+ /**
+ * Add data to a dumpsys request for Launcher (e.g. for bug reports).
+ *
+ * @see com.android.launcher3.Launcher#dump(java.lang.String, java.io.FileDescriptor,
+ * java.io.PrintWriter, java.lang.String[])
+ **/
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { }
}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index f7b43d6..312435d 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -28,7 +28,6 @@
import android.util.Log;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetHost;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
@@ -43,6 +42,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index e3e8769..c0dc34a 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.model;
-import android.content.ComponentName;
import android.os.UserHandle;
import com.android.launcher3.LauncherAppState;
@@ -66,8 +65,7 @@
final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
synchronized (dataModel) {
dataModel.forAllWorkspaceItemInfos(mUser, si -> {
- ComponentName cn = si.getTargetComponent();
- if ((cn != null) && cn.getPackageName().equals(mPackageName)) {
+ if (mPackageName.equals(si.getTargetPackage())) {
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..9889a80 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.model;
-import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -72,9 +71,8 @@
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)) {
+ if (si.hasPromiseIconUi()
+ && mInstallInfo.packageName.equals(si.getTargetPackage())) {
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..97071bb 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -4,11 +4,11 @@
import android.content.pm.PackageManager;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
/**
* An wrapper over various items displayed in a widget picker,
@@ -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/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 00ac12f..e54f1e7 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -184,6 +184,24 @@
return Optional.ofNullable(getIntent()).map(Intent::getComponent).orElse(mComponentName);
}
+ /**
+ * Returns this item's package name.
+ *
+ * Prioritizes the component package name, then uses the intent package name as a fallback.
+ * This ensures deep shortcuts are supported.
+ */
+ @Nullable
+ public String getTargetPackage() {
+ ComponentName component = getTargetComponent();
+ Intent intent = getIntent();
+
+ return component != null
+ ? component.getPackageName()
+ : intent != null
+ ? intent.getPackage()
+ : null;
+ }
+
public void writeToValues(ContentWriter writer) {
writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
.put(LauncherSettings.Favorites.CONTAINER, container)
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 25355c9..b3057d5 100644
--- a/src/com/android/launcher3/model/data/SearchActionItemInfo.java
+++ b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
@@ -25,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
*/
@@ -34,17 +37,21 @@
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;
@@ -59,6 +66,7 @@
mFlags = info.mFlags;
title = info.title;
this.container = EXTENDED_CONTAINERS;
+ this.mIsPersonalTitle = info.mIsPersonalTitle;
}
/**
@@ -112,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/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 0320aa3..e954480 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -21,10 +21,13 @@
import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.Outline;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewOutlineProvider;
import android.widget.TextView;
import com.android.launcher3.R;
@@ -43,7 +46,8 @@
private static final Rect sTempRect = new Rect();
private final Context mContext;
- private final PopupContainerWithArrow mContainer;
+ private final PopupContainerWithArrow mPopupContainer;
+ private final ViewGroup mRootView;
private final TextView mHeaderText;
private final TextView mHeaderCount;
@@ -53,7 +57,6 @@
private final View mIconView;
private final View mHeader;
- private final View mDivider;
private View mGutter;
@@ -61,8 +64,9 @@
private boolean mAnimatingNextIcon;
private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
- public NotificationItemView(PopupContainerWithArrow container) {
- mContainer = container;
+ public NotificationItemView(PopupContainerWithArrow container, ViewGroup rootView) {
+ mPopupContainer = container;
+ mRootView = rootView;
mContext = container.getContext();
mHeaderText = container.findViewById(R.id.notification_text);
@@ -72,17 +76,29 @@
mIconView = container.findViewById(R.id.popup_item_icon);
mHeader = container.findViewById(R.id.header);
- mDivider = container.findViewById(R.id.divider);
mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL);
mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
mMainView.setSwipeDetector(mSwipeDetector);
mFooter.setContainer(this);
+
+ float radius = Themes.getDialogCornerRadius(mContext);
+ rootView.setClipToOutline(true);
+ rootView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
+ }
+ });
+ }
+
+ public void updateBackgroundColor(int color) {
+ mMainView.updateBackgroundColor(color);
}
public void addGutter() {
if (mGutter == null) {
- mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter, mContainer);
+ mGutter = mPopupContainer.inflateAndAdd(R.layout.notification_gutter, mRootView);
}
}
@@ -94,9 +110,8 @@
}
public void removeFooter() {
- if (mContainer.indexOfChild(mFooter) >= 0) {
- mContainer.removeView(mFooter);
- mContainer.removeView(mDivider);
+ if (mRootView.indexOfChild(mFooter) >= 0) {
+ mRootView.removeView(mFooter);
}
}
@@ -108,16 +123,15 @@
}
public void removeAllViews() {
- mContainer.removeView(mMainView);
- mContainer.removeView(mHeader);
+ mRootView.removeView(mMainView);
+ mRootView.removeView(mHeader);
- if (mContainer.indexOfChild(mFooter) >= 0) {
- mContainer.removeView(mFooter);
- mContainer.removeView(mDivider);
+ if (mRootView.indexOfChild(mFooter) >= 0) {
+ mRootView.removeView(mFooter);
}
if (mGutter != null) {
- mContainer.removeView(mGutter);
+ mRootView.removeView(mGutter);
}
}
@@ -136,11 +150,11 @@
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
- mMainView.getRight(), mMainView.getBottom());
+ sTempRect.set(mRootView.getLeft(), mRootView.getTop(),
+ mRootView.getRight(), mRootView.getBottom());
mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
if (!mIgnoreTouch) {
- mContainer.getParent().requestDisallowInterceptTouchEvent(true);
+ mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true);
}
}
if (mIgnoreTouch) {
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 9b06523..c995666 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -97,15 +97,24 @@
super.onFinishInflate();
mTextAndBackground = findViewById(R.id.text_and_background);
- ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
- mBackgroundColor = colorBackground.getColor();
- RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
- Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
- colorBackground, null);
- mTextAndBackground.setBackground(rippleBackground);
mTitleView = mTextAndBackground.findViewById(R.id.title);
mTextView = mTextAndBackground.findViewById(R.id.text);
mIconView = findViewById(R.id.popup_item_icon);
+
+ ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
+ updateBackgroundColor(colorBackground.getColor());
+ }
+
+ public void updateBackgroundColor(int color) {
+ mBackgroundColor = color;
+ RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
+ Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
+ new ColorDrawable(color), null);
+ mTextAndBackground.setBackground(rippleBackground);
+ if (mNotificationInfo != null) {
+ mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+ mBackgroundColor));
+ }
}
public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) {
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 408796f..a7cd10d 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -268,7 +268,9 @@
} else {
lp.leftMargin = lp.rightMargin = 0;
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- lp.bottomMargin = grid.hotseatBarSizePx + insets.bottom;
+ lp.bottomMargin = grid.isTaskbarPresent
+ ? grid.workspacePadding.bottom + grid.taskbarSize
+ : grid.hotseatBarSizePx + insets.bottom;
}
setLayoutParams(lp);
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 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 56438d0..3736538 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -17,20 +17,22 @@
package com.android.launcher3.popup;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.CornerPathEffect;
import android.graphics.Outline;
-import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.Gravity;
@@ -51,7 +53,7 @@
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
@@ -65,6 +67,9 @@
*/
public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
+ // +1 for system shortcut view
+ private static final int MAX_NUM_CHILDREN = MAX_SHORTCUTS + 1;
+
private final Rect mTempRect = new Rect();
protected final LayoutInflater mInflater;
@@ -72,9 +77,15 @@
protected final T mLauncher;
protected final boolean mIsRtl;
- private final int mArrowOffset;
+ private final int mArrowOffsetVertical;
+ private final int mArrowOffsetHorizontal;
+ private final int mArrowWidth;
+ private final int mArrowHeight;
+ private final int mArrowPointRadius;
private final View mArrow;
+ private final int mMargin;
+
protected boolean mIsLeftAligned;
protected boolean mIsAboveIcon;
private int mGravity;
@@ -84,8 +95,14 @@
private final Rect mStartRect = new Rect();
private final Rect mEndRect = new Rect();
+ private final GradientDrawable mRoundedTop;
+ private final GradientDrawable mRoundedBottom;
+
private Runnable mOnCloseCallback = () -> { };
+ private int mArrowColor;
+ private final int[] mColors;
+
public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mInflater = LayoutInflater.from(context);
@@ -103,11 +120,34 @@
// Initialize arrow view
final Resources resources = getResources();
- final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
- final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
+ mMargin = resources.getDimensionPixelSize(R.dimen.popup_margin);
+ mArrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
+ mArrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
mArrow = new View(context);
- mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight));
- mArrowOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
+ mArrow.setLayoutParams(new DragLayer.LayoutParams(mArrowWidth, mArrowHeight));
+ mArrowOffsetVertical = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
+ mArrowOffsetHorizontal = resources.getDimensionPixelSize(
+ R.dimen.popup_arrow_horizontal_center_offset) - (mArrowWidth / 2);
+ mArrowPointRadius = resources.getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+
+ int smallerRadius = resources.getDimensionPixelSize(R.dimen.popup_smaller_radius);
+ mRoundedTop = new GradientDrawable();
+ mRoundedTop.setCornerRadii(new float[] { mOutlineRadius, mOutlineRadius, mOutlineRadius,
+ mOutlineRadius, smallerRadius, smallerRadius, smallerRadius, smallerRadius});
+
+ mRoundedBottom = new GradientDrawable();
+ mRoundedBottom.setCornerRadii(new float[] { smallerRadius, smallerRadius, smallerRadius,
+ smallerRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius});
+
+ int primaryColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
+ int secondaryColor = Themes.getAttrColor(context, R.attr.popupColorSecondary);
+ ArgbEvaluator argb = new ArgbEvaluator();
+ mColors = new int[MAX_NUM_CHILDREN];
+ // Interpolate between the two colors, exclusive.
+ float step = 1f / (MAX_NUM_CHILDREN + 1);
+ for (int i = 0; i < mColors.length; ++i) {
+ mColors[i] = (int) argb.evaluate((i + 1) * step, primaryColor, secondaryColor);
+ }
}
public ArrowPopup(Context context, AttributeSet attrs) {
@@ -151,6 +191,77 @@
protected void onInflationComplete(boolean isReversed) { }
/**
+ * Set the margins and radius of backgrounds after views are properly ordered.
+ */
+ protected void assignMarginsAndBackgrounds() {
+ int count = getChildCount();
+ int totalVisibleShortcuts = 0;
+ for (int i = 0; i < count; i++) {
+ View view = getChildAt(i);
+ if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
+ totalVisibleShortcuts++;
+ }
+ }
+
+ int numVisibleShortcut = 0;
+ View lastView = null;
+ int numVisibleChild = 0;
+ for (int i = 0; i < count; i++) {
+ View view = getChildAt(i);
+ boolean isShortcut = view instanceof DeepShortcutView;
+ if (view.getVisibility() == VISIBLE) {
+ if (lastView != null) {
+ MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams();
+ mlp.bottomMargin = mMargin;
+ }
+ lastView = view;
+ MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams();
+ mlp.bottomMargin = 0;
+
+ if (isShortcut) {
+ if (totalVisibleShortcuts == 1) {
+ view.setBackgroundResource(R.drawable.single_item_primary);
+ } else if (totalVisibleShortcuts > 1) {
+ if (numVisibleShortcut == 0) {
+ view.setBackground(mRoundedTop);
+ } else if (numVisibleShortcut == (totalVisibleShortcuts - 1)) {
+ view.setBackground(mRoundedBottom);
+ } else {
+ view.setBackgroundResource(R.drawable.middle_item_primary);
+ }
+ numVisibleShortcut++;
+ }
+ }
+
+ int color = mColors[numVisibleChild % mColors.length];
+ setChildColor(view, color);
+
+ // Arrow color matches the first child or the last child.
+ if (!mIsAboveIcon && numVisibleChild == 0) {
+ mArrowColor = color;
+ } else if (mIsAboveIcon) {
+ mArrowColor = color;
+ }
+
+ numVisibleChild++;
+ }
+ }
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+
+ /**
+ * Sets the background color of the child.
+ */
+ protected void setChildColor(View view, int color) {
+ Drawable bg = view.getBackground();
+ if (bg instanceof GradientDrawable) {
+ ((GradientDrawable) bg.mutate()).setColor(color);
+ } else if (bg instanceof ColorDrawable) {
+ ((ColorDrawable) bg.mutate()).setColor(color);
+ }
+ }
+
+ /**
* Shows the popup at the desired location, optionally reversing the children.
* @param viewsToFlip number of views from the top to to flip in case of reverse order
*/
@@ -161,7 +272,10 @@
reverseOrder(viewsToFlip);
}
onInflationComplete(reverseOrder);
- addArrow();
+ assignMarginsAndBackgrounds();
+ if (shouldAddArrow()) {
+ addArrow();
+ }
animateOpen();
}
@@ -171,7 +285,10 @@
protected void show() {
setupForDisplay();
onInflationComplete(false);
- addArrow();
+ assignMarginsAndBackgrounds();
+ if (shouldAddArrow()) {
+ addArrow();
+ }
animateOpen();
}
@@ -196,52 +313,42 @@
for (int i = 0; i < count; i++) {
addView(allViews.get(i));
}
+ }
- orientAboutObject();
+ private int getArrowLeft() {
+ if (mIsLeftAligned) {
+ return mArrowOffsetHorizontal;
+ }
+ return getMeasuredWidth() - mArrowOffsetHorizontal - mArrowWidth;
}
private void addArrow() {
- final Resources res = getResources();
- final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart()
- ? R.dimen.popup_arrow_horizontal_center_start
- : R.dimen.popup_arrow_horizontal_center_end);
- final int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2;
getPopupContainer().addView(mArrow);
- DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
- if (mIsLeftAligned) {
- mArrow.setX(getX() + arrowCenterOffset - halfArrowWidth);
- } else {
- mArrow.setX(getX() + getMeasuredWidth() - arrowCenterOffset - halfArrowWidth);
- }
+ mArrow.setX(getX() + getArrowLeft());
if (Gravity.isVertical(mGravity)) {
// This is only true if there wasn't room for the container next to the icon,
// so we centered it instead. In that case we don't want to showDefaultOptions the arrow.
mArrow.setVisibility(INVISIBLE);
} else {
- ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
- arrowLp.width, arrowLp.height, !mIsAboveIcon));
- Paint arrowPaint = arrowDrawable.getPaint();
- arrowPaint.setColor(Themes.getAttrColor(getContext(), R.attr.popupColorPrimary));
- // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
- int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
- arrowPaint.setPathEffect(new CornerPathEffect(radius));
- mArrow.setBackground(arrowDrawable);
- // Clip off the part of the arrow that is underneath the popup.
- if (mIsAboveIcon) {
- mArrow.setClipBounds(new Rect(0, -mArrowOffset, arrowLp.width, arrowLp.height));
- } else {
- mArrow.setClipBounds(new Rect(0, 0, arrowLp.width, arrowLp.height + mArrowOffset));
- }
+ mArrow.setBackground(new RoundedArrowDrawable(
+ mArrowWidth, mArrowHeight, mArrowPointRadius,
+ mOutlineRadius, getMeasuredWidth(), getMeasuredHeight(),
+ mArrowOffsetHorizontal, -mArrowOffsetVertical,
+ !mIsAboveIcon, mIsLeftAligned,
+ mArrowColor));
mArrow.setElevation(getElevation());
}
- mArrow.setPivotX(arrowLp.width / 2);
- mArrow.setPivotY(mIsAboveIcon ? arrowLp.height : 0);
+ mArrow.setPivotX(mArrowWidth / 2.0f);
+ mArrow.setPivotY(mIsAboveIcon ? mArrowHeight : 0);
}
- protected boolean isAlignedWithStart() {
- return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
+ /**
+ * Returns whether or not we should add the arrow.
+ */
+ protected boolean shouldAddArrow() {
+ return true;
}
/**
@@ -274,10 +381,19 @@
*/
private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- int width = getMeasuredWidth();
- int extraVerticalSpace = mArrow.getLayoutParams().height + mArrowOffset
+
+ int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical
+ getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
- int height = getMeasuredHeight() + extraVerticalSpace;
+ // The margins are added after we call this method, so we need to account for them here.
+ int numVisibleChildren = 0;
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ if (getChildAt(i).getVisibility() == VISIBLE) {
+ numVisibleChildren++;
+ }
+ }
+ int childMargins = (numVisibleChildren - 1) * mMargin;
+ int height = getMeasuredHeight() + extraVerticalSpace + childMargins;
+ int width = getMeasuredWidth();
getTargetObjectLocation(mTempRect);
InsettableFrameLayout dragLayer = getPopupContainer();
@@ -291,22 +407,7 @@
// Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
int iconWidth = mTempRect.width();
- Resources resources = getResources();
- int xOffset;
- if (isAlignedWithStart()) {
- // Aligning with the shortcut icon.
- int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
- int shortcutPaddingStart = resources.getDimensionPixelSize(
- R.dimen.popup_padding_start);
- xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
- } else {
- // Aligning with the drag handle.
- int shortcutDragHandleWidth = resources.getDimensionPixelSize(
- R.dimen.deep_shortcut_drag_handle_size);
- int shortcutPaddingEnd = resources.getDimensionPixelSize(
- R.dimen.popup_padding_end);
- xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
- }
+ int xOffset = iconWidth / 2 - mArrowOffsetHorizontal - mArrowWidth / 2;
x += mIsLeftAligned ? xOffset : -xOffset;
// Check whether we can still align as we originally wanted, now that we've calculated x.
@@ -375,12 +476,14 @@
FrameLayout.LayoutParams arrowLp = (FrameLayout.LayoutParams) mArrow.getLayoutParams();
if (mIsAboveIcon) {
arrowLp.gravity = lp.gravity = Gravity.BOTTOM;
- lp.bottomMargin = getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top;
- arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrowOffset - insets.bottom;
+ lp.bottomMargin =
+ getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top;
+ arrowLp.bottomMargin =
+ lp.bottomMargin - arrowLp.height - mArrowOffsetVertical - insets.bottom;
} else {
arrowLp.gravity = lp.gravity = Gravity.TOP;
lp.topMargin = y + insets.top;
- arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrowOffset;
+ arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrowOffsetVertical;
}
}
@@ -416,13 +519,19 @@
return getChildCount() > 0 ? getChildAt(0) : this;
}
+ private int getArrowDuration() {
+ return shouldAddArrow()
+ ? getResources().getInteger(R.integer.config_popupArrowOpenCloseDuration)
+ : 0;
+ }
+
private void animateOpen() {
setVisibility(View.VISIBLE);
final AnimatorSet openAnim = new AnimatorSet();
final Resources res = getResources();
final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
- final long arrowDuration = res.getInteger(R.integer.config_popupArrowOpenCloseDuration);
+ final long arrowDuration = getArrowDuration();
final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
// Rectangular reveal.
@@ -484,7 +593,7 @@
final Resources res = getResources();
final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
final long revealDuration = res.getInteger(R.integer.config_popupOpenCloseDuration);
- final long arrowDuration = res.getInteger(R.integer.config_popupArrowOpenCloseDuration);
+ final long arrowDuration = getArrowDuration();
// Hide the arrow
Animator scaleArrow = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0)
@@ -529,22 +638,13 @@
protected void onCreateCloseAnimation(AnimatorSet anim) { }
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
- Resources res = getResources();
- int arrowCenterX = res.getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
- R.dimen.popup_arrow_horizontal_center_start:
- R.dimen.popup_arrow_horizontal_center_end);
- int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2;
- float arrowCornerRadius = res.getDimension(R.dimen.popup_arrow_corner_radius);
- if (!mIsLeftAligned) {
- arrowCenterX = getMeasuredWidth() - arrowCenterX;
- }
+ int arrowLeft = getArrowLeft();
int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0;
- mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth,
- arrowCenterY);
+ mStartRect.set(arrowLeft, arrowCenterY, arrowLeft + mArrowWidth, arrowCenterY);
- return new RoundedRectRevealOutlineProvider
- (arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect);
+ return new RoundedRectRevealOutlineProvider(
+ mArrowPointRadius, mOutlineRadius, mStartRect, mEndRect);
}
/**
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index a1ba747..c282ae8 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -60,6 +60,7 @@
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.notification.NotificationMainView;
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -91,6 +92,7 @@
private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
private int mNumNotifications;
+ private ViewGroup mNotificationContainer;
private ViewGroup mSystemShortcutContainer;
@@ -169,6 +171,14 @@
return false;
}
+ @Override
+ protected void setChildColor(View v, int color) {
+ super.setChildColor(v, color);
+ if (v.getId() == R.id.notification_container && mNotificationItemView != null) {
+ mNotificationItemView.updateBackgroundColor(color);
+ }
+ }
+
/**
* Returns true if we can show the container.
*/
@@ -222,20 +232,6 @@
if (isReversed && mNotificationItemView != null) {
mNotificationItemView.inverseGutterMargin();
}
-
- // Update dividers
- int count = getChildCount();
- DeepShortcutView lastView = null;
- for (int i = 0; i < count; i++) {
- View view = getChildAt(i);
- if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
- if (lastView != null) {
- lastView.setDividerVisibility(VISIBLE);
- }
- lastView = (DeepShortcutView) view;
- lastView.setDividerVisibility(INVISIBLE);
- }
- }
}
@TargetApi(Build.VERSION_CODES.P)
@@ -257,8 +253,12 @@
// Add views
if (mNumNotifications > 0) {
// Add notification entries
- View.inflate(getContext(), R.layout.notification_content, this);
- mNotificationItemView = new NotificationItemView(this);
+ if (mNotificationContainer == null) {
+ mNotificationContainer = findViewById(R.id.notification_container);
+ mNotificationContainer.setVisibility(VISIBLE);
+ }
+ View.inflate(getContext(), R.layout.notification_content, mNotificationContainer);
+ mNotificationItemView = new NotificationItemView(this, mNotificationContainer);
if (mNumNotifications == 1) {
mNotificationItemView.removeFooter();
}
@@ -342,34 +342,11 @@
private void updateHiddenShortcuts() {
int allowedCount = mNotificationItemView != null
? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
- int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
- int itemHeight = mNotificationItemView != null ?
- getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
- : originalHeight;
- float iconScale = ((float) itemHeight) / originalHeight;
int total = mShortcuts.size();
for (int i = 0; i < total; i++) {
DeepShortcutView view = mShortcuts.get(i);
view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
- view.getLayoutParams().height = itemHeight;
- view.getIconView().setScaleX(iconScale);
- view.getIconView().setScaleY(iconScale);
- }
- }
-
- private void updateDividers() {
- int count = getChildCount();
- DeepShortcutView lastView = null;
- for (int i = 0; i < count; i++) {
- View view = getChildAt(i);
- if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
- if (lastView != null) {
- lastView.setDividerVisibility(VISIBLE);
- }
- lastView = (DeepShortcutView) view;
- lastView.setDividerVisibility(INVISIBLE);
- }
}
}
@@ -591,8 +568,9 @@
// No more notifications, remove the notification views and expand all shortcuts.
mNotificationItemView.removeAllViews();
mNotificationItemView = null;
+ mNotificationContainer.setVisibility(GONE);
updateHiddenShortcuts();
- updateDividers();
+ assignMarginsAndBackgrounds();
} else {
mNotificationItemView.trimNotifications(
NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 1853632..7780894 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -58,8 +58,11 @@
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
/** Maps packages to their DotInfo's . */
private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
- /** Maps packages to their Widgets */
+
+ /** All installed widgets. */
private List<WidgetsListBaseEntry> mAllWidgets = List.of();
+ /** Widgets that can be recommended to the users. */
+ private List<ItemInfo> mRecommendedWidgets = List.of();
private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
@@ -187,6 +190,15 @@
notificationListener.cancelNotificationFromLauncher(notificationKey);
}
+ /**
+ * Sets a list of recommended widgets ordered by their order of appearance in the widgets
+ * recommendation UI.
+ */
+ public void setRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
+ mRecommendedWidgets = recommendedWidgets;
+ mChangeListener.onRecommendedWidgetsBound();
+ }
+
public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
mAllWidgets = allWidgets;
mChangeListener.onWidgetsBound();
@@ -200,6 +212,21 @@
return mAllWidgets;
}
+ /** Returns a list of recommended widgets. */
+ public List<WidgetItem> getRecommendedWidgets() {
+ HashMap<ComponentKey, WidgetItem> allWidgetItems = new HashMap<>();
+ mAllWidgets.stream()
+ .filter(entry -> entry instanceof WidgetsListContentEntry)
+ .forEach(entry -> ((WidgetsListContentEntry) entry).mWidgets
+ .forEach(widget -> allWidgetItems.put(
+ new ComponentKey(widget.componentName, widget.user), widget)));
+ return mRecommendedWidgets.stream()
+ .map(recommendedWidget -> allWidgetItems.get(
+ new ComponentKey(recommendedWidget.getTargetComponent(),
+ recommendedWidget.user)))
+ .collect(Collectors.toList());
+ }
+
public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
return mAllWidgets.stream()
.filter(row -> row instanceof WidgetsListContentEntry
@@ -244,5 +271,8 @@
default void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) { }
default void onWidgetsBound() { }
+
+ /** A callback to get notified when recommended widgets are bound. */
+ default void onRecommendedWidgetsBound() { }
}
}
diff --git a/src/com/android/launcher3/popup/RoundedArrowDrawable.java b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
new file mode 100644
index 0000000..e662d5c
--- /dev/null
+++ b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
@@ -0,0 +1,161 @@
+/*
+ * 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.popup;
+
+import static java.lang.Math.atan;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+import static java.lang.Math.toDegrees;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A drawable for a very specific purpose. Used for the caret arrow on a rounded rectangle popup
+ * bubble.
+ * Draws a triangle with one rounded tip, the opposite edge is clipped by the body of the popup
+ * so there is no overlap when drawing them together.
+ */
+public class RoundedArrowDrawable extends Drawable {
+
+ private final Path mPath;
+ private final Paint mPaint;
+
+ /**
+ * Default constructor.
+ *
+ * @param width of the arrow.
+ * @param height of the arrow.
+ * @param radius of the tip of the arrow.
+ * @param popupRadius of the rect to clip this by.
+ * @param popupWidth of the rect to clip this by.
+ * @param popupHeight of the rect to clip this by.
+ * @param arrowOffsetX from the edge of the popup to the arrow.
+ * @param arrowOffsetY how much the arrow will overlap the popup.
+ * @param isPointingUp or not.
+ * @param leftAligned or false for right aligned.
+ * @param color to draw the triangle.
+ */
+ public RoundedArrowDrawable(float width, float height, float radius, float popupRadius,
+ float popupWidth, float popupHeight,
+ float arrowOffsetX, float arrowOffsetY, boolean isPointingUp, boolean leftAligned,
+ int color) {
+ mPath = new Path();
+ mPaint = new Paint();
+ mPaint.setColor(color);
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setAntiAlias(true);
+
+ // Make the drawable with the triangle pointing down and positioned on the left..
+ addDownPointingRoundedTriangleToPath(width, height, radius, mPath);
+ clipPopupBodyFromPath(popupRadius, popupWidth, popupHeight, arrowOffsetX, arrowOffsetY,
+ mPath);
+
+ // ... then flip it horizontal or vertical based on where it will be used.
+ Matrix pathTransform = new Matrix();
+ pathTransform.setScale(
+ leftAligned ? 1 : -1, isPointingUp ? -1 : 1, width * 0.5f, height * 0.5f);
+ mPath.transform(pathTransform);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawPath(mPath, mPaint);
+ }
+
+ @Override
+ public void getOutline(Outline outline) {
+ outline.setPath(mPath);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int i) {
+ mPaint.setAlpha(i);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ private static void addDownPointingRoundedTriangleToPath(float width, float height,
+ float radius, Path path) {
+ // Calculated for the arrow pointing down, will be flipped later if needed.
+
+ // Theta is half of the angle inside the triangle tip
+ float tanTheta = width / (2.0f * height);
+ float theta = (float) atan(tanTheta);
+
+ // Some trigonometry to find the center of the circle for the rounded tip
+ float roundedPointCenterY = (float) (height - (radius / sin(theta)));
+
+ // p is the distance along the triangle side to the intersection with the point circle
+ float p = radius / tanTheta;
+ float lineRoundPointIntersectFromCenter = (float) (p * sin(theta));
+ float lineRoundPointIntersectFromTop = (float) (height - (p * cos(theta)));
+
+ float centerX = width / 2.0f;
+ float thetaDeg = (float) toDegrees(theta);
+
+ path.reset();
+ path.moveTo(0, 0);
+ // Draw the top
+ path.lineTo(width, 0);
+ // Draw the right side up to the circle intersection
+ path.lineTo(
+ centerX + lineRoundPointIntersectFromCenter,
+ lineRoundPointIntersectFromTop);
+ // Draw the rounded point
+ path.arcTo(
+ centerX - radius,
+ roundedPointCenterY - radius,
+ centerX + radius,
+ roundedPointCenterY + radius,
+ thetaDeg,
+ 180 - (2 * thetaDeg),
+ false);
+ // Draw the left edge to close
+ path.lineTo(0, 0);
+ path.close();
+ }
+
+ private static void clipPopupBodyFromPath(float popupRadius, float popupWidth,
+ float popupHeight, float arrowOffsetX, float arrowOffsetY, Path path) {
+ // Make a path that is used to clip the triangle, this represents the body of the popup
+ Path clipPiece = new Path();
+ clipPiece.addRoundRect(
+ 0, 0, popupWidth, popupHeight,
+ popupRadius, popupRadius, Path.Direction.CW);
+ // clipping is performed as if the arrow is pointing down and positioned on the left, the
+ // resulting path will be flipped as needed later.
+ // The extra 0.5 in the vertical offset is to close the gap between this anti-aliased object
+ // and the anti-aliased body of the popup.
+ clipPiece.offset(-arrowOffsetX, -popupHeight + arrowOffsetY - 0.5f);
+ path.op(clipPiece, Path.Op.DIFFERENCE);
+ }
+}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 577fe4a..e5424cf 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -45,6 +45,11 @@
protected final T mTarget;
protected final ItemInfo mItemInfo;
+ /**
+ * Indicates if it's invokable or not through some disabled UI
+ */
+ private boolean isEnabled = true;
+
public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
mIconResId = iconResId;
mLabelResId = labelResId;
@@ -83,6 +88,14 @@
mAccessibilityActionId, context.getText(mLabelResId));
}
+ public void setEnabled(boolean enabled) {
+ isEnabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return isEnabled;
+ }
+
public boolean hasHandlerForAction(int action) {
return mAccessibilityActionId == action;
}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 289e0d8..a191df4 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;
@@ -34,6 +36,7 @@
import android.os.Bundle;
import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.SizeF;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -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<SizeF> 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
index 4653774..5b8d5bc 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -33,7 +33,7 @@
V newViewHolder(ViewGroup parent);
/** Populate UI references in {@link ViewHolder} with data. */
- void bindViewHolder(V viewHolder, T data);
+ void bindViewHolder(V viewHolder, T data, int position);
/**
* Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/allapps/search/SearchAlgorithm.java b/src/com/android/launcher3/search/SearchAlgorithm.java
similarity index 69%
rename from src/com/android/launcher3/allapps/search/SearchAlgorithm.java
rename to src/com/android/launcher3/search/SearchAlgorithm.java
index c409b1c..a1720c7 100644
--- a/src/com/android/launcher3/allapps/search/SearchAlgorithm.java
+++ b/src/com/android/launcher3/search/SearchAlgorithm.java
@@ -13,20 +13,27 @@
* 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.
*/
void cancel(boolean interruptActiveRequests);
+
+ /**
+ * Cleans up after search is no longer needed.
+ */
+ default void destroy() {};
}
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/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
new file mode 100644
index 0000000..acab52b
--- /dev/null
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.search;
+
+import java.text.Collator;
+
+/**
+ * Utilities for matching query string to target string.
+ */
+public class StringMatcherUtility {
+
+ /**
+ * Returns {@code true} is {@code query} is a prefix substring of a complete word/phrase in
+ * {@code target}.
+ */
+ public static boolean matches(String query, String target, StringMatcher matcher) {
+ int queryLength = query.length();
+
+ int targetLength = target.length();
+
+ if (targetLength < queryLength || queryLength <= 0) {
+ return false;
+ }
+
+ if (requestSimpleFuzzySearch(query)) {
+ return target.toLowerCase().contains(query);
+ }
+
+ int lastType;
+ int thisType = Character.UNASSIGNED;
+ int nextType = Character.getType(target.codePointAt(0));
+
+ int end = targetLength - queryLength;
+ for (int i = 0; i <= end; i++) {
+ lastType = thisType;
+ thisType = nextType;
+ nextType = i < (targetLength - 1)
+ ? Character.getType(target.codePointAt(i + 1)) : Character.UNASSIGNED;
+ if (isBreak(thisType, lastType, nextType)
+ && matcher.matches(query, target.substring(i, i + queryLength))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the current point should be a break point. Following cases
+ * are considered as break points:
+ * 1) Any non space character after a space character
+ * 2) Any digit after a non-digit character
+ * 3) Any capital character after a digit or small character
+ * 4) Any capital character before a small character
+ */
+ private static boolean isBreak(int thisType, int prevType, int nextType) {
+ switch (prevType) {
+ case Character.UNASSIGNED:
+ case Character.SPACE_SEPARATOR:
+ case Character.LINE_SEPARATOR:
+ case Character.PARAGRAPH_SEPARATOR:
+ return true;
+ }
+ switch (thisType) {
+ case Character.UPPERCASE_LETTER:
+ if (nextType == Character.UPPERCASE_LETTER) {
+ return true;
+ }
+ // Follow through
+ case Character.TITLECASE_LETTER:
+ // Break point if previous was not a upper case
+ return prevType != Character.UPPERCASE_LETTER;
+ case Character.LOWERCASE_LETTER:
+ // Break point if previous was not a letter.
+ return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
+ case Character.DECIMAL_DIGIT_NUMBER:
+ case Character.LETTER_NUMBER:
+ case Character.OTHER_NUMBER:
+ // Break point if previous was not a number
+ return !(prevType == Character.DECIMAL_DIGIT_NUMBER
+ || prevType == Character.LETTER_NUMBER
+ || prevType == Character.OTHER_NUMBER);
+ case Character.MATH_SYMBOL:
+ case Character.CURRENCY_SYMBOL:
+ case Character.OTHER_PUNCTUATION:
+ case Character.DASH_PUNCTUATION:
+ // Always a break point for a symbol
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Performs locale sensitive string comparison using {@link Collator}.
+ */
+ public static class StringMatcher {
+
+ private static final char MAX_UNICODE = '\uFFFF';
+
+ private final Collator mCollator;
+
+ StringMatcher() {
+ // On android N and above, Collator uses ICU implementation which has a much better
+ // support for non-latin locales.
+ mCollator = Collator.getInstance();
+ mCollator.setStrength(Collator.PRIMARY);
+ mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
+ }
+
+ /**
+ * Returns true if {@param query} is a prefix of {@param target}
+ */
+ public boolean matches(String query, String target) {
+ switch (mCollator.compare(query, target)) {
+ case 0:
+ return true;
+ case -1:
+ // The target string can contain a modifier which would make it larger than
+ // the query string (even though the length is same). If the query becomes
+ // larger after appending a unicode character, it was originally a prefix of
+ // the target string and hence should match.
+ return mCollator.compare(query + MAX_UNICODE, target) > -1;
+ default:
+ return false;
+ }
+ }
+
+ public static StringMatcher getInstance() {
+ return new StringMatcher();
+ }
+ }
+
+ /**
+ * Matching optimization to search in Chinese.
+ */
+ private static boolean requestSimpleFuzzySearch(String s) {
+ for (int i = 0; i < s.length(); ) {
+ int codepoint = s.codePointAt(i);
+ i += Character.charCount(codepoint);
+ switch (Character.UnicodeScript.of(codepoint)) {
+ case HAN:
+ //Character.UnicodeScript.HAN: use String.contains to match
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 0e8d4ae..f5e74b7 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -17,7 +17,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.app.ActivityOptions;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@@ -168,11 +167,6 @@
}
@Override
- public ActivityOptions getActivityLaunchOptions(View v) {
- return null;
- }
-
- @Override
protected void reapplyUi() { }
@Override
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index c9fb956..8d676c9 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -88,7 +88,6 @@
private PreferenceCategory mPluginsCategory;
private FlagTogglerPrefUi mFlagTogglerPrefUi;
-
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index ac8dac5..f03065c 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -129,6 +129,7 @@
private String mHighLightKey;
private boolean mPreferenceHighlighted = false;
private NotificationDotsPreference mNotificationSettingsChangedListener;
+ private Preference mDeveloperOptionPref;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -202,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) {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index e9b92e2..1c1418c 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -41,7 +41,6 @@
private BubbleTextView mBubbleText;
private View mIconView;
- private View mDivider;
private WorkspaceItemInfo mInfo;
private ShortcutInfo mDetail;
@@ -63,11 +62,6 @@
super.onFinishInflate();
mBubbleText = findViewById(R.id.bubble_text);
mIconView = findViewById(R.id.icon);
- mDivider = findViewById(R.id.divider);
- }
-
- public void setDividerVisibility(int visibility) {
- mDivider.setVisibility(visibility);
}
public BubbleTextView getBubbleText() {
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 3e59b61..530aaed 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable;
import android.view.View;
+import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
@@ -42,15 +43,16 @@
}
@Override
- public Bitmap createDragBitmap() {
+ public Drawable createDrawable() {
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
- return BitmapRenderer.createHardwareBitmap(
- size + blurSizeOutline,
- size + blurSizeOutline,
- (c) -> drawDragViewOnBackground(c, size));
+ return new FastBitmapDrawable(
+ BitmapRenderer.createHardwareBitmap(
+ size + blurSizeOutline,
+ size + blurSizeOutline,
+ (c) -> drawDragViewOnBackground(c, size)));
} else {
- return createDragBitmapLegacy();
+ return new FastBitmapDrawable(createDragBitmapLegacy());
}
}
@@ -81,7 +83,7 @@
}
@Override
- public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+ public float getScaleAndPosition(Drawable preview, int[] outPos) {
Launcher launcher = Launcher.getLauncher(mView.getContext());
int iconSize = getDrawableBounds(mView.getBackground()).width();
float scale = launcher.getDragLayer().getLocationInDragLayer(mView, outPos);
@@ -91,9 +93,10 @@
iconLeft = mView.getWidth() - iconSize - iconLeft;
}
- outPos[0] += Math.round(scale * iconLeft + (scale * iconSize - preview.getWidth()) / 2 +
- mPositionShift.x);
- outPos[1] += Math.round((scale * mView.getHeight() - preview.getHeight()) / 2
+ outPos[0] += Math.round(
+ scale * iconLeft + (scale * iconSize - preview.getIntrinsicWidth()) / 2
+ + mPositionShift.x);
+ outPos[1] += Math.round((scale * mView.getHeight() - preview.getIntrinsicHeight()) / 2
+ mPositionShift.y);
float size = launcher.getDeviceProfile().iconSizePx;
return scale * iconSize / size;
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index daec1d8..122573c 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -17,6 +17,8 @@
import android.content.Context;
+import com.android.launcher3.DeviceProfile;
+
/**
* Interface representing a state of a StatefulActivity
*/
@@ -52,4 +54,11 @@
* Returns if the state has the provided flag
*/
boolean hasFlag(int flagMask);
+
+ /**
+ * For this state, whether tasks should layout as a grid rather than a list.
+ */
+ default boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index beb5b68..51767e7 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -26,14 +26,12 @@
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -79,6 +77,15 @@
return mCurrentStableState;
}
+ @Override
+ public String toString() {
+ return " StateManager(mLastStableState:" + mLastStableState
+ + ", mCurrentStableState:" + mCurrentStableState
+ + ", mState:" + mState
+ + ", mRestState:" + mRestState
+ + ", isInTransition:" + (mConfig.currentAnimation != null) + ")";
+ }
+
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "StateManager:");
writer.println(prefix + "\tmLastStableState:" + mLastStableState);
@@ -90,7 +97,9 @@
public StateHandler[] getStateHandlers() {
if (mStateHandlers == null) {
- mStateHandlers = mActivity.createStateHandlers();
+ ArrayList<StateHandler> handlers = new ArrayList<>();
+ mActivity.collectStateHandlers(handlers);
+ mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
}
return mStateHandlers;
}
@@ -302,10 +311,6 @@
}
private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "createAnimationToNewWorkspaceInternal: "
- + state);
- }
PendingAnimation builder = new PendingAnimation(mConfig.duration);
if (mConfig.getAnimComponents() != 0) {
for (StateHandler handler : getStateHandlers()) {
@@ -328,9 +333,6 @@
@Override
public void onAnimationSuccess(Animator animator) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "onAnimationSuccess: " + state);
- }
onStateTransitionEnd(state);
}
};
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 7abb653..8a35cb3 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -30,6 +30,8 @@
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.views.BaseDragLayer;
+import java.util.List;
+
/**
* Abstract activity with state management
* @param <STATE_TYPE> Type of state object
@@ -46,7 +48,7 @@
/**
* Create handlers to control the property changes for this activity
*/
- protected abstract StateHandler<STATE_TYPE>[] createStateHandlers();
+ protected abstract void collectStateHandlers(List<StateHandler> out);
/**
* Returns true if the activity is in the provided state
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index fd1d965..eb2c551 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -53,10 +53,4 @@
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
return new ScaleAndTranslation(0.92f, 0, 0);
}
-
- @Override
- public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
- // Treat the QSB as part of the hotseat so they move together.
- return getHotseatScaleAndTranslation(launcher);
- }
}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 44bcc34..d593013 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -59,10 +59,11 @@
float scale = grid.workspaceSpringLoadShrinkFactor;
Rect insets = launcher.getDragLayer().getInsets();
+ int insetsBottom = grid.isTaskbarPresent ? grid.taskbarSize : insets.bottom;
float scaledHeight = scale * ws.getNormalChildHeight();
float shrunkTop = insets.top + grid.dropTargetBarSizePx;
- float shrunkBottom = ws.getMeasuredHeight() - insets.bottom
+ float shrunkBottom = ws.getMeasuredHeight() - insetsBottom
- grid.workspacePadding.bottom
- grid.workspaceSpringLoadedBottomSpace;
float totalShrunkSpace = shrunkBottom - shrunkTop;
@@ -93,6 +94,6 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return super.getVisibleElements(launcher) & ~TASKBAR;
+ return (super.getVisibleElements(launcher) | HOTSEAT_ICONS) & ~TASKBAR;
}
}
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index ec949eb..cd74390 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -38,7 +38,6 @@
PLAY_ATOMIC_OVERVIEW_PEEK,
SKIP_OVERVIEW,
SKIP_DEPTH_CONTROLLER,
- SKIP_TASKBAR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationFlags {}
@@ -47,7 +46,6 @@
public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
public static final int SKIP_OVERVIEW = 1 << 3;
public static final int SKIP_DEPTH_CONTROLLER = 1 << 4;
- public static final int SKIP_TASKBAR = 1 << 5;
public long duration;
public boolean userControlled;
@@ -74,7 +72,6 @@
ANIM_OVERVIEW_MODAL,
ANIM_DEPTH,
ANIM_OVERVIEW_ACTIONS_FADE,
- ANIM_TASKBAR_FADE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimType {}
@@ -94,9 +91,8 @@
public static final int ANIM_OVERVIEW_MODAL = 13;
public static final int ANIM_DEPTH = 14;
public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15;
- public static final int ANIM_TASKBAR_FADE = 16;
- private static final int ANIM_TYPES_COUNT = 17;
+ private static final int ANIM_TYPES_COUNT = 16;
protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 218172b..f34bff6 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -32,6 +32,7 @@
public static final int ALL_APPS_STATE_ORDINAL = 5;
public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
public static final int HINT_STATE_ORDINAL = 7;
+ public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 8;
public static final String TAPL_EVENTS_TAG = "TaplEvents";
public static final String SEQUENCE_MAIN = "Main";
public static final String SEQUENCE_TIS = "TIS";
@@ -55,6 +56,8 @@
return "Background";
case HINT_STATE_ORDINAL:
return "Hint";
+ case OVERVIEW_SPLIT_SELECT_ORDINAL:
+ return "OverviewSplitSelect";
default:
return "Unknown";
}
@@ -103,7 +106,8 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
- public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
public static final String NO_SWIPE_TO_HOME = "b/158017601";
public static final String WORK_PROFILE_REMOVED = "b/159671700";
+ public static final String TIS_NO_EVENTS = "b/180915942";
+ public static final String GET_RECENTS_FAILED = "b/177472267";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 25ecea5..516fc74 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -38,24 +38,19 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.os.SystemClock;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
-import androidx.core.os.BuildCompat;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.TouchController;
@@ -67,8 +62,9 @@
/**
* Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
+ * TODO: Remove the atomic animation altogether and just go to OVERVIEW directly (b/175137718).
*/
- public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 0.5f;
+ public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 1f;
protected final long ATOMIC_DURATION = getAtomicDuration();
protected final Launcher mLauncher;
@@ -124,7 +120,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,16 +189,14 @@
: reachedToState ? mToState : mFromState;
LauncherState newToState = getTargetState(newFromState, isDragTowardPositive);
+ onReinitToState(newToState);
+
if (newFromState == mFromState && newToState == mToState || (newFromState == newToState)) {
return false;
}
mFromState = newFromState;
mToState = newToState;
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "reinitCurrentAnimation: "
- + newToState.ordinal + " " + getClass().getSimpleName());
- }
mStartProgress = 0;
mPassedOverviewAtomicThreshold = false;
@@ -231,6 +225,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)
@@ -260,14 +260,6 @@
}
mCanBlockFling = mFromState == NORMAL;
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(
- mFromState == NORMAL ? 1f : 0f);
- }
}
@Override
@@ -335,9 +327,7 @@
if (!goingBetweenNormalAndOverview(fromState, toState)) {
return;
}
- float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD
- : 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD;
- boolean passedThreshold = progress >= threshold;
+ boolean passedThreshold = progress >= ATOMIC_OVERVIEW_ANIM_THRESHOLD;
if (passedThreshold != mPassedOverviewAtomicThreshold) {
LauncherState atomicFromState = passedThreshold ? fromState: toState;
LauncherState atomicToState = passedThreshold ? toState : fromState;
@@ -527,6 +517,7 @@
mAtomicComponentsController.getAnimationPlayer().end();
mAtomicComponentsController = null;
}
+ onReachedFinalState(mToState);
clearState();
boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState);
if (shouldGoToTargetState) {
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 2647d6f..2e54904 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;
@@ -40,7 +41,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.folder.Folder;
@@ -58,6 +58,7 @@
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.FloatingIconView;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetManagerHelper;
@@ -179,7 +180,7 @@
LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
try {
launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
- launcher.getActivityLaunchOptionsAsBundle(v));
+ launcher.getActivityLaunchOptions(v).toBundle());
return;
} catch (Exception e) {
Log.e(TAG, "Unable to launch market intent for package=" + packageName, e);
@@ -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) {
@@ -302,7 +304,7 @@
intent.setPackage(null);
}
}
- if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation(v)) {
+ if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 7baeab8..f876dd9 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -21,6 +21,7 @@
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.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED;
import android.view.View;
import android.view.View.OnLongClickListener;
@@ -32,6 +33,7 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
@@ -55,7 +57,7 @@
if (!(v.getTag() instanceof ItemInfo)) return false;
launcher.setWaitingForResult(null);
- beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
+ beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
return true;
}
@@ -86,6 +88,12 @@
if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
if (launcher.getWorkspace().isSwitchingState()) return false;
+ StatsLogger logger = launcher.getStatsLogManager().logger();
+ if (v.getTag() instanceof ItemInfo) {
+ logger.withItemInfo((ItemInfo) v.getTag());
+ }
+ logger.log(LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED);
+
// Start the drag
final DragController dragController = launcher.getDragController();
dragController.addDragListener(new DragController.DragListener() {
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index f05f15e..19dfe15 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -21,6 +21,10 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -36,8 +40,13 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
public class LandscapePagedViewHandler implements PagedOrientationHandler {
@@ -127,7 +136,7 @@
}
@Override
- public int getClearAllScrollOffset(View view, boolean isRtl) {
+ public int getClearAllSidePadding(View view, boolean isRtl) {
return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2;
}
@@ -147,13 +156,6 @@
}
@Override
- public void setPrimaryAndResetSecondaryTranslate(
- View view, float translation, float defaultTranslationX, float defaultTranslationY) {
- view.setTranslationX(defaultTranslationX);
- view.setTranslationY(translation);
- }
-
- @Override
public int getPrimaryScroll(View view) {
return view.getScrollY();
}
@@ -219,6 +221,20 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ return translationOffset;
+ }
+
+ @Override
public float getTaskMenuX(float x, View thumbnailView) {
return thumbnailView.getMeasuredWidth() + x;
}
@@ -289,4 +305,23 @@
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return rect.left;
}
+
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ List<SplitPositionOption> options = new ArrayList<>(2);
+ // Add left/right options where left => position top, right => position bottom
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+ return options;
+ }
+
+ @Override
+ public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile deviceProfile) {
+ return primary;
+ }
}
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index b9acfa3..9140a04 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -32,6 +32,10 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+
+import java.util.List;
/**
* Abstraction layer to separate horizontal and vertical specific implementations
@@ -62,12 +66,10 @@
float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
int getMeasuredSize(View view);
float getPrimarySize(RectF rect);
- int getClearAllScrollOffset(View view, boolean isRtl);
+ int getClearAllSidePadding(View view, boolean isRtl);
int getSecondaryDimension(View view);
FloatProperty<View> getPrimaryViewTranslate();
FloatProperty<View> getSecondaryViewTranslate();
- void setPrimaryAndResetSecondaryTranslate(
- View view, float translation, float defaultTranslationX, float defaultTranslationY);
int getPrimaryScroll(View view);
float getPrimaryScale(View view);
int getChildStart(View view);
@@ -77,6 +79,8 @@
int getScrollOffsetEnd(View view, Rect insets);
int getPrimaryTranslationDirectionFactor();
int getSecondaryTranslationDirectionFactor();
+ int getSplitTranslationDirectionFactor(@StagePosition int stagePosition);
+ int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp);
ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
void setMaxScroll(AccessibilityEvent event, int maxScroll);
boolean getRecentsRtlSetting(Resources resources);
@@ -97,6 +101,9 @@
int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout);
void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
+ List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+ FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile deviceProfile);
// The following are only used by TaskViewTouchHandler.
/** @return Either VERTICAL or HORIZONTAL. */
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 3663b5f..29be627 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -19,6 +19,10 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -34,8 +38,13 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
public class PortraitPagedViewHandler implements PagedOrientationHandler {
@@ -123,7 +132,7 @@
}
@Override
- public int getClearAllScrollOffset(View view, boolean isRtl) {
+ public int getClearAllSidePadding(View view, boolean isRtl) {
return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
}
@@ -143,13 +152,6 @@
}
@Override
- public void setPrimaryAndResetSecondaryTranslate(
- View view, float translation, float defaultTranslationX, float defaultTranslationY) {
- view.setTranslationX(translation);
- view.setTranslationY(defaultTranslationY);
- }
-
- @Override
public int getPrimaryScroll(View view) {
return view.getScrollX();
}
@@ -215,6 +217,23 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ if (dp.isLandscape) {
+ return translationOffset;
+ }
+ return 0;
+ }
+
+ @Override
public float getTaskMenuX(float x, View thumbnailView) {
return x;
}
@@ -284,4 +303,35 @@
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return dp.heightPx - rect.bottom;
}
+
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ List<SplitPositionOption> options = new ArrayList<>(2);
+ // TODO: Add in correct icons
+ if (dp.isLandscape) { // or seascape
+ // Add left/right options
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+ } else {
+ // Only add top option
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_top,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ }
+ return options;
+ }
+
+ @Override
+ public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+ DeviceProfile dp) {
+ if (dp.isLandscape) { // or seascape
+ return primary;
+ } else {
+ return secondary;
+ }
+ }
}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 54af029..b5252f7 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -17,6 +17,10 @@
package com.android.launcher3.touch;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -25,7 +29,12 @@
import android.view.View;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
@@ -35,6 +44,20 @@
}
@Override
+ public int getSplitTranslationDirectionFactor(int stagePosition) {
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+ return translationOffset;
+ }
+
+ @Override
public boolean getRecentsRtlSetting(Resources resources) {
return Utilities.isRtl(resources);
}
@@ -71,6 +94,19 @@
return dp.widthPx - rect.right;
}
+ @Override
+ public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+ List<SplitPositionOption> options = new ArrayList<>(2);
+ // Add left/right options where left => position bottom, right => position top
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_left,
+ STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+ options.add(new SplitPositionOption(
+ R.drawable.ic_split_screen, R.string.split_screen_position_right,
+ STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+ return options;
+ }
+
/* ---------- The following are only used by TaskViewTouchHandler. ---------- */
@Override
diff --git a/src/com/android/launcher3/util/ActivityOptionsWrapper.java b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
new file mode 100644
index 0000000..99cc1f7
--- /dev/null
+++ b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+
+import android.app.ActivityOptions;
+import android.os.Bundle;
+
+/**
+ * A wrapper around {@link ActivityOptions} to allow custom functionality in launcher
+ */
+public class ActivityOptionsWrapper {
+
+ public final ActivityOptions options;
+ public final RunnableList onEndCallback;
+
+ public ActivityOptionsWrapper(ActivityOptions options, RunnableList onEndCallback) {
+ this.options = options;
+ this.onEndCallback = onEndCallback;
+ }
+
+ /**
+ * @see {@link ActivityOptions#toBundle()}
+ */
+ public Bundle toBundle() {
+ return options.toBundle();
+ }
+}
diff --git a/src/com/android/launcher3/util/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/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
deleted file mode 100644
index 4f4cccd..0000000
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.config.FeatureFlags;
-
-import java.util.Arrays;
-
-/**
- * Calculates the next item that a {@link KeyEvent} should change the focus to.
- *<p>
- * Note, this utility class calculates everything regards to icon index and its (x,y) coordinates.
- * Currently supports:
- * <ul>
- * <li> full matrix of cells that are 1x1
- * <li> sparse matrix of cells that are 1x1
- * [ 1][ ][ 2][ ]
- * [ ][ ][ 3][ ]
- * [ ][ 4][ ][ ]
- * [ ][ 5][ 6][ 7]
- * </ul>
- * *<p>
- * For testing, one can use a BT keyboard, or use following adb command.
- * ex. $ adb shell input keyevent 20 // KEYCODE_DPAD_LEFT
- */
-public class FocusLogic {
-
- private static final String TAG = "FocusLogic";
- private static final boolean DEBUG = false;
-
- /** Item and page index related constant used by {@link #handleKeyEvent}. */
- public static final int NOOP = -1;
-
- public static final int PREVIOUS_PAGE_RIGHT_COLUMN = -2;
- public static final int PREVIOUS_PAGE_FIRST_ITEM = -3;
- public static final int PREVIOUS_PAGE_LAST_ITEM = -4;
- public static final int PREVIOUS_PAGE_LEFT_COLUMN = -5;
-
- public static final int CURRENT_PAGE_FIRST_ITEM = -6;
- public static final int CURRENT_PAGE_LAST_ITEM = -7;
-
- public static final int NEXT_PAGE_FIRST_ITEM = -8;
- public static final int NEXT_PAGE_LEFT_COLUMN = -9;
- public static final int NEXT_PAGE_RIGHT_COLUMN = -10;
-
- public static final int ALL_APPS_COLUMN = -11;
-
- // Matrix related constant.
- public static final int EMPTY = -1;
- public static final int PIVOT = 100;
-
- /**
- * Returns true only if this utility class handles the key code.
- */
- public static boolean shouldConsume(int keyCode) {
- return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
- keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
- keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END ||
- keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
- }
-
- public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
- int pageCount, boolean isRtl) {
-
- int cntX = map == null ? -1 : map.length;
- int cntY = map == null ? -1 : map[0].length;
-
- if (DEBUG) {
- Log.v(TAG, String.format(
- "handleKeyEvent START: cntX=%d, cntY=%d, iconIdx=%d, pageIdx=%d, pageCnt=%d",
- cntX, cntY, iconIdx, pageIndex, pageCount));
- }
-
- int newIndex = NOOP;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/, isRtl);
- if (!isRtl && newIndex == NOOP && pageIndex > 0) {
- newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
- } else if (isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
- newIndex = NEXT_PAGE_RIGHT_COLUMN;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/, isRtl);
- if (!isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
- newIndex = NEXT_PAGE_LEFT_COLUMN;
- } else if (isRtl && newIndex == NOOP && pageIndex > 0) {
- newIndex = PREVIOUS_PAGE_LEFT_COLUMN;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, 1 /*increment*/);
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, -1 /*increment*/);
- break;
- case KeyEvent.KEYCODE_MOVE_HOME:
- newIndex = handleMoveHome();
- break;
- case KeyEvent.KEYCODE_MOVE_END:
- newIndex = handleMoveEnd();
- break;
- case KeyEvent.KEYCODE_PAGE_DOWN:
- newIndex = handlePageDown(pageIndex, pageCount);
- break;
- case KeyEvent.KEYCODE_PAGE_UP:
- newIndex = handlePageUp(pageIndex);
- break;
- default:
- break;
- }
-
- if (DEBUG) {
- Log.v(TAG, String.format("handleKeyEvent FINISH: index [%d -> %s]",
- iconIdx, getStringIndex(newIndex)));
- }
- return newIndex;
- }
-
- /**
- * Returns a matrix of size (m x n) that has been initialized with {@link #EMPTY}.
- *
- * @param m number of columns in the matrix
- * @param n number of rows in the matrix
- */
- // TODO: get rid of dynamic matrix creation.
- private static int[][] createFullMatrix(int m, int n) {
- int[][] matrix = new int [m][n];
-
- for (int i=0; i < m;i++) {
- Arrays.fill(matrix[i], EMPTY);
- }
- return matrix;
- }
-
- /**
- * Returns a matrix of size same as the {@link CellLayout} dimension that is initialized with the
- * index of the child view.
- */
- // TODO: get rid of the dynamic matrix creation
- public static int[][] createSparseMatrix(CellLayout layout) {
- ShortcutAndWidgetContainer parent = layout.getShortcutsAndWidgets();
- final int m = layout.getCountX();
- final int n = layout.getCountY();
- final boolean invert = parent.invertLayoutHorizontally();
-
- int[][] matrix = createFullMatrix(m, n);
-
- // Iterate thru the children.
- for (int i = 0; i < parent.getChildCount(); i++ ) {
- View cell = parent.getChildAt(i);
- if (!cell.isFocusable()) {
- continue;
- }
- int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
- int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
- int x = invert ? (m - cx - 1) : cx;
- if (x < m && cy < n) { // check if view fits into matrix, else skip
- matrix[x][cy] = i;
- }
- }
- if (DEBUG) {
- printMatrix(matrix);
- }
- return matrix;
- }
-
- /**
- * Creates a sparse matrix that merges the icon and hotseat view group using the cell layout.
- * The size of the returning matrix is [icon column count x (icon + hotseat row count)]
- * in portrait orientation. In landscape, [(icon + hotseat) column count x (icon row count)]
- */
- // TODO: get rid of the dynamic matrix creation
- public static int[][] createSparseMatrixWithHotseat(
- CellLayout iconLayout, CellLayout hotseatLayout, DeviceProfile dp) {
-
- ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
- ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets();
-
- boolean isHotseatHorizontal = !dp.isVerticalBarLayout();
-
- int m, n;
- if (isHotseatHorizontal) {
- m = hotseatLayout.getCountX();
- n = iconLayout.getCountY() + hotseatLayout.getCountY();
- } else {
- m = iconLayout.getCountX() + hotseatLayout.getCountX();
- n = hotseatLayout.getCountY();
- }
- int[][] matrix = createFullMatrix(m, n);
- // Iterate through the children of the workspace.
- for (int i = 0; i < iconParent.getChildCount(); i++) {
- View cell = iconParent.getChildAt(i);
- if (!cell.isFocusable()) {
- continue;
- }
- int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
- int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
- matrix[cx][cy] = i;
- }
-
- // Iterate thru the children of the hotseat.
- for (int i = hotseatParent.getChildCount() - 1; i >= 0; i--) {
- if (isHotseatHorizontal) {
- int cx = ((CellLayout.LayoutParams)
- hotseatParent.getChildAt(i).getLayoutParams()).cellX;
- matrix[cx][iconLayout.getCountY()] = iconParent.getChildCount() + i;
- } else {
- int cy = ((CellLayout.LayoutParams)
- hotseatParent.getChildAt(i).getLayoutParams()).cellY;
- matrix[iconLayout.getCountX()][cy] = iconParent.getChildCount() + i;
- }
- }
- if (DEBUG) {
- printMatrix(matrix);
- }
- return matrix;
- }
-
- /**
- * Creates a sparse matrix that merges the icon of previous/next page and last column of
- * current page. When left key is triggered on the leftmost column, sparse matrix is created
- * that combines previous page matrix and an extra column on the right. Likewise, when right
- * key is triggered on the rightmost column, sparse matrix is created that combines this column
- * on the 0th column and the next page matrix.
- *
- * @param pivotX x coordinate of the focused item in the current page
- * @param pivotY y coordinate of the focused item in the current page
- */
- // TODO: get rid of the dynamic matrix creation
- public static int[][] createSparseMatrixWithPivotColumn(CellLayout iconLayout,
- int pivotX, int pivotY) {
-
- ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
-
- int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY());
-
- // Iterate thru the children of the top parent.
- for (int i = 0; i < iconParent.getChildCount(); i++) {
- View cell = iconParent.getChildAt(i);
- if (!cell.isFocusable()) {
- continue;
- }
- int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
- int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
- if (pivotX < 0) {
- matrix[cx - pivotX][cy] = i;
- } else {
- matrix[cx][cy] = i;
- }
- }
-
- if (pivotX < 0) {
- matrix[0][pivotY] = PIVOT;
- } else {
- matrix[pivotX][pivotY] = PIVOT;
- }
- if (DEBUG) {
- printMatrix(matrix);
- }
- return matrix;
- }
-
- //
- // key event handling methods.
- //
-
- /**
- * Calculates icon that has is closest to the horizontal axis in reference to the cur icon.
- *
- * Example of the check order for KEYCODE_DPAD_RIGHT:
- * [ ][ ][13][14][15]
- * [ ][ 6][ 8][10][12]
- * [ X][ 1][ 2][ 3][ 4]
- * [ ][ 5][ 7][ 9][11]
- */
- // TODO: add unit tests to verify all permutation.
- private static int handleDpadHorizontal(int iconIdx, int cntX, int cntY,
- int[][] matrix, int increment, boolean isRtl) {
- if(matrix == null) {
- throw new IllegalStateException("Dpad navigation requires a matrix.");
- }
- int newIconIndex = NOOP;
-
- int xPos = -1;
- int yPos = -1;
- // Figure out the location of the icon.
- for (int i = 0; i < cntX; i++) {
- for (int j = 0; j < cntY; j++) {
- if (matrix[i][j] == iconIdx) {
- xPos = i;
- yPos = j;
- }
- }
- }
- if (DEBUG) {
- Log.v(TAG, String.format("\thandleDpadHorizontal: \t[x, y]=[%d, %d] iconIndex=%d",
- xPos, yPos, iconIdx));
- }
-
- // Rule1: check first in the horizontal direction
- for (int x = xPos + increment; 0 <= x && x < cntX; x += increment) {
- if ((newIconIndex = inspectMatrix(x, yPos, cntX, cntY, matrix)) != NOOP
- && newIconIndex != ALL_APPS_COLUMN) {
- return newIconIndex;
- }
- }
-
- // Rule2: check (x1-n, yPos + increment), (x1-n, yPos - increment)
- // (x2-n, yPos + 2*increment), (x2-n, yPos - 2*increment)
- int nextYPos1;
- int nextYPos2;
- boolean haveCrossedAllAppsColumn1 = false;
- boolean haveCrossedAllAppsColumn2 = false;
- int x = -1;
- for (int coeff = 1; coeff < cntY; coeff++) {
- nextYPos1 = yPos + coeff * increment;
- nextYPos2 = yPos - coeff * increment;
- x = xPos + increment * coeff;
- if (inspectMatrix(x, nextYPos1, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
- haveCrossedAllAppsColumn1 = true;
- }
- if (inspectMatrix(x, nextYPos2, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
- haveCrossedAllAppsColumn2 = true;
- }
- for (; 0 <= x && x < cntX; x += increment) {
- int offset1 = haveCrossedAllAppsColumn1 && x < cntX - 1 ? increment : 0;
- newIconIndex = inspectMatrix(x, nextYPos1 + offset1, cntX, cntY, matrix);
- if (newIconIndex != NOOP) {
- return newIconIndex;
- }
- int offset2 = haveCrossedAllAppsColumn2 && x < cntX - 1 ? -increment : 0;
- newIconIndex = inspectMatrix(x, nextYPos2 + offset2, cntX, cntY, matrix);
- if (newIconIndex != NOOP) {
- return newIconIndex;
- }
- }
- }
-
- // Rule3: if switching between pages, do a brute-force search to find an item that was
- // missed by rules 1 and 2 (such as when going from a bottom right icon to top left)
- if (iconIdx == PIVOT) {
- if (isRtl) {
- return increment < 0 ? NEXT_PAGE_FIRST_ITEM : PREVIOUS_PAGE_LAST_ITEM;
- }
- return increment < 0 ? PREVIOUS_PAGE_LAST_ITEM : NEXT_PAGE_FIRST_ITEM;
- }
- return newIconIndex;
- }
-
- /**
- * Calculates icon that is closest to the vertical axis in reference to the current icon.
- *
- * Example of the check order for KEYCODE_DPAD_DOWN:
- * [ ][ ][ ][ X][ ][ ][ ]
- * [ ][ ][ 5][ 1][ 4][ ][ ]
- * [ ][10][ 7][ 2][ 6][ 9][ ]
- * [14][12][ 9][ 3][ 8][11][13]
- */
- // TODO: add unit tests to verify all permutation.
- private static int handleDpadVertical(int iconIndex, int cntX, int cntY,
- int [][] matrix, int increment) {
- int newIconIndex = NOOP;
- if(matrix == null) {
- throw new IllegalStateException("Dpad navigation requires a matrix.");
- }
-
- int xPos = -1;
- int yPos = -1;
- // Figure out the location of the icon.
- for (int i = 0; i< cntX; i++) {
- for (int j = 0; j < cntY; j++) {
- if (matrix[i][j] == iconIndex) {
- xPos = i;
- yPos = j;
- }
- }
- }
-
- if (DEBUG) {
- Log.v(TAG, String.format("\thandleDpadVertical: \t[x, y]=[%d, %d] iconIndex=%d",
- xPos, yPos, iconIndex));
- }
-
- // Rule1: check first in the dpad direction
- for (int y = yPos + increment; 0 <= y && y <cntY && 0 <= y; y += increment) {
- if ((newIconIndex = inspectMatrix(xPos, y, cntX, cntY, matrix)) != NOOP
- && newIconIndex != ALL_APPS_COLUMN) {
- return newIconIndex;
- }
- }
-
- // Rule2: check (xPos + increment, y_(1-n)), (xPos - increment, y_(1-n))
- // (xPos + 2*increment, y_(2-n))), (xPos - 2*increment, y_(2-n))
- int nextXPos1;
- int nextXPos2;
- boolean haveCrossedAllAppsColumn1 = false;
- boolean haveCrossedAllAppsColumn2 = false;
- int y = -1;
- for (int coeff = 1; coeff < cntX; coeff++) {
- nextXPos1 = xPos + coeff * increment;
- nextXPos2 = xPos - coeff * increment;
- y = yPos + increment * coeff;
- if (inspectMatrix(nextXPos1, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
- haveCrossedAllAppsColumn1 = true;
- }
- if (inspectMatrix(nextXPos2, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
- haveCrossedAllAppsColumn2 = true;
- }
- for (; 0 <= y && y < cntY; y = y + increment) {
- int offset1 = haveCrossedAllAppsColumn1 && y < cntY - 1 ? increment : 0;
- newIconIndex = inspectMatrix(nextXPos1 + offset1, y, cntX, cntY, matrix);
- if (newIconIndex != NOOP) {
- return newIconIndex;
- }
- int offset2 = haveCrossedAllAppsColumn2 && y < cntY - 1 ? -increment : 0;
- newIconIndex = inspectMatrix(nextXPos2 + offset2, y, cntX, cntY, matrix);
- if (newIconIndex != NOOP) {
- return newIconIndex;
- }
- }
- }
- return newIconIndex;
- }
-
- private static int handleMoveHome() {
- return CURRENT_PAGE_FIRST_ITEM;
- }
-
- private static int handleMoveEnd() {
- return CURRENT_PAGE_LAST_ITEM;
- }
-
- private static int handlePageDown(int pageIndex, int pageCount) {
- if (pageIndex < pageCount -1) {
- return NEXT_PAGE_FIRST_ITEM;
- }
- return CURRENT_PAGE_LAST_ITEM;
- }
-
- private static int handlePageUp(int pageIndex) {
- if (pageIndex > 0) {
- return PREVIOUS_PAGE_FIRST_ITEM;
- } else {
- return CURRENT_PAGE_FIRST_ITEM;
- }
- }
-
- //
- // Helper methods.
- //
-
- private static boolean isValid(int xPos, int yPos, int countX, int countY) {
- return (0 <= xPos && xPos < countX && 0 <= yPos && yPos < countY);
- }
-
- private static int inspectMatrix(int x, int y, int cntX, int cntY, int[][] matrix) {
- int newIconIndex = NOOP;
- if (isValid(x, y, cntX, cntY)) {
- if (matrix[x][y] != -1) {
- newIconIndex = matrix[x][y];
- if (DEBUG) {
- Log.v(TAG, String.format("\t\tinspect: \t[x, y]=[%d, %d] %d",
- x, y, matrix[x][y]));
- }
- return newIconIndex;
- }
- }
- return newIconIndex;
- }
-
- /**
- * Only used for debugging.
- */
- private static String getStringIndex(int index) {
- switch(index) {
- case NOOP: return "NOOP";
- case PREVIOUS_PAGE_FIRST_ITEM: return "PREVIOUS_PAGE_FIRST";
- case PREVIOUS_PAGE_LAST_ITEM: return "PREVIOUS_PAGE_LAST";
- case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN";
- case CURRENT_PAGE_FIRST_ITEM: return "CURRENT_PAGE_FIRST";
- case CURRENT_PAGE_LAST_ITEM: return "CURRENT_PAGE_LAST";
- case NEXT_PAGE_FIRST_ITEM: return "NEXT_PAGE_FIRST";
- case NEXT_PAGE_LEFT_COLUMN: return "NEXT_PAGE_LEFT_COLUMN";
- case ALL_APPS_COLUMN: return "ALL_APPS_COLUMN";
- default:
- return Integer.toString(index);
- }
- }
-
- /**
- * Only used for debugging.
- */
- private static void printMatrix(int[][] matrix) {
- Log.v(TAG, "\tprintMap:");
- int m = matrix.length;
- int n = matrix[0].length;
-
- for (int j=0; j < n; j++) {
- String colY = "\t\t";
- for (int i=0; i < m; i++) {
- colY += String.format("%3d",matrix[i][j]);
- }
- Log.v(TAG, colY);
- }
- }
-
- /**
- * @param edgeColumn the column of the new icon. either {@link #NEXT_PAGE_LEFT_COLUMN} or
- * {@link #NEXT_PAGE_RIGHT_COLUMN}
- * @return the view adjacent to {@param oldView} in the {@param nextPage} of the folder.
- */
- public static View getAdjacentChildInNextFolderPage(
- ShortcutAndWidgetContainer nextPage, View oldView, int edgeColumn) {
- final int newRow = ((CellLayout.LayoutParams) oldView.getLayoutParams()).cellY;
-
- int column = (edgeColumn == NEXT_PAGE_LEFT_COLUMN) ^ nextPage.invertLayoutHorizontally()
- ? 0 : (((CellLayout) nextPage.getParent()).getCountX() - 1);
-
- for (; column >= 0; column--) {
- for (int row = newRow; row >= 0; row--) {
- View newView = nextPage.getChildAt(column, row);
- if (newView != null) {
- return newView;
- }
- }
- }
- return null;
- }
-}
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/RunnableList.java b/src/com/android/launcher3/util/RunnableList.java
new file mode 100644
index 0000000..55add14
--- /dev/null
+++ b/src/com/android/launcher3/util/RunnableList.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to hold a list of runnable
+ */
+public class RunnableList {
+
+ private ArrayList<Runnable> mList = null;
+ private boolean mDestroyed = false;
+
+ /**
+ * Ads a runnable to this list
+ */
+ public void add(Runnable runnable) {
+ if (mDestroyed) {
+ runnable.run();
+ return;
+ }
+ if (mList == null) {
+ mList = new ArrayList<>();
+ }
+ mList.add(runnable);
+ }
+
+ /**
+ * Destroys the list, executing any pending callbacks. All new callbacks are
+ * immediately executed
+ */
+ public void executeAllAndDestroy() {
+ mDestroyed = true;
+ executeAllAndClear();
+ }
+
+ /**
+ * Executes all previously added runnable and clears the list
+ */
+ public void executeAllAndClear() {
+ if (mList != null) {
+ ArrayList<Runnable> list = mList;
+ mList = null;
+ int count = list.size();
+ for (int i = 0; i < count; i++) {
+ list.get(i).run();
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
new file mode 100644
index 0000000..573c8bd
--- /dev/null
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+public final class SplitConfigurationOptions {
+
+ ///////////////////////////////////
+ // Taken from
+ // frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+ /**
+ * Stage position isn't specified normally meaning to use what ever it is currently set to.
+ */
+ public static final int STAGE_POSITION_UNDEFINED = -1;
+ /**
+ * Specifies that a stage is positioned at the top half of the screen if
+ * in portrait mode or at the left half of the screen if in landscape mode.
+ */
+ public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+ /**
+ * Specifies that a stage is positioned at the bottom half of the screen if
+ * in portrait mode or at the right half of the screen if in landscape mode.
+ */
+ public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+ @Retention(SOURCE)
+ @IntDef({STAGE_POSITION_UNDEFINED, STAGE_POSITION_TOP_OR_LEFT, STAGE_POSITION_BOTTOM_OR_RIGHT})
+ public @interface StagePosition {}
+
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ public static final int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ */
+ public static final int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ */
+ public static final int STAGE_TYPE_SIDE = 1;
+
+ @IntDef({STAGE_TYPE_UNDEFINED, STAGE_TYPE_MAIN, STAGE_TYPE_SIDE})
+ public @interface StageType {}
+ ///////////////////////////////////
+
+ public static class SplitPositionOption {
+ public final int mIconResId;
+ public final int mTextResId;
+ @StagePosition
+ public final int mStagePosition;
+
+ @StageType
+ public final int mStageType;
+
+ public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) {
+ mIconResId = iconResId;
+ mTextResId = textResId;
+ mStagePosition = stagePosition;
+ mStageType = stageType;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 5b33f18..e413d7f 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -58,7 +58,7 @@
Preconditions.assertUIThread();
Handler handler = new Handler();
- // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+ // LayoutInflater is not thread safe as it maintains a global variable 'mConstructorArgs'.
// Create a different copy to use on the background thread.
LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 2ad80cf..b8554e4 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -145,14 +145,17 @@
msg.sendToTarget();
}
- private void updateOffset() {
- int numPagesForWallpaperParallax;
+ /** Returns the number of pages used for the wallpaper parallax. */
+ public int getNumPagesForWallpaperParallax() {
if (mWallpaperIsLiveWallpaper) {
- numPagesForWallpaperParallax = mNumScreens;
+ return mNumScreens;
} else {
- numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
+ return Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
}
- Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0,
+ }
+
+ private void updateOffset() {
+ Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, getNumPagesForWallpaperParallax(), 0,
mWindowToken).sendToTarget();
}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 505c6ce..71aa4ac 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Rect;
+import android.view.LayoutInflater;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.DeviceProfile;
@@ -67,13 +68,28 @@
}
/**
+ * Returns a LayoutInflater that is cloned in this Context, so that Views inflated by it will
+ * have the same Context. (i.e. {@link #lookupContext(Context)} will find this ActivityContext.)
+ */
+ default LayoutInflater getLayoutInflater() {
+ if (this instanceof Context) {
+ Context context = (Context) this;
+ return LayoutInflater.from(context).cloneInContext(context);
+ }
+ return null;
+ }
+
+ /**
* The root view to support drag-and-drop and popup support.
*/
BaseDragLayer getDragLayer();
DeviceProfile getDeviceProfile();
- static <T extends ActivityContext> T lookupContext(Context context) {
+ /**
+ * Returns the ActivityContext associated with the given Context.
+ */
+ static <T extends Context & ActivityContext> T lookupContext(Context context) {
if (context instanceof ActivityContext) {
return (T) context;
} else if (context instanceof ContextWrapper) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 0cb8c1e..9a2db10 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -114,6 +114,11 @@
}
@Override
+ protected boolean shouldAddArrow() {
+ return false;
+ }
+
+ @Override
protected void getTargetObjectLocation(Rect outPos) {
mTargetRect.roundOut(outPos);
}
@@ -129,7 +134,6 @@
(DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
view.getIconView().setBackgroundResource(item.iconRes);
view.getBubbleText().setText(item.labelRes);
- view.setDividerVisibility(View.INVISIBLE);
view.setOnClickListener(popup);
view.setOnLongClickListener(popup);
popup.mItemMap.put(view, item);
@@ -158,6 +162,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) ?
@@ -165,15 +177,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 c9bd284..7f0765b 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -32,7 +32,6 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
import com.android.launcher3.util.Themes;
@@ -42,7 +41,6 @@
*/
public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
- private static final float SCRIM_ALPHA = .95f;
protected final T mLauncher;
private final WallpaperColorInfo mWallpaperColorInfo;
protected final int mEndScrim;
@@ -61,12 +59,7 @@
super(context, attrs);
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
- int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- endScrim = Themes.getColorBackgroundFloating(context);
- endScrim = ColorUtils.setAlphaComponent(endScrim, (int) (255 * SCRIM_ALPHA));
- }
- mEndScrim = endScrim;
+ mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
mMaxScrimAlpha = 0.7f;
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index 03d3026..c8cf627 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -168,8 +168,8 @@
}
private AllAppsPagedView getAllAppsPagedView() {
- View v = mLauncher.getAppsView().getContentView();
- return (v instanceof AllAppsPagedView) ? (AllAppsPagedView) v : null;
+ View v = mLauncher.getAppsView().getContentView();
+ return (v instanceof AllAppsPagedView) ? (AllAppsPagedView) v : null;
}
/**
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index a38e90d..fc63af0 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -74,7 +74,18 @@
@Override
public final void onClick(View v) {
- mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+ Object tag = null;
+ if (v instanceof WidgetCell) {
+ tag = v.getTag();
+ } else if (v.getParent() instanceof WidgetCell) {
+ tag = ((WidgetCell) v.getParent()).getTag();
+ }
+ if (tag instanceof PendingAddShortcutInfo) {
+ mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
+ } else {
+ mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+ }
+
}
@Override
@@ -85,6 +96,8 @@
if (v instanceof WidgetCell) {
return beginDraggingWidget((WidgetCell) v);
+ } else if (v.getParent() instanceof WidgetCell) {
+ return beginDraggingWidget((WidgetCell) v.getParent());
}
return true;
}
@@ -95,16 +108,19 @@
// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
// we abort the drag.
- if (image.getBitmap() == null) {
+ if (image.getDrawable() == null) {
return false;
}
+ PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
+ dragHelper.setRemoteViewsPreview(v.getPreview());
+ dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
+
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.getDrawable().getIntrinsicWidth(),
+ image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
close(true);
return true;
}
@@ -154,4 +170,21 @@
toast.show();
return toast;
}
+
+ /**
+ * Show shortcut tap toast prompting user to drag instead.
+ */
+ private static Toast showShortcutToast(Context context, Toast toast) {
+ // Let the user know that they have to long press to add a widget
+ if (toast != null) {
+ toast.cancel();
+ }
+
+ CharSequence msg = Utilities.wrapForTts(
+ context.getText(R.string.long_press_shortcut_to_add),
+ context.getString(R.string.long_accessible_way_to_add_shortcut));
+ toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
+ toast.show();
+ return toast;
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
similarity index 96%
rename from src/com/android/launcher3/LauncherAppWidgetHost.java
rename to src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index fea26df..d745754 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.widget;
import static android.app.Activity.RESULT_CANCELED;
@@ -29,12 +29,13 @@
import android.util.SparseArray;
import android.widget.Toast;
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.widget.DeferredAppWidgetHostView;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
@@ -199,7 +200,7 @@
}
}
- void addPendingView(int appWidgetId, PendingAppWidgetHostView view) {
+ public void addPendingView(int appWidgetId, PendingAppWidgetHostView view) {
mPendingViews.put(appWidgetId, view);
}
@@ -247,7 +248,7 @@
super.onProviderChanged(appWidgetId, info);
// The super method updates the dimensions of the providerInfo. Update the
// launcher spans accordingly.
- info.initSpans(mContext);
+ info.initSpans(mContext, LauncherAppState.getIDP(mContext));
}
/**
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 780a1a1..2e542ed 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,36 +19,52 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.os.SystemClock;
+import android.util.Log;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AdapterView;
import android.widget.Advanceable;
import android.widget.RemoteViews;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
+
+import java.util.List;
/**
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
- implements TouchCompleteListener, View.OnLongClickListener {
+ implements TouchCompleteListener, View.OnLongClickListener,
+ LocalColorExtractor.Listener {
+
+ private static final String LOG_TAG = "LauncherAppWidgetHostView";
// Related to the auto-advancing of widgets
private static final long ADVANCE_INTERVAL = 20000;
@@ -61,20 +77,44 @@
private final CheckLongPressHelper mLongPressHelper;
protected final Launcher mLauncher;
+ private final Workspace mWorkspace;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mReinflateOnConfigChange;
+ // Maintain the color manager.
+ private final LocalColorExtractor mColorExtractor;
+
private boolean mIsScrollable;
private boolean mIsAttachedToWindow;
private boolean mIsAutoAdvanceRegistered;
+ private boolean mIsInDragMode = false;
private Runnable mAutoAdvanceRunnable;
+ private RectF mLastLocationRegistered = null;
+ @Nullable private AppWidgetHostViewDragListener mDragListener;
-
+ // Used to store the widget size during onLayout.
+ private final Rect mCurrentWidgetSize = new Rect();
+ private final Rect mWidgetSizeAtDrag = new Rect();
+ private final RectF mTempRectF = new RectF();
+ private final boolean mIsRtl;
+ private final Rect mEnforcedRectangle = new Rect();
+ private final float mEnforcedCornerRadius;
+ private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
+ outline.setEmpty();
+ } else {
+ outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
+ }
+ }
+ };
public LauncherAppWidgetHostView(Context context) {
super(context);
mLauncher = Launcher.getLauncher(context);
+ mWorkspace = mLauncher.getWorkspace();
mLongPressHelper = new CheckLongPressHelper(this, this);
mInflater = LayoutInflater.from(context);
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
@@ -84,12 +124,34 @@
if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
setOnLightBackground(true);
}
+ mIsRtl = Utilities.isRtl(context.getResources());
+ mColorExtractor = LocalColorExtractor.newInstance(getContext());
+ mColorExtractor.setListener(this);
+
+ mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
+ }
+
+ @Override
+ public void setColorResources(@Nullable SparseIntArray colors) {
+ if (colors == null) {
+ resetColorResources();
+ } else {
+ super.setColorResources(colors);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mIsInDragMode && mDragListener != null) {
+ mDragListener.onDragContentChanged();
+ }
}
@Override
public boolean onLongClick(View view) {
if (mIsScrollable) {
- DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+ DragLayer dragLayer = mLauncher.getDragLayer();
dragLayer.requestDisallowInterceptTouchEvent(false);
}
view.performLongClick();
@@ -124,7 +186,7 @@
if (viewGroup instanceof AdapterView) {
return true;
} else {
- for (int i=0; i < viewGroup.getChildCount(); i++) {
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup) {
if (checkScrollableRecursively((ViewGroup) child)) {
@@ -138,7 +200,7 @@
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+ DragLayer dragLayer = mLauncher.getDragLayer();
if (mIsScrollable) {
dragLayer.requestDisallowInterceptTouchEvent(true);
}
@@ -170,6 +232,7 @@
// state is updated. So isAttachedToWindow() will return true until next frame.
mIsAttachedToWindow = false;
checkIfAutoAdvance();
+ mColorExtractor.removeLocations();
}
@Override
@@ -216,6 +279,99 @@
}
mIsScrollable = checkScrollableRecursively(this);
+
+ if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) {
+ mCurrentWidgetSize.left = left;
+ mCurrentWidgetSize.top = top;
+ mCurrentWidgetSize.right = right;
+ mCurrentWidgetSize.bottom = bottom;
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+ int pageId = mWorkspace.getPageIndexForScreenId(info.screenId);
+ updateColorExtraction(mCurrentWidgetSize, pageId);
+ }
+
+ enforceRoundedCorners();
+ }
+
+ /** Starts the drag mode. */
+ public void startDrag(AppWidgetHostViewDragListener dragListener) {
+ mIsInDragMode = true;
+ mDragListener = dragListener;
+ }
+
+ /** Handles a drag event occurred on a workspace page, {@code pageId}. */
+ public void handleDrag(Rect rect, int pageId) {
+ mWidgetSizeAtDrag.set(rect);
+ updateColorExtraction(mWidgetSizeAtDrag, pageId);
+ }
+
+ /** Ends the drag mode. */
+ public void endDrag() {
+ mIsInDragMode = false;
+ mDragListener = null;
+ mWidgetSizeAtDrag.setEmpty();
+ requestLayout();
+ }
+
+ private void updateColorExtraction(Rect widgetLocation, int pageId) {
+ // If the widget hasn't been measured and laid out, we cannot do this.
+ if (widgetLocation.isEmpty()) {
+ return;
+ }
+ int screenWidth = mLauncher.getDeviceProfile().widthPx;
+ int screenHeight = mLauncher.getDeviceProfile().heightPx;
+ int numScreens = mWorkspace.getNumPagesForWallpaperParallax();
+ pageId = mIsRtl ? numScreens - pageId - 1 : pageId;
+ float relativeScreenWidth = 1f / numScreens;
+ float absoluteTop = widgetLocation.top;
+ float absoluteBottom = widgetLocation.bottom;
+ for (View v = (View) getParent();
+ v != null && v.getId() != R.id.launcher;
+ v = (View) v.getParent()) {
+ absoluteBottom += v.getTop();
+ absoluteTop += v.getTop();
+ }
+ float xOffset = 0;
+ View parentView = (View) getParent();
+ // The layout depends on the orientation.
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ int parentViewWidth = parentView == null ? 0 : parentView.getWidth();
+ xOffset = screenHeight - mWorkspace.getPaddingRight() - parentViewWidth;
+ } else {
+ int parentViewPaddingLeft = parentView == null ? 0 : parentView.getPaddingLeft();
+ xOffset = mWorkspace.getPaddingLeft() + parentViewPaddingLeft;
+ }
+ // This is the position of the widget relative to the wallpaper, as expected by the
+ // local color extraction of the WallpaperManager.
+ // The coordinate system is such that, on the horizontal axis, each screen has a
+ // distinct range on the [0,1] segment. So if there are 3 screens, they will have the
+ // ranges [0, 1/3], [1/3, 2/3] and [2/3, 1]. The position on the subrange should be
+ // the position of the widget relative to the screen. For the vertical axis, this is
+ // simply the location of the widget relative to the screen.
+ mTempRectF.left = ((widgetLocation.left + xOffset) / screenWidth + pageId)
+ * relativeScreenWidth;
+ mTempRectF.right = ((widgetLocation.right + xOffset) / screenWidth + pageId)
+ * relativeScreenWidth;
+ mTempRectF.top = absoluteTop / screenHeight;
+ mTempRectF.bottom = absoluteBottom / screenHeight;
+ if (mTempRectF.left < 0 || mTempRectF.right > 1 || mTempRectF.top < 0
+ || mTempRectF.bottom > 1) {
+ Log.e(LOG_TAG, " Error, invalid relative position");
+ return;
+ }
+ if (!mTempRectF.equals(mLastLocationRegistered)) {
+ if (mLastLocationRegistered != null) {
+ mColorExtractor.removeLocations();
+ }
+ mLastLocationRegistered = new RectF(mTempRectF);
+ mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+ }
+ }
+
+ @Override
+ public void onColorsChanged(RectF rectF, SparseIntArray colors) {
+ // setColorResources will reapply the view, which must happen in the UI thread.
+ post(() -> setColorResources(colors));
}
@Override
@@ -228,6 +384,14 @@
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
maybeRegisterAutoAdvance();
+
+ if (visibility == View.VISIBLE) {
+ if (mLastLocationRegistered != null) {
+ mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+ }
+ } else {
+ mColorExtractor.removeLocations();
+ }
}
private void checkIfAutoAdvance() {
@@ -324,4 +488,39 @@
}
return false;
}
+
+ @UiThread
+ private void resetRoundedCorners() {
+ setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+ setClipToOutline(false);
+ }
+
+ @UiThread
+ private void enforceRoundedCorners() {
+ if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled(this)) {
+ resetRoundedCorners();
+ return;
+ }
+ View background = RoundedCornerEnforcement.findBackground(this);
+ if (RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+ resetRoundedCorners();
+ return;
+ }
+ RoundedCornerEnforcement.computeRoundedRectangle(this,
+ background,
+ mEnforcedRectangle);
+ setOutlineProvider(mCornerRadiusEnforcementOutline);
+ setClipToOutline(true);
+ }
+
+ /** Returns the corner radius currently enforced, in pixels. */
+ public float getEnforcedCornerRadius() {
+ return mEnforcedCornerRadius;
+ }
+
+ /** Returns true if the corner radius are enforced for this App Widget. */
+ public boolean hasEnforcedCornerRadius() {
+ return getClipToOutline();
+ }
+
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
new file mode 100644
index 0000000..ad61495
--- /dev/null
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -0,0 +1,160 @@
+package com.android.launcher3.widget;
+
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+
+/**
+ * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
+ * a common object for describing both framework provided AppWidgets as well as custom widgets
+ * (who's implementation is owned by the launcher). This object represents a widget type / class,
+ * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo}
+ */
+public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
+ implements ComponentWithLabelAndIcon {
+
+ public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
+
+ public int spanX;
+ public int spanY;
+ public int minSpanX;
+ public int minSpanY;
+ public int maxSpanX;
+ public int maxSpanY;
+
+ public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
+ AppWidgetProviderInfo info) {
+ final LauncherAppWidgetProviderInfo launcherInfo;
+ if (info instanceof LauncherAppWidgetProviderInfo) {
+ launcherInfo = (LauncherAppWidgetProviderInfo) info;
+ } else {
+
+ // In lieu of a public super copy constructor, we first write the AppWidgetProviderInfo
+ // into a parcel, and then construct a new LauncherAppWidgetProvider info from the
+ // associated super parcel constructor. This allows us to copy non-public members without
+ // using reflection.
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ launcherInfo = new LauncherAppWidgetProviderInfo(p);
+ p.recycle();
+ }
+ launcherInfo.initSpans(context, LauncherAppState.getIDP(context));
+ return launcherInfo;
+ }
+
+ protected LauncherAppWidgetProviderInfo() {}
+
+ protected LauncherAppWidgetProviderInfo(Parcel in) {
+ super(in);
+ }
+
+ public void initSpans(Context context, InvariantDeviceProfile idp) {
+ Point landCellSize = idp.landscapeProfile.getCellSize();
+ Point portCellSize = idp.portraitProfile.getCellSize();
+
+ // Always assume we're working with the smallest span to make sure we
+ // reserve enough space in both orientations.
+ float smallestCellWidth = Math.min(landCellSize.x, portCellSize.x);
+ float smallestCellHeight = Math.min(landCellSize.y, portCellSize.y);
+
+ // We want to account for the extra amount of padding that we are adding to the widget
+ // to ensure that it gets the full amount of space that it has requested.
+ // If grids supports insetting widgets, we do not account for widget padding.
+ Rect widgetPadding = new Rect();
+ if (!idp.landscapeProfile.shouldInsetWidgets()
+ || !idp.portraitProfile.shouldInsetWidgets()) {
+ AppWidgetHostView.getDefaultPaddingForWidget(context, provider, widgetPadding);
+ }
+
+ minSpanX = getSpanX(widgetPadding, minResizeWidth, smallestCellWidth);
+ minSpanY = getSpanY(widgetPadding, minResizeHeight, smallestCellHeight);
+
+ // Use maxResizeWidth/Height if they are defined and we're on S or above.
+ maxSpanX =
+ (ATLEAST_S && maxResizeWidth > 0)
+ ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)
+ : idp.numColumns;
+ maxSpanY =
+ (ATLEAST_S && maxResizeHeight > 0)
+ ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)
+ : idp.numRows;
+
+ // Use targetCellWidth/Height if it is within the min/max ranges and we're on S or above.
+ // Otherwise, fall back to minWidth/Height.
+ if (ATLEAST_S && targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
+ && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
+ spanX = targetCellWidth;
+ spanY = targetCellHeight;
+ } else {
+ spanX = getSpanX(widgetPadding, minWidth, smallestCellWidth);
+ spanY = getSpanY(widgetPadding, minHeight, smallestCellHeight);
+ }
+ }
+
+ private int getSpanX(Rect widgetPadding, int widgetWidth, float cellWidth) {
+ return Math.max(1, (int) Math.ceil(
+ (widgetWidth + widgetPadding.left + widgetPadding.right) / cellWidth));
+ }
+
+ private int getSpanY(Rect widgetPadding, int widgetHeight, float cellHeight) {
+ return Math.max(1, (int) Math.ceil(
+ (widgetHeight + widgetPadding.top + widgetPadding.bottom) / cellHeight));
+ }
+
+ public String getLabel(PackageManager packageManager) {
+ return super.loadLabel(packageManager);
+ }
+
+ public Point getMinSpans() {
+ return new Point((resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
+ (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
+ }
+
+ public boolean isCustomWidget() {
+ return provider.getClassName().startsWith(CLS_CUSTOM_WIDGET_PREFIX);
+ }
+
+ public int getWidgetFeatures() {
+ if (Utilities.ATLEAST_P) {
+ return widgetFeatures;
+ } else {
+ return 0;
+ }
+ }
+
+ public boolean isReconfigurable() {
+ return configure != null && (getWidgetFeatures() & WIDGET_FEATURE_RECONFIGURABLE) != 0;
+ }
+
+ @Override
+ public final ComponentName getComponent() {
+ return provider;
+ }
+
+ @Override
+ public final UserHandle getUser() {
+ return getProfile();
+ }
+
+ @Override
+ public Drawable getFullResIcon(IconCache cache) {
+ return cache.getFullResIcon(provider.getPackageName(), icon);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java
new file mode 100644
index 0000000..097158b
--- /dev/null
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.graphics.RectF;
+import android.util.SparseIntArray;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.util.List;
+
+/** Extracts the colors we need from the wallpaper at given locations. */
+public class LocalColorExtractor implements ResourceBasedOverride {
+
+ /** Listener for color changes on a screen location. */
+ public interface Listener {
+ /**
+ * Method called when the colors on a registered location has changed.
+ *
+ * {@code extractedColors} maps the color resources {@code android.R.colors.system_*} to
+ * their value, in a format that can be passed directly to
+ * {@link AppWidgetHostView#setColorResources(SparseIntArray)}.
+ */
+ void onColorsChanged(RectF rect, SparseIntArray extractedColors);
+ }
+
+ static LocalColorExtractor newInstance(Context context) {
+ return Overrides.getObject(LocalColorExtractor.class, context.getApplicationContext(),
+ R.string.local_colors_extraction_class);
+ }
+
+ /** Sets the object that will receive the color changes. */
+ public void setListener(@Nullable Listener listener) {
+ // no-op
+ }
+
+ /** Adds a list of locations to track with this listener. */
+ public void addLocation(List<RectF> locations) {
+ // no-op
+ }
+
+ /** Stops tracking any locations. */
+ public void removeLocations() {
+ // no-op
+ }
+}
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index ed42bc4..6163b51 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -17,6 +17,7 @@
package com.android.launcher3.widget;
import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -25,8 +26,11 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
@@ -50,11 +54,16 @@
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
+ private final Rect mTempRect = new Rect();
+
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
+ protected final BaseActivity mActivity;
+
public NavigableAppWidgetHostView(Context context) {
super(context);
+ mActivity = ActivityContext.lookupContext(context);
}
@Override
@@ -222,6 +231,25 @@
int width = (int) (getMeasuredWidth() * mScaleToFit);
int height = (int) (getMeasuredHeight() * mScaleToFit);
- bounds.set(0, 0 , width, height);
+ getWidgetInset(mActivity.getDeviceProfile(), mTempRect);
+ bounds.set(mTempRect.left, mTempRect.top, width - mTempRect.right,
+ height - mTempRect.bottom);
+ }
+
+ /**
+ * Widgets have padding added by the system. We may choose to inset this padding if the grid
+ * supports it.
+ */
+ public void getWidgetInset(DeviceProfile grid, Rect out) {
+ if (!grid.shouldInsetWidgets()) {
+ out.setEmpty();
+ return;
+ }
+ AppWidgetProviderInfo info = getAppWidgetInfo();
+ if (info == null) {
+ out.set(grid.inv.defaultWidgetPadding);
+ } else {
+ AppWidgetHostView.getDefaultPaddingForWidget(getContext(), info.provider, out);
+ }
}
}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index bef9a08..ee0b84e 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -20,7 +20,6 @@
import android.appwidget.AppWidgetHostView;
import android.os.Bundle;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.PendingAddItemInfo;
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index ca47728..4b113d8 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -29,6 +29,7 @@
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
+import android.util.SizeF;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.View;
@@ -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<SizeF> 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..247a748 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -25,17 +25,21 @@
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.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
/**
* Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
@@ -48,15 +52,26 @@
private final PendingAddItemInfo mAddInfo;
private int[] mEstimatedCellSize;
- private RemoteViews mPreview;
+ @Nullable private RemoteViews mRemoteViewsPreview;
+ @Nullable private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
public PendingItemDragHelper(View view) {
super(view);
mAddInfo = (PendingAddItemInfo) view.getTag();
}
- public void setPreview(RemoteViews preview) {
- mPreview = preview;
+ /**
+ * Sets a {@link RemoteViews} which shows an app widget preview provided by app developers in
+ * the pin widget flow.
+ */
+ public void setRemoteViewsPreview(@Nullable RemoteViews remoteViewsPreview) {
+ mRemoteViewsPreview = remoteViewsPreview;
+ }
+
+ /** Sets a {@link LauncherAppWidgetHostView} which shows a preview layout of an app widget. */
+ public void setAppWidgetHostViewPreview(
+ @Nullable LauncherAppWidgetHostView appWidgetHostViewPreview) {
+ mAppWidgetHostViewPreview = appWidgetHostViewPreview;
}
/**
@@ -73,7 +88,7 @@
final Launcher launcher = Launcher.getLauncher(mView.getContext());
LauncherAppState app = LauncherAppState.getInstance(launcher);
- Bitmap preview = null;
+ Drawable preview = null;
final float scale;
final Point dragOffset;
final Rect dragRegion;
@@ -89,13 +104,21 @@
int[] previewSizeBeforeScale = new int[1];
- if (mPreview != null) {
- preview = LivePreviewWidgetCell.generateFromRemoteViews(launcher, mPreview,
- createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
+ if (mRemoteViewsPreview != null) {
+ preview = new FastBitmapDrawable(
+ WidgetCell.generateFromRemoteViews(launcher, mRemoteViewsPreview,
+ createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
+ }
+ if (mAppWidgetHostViewPreview != null) {
+ preview = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
+ launcher.getDragController()
+ .addDragListener(new AppWidgetHostViewDragListener(launcher));
}
if (preview == null) {
- preview = app.getWidgetCache().generateWidgetPreview(launcher,
- createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale).first;
+ preview = new FastBitmapDrawable(
+ app.getWidgetCache().generateWidgetPreview(launcher,
+ createWidgetInfo.info, maxWidth, null,
+ previewSizeBeforeScale).first);
}
if (previewSizeBeforeScale[0] < previewBitmapWidth) {
@@ -108,7 +131,7 @@
previewBounds.left += padding;
previewBounds.right -= padding;
}
- scale = previewBounds.width() / (float) preview.getWidth();
+ scale = previewBounds.width() / (float) preview.getIntrinsicWidth();
launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
dragOffset = null;
@@ -118,9 +141,10 @@
PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
LauncherIcons li = LauncherIcons.obtain(launcher);
- preview = li.createScaledBitmapWithoutShadow(icon, 0);
+ preview = new FastBitmapDrawable(
+ li.createScaledBitmapWithoutShadow(icon, 0));
li.recycle();
- scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
+ scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getIntrinsicWidth();
dragOffset = new Point(previewPadding / 2, previewPadding / 2);
@@ -148,9 +172,9 @@
launcher.getWorkspace().prepareDragWithProvider(this);
int dragLayerX = screenPos.x + previewBounds.left
- + (int) ((scale * preview.getWidth() - preview.getWidth()) / 2);
+ + (int) ((scale * preview.getIntrinsicWidth() - preview.getIntrinsicWidth()) / 2);
int dragLayerY = screenPos.y + previewBounds.top
- + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
+ + (int) ((scale * preview.getIntrinsicHeight() - preview.getIntrinsicHeight()) / 2);
// Start the drag
launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
new file mode 100644
index 0000000..99eccd1
--- /dev/null
+++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities to compute the enforced the use of rounded corners on App Widgets.
+ */
+public class RoundedCornerEnforcement {
+ // This class is only a namespace and not meant to be instantiated.
+ private RoundedCornerEnforcement() {
+ }
+
+ /**
+ * Find the background view for a widget.
+ *
+ * @param appWidget the view containing the App Widget (typically the instance of
+ * {@link AppWidgetHostView}).
+ */
+ @Nullable
+ public static View findBackground(@NonNull View appWidget) {
+ List<View> backgrounds = findViewsWithId(appWidget, android.R.id.background);
+ if (backgrounds.size() == 1) {
+ return backgrounds.get(0);
+ }
+ // Really, the argument should contain the widget, so it cannot be the background.
+ if (appWidget instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) appWidget;
+ if (vg.getChildCount() > 0) {
+ return findUndefinedBackground(vg.getChildAt(0));
+ }
+ }
+ return appWidget;
+ }
+
+ /**
+ * Check whether the app widget has opted out of the enforcement.
+ */
+ public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) {
+ return background.getId() == android.R.id.background && background.getClipToOutline();
+ }
+
+ /** Check if the app widget is in the deny list. */
+ public static boolean isRoundedCornerEnabled(@NonNull View view) {
+ if (!Utilities.ATLEAST_S || !FeatureFlags.ENABLE_ENFORCED_ROUNDED_CORNERS.get()) {
+ return false;
+ }
+ // Here we need to test if the view's component is in the (to be created) deny list.
+ return true;
+ }
+
+ /**
+ * Computes the rounded rectangle needed for this app widget.
+ *
+ * @param appWidget View onto which the rounded rectangle will be applied.
+ * @param background Background view. This must be either {@code appWidget} or a descendant
+ * of {@code appWidget}.
+ * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame
+ * of {@code appWidget}.
+ */
+ public static void computeRoundedRectangle(@NonNull View appWidget, @NonNull View background,
+ @NonNull Rect outRect) {
+ outRect.left = 0;
+ outRect.right = background.getWidth();
+ outRect.top = 0;
+ outRect.bottom = background.getHeight();
+ while (background != appWidget) {
+ outRect.offset(background.getLeft(), background.getTop());
+ background = (View) background.getParent();
+ }
+ }
+
+ /**
+ * Computes the radius of the rounded rectangle that should be applied to a widget expanded
+ * in the given context.
+ */
+ public static float computeEnforcedRadius(@NonNull Context context) {
+ if (!Utilities.ATLEAST_S) {
+ return 0;
+ }
+ Resources res = context.getResources();
+ float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
+ float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
+ return Math.min(defaultRadius, systemRadius);
+ }
+
+ private static List<View> findViewsWithId(View view, @IdRes int viewId) {
+ List<View> output = new ArrayList<>();
+ accumulateViewsWithId(view, viewId, output);
+ return output;
+ }
+
+ // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+ private static void accumulateViewsWithId(View view, @IdRes int viewId, List<View> output) {
+ if (view.getId() == viewId) {
+ output.add(view);
+ return;
+ }
+ if (view instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) view;
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ accumulateViewsWithId(vg.getChildAt(i), viewId, output);
+ }
+ }
+ }
+
+ private static boolean isViewVisible(View view) {
+ if (view.getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ return !view.willNotDraw() || view.getForeground() != null || view.getBackground() != null;
+ }
+
+ @Nullable
+ private static View findUndefinedBackground(View current) {
+ if (current.getVisibility() != View.VISIBLE) {
+ return null;
+ }
+ if (isViewVisible(current)) {
+ return current;
+ }
+ View lastVisibleView = null;
+ // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+ // something, or a ViewGroup that contains more than one view.
+ if (current instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) current;
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ View visibleView = findUndefinedBackground(vg.getChildAt(i));
+ if (visibleView != null) {
+ if (lastVisibleView != null) {
+ return current; // At least two visible children
+ }
+ lastVisibleView = visibleView;
+ }
+ }
+ }
+ return lastVisibleView;
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
index ebc2a25..9313266 100644
--- a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
+++ b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
@@ -15,13 +15,15 @@
*/
package com.android.launcher3.widget;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.PendingRequestArgs;
@@ -79,8 +81,22 @@
return true;
}
+ /**
+ * Checks whether the widget needs configuration.
+ *
+ * A widget needs configuration if (1) it has a configuration activity and (2)
+ * it's configuration is not optional.
+ *
+ * @return true if the widget needs configuration, false otherwise.
+ */
public boolean needsConfigure() {
- return mProviderInfo.configure != null;
+ int featureFlags = mProviderInfo.widgetFeatures;
+ // A widget's configuration is optional only if it's configuration is marked as optional AND
+ // it can be reconfigured later.
+ boolean configurationOptional = (featureFlags & WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0
+ && (featureFlags & WIDGET_FEATURE_RECONFIGURABLE) != 0;
+
+ return mProviderInfo.configure != null && !configurationOptional;
}
public LauncherAppWidgetProviderInfo getProviderInfo(Context context) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index bef91d2..1b0e1ce 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -16,26 +16,36 @@
package com.android.launcher3.widget;
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
import android.os.CancellationSignal;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import android.widget.RemoteViews;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.BaseActivity;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.model.WidgetItem;
/**
@@ -60,12 +70,16 @@
/** Widget preview width is calculated by multiplying this factor to the widget cell width. */
private static final float PREVIEW_SCALE = 0.8f;
+ protected int mPreviewWidth;
+ protected int mPreviewHeight;
protected int mPresetPreviewSize;
private int mCellSize;
+ private float mPreviewScale = 1f;
private WidgetImageView mWidgetImage;
private TextView mWidgetName;
private TextView mWidgetDims;
+ private TextView mWidgetDescription;
protected WidgetItem mItem;
@@ -75,12 +89,15 @@
private boolean mAnimatePreview = true;
private boolean mApplyBitmapDeferred = false;
- private Bitmap mDeferredBitmap;
+ private Drawable mDeferredDrawable;
protected final BaseActivity mActivity;
protected final DeviceProfile mDeviceProfile;
private final CheckLongPressHelper mLongPressHelper;
+ private RemoteViews mPreview;
+ private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
+
public WidgetCell(Context context) {
this(context, null);
}
@@ -106,15 +123,25 @@
private void setContainerWidth() {
mCellSize = (int) (mDeviceProfile.allAppsIconSizePx * WIDTH_SCALE);
mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
+ mPreviewWidth = mPreviewHeight = mPresetPreviewSize;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview);
- mWidgetName = ((TextView) findViewById(R.id.widget_name));
- mWidgetDims = ((TextView) findViewById(R.id.widget_dims));
+ mWidgetImage = findViewById(R.id.widget_preview);
+ mWidgetName = findViewById(R.id.widget_name);
+ mWidgetDims = findViewById(R.id.widget_dims);
+ mWidgetDescription = findViewById(R.id.widget_description);
+ }
+
+ public void setPreview(RemoteViews view) {
+ mPreview = view;
+ }
+
+ public RemoteViews getPreview() {
+ return mPreview;
}
/**
@@ -125,25 +152,41 @@
Log.d(TAG, "reset called on:" + mWidgetName.getText());
}
mWidgetImage.animate().cancel();
- mWidgetImage.setBitmap(null, null);
+ mWidgetImage.setDrawable(null, null);
mWidgetName.setText(null);
mWidgetDims.setText(null);
+ mWidgetDescription.setText(null);
+ mWidgetDescription.setVisibility(GONE);
+ mPreviewWidth = mPreviewHeight = mPresetPreviewSize;
if (mActiveRequest != null) {
mActiveRequest.cancel();
mActiveRequest = null;
}
+ mPreview = null;
+ mAppWidgetHostViewPreview = null;
}
public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
+ applyPreviewLayout(item);
+
mItem = item;
mWidgetName.setText(mItem.label);
mWidgetDims.setText(getContext().getString(R.string.widget_dims_format,
mItem.spanX, mItem.spanY));
mWidgetDims.setContentDescription(getContext().getString(
R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY));
- mWidgetPreviewLoader = loader;
+ if (ATLEAST_S && mItem.widgetInfo != null) {
+ CharSequence description = mItem.widgetInfo.loadDescription(getContext());
+ if (description != null && description.length() > 0) {
+ mWidgetDescription.setText(description);
+ mWidgetDescription.setVisibility(VISIBLE);
+ } else {
+ mWidgetDescription.setVisibility(GONE);
+ }
+ }
+ mWidgetPreviewLoader = loader;
if (item.activityInfo != null) {
setTag(new PendingAddShortcutInfo(item.activityInfo));
} else {
@@ -151,10 +194,36 @@
}
}
+ private void applyPreviewLayout(WidgetItem item) {
+ if (ATLEAST_S
+ && mPreview == null
+ && item.widgetInfo != null
+ && item.widgetInfo.previewLayout != Resources.ID_NULL) {
+ mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext());
+ LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
+ LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
+ item.widgetInfo.clone());
+ // A hack to force the initial layout to be the preview layout since there is no API for
+ // rendering a preview layout for work profile apps yet. For non-work profile layout, a
+ // proper solution is to use RemoteViews(PackageName, LayoutId).
+ launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
+ mAppWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1,
+ launcherAppWidgetProviderInfo);
+ mAppWidgetHostViewPreview.setPadding(/* left= */ 0, /* top= */0, /* right= */
+ 0, /* bottom= */ 0);
+ mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ null);
+ }
+ }
+
public WidgetImageView getWidgetView() {
return mWidgetImage;
}
+ @Nullable
+ public LauncherAppWidgetHostView getAppWidgetHostViewPreview() {
+ return mAppWidgetHostViewPreview;
+ }
+
/**
* Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
* will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
@@ -164,9 +233,9 @@
public void setApplyBitmapDeferred(boolean isDeferred) {
if (mApplyBitmapDeferred != isDeferred) {
mApplyBitmapDeferred = isDeferred;
- if (!mApplyBitmapDeferred && mDeferredBitmap != null) {
- applyPreview(mDeferredBitmap);
- mDeferredBitmap = null;
+ if (!mApplyBitmapDeferred && mDeferredDrawable != null) {
+ applyPreview(mDeferredDrawable);
+ mDeferredDrawable = null;
}
}
}
@@ -176,12 +245,21 @@
}
public void applyPreview(Bitmap bitmap) {
+ applyPreview(new FastBitmapDrawable(bitmap));
+ }
+
+ private void applyPreview(Drawable drawable) {
if (mApplyBitmapDeferred) {
- mDeferredBitmap = bitmap;
+ mDeferredDrawable = drawable;
return;
}
- if (bitmap != null) {
- mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+ if (drawable != null) {
+ LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
+ layoutParams.width = (int) (drawable.getIntrinsicWidth() * mPreviewScale);
+ layoutParams.height = (int) (drawable.getIntrinsicHeight() * mPreviewScale);
+ mWidgetImage.setLayoutParams(layoutParams);
+
+ mWidgetImage.setDrawable(drawable, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
if (mAnimatePreview) {
mWidgetImage.setAlpha(0f);
@@ -194,11 +272,50 @@
}
public void ensurePreview() {
+ if (mPreview != null && mActiveRequest == null) {
+ Bitmap preview = generateFromRemoteViews(
+ mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
+ if (preview != null) {
+ applyPreview(new FastBitmapDrawable(preview));
+ return;
+ }
+ }
+
+ if (mAppWidgetHostViewPreview != null) {
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ int viewWidth = dp.cellWidthPx * mItem.spanX;
+ int viewHeight = dp.cellHeightPx * mItem.spanY;
+
+ mAppWidgetHostViewPreview.measure(
+ MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+ viewWidth = mAppWidgetHostViewPreview.getMeasuredWidth();
+ viewHeight = mAppWidgetHostViewPreview.getMeasuredHeight();
+ mAppWidgetHostViewPreview.layout(0, 0, viewWidth, viewHeight);
+ Drawable drawable = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
+ applyPreview(drawable);
+ return;
+ }
if (mActiveRequest != null) {
return;
}
- mActiveRequest = mWidgetPreviewLoader.getPreview(
- mItem, mPresetPreviewSize, mPresetPreviewSize, this);
+ mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, mPreviewWidth, mPreviewHeight,
+ this);
+ }
+
+ /** Sets the widget preview image size in number of cells. */
+ public void setPreviewSize(int spanX, int spanY) {
+ setPreviewSize(spanX, spanY, 1f);
+ }
+
+ /** Sets the widget preview image size, in number of cells, and preview scale. */
+ public void setPreviewSize(int spanX, int spanY, float previewScale) {
+ int padding = 2 * getResources()
+ .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+ mPreviewWidth = mDeviceProfile.cellWidthPx * spanX + padding;
+ mPreviewHeight = mDeviceProfile.cellHeightPx * spanY + padding;
+ mPreviewScale = previewScale;
}
@Override
@@ -233,12 +350,6 @@
}
@Override
- public void setLayoutParams(ViewGroup.LayoutParams params) {
- params.width = params.height = mCellSize;
- super.setLayoutParams(params);
- }
-
- @Override
public CharSequence getAccessibilityClassName() {
return WidgetCell.class.getName();
}
@@ -248,4 +359,53 @@
super.onInitializeAccessibilityNodeInfo(info);
info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
}
+
+ /**
+ * Generates a bitmap by inflating {@param views}.
+ * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
+ *
+ * TODO: Consider moving this to the background thread.
+ */
+ public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
+ LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
+ try {
+ return generateFromView(activity, views.apply(activity, new FrameLayout(activity)),
+ info, previewSize, preScaledWidthOut);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static Bitmap generateFromView(BaseActivity activity, View v,
+ LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
+
+ DeviceProfile dp = activity.getDeviceProfile();
+ int viewWidth = dp.cellWidthPx * info.spanX;
+ int viewHeight = dp.cellHeightPx * info.spanY;
+
+ v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+ viewWidth = v.getMeasuredWidth();
+ viewHeight = v.getMeasuredHeight();
+ v.layout(0, 0, viewWidth, viewHeight);
+
+ preScaledWidthOut[0] = viewWidth;
+ final int bitmapWidth, bitmapHeight;
+ final float scale;
+ if (viewWidth > previewSize) {
+ scale = ((float) previewSize) / viewWidth;
+ bitmapWidth = previewSize;
+ bitmapHeight = (int) (viewHeight * scale);
+ } else {
+ scale = 1;
+ bitmapWidth = viewWidth;
+ bitmapHeight = viewHeight;
+ }
+
+ return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
+ c.scale(scale, scale);
+ v.draw(c);
+ });
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index c022374..12e0d43 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -7,17 +7,20 @@
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
+import android.util.SizeF;
import android.view.View;
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.DropTarget;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.util.Thunk;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+
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<SizeF> 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;
+
+ ArrayList<SizeF> paddedSizes = sizes.stream().map(
+ size -> new SizeF(Math.max(0.f, size.getWidth() - xPaddingDips),
+ Math.max(0.f, size.getHeight() - yPaddingDips))).collect(
+ Collectors.toCollection(ArrayList::new));
+
+ Rect rect = AppWidgetResizeFrame.getMinMaxSizes(paddedSizes, 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, paddedSizes);
return options;
}
}
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index df2bcff..39d701c 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -17,9 +17,7 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
@@ -35,11 +33,10 @@
*/
public class WidgetImageView extends View {
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final RectF mDstRectF = new RectF();
private final int mBadgeMargin;
- private Bitmap mBitmap;
+ private Drawable mDrawable;
private Drawable mBadge;
public WidgetImageView(Context context) {
@@ -57,21 +54,22 @@
.getDimensionPixelSize(R.dimen.profile_badge_margin);
}
- public void setBitmap(Bitmap bitmap, Drawable badge) {
- mBitmap = bitmap;
+ public void setDrawable(Drawable drawable, Drawable badge) {
+ mDrawable = drawable;
mBadge = badge;
invalidate();
}
- public Bitmap getBitmap() {
- return mBitmap;
+ public Drawable getDrawable() {
+ return mDrawable;
}
@Override
protected void onDraw(Canvas canvas) {
- if (mBitmap != null) {
+ if (mDrawable != null) {
updateDstRectF();
- canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
+ mDrawable.setBounds(getBitmapBounds());
+ mDrawable.draw(canvas);
// Only draw the badge if a preview was drawn.
if (mBadge != null) {
@@ -91,11 +89,11 @@
private void updateDstRectF() {
float myWidth = getWidth();
float myHeight = getHeight();
- float bitmapWidth = mBitmap.getWidth();
+ float bitmapWidth = mDrawable.getIntrinsicWidth();
final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
float scaledWidth = bitmapWidth * scale;
- float scaledHeight = mBitmap.getHeight() * scale;
+ float scaledHeight = mDrawable.getIntrinsicHeight() * scale;
mDstRectF.left = (myWidth - scaledWidth) / 2;
mDstRectF.right = (myWidth + scaledWidth) / 2;
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index c0c5c48..15fa844 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -27,18 +27,14 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -122,15 +118,6 @@
appWidgetId).getBoolean(WIDGET_OPTION_RESTORE_COMPLETED);
}
- public static Map<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap(Context context) {
- if (WidgetsModel.GO_DISABLE_WIDGETS) {
- return Collections.emptyMap();
- }
- return allWidgetsSteam(context).collect(
- Collectors.toMap(info -> new ComponentKey(info.provider, info.getProfile()),
- Function.identity()));
- }
-
private static Stream<AppWidgetProviderInfo> allWidgetsSteam(Context context) {
AppWidgetManager awm = context.getSystemService(AppWidgetManager.class);
return Stream.concat(
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 3585a18..bbb0d92 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -26,19 +26,24 @@
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
+import android.widget.ScrollView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
import android.widget.TextView;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.util.WidgetsTableUtils;
import java.util.List;
@@ -64,6 +69,8 @@
private static final int DEFAULT_CLOSE_DURATION = 200;
private ItemInfo mOriginalItemInfo;
private Rect mInsets;
+ private final int mMaxTableHeight;
+ private int mMaxHorizontalSpan = 4;
public WidgetsBottomSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -74,18 +81,46 @@
setWillNotDraw(false);
mInsets = new Rect();
mContent = this;
+ DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
+ // Set the max table height to 2 / 3 of the grid height so that the bottom picker won't
+ // take over the entire view vertically.
+ mMaxTableHeight = deviceProfile.inv.numRows * 2 / 3 * deviceProfile.cellHeightPx;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int paddingPx = 2 * getResources().getDimensionPixelOffset(
+ R.dimen.widget_cell_horizontal_padding);
+ int maxHorizontalSpan = findViewById(R.id.widgets_table).getMeasuredWidth()
+ / (mLauncher.getDeviceProfile().cellWidthPx + paddingPx);
+
+ if (mMaxHorizontalSpan != maxHorizontalSpan) {
+ // Ensure the table layout is showing widgets in the right column after measure.
+ mMaxHorizontalSpan = maxHorizontalSpan;
+ onWidgetsBound();
+ }
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
setTranslationShift(mTranslationShift);
+
+ // Ensure the scroll view height is not larger than mMaxTableHeight, which is a value
+ // smaller than the entire screen height.
+ ScrollView widgetsTableScrollView = findViewById(R.id.widgets_table_scroll_view);
+ if (widgetsTableScrollView.getMeasuredHeight() > mMaxTableHeight) {
+ ViewGroup.LayoutParams layoutParams = widgetsTableScrollView.getLayoutParams();
+ layoutParams.height = mMaxTableHeight;
+ widgetsTableScrollView.setLayoutParams(layoutParams);
+ }
}
public void populateAndShow(ItemInfo itemInfo) {
mOriginalItemInfo = itemInfo;
- ((TextView) findViewById(R.id.title)).setText(getContext().getString(
- R.string.widgets_bottom_sheet_title, mOriginalItemInfo.title));
+ ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
onWidgetsBound();
attachToContainer();
@@ -100,47 +135,44 @@
mOriginalItemInfo.getTargetComponent().getPackageName(),
mOriginalItemInfo.user));
- ViewGroup widgetRow = findViewById(R.id.widgets);
- ViewGroup widgetCells = widgetRow.findViewById(R.id.widgets_cell_list);
+ TableLayout widgetsTable = findViewById(R.id.widgets_table);
+ widgetsTable.removeAllViews();
- widgetCells.removeAllViews();
-
- for (int i = 0; i < widgets.size(); i++) {
- WidgetCell widget = addItemCell(widgetCells);
- widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher)
- .getWidgetCache());
- widget.ensurePreview();
- widget.setVisibility(View.VISIBLE);
- if (i < widgets.size() - 1) {
- addDivider(widgetCells);
- }
- }
-
- if (widgets.size() == 1) {
- // If there is only one widget, we want to center it instead of left-align.
- WidgetsBottomSheet.LayoutParams params = (WidgetsBottomSheet.LayoutParams)
- widgetRow.getLayoutParams();
- params.gravity = Gravity.CENTER_HORIZONTAL;
- } else {
- // Otherwise, add an empty view to the start as padding (but still scroll edge to edge).
- View leftPaddingView = LayoutInflater.from(getContext()).inflate(
- R.layout.widget_list_divider, widgetRow, false);
- leftPaddingView.getLayoutParams().width = ResourceUtils.pxFromDp(
- 16, getResources().getDisplayMetrics());
- widgetCells.addView(leftPaddingView, 0);
- }
+ WidgetsTableUtils.groupWidgetItemsIntoTable(widgets, mMaxHorizontalSpan).forEach(row -> {
+ TableRow tableRow = new TableRow(getContext());
+ tableRow.setGravity(Gravity.TOP);
+ row.forEach(widgetItem -> {
+ WidgetCell widget = addItemCell(tableRow);
+ widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
+ widget.applyFromCellItem(widgetItem, LauncherAppState.getInstance(mLauncher)
+ .getWidgetCache());
+ widget.ensurePreview();
+ widget.setVisibility(View.VISIBLE);
+ });
+ widgetsTable.addView(tableRow);
+ });
}
- private void addDivider(ViewGroup parent) {
- LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = false;
+ ScrollView scrollView = findViewById(R.id.widgets_table_scroll_view);
+ if (getPopupContainer().isEventOverView(scrollView, ev)
+ && scrollView.getScrollY() > 0) {
+ mNoIntercept = true;
+ }
+ }
+ return super.onControllerInterceptTouchEvent(ev);
}
protected WidgetCell addItemCell(ViewGroup parent) {
- WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
- R.layout.widget_cell, parent, false);
+ WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
+ .inflate(R.layout.widget_cell, parent, false);
- widget.setOnClickListener(this);
- widget.setOnLongClickListener(this);
+ WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+ preview.setOnClickListener(this);
+ preview.setOnLongClickListener(this);
widget.setAnimatePreview(false);
parent.addView(widget);
diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
index 1086987..8b3bbce 100644
--- a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
@@ -22,8 +22,9 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
/**
* Custom app widget provider info that can be used as a widget, but provide extra functionality
@@ -57,7 +58,7 @@
}
@Override
- public void initSpans(Context context) { }
+ public void initSpans(Context context, InvariantDeviceProfile idp) { }
@Override
public String getLabel(PackageManager packageManager) {
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 0b66ec0..329a444 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -16,7 +16,7 @@
package com.android.launcher3.widget.custom;
-import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
+import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -29,12 +29,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.systemui.plugins.CustomWidgetPlugin;
import com.android.systemui.plugins.PluginListener;
diff --git a/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java b/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
new file mode 100644
index 0000000..66bb363
--- /dev/null
+++ b/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.dragndrop;
+
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/** A drag listener of {@link LauncherAppWidgetHostView}. */
+public final class AppWidgetHostViewDragListener implements DragController.DragListener {
+ private final Launcher mLauncher;
+ private DropTarget.DragObject mDragObject;
+ private AppWidgetHostViewDrawable mAppWidgetHostViewDrawable;
+ private LauncherAppWidgetHostView mAppWidgetHostView;
+
+ public AppWidgetHostViewDragListener(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions unused) {
+ if (dragObject.dragView.getDrawable() instanceof AppWidgetHostViewDrawable) {
+ mDragObject = dragObject;
+ mAppWidgetHostViewDrawable =
+ (AppWidgetHostViewDrawable) mDragObject.dragView.getDrawable();
+ mAppWidgetHostView = mAppWidgetHostViewDrawable.getAppWidgetHostView();
+ mAppWidgetHostView.startDrag(this);
+ } else {
+ mLauncher.getDragController().removeDragListener(this);
+ }
+ }
+
+ @Override
+ public void onDragEnd() {
+ mAppWidgetHostView.endDrag();
+ mLauncher.getDragController().removeDragListener(this);
+ }
+
+ /** Notifies when there is a content change in the drag view. */
+ public void onDragContentChanged() {
+ if (mDragObject.dragView != null) {
+ mDragObject.dragView.invalidate();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index 09517e1..73bae6f 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -20,10 +20,14 @@
import androidx.annotation.IntDef;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.widget.WidgetItemComparator;
import java.lang.annotation.Retention;
+import java.util.List;
+import java.util.stream.Collectors;
/** Holder class to store the package information of an entry shown in the widgets list. */
public abstract class WidgetsListBaseEntry {
@@ -35,9 +39,14 @@
*/
public final String mTitleSectionName;
- public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName) {
+ public final List<WidgetItem> mWidgets;
+
+ public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items) {
mPkgItem = pkgItem;
mTitleSectionName = titleSectionName;
+ this.mWidgets =
+ items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
}
/**
@@ -51,10 +60,11 @@
public abstract int getRank();
@Retention(SOURCE)
- @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_CONTENT})
+ @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT})
public @interface Rank {
}
public static final int RANK_WIDGETS_LIST_HEADER = 1;
- public static final int RANK_WIDGETS_LIST_CONTENT = 2;
+ public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 2;
+ public static final int RANK_WIDGETS_LIST_CONTENT = 3;
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
index b0cb8c7..0328cf6 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -17,10 +17,8 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.widget.WidgetItemComparator;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Holder class to store all the information related to a list of widgets from the same app which is
@@ -28,18 +26,14 @@
*/
public final class WidgetsListContentEntry extends WidgetsListBaseEntry {
- public final List<WidgetItem> mWidgets;
-
public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName,
List<WidgetItem> items) {
- super(pkgItem, titleSectionName);
- this.mWidgets =
- items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
+ super(pkgItem, titleSectionName, items);
}
@Override
public String toString() {
- return mPkgItem.packageName + ":" + mWidgets.size();
+ return "Content:" + mPkgItem.packageName + ":" + mWidgets.size();
}
@Override
@@ -47,4 +41,12 @@
public int getRank() {
return RANK_WIDGETS_LIST_CONTENT;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WidgetsListContentEntry)) return false;
+ WidgetsListContentEntry otherEntry = (WidgetsListContentEntry) obj;
+ return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+ && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+ }
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index 6899647..1fdc399 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -18,7 +18,7 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import java.util.Collection;
+import java.util.List;
/** An information holder for an app which has widgets or/and shortcuts. */
public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry {
@@ -30,8 +30,8 @@
private boolean mHasEntryUpdated = false;
public WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
- Collection<WidgetItem> items) {
- super(pkgItem, titleSectionName);
+ List<WidgetItem> items) {
+ super(pkgItem, titleSectionName, items);
widgetsCount = (int) items.stream().filter(item -> item.widgetInfo != null).count();
shortcutsCount = Math.max(0, items.size() - widgetsCount);
}
@@ -57,8 +57,21 @@
}
@Override
+ public String toString() {
+ return "Header:" + mPkgItem.packageName + ":" + mWidgets.size();
+ }
+
+ @Override
@Rank
public int getRank() {
return RANK_WIDGETS_LIST_HEADER;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WidgetsListHeaderEntry)) return false;
+ WidgetsListHeaderEntry otherEntry = (WidgetsListHeaderEntry) obj;
+ return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+ && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+ }
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
new file mode 100644
index 0000000..2aec3f8
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.model;
+
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+
+import java.util.List;
+
+/** An information holder for an app which has widgets or/and shortcuts, to be shown in search. */
+public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry {
+
+ private boolean mIsWidgetListShown = false;
+ private boolean mHasEntryUpdated = false;
+
+ public WidgetsListSearchHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items) {
+ super(pkgItem, titleSectionName, items);
+ }
+
+ /** Sets if the widgets list associated with this header is shown. */
+ public void setIsWidgetListShown(boolean isWidgetListShown) {
+ if (mIsWidgetListShown != isWidgetListShown) {
+ this.mIsWidgetListShown = isWidgetListShown;
+ mHasEntryUpdated = true;
+ } else {
+ mHasEntryUpdated = false;
+ }
+ }
+
+ /** Returns {@code true} if the widgets list associated with this header is shown. */
+ public boolean isWidgetListShown() {
+ return mIsWidgetListShown;
+ }
+
+ /** Returns {@code true} if this entry has been updated due to user interactions. */
+ public boolean hasEntryUpdated() {
+ return mHasEntryUpdated;
+ }
+
+ @Override
+ public String toString() {
+ return "SearchHeader:" + mPkgItem.packageName + ":" + mWidgets.size();
+ }
+
+ @Override
+ @Rank
+ public int getRank() {
+ return RANK_WIDGETS_LIST_SEARCH_HEADER;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WidgetsListSearchHeaderEntry)) return false;
+ WidgetsListSearchHeaderEntry otherEntry = (WidgetsListSearchHeaderEntry) obj;
+ return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+ && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java b/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java
new file mode 100644
index 0000000..7372751
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import com.android.launcher3.util.PackageUserKey;
+
+/**
+ * A listener to be invoked when a header is clicked.
+ */
+public interface OnHeaderClickListener {
+ /**
+ * Calls when a header is clicked to show / hide widgets for a package.
+ */
+ void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey);
+}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
new file mode 100644
index 0000000..7f84077
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -0,0 +1,257 @@
+/*
+ * 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.view.ViewGroup.MarginLayoutParams;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.Nullable;
+
+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 WidgetsRecyclerView mPrimaryRecyclerView;
+ private final WidgetsRecyclerView mSearchRecyclerView;
+ private final int mTabsHeight;
+
+ // The following are only non null if mHasWorkProfile is true.
+ @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
+ @Nullable private final View mPrimaryWorkTabsView;
+ @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
+
+ private WidgetsRecyclerView mCurrentRecyclerView;
+
+ /**
+ * The vertical distance, in pixels, until the search is pinned at the top of the screen when
+ * the user scrolls down the recycler view.
+ */
+ private int mCollapsibleHeightForSearch = 0;
+ /**
+ * The vertical distance, in pixels, until the recommendation table disappears from the top of
+ * the screen when the user scrolls down the recycler view.
+ */
+ private int mCollapsibleHeightForRecommendation = 0;
+ /**
+ * The vertical distance, in pixels, until the tabs is pinned at the top of the screen when the
+ * user scrolls down the recycler view.
+ *
+ * <p>Always 0 if there is no work profile.
+ */
+ private int mCollapsibleHeightForTabs = 0;
+
+ SearchAndRecommendationsScrollController(
+ boolean hasWorkProfile,
+ int tabsHeight,
+ SearchAndRecommendationViewHolder viewHolder,
+ WidgetsRecyclerView primaryRecyclerView,
+ @Nullable WidgetsRecyclerView workRecyclerView,
+ WidgetsRecyclerView searchRecyclerView,
+ @Nullable View personalWorkTabsView,
+ @Nullable PersonalWorkPagedView primaryWorkViewPager) {
+ mHasWorkProfile = hasWorkProfile;
+ mViewHolder = viewHolder;
+ mPrimaryRecyclerView = primaryRecyclerView;
+ mCurrentRecyclerView = mPrimaryRecyclerView;
+ mWorkRecyclerView = workRecyclerView;
+ mSearchRecyclerView = searchRecyclerView;
+ mPrimaryWorkTabsView = personalWorkTabsView;
+ mPrimaryWorkViewPager = primaryWorkViewPager;
+ mCurrentRecyclerView = mPrimaryRecyclerView;
+ mTabsHeight = tabsHeight;
+ }
+
+ /** Sets the current active {@link WidgetsRecyclerView}. */
+ public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
+ mCurrentRecyclerView = currentRecyclerView;
+ mCurrentRecyclerView = currentRecyclerView;
+ mViewHolder.mHeaderTitle.setTranslationY(0);
+ mViewHolder.mRecommendedWidgetsTable.setTranslationY(0);
+ mViewHolder.mSearchBar.setTranslationY(0);
+
+ if (mHasWorkProfile) {
+ mPrimaryWorkTabsView.setTranslationY(0);
+ }
+ }
+
+ /**
+ * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
+ *
+ * @return {@code true} if margins or/and padding of views in the search and recommendations
+ * container have been updated.
+ */
+ public boolean updateMarginAndPadding() {
+ boolean hasMarginOrPaddingUpdated = false;
+ mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
+ mCollapsibleHeightForRecommendation =
+ measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
+ + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
+ + measureHeightWithVerticalMargins((View) mViewHolder.mSearchBar)
+ + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
+
+ int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
+ if (mHasWorkProfile) {
+ mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
+ + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
+ // In a work profile setup, the full widget sheet contains the following views:
+ // ------- (pinned) -|
+ // Widgets (collapsible) -|---> LinearLayout for search & recommendations
+ // Search bar (pinned) -|
+ // Widgets recommendation (collapsible)-|
+ // Personal | Work (pinned)
+ // View Pager
+ //
+ // Views after the search & recommendations are not bound by RelativelyLayout param.
+ // To position them on the expected location, padding & margin are added to these views
+
+ // Tabs should have a padding of the height of the search & recommendations container.
+ RelativeLayout.LayoutParams tabsLayoutParams =
+ (RelativeLayout.LayoutParams) mPrimaryWorkTabsView.getLayoutParams();
+ tabsLayoutParams.topMargin = topContainerHeight;
+ mPrimaryWorkTabsView.setLayoutParams(tabsLayoutParams);
+
+ // Instead of setting the top offset directly, we split the top offset into two values:
+ // 1. topOffsetAfterAllViewsCollapsed: this is the top offset after all collapsible
+ // 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 topOffsetAfterAllViewsCollapsed =
+ topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
+
+ RelativeLayout.LayoutParams viewPagerLayoutParams =
+ (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
+ if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
+ viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
+ mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
+ hasMarginOrPaddingUpdated = true;
+ }
+
+ if (mPrimaryRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
+ mPrimaryRecyclerView.setPadding(
+ mPrimaryRecyclerView.getPaddingLeft(),
+ mCollapsibleHeightForTabs,
+ mPrimaryRecyclerView.getPaddingRight(),
+ mPrimaryRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
+ if (mWorkRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
+ mWorkRecyclerView.setPadding(
+ mWorkRecyclerView.getPaddingLeft(),
+ mCollapsibleHeightForTabs,
+ mWorkRecyclerView.getPaddingRight(),
+ mWorkRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
+ } else {
+ if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
+ mPrimaryRecyclerView.setPadding(
+ mPrimaryRecyclerView.getPaddingLeft(),
+ topContainerHeight,
+ mPrimaryRecyclerView.getPaddingRight(),
+ mPrimaryRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
+ }
+ if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
+ mSearchRecyclerView.setPadding(
+ mSearchRecyclerView.getPaddingLeft(),
+ topContainerHeight,
+ mSearchRecyclerView.getPaddingRight(),
+ mSearchRecyclerView.getPaddingBottom());
+ hasMarginOrPaddingUpdated = true;
+ }
+ return hasMarginOrPaddingUpdated;
+ }
+
+ /**
+ * 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 unused) {
+ // Always use the recycler view offset because fast scroller offset has a different scale.
+ int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
+ if (recyclerViewYOffset < 0) return;
+
+ if (mCollapsibleHeightForRecommendation > 0) {
+ int yDisplacement = Math.max(-recyclerViewYOffset,
+ -mCollapsibleHeightForRecommendation);
+ mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
+ mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
+ }
+
+ if (mCollapsibleHeightForSearch > 0) {
+ int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch);
+ mViewHolder.mSearchBar.setTranslationY(searchYDisplacement);
+ }
+
+ if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
+ int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs);
+ mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
+ }
+ }
+
+ /** Resets any previous view translation. */
+ public void reset() {
+ mViewHolder.mHeaderTitle.setTranslationY(0);
+ mViewHolder.mSearchBar.setTranslationY(0);
+ if (mHasWorkProfile) {
+ mPrimaryWorkTabsView.setTranslationY(0);
+ }
+ }
+
+ /** private the height, in pixel, + the vertical margins of a given view. */
+ private static int measureHeightWithVerticalMargins(View view) {
+ if (view.getVisibility() != View.VISIBLE) {
+ return 0;
+ }
+ MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
+ return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+ + marginLayoutParams.topMargin;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
index dbd1bdf..2366609 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
@@ -25,6 +25,7 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
import java.util.ArrayList;
@@ -113,7 +114,7 @@
// or did the header view changed due to user interactions?
// or did the widget size and desc, span, etc change?
if (!isSamePackageItemInfo(orgRowEntry.mPkgItem, newRowEntry.mPkgItem)
- || hasHeaderUpdated(newRowEntry)
+ || hasHeaderUpdated(orgRowEntry, newRowEntry)
|| hasWidgetsListChanged(orgRowEntry, newRowEntry)) {
index = currentEntries.indexOf(orgRowEntry);
currentEntries.set(index, newRowEntry);
@@ -174,12 +175,16 @@
* Returns {@code true} if {@code newRow} is {@link WidgetsListHeaderEntry} and its content has
* been changed due to user interactions.
*/
- private boolean hasHeaderUpdated(WidgetsListBaseEntry newRow) {
- if (!(newRow instanceof WidgetsListHeaderEntry)) {
- return false;
+ private boolean hasHeaderUpdated(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow) {
+ if (newRow instanceof WidgetsListHeaderEntry && curRow instanceof WidgetsListHeaderEntry) {
+ return ((WidgetsListHeaderEntry) newRow).hasEntryUpdated() || !curRow.equals(newRow);
}
- WidgetsListHeaderEntry newRowEntry = (WidgetsListHeaderEntry) newRow;
- return newRowEntry.hasEntryUpdated();
+ if (newRow instanceof WidgetsListSearchHeaderEntry
+ && curRow instanceof WidgetsListSearchHeaderEntry) {
+ return ((WidgetsListSearchHeaderEntry) newRow).hasEntryUpdated()
+ || !curRow.equals(newRow);
+ }
+ return false;
}
private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 03623d5..29c00b2 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -22,51 +22,99 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Process;
+import android.os.UserHandle;
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.ViewGroup;
+import android.view.WindowInsets;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+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.model.WidgetItem;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.TopRoundedCornerView;
import com.android.launcher3.widget.BaseWidgetSheet;
+import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.search.SearchModeListener;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
+import com.android.launcher3.widget.util.WidgetsTableUtils;
+import com.android.launcher3.workprofile.PersonalWorkPagedView;
+import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
/**
* Popup for showing the full list of available widgets
*/
public class WidgetsFullSheet extends BaseWidgetSheet
- implements Insettable, ProviderChangedListener {
+ implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
+ WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener,
+ WidgetsSearchBarUIHelper {
+ private static final String TAG = WidgetsFullSheet.class.getSimpleName();
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
private static final float VERTICAL_START_POSITION = 0.3f;
+ // The widget recommendation table can easily take over the entire screen on devices with small
+ // resolution or landscape on phone. This ratio defines the max percentage of content area that
+ // the table can display.
+ private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
private final Rect mInsets = new Rect();
+ private final boolean mHasWorkProfile;
+ 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();
+ private final int mTabsHeight;
+ private final int mWidgetCellHorizontalPadding;
- private final WidgetsListAdapter mAdapter;
-
- private WidgetsRecyclerView mRecyclerView;
+ @Nullable private PersonalWorkPagedView mViewPager;
+ private boolean mIsInSearchMode;
+ private int mMaxSpansPerRow = 4;
+ private View mTabsView;
+ private TextView mNoWidgetsView;
+ private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
+ private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
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);
-
+ mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
+ mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
+ mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
+ mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
+ mTabsHeight = mHasWorkProfile
+ ? getContext().getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
+ : 0;
+ mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
+ R.dimen.widget_cell_horizontal_padding);
}
public WidgetsFullSheet(Context context, AttributeSet attrs) {
@@ -77,25 +125,114 @@
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());
+
+ 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);
+ 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,
+ mTabsHeight,
+ mSearchAndRecommendationViewHolder,
+ findViewById(R.id.primary_widgets_list_view),
+ mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
+ findViewById(R.id.search_widgets_list_view),
+ mTabsView,
+ mViewPager);
+ fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
+
+ mNoWidgetsView = findViewById(R.id.no_widgets_text);
+
+ onRecommendedWidgetsBound();
onWidgetsBound();
+
+ mSearchAndRecommendationViewHolder.mSearchBar.initialize(
+ mLauncher.getPopupDataProvider().getAllWidgets(), /* searchModeListener= */ this);
+ }
+
+ @Override
+ public void onActivePageChanged(int currentActivePage) {
+ AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
+ WidgetsRecyclerView currentRecyclerView =
+ mAdapters.get(currentActivePage).mWidgetsRecyclerView;
+
+ updateNoWidgetsView(currentAdapterHolder);
+ attachScrollbarToRecyclerView(currentRecyclerView);
+ resetExpandedHeaders();
+ }
+
+ private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
+ recyclerView.bindFastScrollbar();
+ mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
+ reset();
+ }
+
+ private void updateNoWidgetsView(AdapterHolder adapterHolder) {
+ boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
+ adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
+
+ // Always resets the text in case this is updated by search.
+ mNoWidgetsView.setText(R.string.no_widgets_available);
+ mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
+ }
+
+ private void updateNoSearchResultsView(boolean isVisible) {
+ mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
+ if (isVisible) {
+ mNoWidgetsView.setText(R.string.no_search_results);
+ }
+ }
+
+ private void reset() {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
+ if (mHasWorkProfile) {
+ mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
+ }
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
+ mSearchAndRecommendationsScrollController.reset();
}
@VisibleForTesting
public WidgetsRecyclerView getRecyclerView() {
- return mRecyclerView;
+ if (mIsInSearchMode) {
+ return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
+ }
+ 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(mRecyclerView, getContext().getString(
+ return Pair.create(getRecyclerView(), getContext().getString(
mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
}
@@ -116,9 +253,11 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
- mRecyclerView.setPadding(
- mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
- mRecyclerView.getPaddingRight(), insets.bottom);
+ setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
+ setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
+ if (mHasWorkProfile) {
+ setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
+ }
if (insets.bottom > 0) {
setupNavBarColor();
} else {
@@ -129,24 +268,72 @@
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) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ if (updateMaxSpansPerRow()) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ doMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ }
+
+ private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
int widthUsed;
if (mInsets.bottom > 0) {
widthUsed = mInsets.left + mInsets.right;
} else {
- Rect padding = mLauncher.getDeviceProfile().workspacePadding;
+ Rect padding = deviceProfile.workspacePadding;
widthUsed = Math.max(padding.left + padding.right,
2 * (mInsets.left + mInsets.right));
}
- int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx;
+ int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
measureChildWithMargins(mContent, widthMeasureSpec,
widthUsed, heightMeasureSpec, heightUsed);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
+ /** Returns {@code true} if the max spans have been updated. */
+ private boolean updateMaxSpansPerRow() {
+ if (getMeasuredWidth() == 0) return false;
+
+ int previousMaxSpansPerRow = mMaxSpansPerRow;
+ mMaxSpansPerRow = getMeasuredWidth()
+ / (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
+
+ if (previousMaxSpansPerRow != mMaxSpansPerRow) {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ mMaxSpansPerRow);
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ mMaxSpansPerRow);
+ if (mHasWorkProfile) {
+ mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+ mMaxSpansPerRow);
+ }
+ onRecommendedWidgetsBound();
+ return true;
+ }
+ return false;
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
@@ -168,7 +355,88 @@
@Override
public void onWidgetsBound() {
- mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
+ List<WidgetsListBaseEntry> allWidgets = mLauncher.getPopupDataProvider().getAllWidgets();
+
+ AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
+ primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
+ AdapterHolder searchAdapterHolder = mAdapters.get(AdapterHolder.SEARCH);
+ searchAdapterHolder.setup(findViewById(R.id.search_widgets_list_view));
+ primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+ updateNoWidgetsView(primaryUserAdapterHolder);
+
+ if (mHasWorkProfile) {
+ AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
+ workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
+ workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+ onActivePageChanged(mViewPager.getCurrentPage());
+ }
+ }
+
+ @Override
+ public void enterSearchMode() {
+ if (mIsInSearchMode) return;
+ setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
+ attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
+ resetExpandedHeaders();
+ }
+
+ @Override
+ public void exitSearchMode() {
+ onSearchResults(new ArrayList<>());
+ setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
+ if (mHasWorkProfile) {
+ mViewPager.snapToPage(AdapterHolder.PRIMARY);
+ }
+ attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
+
+ mSearchAndRecommendationsScrollController.updateMarginAndPadding();
+ }
+
+ @Override
+ public void onSearchResults(List<WidgetsListBaseEntry> entries) {
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
+ updateNoSearchResultsView(
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.getItemCount() == 0);
+ }
+
+ private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
+ mIsInSearchMode = isInSearchMode;
+ mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
+ .setVisibility(isInSearchMode ? GONE : VISIBLE);
+ if (mHasWorkProfile) {
+ mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
+ mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
+ } else {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
+ .setVisibility(isInSearchMode ? GONE : VISIBLE);
+ }
+ mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
+ .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
+ mNoWidgetsView.setVisibility(GONE);
+ }
+
+ private void resetExpandedHeaders() {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
+ mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
+ }
+
+ @Override
+ public void onRecommendedWidgetsBound() {
+ List<WidgetItem> recommendedWidgets =
+ mLauncher.getPopupDataProvider().getRecommendedWidgets();
+ WidgetsRecommendationTableLayout table =
+ mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
+ if (recommendedWidgets.size() > 0) {
+ float maxTableHeight =
+ (mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
+ * RECOMMENDATION_TABLE_HEIGHT_RATIO;
+ List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
+ WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
+ mMaxSpansPerRow);
+ table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
+ } else {
+ table.setVisibility(GONE);
+ }
}
private void open(boolean animate) {
@@ -183,12 +451,9 @@
.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);
}
});
@@ -198,7 +463,6 @@
});
} else {
setTranslationShift(TRANSLATION_SHIFT_OPENED);
- mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
post(this::announceAccessibilityChanges);
}
}
@@ -218,12 +482,12 @@
// Disable swipe down when recycler view is scrolling
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = false;
- RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
+ RecyclerViewFastScroller scroller = getRecyclerView().getScrollbar();
if (scroller.getThumbOffsetY() >= 0
&& getPopupContainer().isEventOverView(scroller, ev)) {
mNoIntercept = true;
} else if (getPopupContainer().isEventOverView(mContent, ev)) {
- mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, getPopupContainer());
+ mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
}
}
return super.onControllerInterceptTouchEvent(ev);
@@ -242,14 +506,14 @@
/** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
@VisibleForTesting
public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
- return launcher.findViewById(R.id.widgets_list_view);
+ return launcher.findViewById(R.id.primary_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);
+ target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
+ target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
}
@Override
@@ -257,4 +521,123 @@
super.onCloseComplete();
AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
}
+
+ @Override
+ public int getHeaderViewHeight() {
+ return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
+ + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
+ + measureHeightWithVerticalMargins(
+ (View) mSearchAndRecommendationViewHolder.mSearchBar);
+ }
+
+ /** private the height, in pixel, + the vertical margins of a given view. */
+ private static int measureHeightWithVerticalMargins(View view) {
+ if (view.getVisibility() != VISIBLE) {
+ return 0;
+ }
+ MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
+ return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+ + marginLayoutParams.topMargin;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mIsInSearchMode) {
+ mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ }
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ if (mIsInSearchMode) {
+ mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ return true;
+ }
+ return super.onBackPressed();
+ }
+
+ @Override
+ public void onDragStart(boolean start, float startDisplacement) {
+ super.onDragStart(start, startDisplacement);
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+
+ @Override
+ public void clearSearchBarFocus() {
+ mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
+ }
+
+ /** A holder class for holding adapters & their corresponding recycler view. */
+ private final class AdapterHolder {
+ static final int PRIMARY = 0;
+ static final int WORK = 1;
+ static final int SEARCH = 2;
+
+ private final int mAdapterType;
+ private final WidgetsListAdapter mWidgetsListAdapter;
+
+ 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,
+ /* WidgetsSearchBarUIHelper= */
+ mAdapterType == SEARCH ? WidgetsFullSheet.this : null);
+ mWidgetsListAdapter.setHasStableIds(true);
+ switch (mAdapterType) {
+ case PRIMARY:
+ mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
+ break;
+ case WORK:
+ mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void setup(WidgetsRecyclerView recyclerView) {
+ mWidgetsRecyclerView = recyclerView;
+ mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
+ // Disables animation because it disrupts the item focus upon adapter item change.
+ mWidgetsRecyclerView.setItemAnimator(null);
+ mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
+ mWidgetsRecyclerView.setEdgeEffectFactory(
+ ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
+ mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
+ mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
+ }
+ }
+
+ final class SearchAndRecommendationViewHolder {
+ final ViewGroup mContainer;
+ final View mCollapseHandle;
+ final WidgetsSearchBar mSearchBar;
+ final TextView mHeaderTitle;
+ final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
+
+ SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
+ mContainer = searchAndRecommendationContainer;
+ mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
+ mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
+ mHeaderTitle = mContainer.findViewById(R.id.title);
+ mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
+ mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
+ getRecyclerView().onTouchEvent(event);
+ return false;
+ });
+ mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
+ mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
+ }
+ }
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 5ec7f3b..d9c9d4d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -16,13 +16,14 @@
package com.android.launcher3.widget.picker;
import android.content.Context;
+import android.os.Process;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
-import android.view.View;
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;
@@ -34,15 +35,19 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.LabelComparator;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListHeaderViewHolderBinder.OnHeaderClickListener;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -61,28 +66,50 @@
private static final boolean DEBUG = false;
/** Uniquely identifies widgets list view type within the app. */
- private static final int VIEW_TYPE_WIDGETS_LIST = R.layout.widgets_list_row_view;
- private static final int VIEW_TYPE_WIDGETS_HEADER = R.layout.widgets_list_row_header;
+ private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
+ private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
+ private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
+ @Nullable private final WidgetsSearchBarUIHelper mSearchBarUIHelper;
private final WidgetsDiffReporter mDiffReporter;
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
- private final WidgetsListRowViewHolderBinder mWidgetsListRowViewHolderBinder;
+ 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;
+ @Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null;
+
+ private Predicate<WidgetsListBaseEntry> mHeaderAndSelectedContentFilter = entry ->
+ entry instanceof WidgetsListHeaderEntry
+ || entry instanceof WidgetsListSearchHeaderEntry
+ || new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+ .equals(mWidgetsContentVisiblePackageUserKey);
+ @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
- OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
+ OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
+ @Nullable WidgetsSearchBarUIHelper searchBarUIHelper) {
+ mSearchBarUIHelper = searchBarUIHelper;
mDiffReporter = new WidgetsDiffReporter(iconCache, this);
- mWidgetsListRowViewHolderBinder = new WidgetsListRowViewHolderBinder(context,
- layoutInflater, iconClickListener, iconLongClickListener, widgetPreviewLoader);
- mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListRowViewHolderBinder);
- mViewHolderBinders.put(VIEW_TYPE_WIDGETS_HEADER,
- new WidgetsListHeaderViewHolderBinder(layoutInflater, this::onHeaderClicked));
+ mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
+ layoutInflater, iconClickListener, iconLongClickListener,
+ widgetPreviewLoader, /* listAdapter= */ this);
+ mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_HEADER,
+ new WidgetsListHeaderViewHolderBinder(
+ layoutInflater, /* onHeaderClickListener= */this, /* listAdapter= */ this));
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_SEARCH_HEADER,
+ new WidgetsListSearchHeaderViewHolderBinder(
+ layoutInflater, /*onHeaderClickListener=*/ this, /* listAdapter= */ this));
+ }
+
+ public void setFilter(Predicate<WidgetsListBaseEntry> filter) {
+ mFilter = filter;
}
/**
@@ -91,16 +118,16 @@
* @see WidgetCell#setApplyBitmapDeferred(boolean)
*/
public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
- mWidgetsListRowViewHolderBinder.setApplyBitmapDeferred(isDeferred);
+ 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.cellContainer.getChildCount() - 1; j >= 0; j--) {
- View v = holder.cellContainer.getChildAt(j);
- if (v instanceof WidgetCell) {
- ((WidgetCell) v).setApplyBitmapDeferred(isDeferred);
+ 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);
}
}
}
@@ -112,36 +139,61 @@
return mVisibleEntries.size();
}
+ /** Returns all items that will be drawn in a recycler view. */
+ public List<WidgetsListBaseEntry> getItems() {
+ return mVisibleEntries;
+ }
+
/** Gets the section name for {@link com.android.launcher3.views.RecyclerViewFastScroller}. */
public String getSectionName(int pos) {
return mVisibleEntries.get(pos).mTitleSectionName;
}
- /** Updates the widget list. */
+ /** Updates the widget list based on {@code tempEntries}. */
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
mAllEntries = tempEntries.stream().sorted(mRowComparator)
.collect(Collectors.toList());
updateVisibleEntries();
}
+ /** Updates the widget list based on {@code searchResults}. */
+ public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
+ // Forget the expanded package every time widget list is refreshed in search mode.
+ mWidgetsContentVisiblePackageUserKey = null;
+ setWidgets(searchResults);
+ }
+
private void updateVisibleEntries() {
mAllEntries.forEach(entry -> {
if (entry instanceof WidgetsListHeaderEntry) {
((WidgetsListHeaderEntry) entry).setIsWidgetListShown(
- entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage));
+ new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+ .equals(mWidgetsContentVisiblePackageUserKey));
+ } else if (entry instanceof WidgetsListSearchHeaderEntry) {
+ ((WidgetsListSearchHeaderEntry) entry).setIsWidgetListShown(
+ new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+ .equals(mWidgetsContentVisiblePackageUserKey));
}
});
List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
- .filter(entry -> entry instanceof WidgetsListHeaderEntry
- || entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage))
+ .filter(entry -> (mFilter == null || mFilter.test(entry))
+ && mHeaderAndSelectedContentFilter.test(entry))
.collect(Collectors.toList());
mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
}
+ /**
+ * Resets any expanded widget header.
+ */
+ public void resetExpandedHeader() {
+ mWidgetsContentVisiblePackageUserKey = null;
+ updateVisibleEntries();
+ }
+
@Override
public void onBindViewHolder(ViewHolder holder, int pos) {
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
- viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos));
+ viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
}
@Override
@@ -169,7 +221,9 @@
@Override
public long getItemId(int pos) {
- return pos;
+ return Arrays.hashCode(new Object[]{
+ mVisibleEntries.get(pos).mPkgItem.hashCode(),
+ getItemViewType(pos)});
}
@Override
@@ -179,21 +233,43 @@
return VIEW_TYPE_WIDGETS_LIST;
} else if (entry instanceof WidgetsListHeaderEntry) {
return VIEW_TYPE_WIDGETS_HEADER;
+ } else if (entry instanceof WidgetsListSearchHeaderEntry) {
+ return VIEW_TYPE_WIDGETS_SEARCH_HEADER;
}
throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
}
@Override
- public void onHeaderClicked(boolean showWidgets, String expandedPackage) {
+ public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) {
+ if (mSearchBarUIHelper != null) {
+ mSearchBarUIHelper.clearSearchBarFocus();
+ }
if (showWidgets) {
- mWidgetsContentVisiblePackage = expandedPackage;
+ mWidgetsContentVisiblePackageUserKey = packageUserKey;
updateVisibleEntries();
- } else if (expandedPackage.equals(mWidgetsContentVisiblePackage)) {
- mWidgetsContentVisiblePackage = null;
+ } else if (packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) {
+ mWidgetsContentVisiblePackageUserKey = null;
updateVisibleEntries();
}
}
+ /**
+ * 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> {
@@ -202,7 +278,14 @@
@Override
public int compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b) {
- return mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+ int i = mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+ if (i != 0) {
+ return i;
+ }
+ // Prioritize entries from current user over other users if the entries are same.
+ if (a.mPkgItem.user.equals(b.mPkgItem.user)) return 0;
+ if (a.mPkgItem.user.equals(Process.myUserHandle())) return -1;
+ return 1;
}
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 823fb7b..75dd409 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -29,6 +29,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.FastBitmapDrawable;
@@ -41,6 +42,9 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import java.util.stream.Collectors;
/**
* A UI represents a header of an app shown in the full widgets tray.
@@ -55,6 +59,7 @@
@Nullable private HandlerRunnable mIconLoadRequest;
@Nullable private Drawable mIconDrawable;
private final int mIconSize;
+ private final int mBottomMarginSize;
private ImageView mAppIcon;
private TextView mTitle;
@@ -80,6 +85,8 @@
R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
grid.iconSizePx);
+ mBottomMarginSize =
+ getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin);
}
@Override
@@ -110,6 +117,13 @@
public void setExpanded(boolean isExpanded) {
this.mIsExpanded = isExpanded;
mExpandToggle.setChecked(isExpanded);
+ if (getLayoutParams() instanceof RecyclerView.LayoutParams) {
+ int bottomMargin = isExpanded ? 0 : mBottomMarginSize;
+ RecyclerView.LayoutParams layoutParams =
+ ((RecyclerView.LayoutParams) getLayoutParams());
+ layoutParams.bottomMargin = bottomMargin;
+ setLayoutParams(layoutParams);
+ }
}
/** Apply app icon, labels and tag using a generic {@link WidgetsListHeaderEntry}. */
@@ -157,14 +171,55 @@
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 {
+ Resources resources = getContext().getResources();
+ if (entry.widgetsCount == 0 && entry.shortcutsCount == 0) {
mSubtitle.setVisibility(GONE);
+ return;
}
+
+ String subtitle;
+ if (entry.widgetsCount > 0 && entry.shortcutsCount > 0) {
+ String widgetsCount = resources.getQuantityString(R.plurals.widgets_count,
+ entry.widgetsCount, entry.widgetsCount);
+ String shortcutsCount = resources.getQuantityString(R.plurals.shortcuts_count,
+ entry.shortcutsCount, entry.shortcutsCount);
+ subtitle = resources.getString(R.string.widgets_and_shortcuts_count, widgetsCount,
+ shortcutsCount);
+ } else if (entry.widgetsCount > 0) {
+ subtitle = resources.getQuantityString(R.plurals.widgets_count,
+ entry.widgetsCount, entry.widgetsCount);
+ } else {
+ subtitle = resources.getQuantityString(R.plurals.shortcuts_count,
+ entry.shortcutsCount, entry.shortcutsCount);
+ }
+ mSubtitle.setText(subtitle);
+ mSubtitle.setVisibility(VISIBLE);
+ }
+
+ /** Apply app icon, labels and tag using a generic {@link WidgetsListSearchHeaderEntry}. */
+ @UiThread
+ public void applyFromItemInfoWithIcon(WidgetsListSearchHeaderEntry entry) {
+ applyIconAndLabel(entry);
+ }
+
+ @UiThread
+ private void applyIconAndLabel(WidgetsListSearchHeaderEntry entry) {
+ PackageItemInfo info = entry.mPkgItem;
+ setIcon(info);
+ setTitles(entry);
+ setExpanded(entry.isWidgetListShown());
+
+ super.setTag(info);
+
+ verifyHighRes();
+ }
+
+ private void setTitles(WidgetsListSearchHeaderEntry entry) {
+ mTitle.setText(entry.mPkgItem.title);
+
+ mSubtitle.setText(entry.mWidgets.stream()
+ .map(item -> item.label).sorted().collect(Collectors.joining(", ")));
+ mSubtitle.setVisibility(VISIBLE);
}
@Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index ed53e6f..e57f4d8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -20,6 +20,7 @@
import com.android.launcher3.R;
import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
/**
@@ -29,11 +30,14 @@
ViewHolderBinder<WidgetsListHeaderEntry, WidgetsListHeaderHolder> {
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
+ private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
- OnHeaderClickListener onHeaderClickListener) {
+ OnHeaderClickListener onHeaderClickListener,
+ WidgetsListAdapter listAdapter) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
+ mWidgetsListAdapter = listAdapter;
}
@Override
@@ -45,17 +49,24 @@
}
@Override
- public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data) {
+ public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
+ int position) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ if (mWidgetsListAdapter.getItemCount() == 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_single_item_ripple);
+ } else if (position == 0) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
+ } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+ } else {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+ }
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
- mOnHeaderClickListener.onHeaderClicked(isExpanded, data.mPkgItem.packageName));
- }
-
- /** A listener to be invoked when {@link WidgetsListHeader} is clicked. */
- public interface OnHeaderClickListener {
- /** Calls when {@link WidgetsListHeader} is clicked to show / hide widgets for a package. */
- void onHeaderClicked(boolean showWidgets, String packageName);
+ mOnHeaderClickListener.onHeaderClicked(
+ isExpanded,
+ new PackageUserKey(data.mPkgItem.packageName, data.mPkgItem.user)
+ ));
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java
deleted file mode 100644
index cec6b80..0000000
--- a/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java
+++ /dev/null
@@ -1,147 +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.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 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 java.util.List;
-
-/**
- * Binds data from {@link WidgetsListContentEntry} to UI elements in {@link WidgetsRowViewHolder}.
- */
-public class WidgetsListRowViewHolderBinder
- implements ViewHolderBinder<WidgetsListContentEntry, WidgetsRowViewHolder> {
- private static final boolean DEBUG = false;
- private static final String TAG = "WidgetsListRowViewHolderBinder";
-
- 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 WidgetsListRowViewHolderBinder(
- 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;
- }
-
- @Override
- public WidgetsRowViewHolder newViewHolder(ViewGroup parent) {
- if (DEBUG) {
- Log.v(TAG, "\nonCreateViewHolder");
- }
-
- ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
- R.layout.widgets_scroll_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_cell_list).setPaddingRelative(mIndent, 0, 1, 0);
-
- return new WidgetsRowViewHolder(container);
- }
-
- @Override
- public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry) {
- List<WidgetItem> infoList = entry.mWidgets;
-
- ViewGroup row = holder.cellContainer;
- if (DEBUG) {
- Log.d(TAG, String.format("onBindViewHolder [widget#=%d, row.getChildCount=%d]",
- 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 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 void unbindViewHolder(WidgetsRowViewHolder holder) {
- int total = holder.cellContainer.getChildCount();
- for (int i = 0; i < total; i += 2) {
- WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
- widget.clear();
- }
- }
-}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
new file mode 100644
index 0000000..9562af3
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * A {@link ViewHolder} for {@link WidgetsListHeader} of an app, which renders the app icon, the app
+ * name, label and a button for showing / hiding widgets.
+ */
+public final class WidgetsListSearchHeaderHolder extends ViewHolder {
+ final WidgetsListHeader mWidgetsListHeader;
+
+ public WidgetsListSearchHeaderHolder(WidgetsListHeader view) {
+ super(view);
+
+ mWidgetsListHeader = view;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
new file mode 100644
index 0000000..b98f5e1
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.launcher3.R;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+/**
+ * Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
+ */
+public final class WidgetsListSearchHeaderViewHolderBinder implements
+ ViewHolderBinder<WidgetsListSearchHeaderEntry, WidgetsListSearchHeaderHolder> {
+ private final LayoutInflater mLayoutInflater;
+ private final OnHeaderClickListener mOnHeaderClickListener;
+ private final WidgetsListAdapter mWidgetsListAdapter;
+
+ public WidgetsListSearchHeaderViewHolderBinder(LayoutInflater layoutInflater,
+ OnHeaderClickListener onHeaderClickListener,
+ WidgetsListAdapter listAdapter) {
+ mLayoutInflater = layoutInflater;
+ mOnHeaderClickListener = onHeaderClickListener;
+ mWidgetsListAdapter = listAdapter;
+ }
+
+ @Override
+ public WidgetsListSearchHeaderHolder newViewHolder(ViewGroup parent) {
+ WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
+ R.layout.widgets_list_row_header, parent, false);
+
+ return new WidgetsListSearchHeaderHolder(header);
+ }
+
+ @Override
+ public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
+ WidgetsListSearchHeaderEntry data, int position) {
+ WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+ if (mWidgetsListAdapter.getItemCount() == 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_single_item_ripple);
+ } else if (position == 0) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
+ } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+ } else {
+ widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+ }
+ widgetsListHeader.applyFromItemInfoWithIcon(data);
+ widgetsListHeader.setExpanded(data.isWidgetListShown());
+ widgetsListHeader.setOnExpandChangeListener(isExpanded ->
+ mOnHeaderClickListener.onHeaderClicked(isExpanded,
+ new PackageUserKey(data.mPkgItem.packageName, data.mPkgItem.user)));
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
new file mode 100644
index 0000000..c1d64b1
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -0,0 +1,194 @@
+/*
+ * 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.Gravity;
+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.WidgetImageView;
+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 final WidgetsListAdapter mWidgetsListAdapter;
+ private boolean mApplyBitmapDeferred = false;
+
+ public WidgetsListTableViewHolderBinder(
+ Context context,
+ LayoutInflater layoutInflater,
+ OnClickListener iconClickListener,
+ OnLongClickListener iconLongClickListener,
+ WidgetPreviewLoader widgetPreviewLoader,
+ WidgetsListAdapter listAdapter) {
+ mLayoutInflater = layoutInflater;
+ mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+ mWidgetPreviewLoader = widgetPreviewLoader;
+ mWidgetsListAdapter = listAdapter;
+ }
+
+ /**
+ * 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,
+ int position) {
+ TableLayout table = holder.mTableContainer;
+ if (DEBUG) {
+ Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
+ entry.mWidgets.size(), table.getChildCount()));
+ }
+
+ if (position == mWidgetsListAdapter.getItemCount() - 1) {
+ table.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+ } else {
+ // WidgetsListContentEntry is never shown in position 0. There must be a header above
+ // it.
+ table.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+ }
+
+ List<ArrayList<WidgetItem>> widgetItemsTable =
+ WidgetsTableUtils.groupWidgetItemsIntoTable(entry.mWidgets, mMaxSpansPerRow);
+ recycleTableBeforeBinding(table, widgetItemsTable);
+ // 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);
+ widget.clear();
+ WidgetItem widgetItem = widgetItemsPerRow.get(j);
+ widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
+ 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());
+ tableRow.setGravity(Gravity.TOP);
+ 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.
+ WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+ preview.setOnClickListener(mIconClickListener);
+ preview.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/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
new file mode 100644
index 0000000..6569fb0
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.WidgetImageView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A {@link TableLayout} for showing recommended widgets. */
+public final class WidgetsRecommendationTableLayout extends TableLayout {
+ private static final float SCALE_DOWN_RATIO = 0.9f;
+ private final DeviceProfile mDeviceProfile;
+ private final float mWidgetCellTextViewsHeight;
+
+ private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
+ @Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
+ @Nullable private OnClickListener mWidgetCellOnClickListener;
+ @Nullable private OnTouchListener mWidgetCellOnTouchListener;
+
+ public WidgetsRecommendationTableLayout(Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+ // There are 1 row for title, 1 row for dimension and 2 rows for description.
+ mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
+ }
+
+ /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
+ public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
+ mWidgetCellOnLongClickListener = onLongClickListener;
+ }
+
+ /** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */
+ public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) {
+ mWidgetCellOnClickListener = widgetCellOnClickListener;
+ }
+
+ /** Sets a {@link android.view.View.OnTouchListener} for all widget cells in this table. */
+ public void setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener) {
+ mWidgetCellOnTouchListener = widgetCellOnTouchListener;
+ }
+
+ /**
+ * Sets a list of recommended widgets that would like to be displayed in this table within the
+ * desired {@code recommendationTableMaxHeight}.
+ *
+ * <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
+ * last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
+ * row still doesn't fit, we scale down the preview image.
+ */
+ public void setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+ float recommendationTableMaxHeight) {
+ mRecommendationTableMaxHeight = recommendationTableMaxHeight;
+ RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
+ recommendedWidgets);
+ bindData(data);
+ }
+
+ private void bindData(RecommendationTableData data) {
+ if (data.mRecommendationTable.size() == 0) {
+ setVisibility(GONE);
+ return;
+ }
+
+ removeAllViews();
+
+ for (int i = 0; i < data.mRecommendationTable.size(); i++) {
+ List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
+ TableRow tableRow = new TableRow(getContext());
+ tableRow.setGravity(Gravity.TOP);
+
+ for (WidgetItem widgetItem : widgetItems) {
+ WidgetCell widgetCell = addItemCell(tableRow);
+ widgetCell.setPreviewSize(widgetItem.spanX, widgetItem.spanY, data.mPreviewScale);
+ widgetCell.applyFromCellItem(widgetItem,
+ LauncherAppState.getInstance(getContext()).getWidgetCache());
+ widgetCell.ensurePreview();
+ }
+ addView(tableRow);
+ }
+ setVisibility(VISIBLE);
+ }
+
+ private WidgetCell addItemCell(ViewGroup parent) {
+ WidgetCell widget = (WidgetCell) LayoutInflater.from(
+ getContext()).inflate(R.layout.widget_cell, parent, false);
+
+ widget.setOnTouchListener(mWidgetCellOnTouchListener);
+ WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+ preview.setOnClickListener(mWidgetCellOnClickListener);
+ preview.setOnLongClickListener(mWidgetCellOnLongClickListener);
+ widget.setAnimatePreview(false);
+
+ parent.addView(widget);
+ return widget;
+ }
+
+ private RecommendationTableData fitRecommendedWidgetsToTableSpace(
+ float previewScale,
+ List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
+ // A naive estimation of the widgets recommendation table height without inflation.
+ float totalHeight = 0;
+ for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
+ List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
+ float rowHeight = 0;
+ for (int j = 0; j < widgetItems.size(); j++) {
+ float previewHeight = widgetItems.get(j).spanY * mDeviceProfile.allAppsCellHeightPx
+ * previewScale;
+ rowHeight = Math.max(rowHeight, previewHeight + mWidgetCellTextViewsHeight);
+ }
+ totalHeight += rowHeight;
+ }
+
+ if (totalHeight < mRecommendationTableMaxHeight) {
+ return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
+ }
+
+ if (recommendedWidgetsInTable.size() > 1) {
+ // We don't want to scale down widgets preview unless we really need to. Reduce the
+ // num of row by 1 to see if it fits.
+ return fitRecommendedWidgetsToTableSpace(
+ previewScale,
+ recommendedWidgetsInTable.subList(/* fromIndex= */0,
+ /* toIndex= */recommendedWidgetsInTable.size() - 1));
+ }
+
+ float nextPreviewScale = previewScale * SCALE_DOWN_RATIO;
+ return fitRecommendedWidgetsToTableSpace(nextPreviewScale, recommendedWidgetsInTable);
+ }
+
+ /** Data class for the widgets recommendation table and widgets preview scaling. */
+ private class RecommendationTableData {
+ private final List<ArrayList<WidgetItem>> mRecommendationTable;
+ private final float mPreviewScale;
+
+ RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
+ float previewScale) {
+ mRecommendationTable = recommendationTable;
+ mPreviewScale = previewScale;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 52e9496..b016b4f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -21,13 +21,20 @@
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.widget.TableLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
/**
* The widgets recycler view.
@@ -39,7 +46,10 @@
private final int mScrollbarTop;
private final Point mFastScrollerOffset = new Point();
+ private final int mEstimatedWidgetListHeaderHeight;
private boolean mTouchDownOnScroller;
+ private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
+ private int mLastVisibleWidgetContentTableHeight = 0;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -54,6 +64,12 @@
super(context, attrs, defStyleAttr);
mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
addOnItemTouchListener(this);
+
+ ActivityContext activity = ActivityContext.lookupContext(getContext());
+ DeviceProfile grid = activity.getDeviceProfile();
+ mEstimatedWidgetListHeaderHeight = grid.iconSizePx
+ + 2 * context.getResources().getDimensionPixelSize(
+ R.dimen.widget_list_header_view_vertical_padding);
}
@Override
@@ -122,21 +138,32 @@
View child = getChildAt(0);
int rowIndex = getChildPosition(child);
- int y = (child.getMeasuredHeight() * rowIndex);
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ if (view instanceof TableLayout) {
+ // This assumes there is ever only one content shown in this recycler view.
+ mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
+ }
+ }
+
+ int scrollPosition = getItemsHeight(rowIndex);
int offset = getLayoutManager().getDecoratedTop(child);
- return getPaddingTop() + y - offset;
+ return getPaddingTop() + scrollPosition - offset;
}
/**
- * Returns the available scroll height:
- * AvailableScrollHeight = Total height of the all items - last page height
+ * Returns the available scroll height, in pixel.
+ *
+ * <p>If the recycler view can't be scrolled, returns 0.
*/
@Override
protected int getAvailableScrollHeight() {
- View child = getChildAt(0);
- return child.getMeasuredHeight() * mAdapter.getItemCount() - getScrollbarTrackHeight()
- - mScrollbarTop;
+ // AvailableScrollHeight = Total height of the all items - first page height
+ int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+ int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ mAdapter.getItemCount());
+ int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
+ return Math.max(0, availableScrollHeight);
}
private boolean isModelNotReady() {
@@ -145,7 +172,9 @@
@Override
public int getScrollBarTop() {
- return mScrollbarTop;
+ return mHeaderViewDimensionsProvider == null
+ ? mScrollbarTop
+ : mHeaderViewDimensionsProvider.getHeaderViewHeight() + mScrollbarTop;
}
@Override
@@ -171,4 +200,47 @@
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
+
+ public void setHeaderViewDimensionsProvider(
+ HeaderViewDimensionsProvider headerViewDimensionsProvider) {
+ mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
+ }
+
+ /**
+ * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
+ * {@code untilIndex}.
+ *
+ * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+ * sum of all items' height.
+ */
+ private int getItemsHeight(int untilIndex) {
+ if (untilIndex > mAdapter.getItems().size()) {
+ untilIndex = mAdapter.getItems().size();
+ }
+ int totalItemsHeight = 0;
+ for (int i = 0; i < untilIndex; i++) {
+ WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
+ if (entry instanceof WidgetsListHeaderEntry
+ || entry instanceof WidgetsListSearchHeaderEntry) {
+ totalItemsHeight += mEstimatedWidgetListHeaderHeight;
+ } else if (entry instanceof WidgetsListContentEntry) {
+ totalItemsHeight += mLastVisibleWidgetContentTableHeight;
+ } else {
+ throw new UnsupportedOperationException("Can't estimate height for " + entry);
+ }
+ }
+ return totalItemsHeight;
+ }
+
+ /**
+ * Provides dimensions of the header view that is shown at the top of a
+ * {@link WidgetsRecyclerView}.
+ */
+ 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/picker/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
index ae94584..aef1103 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
@@ -16,6 +16,7 @@
package com.android.launcher3.widget.picker;
import android.view.ViewGroup;
+import android.widget.TableLayout;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
@@ -24,11 +25,11 @@
/** A {@link ViewHolder} for showing widgets of an app in the full widget picker. */
public final class WidgetsRowViewHolder extends ViewHolder {
- public final ViewGroup cellContainer;
+ public final TableLayout mTableContainer;
public WidgetsRowViewHolder(ViewGroup v) {
super(v);
- cellContainer = v.findViewById(R.id.widgets_cell_list);
+ mTableContainer = v.findViewById(R.id.widgets_table);
}
}
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
new file mode 100644
index 0000000..56a08b1
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.R;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * View for a search bar with an edit text with a cancel button.
+ */
+public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSearchBar {
+ private WidgetsSearchBarController mController;
+ private ExtendedEditText mEditText;
+ private ImageButton mCancelButton;
+
+ public LauncherWidgetsSearchBar(Context context) {
+ this(context, null, 0);
+ }
+
+ public LauncherWidgetsSearchBar(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LauncherWidgetsSearchBar(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void initialize(List<WidgetsListBaseEntry> allWidgets,
+ SearchModeListener searchModeListener) {
+ SearchAlgorithm<WidgetsListBaseEntry> algo =
+ new SimpleWidgetsSearchAlgorithm(new SimpleWidgetsSearchPipeline(allWidgets));
+ mController = new WidgetsSearchBarController(
+ algo, mEditText, mCancelButton, searchModeListener);
+ }
+
+ @Override
+ public void reset() {
+ mController.clearSearchResult();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mEditText = findViewById(R.id.widgets_search_bar_edit_text);
+ mCancelButton = findViewById(R.id.widgets_search_cancel_button);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mController.onDestroy();
+ }
+
+ @Override
+ public void clearSearchBarFocus() {
+ mController.clearFocus();
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SearchModeListener.java b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
new file mode 100644
index 0000000..cee7d67
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * A listener to help with widgets picker search.
+ */
+public interface SearchModeListener {
+ /**
+ * Notifies the subscriber when user enters widget picker search mode.
+ */
+ void enterSearchMode();
+
+ /**
+ * Notifies the subscriber when user exits widget picker search mode.
+ */
+ void exitSearchMode();
+
+ /**
+ * Notifies the subscriber with search results.
+ */
+ void onSearchResults(List<WidgetsListBaseEntry> entries);
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
new file mode 100644
index 0000000..15d2454
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Implementation of {@link SearchAlgorithm} that posts a task to query on the main thread.
+ */
+public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<WidgetsListBaseEntry> {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "SimpleWidgetsSearchAlgo";
+ private static final String DELIM = "\t";
+
+ private final Handler mResultHandler;
+ private final WidgetsPickerSearchPipeline mSearchPipeline;
+
+ public SimpleWidgetsSearchAlgorithm(WidgetsPickerSearchPipeline searchPipeline) {
+ mResultHandler = new Handler();
+ mSearchPipeline = searchPipeline;
+ }
+
+ @Override
+ public void doSearch(String query, SearchCallback<WidgetsListBaseEntry> callback) {
+ long startTime = System.currentTimeMillis();
+ String queryToken = query + DELIM + startTime;
+ if (DEBUG) {
+ Log.d(TAG, "doSearch queryToken:" + queryToken);
+ }
+ mSearchPipeline.query(query,
+ results -> mResultHandler.post(
+ () -> callback.onSearchResult(queryToken, new ArrayList(results))));
+ }
+
+ @Override
+ public void cancel(boolean interruptActiveRequests) {
+ if (interruptActiveRequests) {
+ mResultHandler.removeCallbacksAndMessages(/*token= */null);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java
new file mode 100644
index 0000000..5222e8e
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static com.android.launcher3.search.StringMatcherUtility.matches;
+
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of {@link WidgetsPickerSearchPipeline} that performs search by prefix matching on
+ * app names and widget labels.
+ */
+public final class SimpleWidgetsSearchPipeline implements WidgetsPickerSearchPipeline {
+
+ private final List<WidgetsListBaseEntry> mAllEntries;
+
+ public SimpleWidgetsSearchPipeline(List<WidgetsListBaseEntry> allEntries) {
+ mAllEntries = allEntries;
+ }
+
+ @Override
+ public void query(String input, Consumer<List<WidgetsListBaseEntry>> callback) {
+ ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
+ mAllEntries.stream().filter(entry -> entry instanceof WidgetsListHeaderEntry)
+ .forEach(headerEntry -> {
+ List<WidgetItem> matchedWidgetItems = filterWidgetItems(
+ input, headerEntry.mPkgItem.title.toString(), headerEntry.mWidgets);
+ if (matchedWidgetItems.size() > 0) {
+ results.add(new WidgetsListSearchHeaderEntry(headerEntry.mPkgItem,
+ headerEntry.mTitleSectionName, matchedWidgetItems));
+ results.add(new WidgetsListContentEntry(headerEntry.mPkgItem,
+ headerEntry.mTitleSectionName, matchedWidgetItems));
+ }
+ });
+ callback.accept(results);
+ }
+
+ private List<WidgetItem> filterWidgetItems(String query, String packageTitle,
+ List<WidgetItem> items) {
+ StringMatcher matcher = StringMatcher.getInstance();
+ if (matches(query, packageTitle, matcher)) {
+ return items;
+ }
+ return items.stream()
+ .filter(item -> matches(query, item.label, matcher))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java b/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
new file mode 100644
index 0000000..d12782c
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * An interface for a pipeline to handle widgets search.
+ */
+public interface WidgetsPickerSearchPipeline {
+
+ /**
+ * Performs a search query asynchronically. Invokes {@code callback} when the search is
+ * complete.
+ */
+ void query(String input, Consumer<List<WidgetsListBaseEntry>> callback);
+
+ /**
+ * Cancels any ongoing search request.
+ */
+ default void cancel() {};
+
+ /**
+ * Cleans up after search is no longer needed.
+ */
+ default void destroy() {};
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
new file mode 100644
index 0000000..3ac82c0
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * Interface for a widgets picker search bar.
+ */
+public interface WidgetsSearchBar {
+ /**
+ * Attaches a controller to the search bar which interacts with {@code searchModeListener}.
+ */
+ void initialize(List<WidgetsListBaseEntry> allWidgets, SearchModeListener searchModeListener);
+
+ /**
+ * Clears search bar.
+ */
+ void reset();
+
+ /**
+ * Clears focus from search bar.
+ */
+ void clearSearchBarFocus();
+
+ /**
+ * Sets the vertical location, in pixels, of this search bar relative to its top position.
+ */
+ void setTranslationY(float translationY);
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
new file mode 100644
index 0000000..d35a75b
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Controller for a search bar with an edit text and a cancel button.
+ */
+public class WidgetsSearchBarController implements TextWatcher,
+ SearchCallback<WidgetsListBaseEntry>, ExtendedEditText.OnBackKeyListener,
+ View.OnKeyListener {
+ private static final String TAG = "WidgetsSearchBarController";
+ private static final boolean DEBUG = false;
+
+ protected SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
+ protected ExtendedEditText mInput;
+ protected ImageButton mCancelButton;
+ protected SearchModeListener mSearchModeListener;
+ protected String mQuery;
+
+ public WidgetsSearchBarController(
+ SearchAlgorithm<WidgetsListBaseEntry> algo, ExtendedEditText editText,
+ ImageButton cancelButton, SearchModeListener searchModeListener) {
+ mSearchAlgorithm = algo;
+ mInput = editText;
+ mInput.addTextChangedListener(this);
+ mInput.setOnBackKeyListener(this);
+ mInput.setOnKeyListener(this);
+ mCancelButton = cancelButton;
+ mCancelButton.setOnClickListener(v -> clearSearchResult());
+ mSearchModeListener = searchModeListener;
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ mQuery = s.toString();
+ if (mQuery.isEmpty()) {
+ mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
+ mSearchModeListener.exitSearchMode();
+ mCancelButton.setVisibility(GONE);
+ } else {
+ mSearchAlgorithm.cancel(/* interruptActiveRequests= */ false);
+ mSearchModeListener.enterSearchMode();
+ mSearchAlgorithm.doSearch(mQuery, this);
+ mCancelButton.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onSearchResult(String query, ArrayList<WidgetsListBaseEntry> items) {
+ if (DEBUG) {
+ Log.d(TAG, "onSearchResult query: " + query + " items: " + items);
+ }
+ mSearchModeListener.onSearchResults(items);
+ }
+
+ @Override
+ public void onAppendSearchResult(String query, ArrayList<WidgetsListBaseEntry> items) {
+ // Not needed.
+ }
+
+ @Override
+ public void clearSearchResult() {
+ mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
+ mInput.getText().clear();
+ clearFocus();
+ mSearchModeListener.exitSearchMode();
+ }
+
+ /**
+ * Cleans up after search is no longer needed.
+ */
+ public void onDestroy() {
+ mSearchAlgorithm.destroy();
+ }
+
+ @Override
+ public boolean onBackKey() {
+ clearFocus();
+ return true;
+ }
+
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
+ clearFocus();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Clears focus from edit text.
+ */
+ public void clearFocus() {
+ mInput.clearFocus();
+ mInput.hideKeyboard();
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
new file mode 100644
index 0000000..edfdc65
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+/**
+ * UI helper for {@link WidgetsSearchBar}.
+ */
+public interface WidgetsSearchBarUIHelper {
+ /**
+ * Clears focus from the search bar.
+ */
+ void clearSearchBarFocus();
+}
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_build_config/BuildConfig.java b/src_build_config/com/android/launcher3/BuildConfig.java
similarity index 100%
rename from src_build_config/BuildConfig.java
rename to src_build_config/com/android/launcher3/BuildConfig.java
diff --git a/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
deleted file mode 100644
index f8a9a04..0000000
--- a/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.plugins;
-
-import android.os.Parcelable;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-import java.util.List;
-
-/**
- * Interface to provide SmartspaceTargets to BcSmartspace.
- */
-@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION)
-public interface BcSmartspaceDataPlugin extends Plugin {
- String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
- int VERSION = 1;
-
- /** Register a listener to get Smartspace data. */
- void registerListener(SmartspaceTargetListener listener);
-
- /** Unregister a listener. */
- void unregisterListener(SmartspaceTargetListener listener);
-
- /** Provides Smartspace data to registered listeners. */
- interface SmartspaceTargetListener {
- /** Each Parcelable is a SmartspaceTarget that represents a card. */
- void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
- }
-}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 73b1601..abce2a2 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -48,7 +48,7 @@
@Override
public void bindWidgets() {
final List<WidgetsListBaseEntry> widgets =
- mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
+ mBgDataModel.widgetsModel.getWidgetsListForPicker(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 30c9b5f..f82f2cc 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -5,11 +5,12 @@
import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
+import static java.util.stream.Collectors.toList;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
@@ -18,7 +19,6 @@
import com.android.launcher3.AppFilter;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -28,6 +28,7 @@
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -68,7 +69,7 @@
*
* @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
*/
- public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
+ public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
@@ -83,6 +84,22 @@
return result;
}
+ /** Returns a mapping of packages to their widgets without static shortcuts. */
+ public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
+ Map<PackageUserKey, List<WidgetItem>> packagesToWidgets = new HashMap<>();
+ mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> {
+ List<WidgetItem> widgets = widgetsAndShortcuts.stream()
+ .filter(item -> item.widgetInfo != null)
+ .collect(toList());
+ if (widgets.size() > 0) {
+ packagesToWidgets.put(
+ new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user),
+ widgets);
+ }
+ });
+ return packagesToWidgets;
+ }
+
/**
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
@@ -139,45 +156,25 @@
// 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.
+ // Clear the lists only if this is an update on all widgets and shortcuts. If packageUser
+ // isn't null, only updates the shortcuts and widgets for the app represented in
+ // packageUser.
if (packageUser == null) {
mWidgetsList.clear();
- } 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();
-
// 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/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index a4e53a1..ff28148 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -56,7 +56,7 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return ALL_APPS_HEADER | ALL_APPS_CONTENT;
+ return ALL_APPS_CONTENT;
}
@Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index da5a94f..e85e505 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -49,4 +49,11 @@
public static OverviewState newModalTaskState(int id) {
return new OverviewState(id);
}
+
+ /**
+ * New Overview substate that represents the overview in modal mode (one task shown on its own)
+ */
+ public static OverviewState newSplitSelectState(int id) {
+ return new OverviewState(id);
+ }
}
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 0000000..da55c28
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
+filegroup {
+ name: "launcher3-test-src-common",
+ srcs: ["src_common/**/*.java"],
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 43d51fc..2c7d30a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -36,6 +36,9 @@
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/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index f243f27..7f6c8f8 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -60,6 +60,17 @@
android:resource="@xml/appwidget_with_config"/>
</receiver>
+ <receiver
+ android:name="com.android.launcher3.testcomponent.AppWidgetDynamicColors"
+ android:exported="true"
+ android:label="Dynamic Colors">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_dynamic_colors"/>
+ </receiver>
+
<activity
android:name="com.android.launcher3.testcomponent.WidgetConfigActivity"
android:exported="true">
@@ -98,7 +109,7 @@
<activity
android:name="com.android.launcher3.testcomponent.TestLauncherActivity"
android:clearTaskOnLaunch="true"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout"
android:enabled="false"
android:label="Test launcher"
android:launchMode="singleTask"
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
new file mode 100644
index 0000000..3fff622
--- /dev/null
+++ b/tests/Launcher3Tests.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs Launcher3 tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="set-test-harness" value="true" />
+ <option name="run-command" value="am force-stop com.android.launcher3" />
+ <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher" />
+ <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.out_of_proc_tests" />
+ <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.tests" />
+ <option name="run-command" value="pm disable com.google.android.googlequicksearchbox" />
+
+ <option name="run-command" value="input keyevent 82" />
+ <option name="run-command" value="settings delete secure assistant" />
+ <option name="run-command" value="settings put global airplane_mode_on 1" />
+ <option name="run-command" value="am broadcast -a android.intent.action.AIRPLANE_MODE" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="Launcher3Tests.apk" />
+ <option name="test-file-name" value="Launcher3.apk" />
+ </target_preparer>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.launcher3/files" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.launcher3.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/res/layout/test_layout_appwidget_dynamic_colors.xml b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..21625c6
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="?android:attr/colorBackground"
+ android:padding="8dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation = "horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="prim"/>
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:background="@android:color/system_neutral1_500"/>
+ </LinearLayout>
+ <LinearLayout
+ android:orientation = "horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="second"/>
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:background="@android:color/system_accent1_500"/>
+ </LinearLayout>
+ <LinearLayout
+ android:orientation = "horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="neutral"/>
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:background="@android:color/system_neutral2_500"/>
+ </LinearLayout>
+
+ </LinearLayout>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_dynamic_colors.xml b/tests/res/xml/appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..f6b9a04
--- /dev/null
+++ b/tests/res/xml/appwidget_dynamic_colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="1dp"
+ android:minHeight="1dp"
+ android:updatePeriodMillis="0"
+ android:initialLayout="@layout/test_layout_appwidget_dynamic_colors"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
deleted file mode 100644
index 39709a9..0000000
--- a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps.search;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.model.data.AppInfo;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link DefaultAppSearchAlgorithm}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DefaultAppSearchAlgorithmTest {
- private static final DefaultAppSearchAlgorithm.StringMatcher MATCHER =
- DefaultAppSearchAlgorithm.StringMatcher.getInstance();
-
- @Test
- public void testMatches() {
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("white cow"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whiteCow"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whiteCOW"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecowCOW"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("white2cow"), "cow", MATCHER));
-
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitecow"), "cow", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitEcow"), "cow", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecowCow"), "cow", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecow cow"), "cow", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitecowcow"), "cow", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whit ecowcow"), "cow", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&dogs"), "dog", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&Dogs"), "dog", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&Dogs"), "&", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("2+43"), "43", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("2+43"), "3", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("Q"), "q", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo(" Q"), "q", MATCHER));
-
- // match lower case words
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("elephant"), "e", MATCHER));
-
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "电", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "电子", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "子", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "邮件", MATCHER));
-
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("Bot"), "ba", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("bot"), "ba", MATCHER));
- }
-
- @Test
- public void testMatchesVN() {
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드"), "다", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("드라이브"), "드", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷ", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("운로 드라이브"), "ㄷ", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("abc"), "åbç", MATCHER));
- assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("Alpha"), "ål", MATCHER));
-
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷㄷ", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("로드라이브"), "ㄷ", MATCHER));
- assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("abc"), "åç", MATCHER));
- }
-
- private AppInfo getInfo(String title) {
- AppInfo info = new AppInfo();
- info.title = title;
- info.componentName = new ComponentName("Test", title);
- return info;
- }
-}
diff --git a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
new file mode 100644
index 0000000..413f404
--- /dev/null
+++ b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.search;
+
+import static com.android.launcher3.search.StringMatcherUtility.matches;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link StringMatcherUtility}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StringMatcherUtilityTest {
+ private static final StringMatcher MATCHER =
+ StringMatcher.getInstance();
+
+ @Test
+ public void testMatches() {
+ assertTrue(matches("white ", "white cow", MATCHER));
+ assertTrue(matches("white c", "white cow", MATCHER));
+ assertTrue(matches("cow", "white cow", MATCHER));
+ assertTrue(matches("cow", "whiteCow", MATCHER));
+ assertTrue(matches("cow", "whiteCOW", MATCHER));
+ assertTrue(matches("cow", "whitecowCOW", MATCHER));
+ assertTrue(matches("cow", "white2cow", MATCHER));
+
+ assertFalse(matches("cow", "whitecow", MATCHER));
+ assertFalse(matches("cow", "whitEcow", MATCHER));
+
+ assertTrue(matches("cow", "whitecowCow", MATCHER));
+ assertTrue(matches("cow", "whitecow cow", MATCHER));
+ assertFalse(matches("cow", "whitecowcow", MATCHER));
+ assertFalse(matches("cow", "whit ecowcow", MATCHER));
+
+ assertTrue(matches("dog", "cats&dogs", MATCHER));
+ assertTrue(matches("dog", "cats&Dogs", MATCHER));
+ assertTrue(matches("&", "cats&Dogs", MATCHER));
+
+ assertTrue(matches("43", "2+43", MATCHER));
+ assertFalse(matches("3", "2+43", MATCHER));
+
+ assertTrue(matches("q", "Q", MATCHER));
+ assertTrue(matches("q", " Q", MATCHER));
+
+ // match lower case words
+ assertTrue(matches("e", "elephant", MATCHER));
+ assertTrue(matches("eL", "Elephant", MATCHER));
+
+ assertTrue(matches("电", "电子邮件", MATCHER));
+ assertTrue(matches("电子", "电子邮件", MATCHER));
+ assertTrue(matches("子", "电子邮件", MATCHER));
+ assertTrue(matches("邮件", "电子邮件", MATCHER));
+
+ assertFalse(matches("ba", "Bot", MATCHER));
+ assertFalse(matches("ba", "bot", MATCHER));
+ assertFalse(matches("phant", "elephant", MATCHER));
+ assertFalse(matches("elephants", "elephant", MATCHER));
+ }
+
+ @Test
+ public void testMatchesVN() {
+ assertTrue(matches("다", "다운로드", MATCHER));
+ assertTrue(matches("드", "드라이브", MATCHER));
+ assertTrue(matches("ㄷ", "다운로드 드라이브", MATCHER));
+ assertTrue(matches("ㄷ", "운로 드라이브", MATCHER));
+ assertTrue(matches("åbç", "abc", MATCHER));
+ assertTrue(matches("ål", "Alpha", MATCHER));
+
+ assertFalse(matches("ㄷㄷ", "다운로드 드라이브", MATCHER));
+ assertFalse(matches("ㄷ", "로드라이브", MATCHER));
+ assertFalse(matches("åç", "abc", MATCHER));
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
new file mode 100644
index 0000000..5fb3454
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple app widget showing a primary, secondary and neutral color.
+ */
+public class AppWidgetDynamicColors extends AppWidgetProvider {
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index b3c1240..0edfbed 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -151,23 +151,30 @@
}
public static String dumpHprofData() {
- if (sDumpWasGenerated) return "dump has already been generated by another test";
- try {
- final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath()
- + "/ActivityLeakHeapDump.hprof";
- if (TestHelpers.isInLauncherProcess()) {
- Debug.dumpHprofData(fileName);
- } else {
- final UiDevice device = UiDevice.getInstance(getInstrumentation());
- device.executeShellCommand(
- "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
+ String result;
+ if (sDumpWasGenerated) {
+ result = "dump has already been generated by another test";
+ } else {
+ try {
+ final String fileName =
+ getInstrumentation().getTargetContext().getFilesDir().getPath()
+ + "/ActivityLeakHeapDump.hprof";
+ if (TestHelpers.isInLauncherProcess()) {
+ Debug.dumpHprofData(fileName);
+ } else {
+ final UiDevice device = UiDevice.getInstance(getInstrumentation());
+ device.executeShellCommand(
+ "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
+ }
+ sDumpWasGenerated = true;
+ result = "memory dump filename: " + fileName;
+ } catch (Throwable e) {
+ Log.e(TAG, "dumpHprofData failed", e);
+ result = "failed to save memory dump";
}
- sDumpWasGenerated = true;
- return "memory dump filename: " + fileName;
- } catch (Throwable e) {
- Log.e(TAG, "dumpHprofData failed", e);
- return "failed to save memory dump";
}
+ return result
+ + ". Full list of activities: " + ACTIVITY_LEAK_TRACKER.getActivitiesList();
}
protected AbstractLauncherUiTest() {
diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
index dd216c7..2db7472 100644
--- a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
+++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
@@ -25,6 +25,7 @@
import com.android.launcher3.tapl.TestHelpers;
import java.util.WeakHashMap;
+import java.util.stream.Collectors;
public class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks {
private final WeakHashMap<Activity, Boolean> mActivities = new WeakHashMap<>();
@@ -81,4 +82,9 @@
return mActivities.size() <= 2;
}
+
+ public String getActivitiesList() {
+ return mActivities.keySet().stream().map(a -> a.getClass().getSimpleName())
+ .collect(Collectors.joining(","));
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4c9a8e7..6f47df0 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -334,20 +334,31 @@
// 1. Open all apps and wait for load complete.
// 2. Find the app and long press it to show shortcuts.
// 3. Press icon center until shortcuts appear
- final AllApps allApps = mLauncher.
- getWorkspace().
- switchToAllApps();
+ final AllApps allApps = mLauncher
+ .getWorkspace()
+ .switchToAllApps();
allApps.freeze();
try {
- final AppIconMenuItem menuItem = allApps.
- getAppIcon(APP_NAME).
- openMenu().
- getMenuItem(0);
- final String shortcutName = menuItem.getText();
- assertEquals("Wrong menu item", "Shortcut 3", shortcutName);
+ final AppIconMenu menu = allApps
+ .getAppIcon(APP_NAME)
+ .openMenu();
+ final AppIconMenuItem menuItem0 = menu.getMenuItem(0);
+ final AppIconMenuItem menuItem2 = menu.getMenuItem(2);
+
+ final AppIconMenuItem menuItem;
+
+ final String expectedShortcutName = "Shortcut 3";
+ if (menuItem0.getText().equals(expectedShortcutName)) {
+ menuItem = menuItem0;
+ } else {
+ final String shortcutName2 = menuItem2.getText();
+ assertEquals("Wrong menu item", expectedShortcutName, shortcutName2);
+ menuItem = menuItem2;
+ }
menuItem.dragToWorkspace(false, false);
- mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
+ mLauncher.getWorkspace().getWorkspaceAppIcon(expectedShortcutName)
+ .launch(getAppPackageName());
} finally {
allApps.unfreeze();
}
diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
index eceff34..083f580 100644
--- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java
+++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
@@ -24,9 +24,9 @@
import android.view.View;
import android.view.ViewGroup;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.concurrent.Callable;
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index aef26ae..8f4381b 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -35,6 +36,7 @@
import com.android.launcher3.allapps.AllAppsPagedView;
import com.android.launcher3.allapps.WorkModeSwitch;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.WorkEduView;
@@ -77,6 +79,17 @@
mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
}
+ @After
+ public void resumeAppStoreUpdate() {
+ executeOnLauncher(launcher -> {
+ if (launcher == null || launcher.getAppsView() == null) {
+ return;
+ }
+ launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+ Log.d(TestProtocol.WORK_PROFILE_REMOVED, "resuming AppStore updates");
+ });
+ }
+
@Test
public void workTabExists() {
mDevice.pressHome();
@@ -128,9 +141,13 @@
WorkEduView.KEY_WORK_EDU_STEP).remove(
WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
- waitForLauncherCondition("Work tab not setup",
- launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
- 60000);
+ waitForLauncherCondition("Work tab not setup", launcher -> {
+ if (launcher.getAppsView().getContentView() instanceof AllAppsPagedView) {
+ launcher.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+ return true;
+ }
+ return false;
+ }, LauncherInstrumentation.WAIT_TIME_MS);
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
WorkEduView workEduView = getEduView();
@@ -141,10 +158,6 @@
workEduView.findViewById(R.id.proceed).callOnClick();
});
- executeOnLauncher(launcher -> Log.d(TestProtocol.WORK_PROFILE_REMOVED,
- "work profile status (" + mProfileUserId + ") :"
- + launcher.getAppsView().isWorkTabVisible()));
-
AtomicInteger attempt = new AtomicInteger(0);
// verify work edu is seen next
waitForLauncherCondition("Launcher did not show the next edu screen", l -> {
@@ -154,11 +167,13 @@
Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work tab not setup. Skipping test");
return false;
}
- return ((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage()
- == WORK_PAGE && ((TextView) workEduView.findViewById(
- R.id.content_text)).getText().equals(
+ if (((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage()
+ != WORK_PAGE) {
+ Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work page not highlighted");
+ }
+ return ((TextView) workEduView.findViewById(R.id.content_text)).getText().equals(
l.getResources().getString(R.string.work_profile_edu_work_apps));
- }, 60000);
+ });
}
@Test
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 737f891..b421b0e 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -28,7 +28,6 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Workspace;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -39,6 +38,7 @@
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import org.junit.Before;
import org.junit.Rule;
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index f146db5..714b11b 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -23,12 +23,12 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import org.junit.Rule;
import org.junit.Test;
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index fa495f5..9c6c317 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -37,7 +37,6 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -47,6 +46,7 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import org.junit.After;
diff --git a/tests/src_common/com/android/launcher3/common/WidgetUtils.java b/tests/src_common/com/android/launcher3/common/WidgetUtils.java
index c0913bf..ffad93f 100644
--- a/tests/src_common/com/android/launcher3/common/WidgetUtils.java
+++ b/tests/src_common/com/android/launcher3/common/WidgetUtils.java
@@ -23,12 +23,12 @@
import android.content.Context;
import android.os.Bundle;
-import com.android.launcher3.LauncherAppWidgetHost;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index b6c17df..e32250e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -27,7 +27,6 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
-import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
import java.util.stream.Collectors;
@@ -108,26 +107,24 @@
"apps_list_view");
final UiObject2 searchBox = getSearchBox(allAppsContainer);
- int bottomGestureMargin = ResourceUtils.getNavbarSize(
- ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
- int deviceHeight = mLauncher.getDevice().getDisplayHeight();
- int displayBottom = deviceHeight - bottomGestureMargin;
+ int deviceHeight = mLauncher.getRealDisplaySize().y;
+ int bottomGestureStartOnScreen = mLauncher.getBottomGestureStartOnScreen();
final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
- displayBottom)) {
+ bottomGestureStartOnScreen)) {
scrollBackToBeginning();
int attempts = 0;
int scroll = getAllAppsScroll();
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
- displayBottom)) {
+ bottomGestureStartOnScreen)) {
mLauncher.scrollToLastVisibleRow(
allAppsContainer,
mLauncher.getObjectsInContainer(allAppsContainer, "icon")
.stream()
.filter(icon ->
- mLauncher.getVisibleBounds(icon).bottom
- <= displayBottom)
+ mLauncher.getVisibleBounds(icon).top
+ < bottomGestureStartOnScreen)
.collect(Collectors.toList()),
mLauncher.getVisibleBounds(searchBox).bottom
- mLauncher.getVisibleBounds(allAppsContainer).top);
diff --git a/tests/tapl/com/android/launcher3/tapl/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 0c8f610..7bfe33c 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -153,10 +153,9 @@
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;
+ public static final int WAIT_TIME_MS = 60000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static final String ANDROID_PACKAGE = "android";
@@ -365,9 +364,11 @@
if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
- if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
+ if (!mDevice.wait(Until.hasObject(By.textStartsWith("")), WAIT_TIME_MS)) {
+ return "Screen is empty";
+ }
- final String navigationModeError = getNavigationModeMismatchError();
+ final String navigationModeError = getNavigationModeMismatchError(true);
if (navigationModeError != null) return navigationModeError;
} catch (Throwable e) {
Log.w(TAG, "getSystemAnomalyMessage failed", e);
@@ -535,17 +536,28 @@
mExpectedRotation = expectedRotation;
}
- public String getNavigationModeMismatchError() {
+ public String getNavigationModeMismatchError(boolean waitForCorrectState) {
+ final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0;
final NavigationModel navigationModel = getNavigationModel();
- final boolean hasRecentsButton = hasSystemUiObject("recent_apps");
- final boolean hasHomeButton = hasSystemUiObject("home");
- if ((navigationModel == NavigationModel.THREE_BUTTON) != hasRecentsButton) {
- return "Presence of recents button doesn't match the interaction mode, mode="
- + navigationModel.name() + ", hasRecents=" + hasRecentsButton;
+
+ if (navigationModel == NavigationModel.THREE_BUTTON) {
+ if (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")), waitTime)) {
+ return "Recents button not present in 3-button mode";
+ }
+ } else {
+ if (!mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "recent_apps")), waitTime)) {
+ return "Recents button is present in non-3-button mode";
+ }
}
- if ((navigationModel != NavigationModel.ZERO_BUTTON) != hasHomeButton) {
- return "Presence of home button doesn't match the interaction mode, mode="
- + navigationModel.name() + ", hasHome=" + hasHomeButton;
+
+ if (navigationModel == NavigationModel.ZERO_BUTTON) {
+ if (!mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "home")), waitTime)) {
+ return "Home button is present in gestural mode";
+ }
+ } else {
+ if (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "home")), waitTime)) {
+ return "Home button not present in non-gestural mode";
+ }
}
return null;
}
@@ -556,7 +568,7 @@
assertEquals("Unexpected display rotation",
mExpectedRotation, mDevice.getDisplayRotation());
- final String error = getNavigationModeMismatchError();
+ final String error = getNavigationModeMismatchError(true);
assertTrue(error, error == null);
log("verifyContainerType: " + containerType);
@@ -573,11 +585,7 @@
"but the current state is not " + containerType.name())) {
switch (containerType) {
case WORKSPACE: {
- if (mDevice.isNaturalOrientation()) {
- waitForLauncherObject(APPS_RES_ID);
- } else {
- waitUntilLauncherObjectGone(APPS_RES_ID);
- }
+ waitUntilLauncherObjectGone(APPS_RES_ID);
waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
return waitForLauncherObject(WORKSPACE_RES_ID);
@@ -643,7 +651,7 @@
try {
final AccessibilityEvent event =
mInstrumentation.getUiAutomation().executeAndWaitForEvent(
- command, eventFilter, LONG_WAIT_TIME_MS);
+ command, eventFilter, WAIT_TIME_MS);
assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
final Parcelable parcelableData = event.getParcelableData();
event.recycle();
@@ -1015,16 +1023,20 @@
expectedState);
}
- int getBottomGestureSize() {
+ private int getBottomGestureSize() {
return ResourceUtils.getNavbarSize(
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
}
int getBottomGestureMarginInContainer(UiObject2 container) {
- final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize();
+ final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen();
return getVisibleBounds(container).bottom - bottomGestureStartOnScreen;
}
+ int getBottomGestureStartOnScreen() {
+ return getRealDisplaySize().y - getBottomGestureSize();
+ }
+
void clickLauncherObject(UiObject2 object) {
expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_DOWN);
expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_UP);
@@ -1202,7 +1214,7 @@
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
assertTrue("injectInputEvent failed",
- mInstrumentation.getUiAutomation().injectInputEvent(event, true));
+ mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
event.recycle();
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index f95abdb..f084913 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
-import android.graphics.Point;
import android.graphics.Rect;
import androidx.test.uiautomator.By;
@@ -27,7 +26,6 @@
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;
@@ -102,56 +100,54 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"getting widget " + labelText + " in widgets list")) {
+ final UiObject2 searchBar = findSearchBar();
final UiObject2 fullWidgetsPicker = verifyActiveContainer();
mLauncher.assertTrue("Widgets container didn't become scrollable",
fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
- final Point displaySize = mLauncher.getRealDisplaySize();
- final UiObject2 widgetsContainer = findTestAppWidgetsScrollContainer();
+ final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer(searchBar);
mLauncher.assertTrue("Can't locate widgets list for the test app: "
- + mLauncher.getLauncherPackageName(),
+ + mLauncher.getLauncherPackageName(),
widgetsContainer != null);
final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+ final BySelector previewSelector = By.res(mLauncher.getLauncherPackageName(),
+ "widget_preview");
int i = 0;
for (; ; ) {
- final Collection<UiObject2> cells = widgetsContainer.getChildren();
- mLauncher.assertTrue("Widgets doesn't have 2 rows: ", cells.size() >= 2);
- for (UiObject2 cell : cells) {
- final UiObject2 label = cell.findObject(labelSelector);
- // The logic below doesn't handle the case which a widget cell of the given
- // label is not yet visible on the horizontal scrolling container. This won't be
- // an issue once we get rid of the horizontal scrolling container.
- if (label == null) continue;
-
- final UiObject2 widget = cell;
- 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.getParent());
- 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;
}
-
- return new Widget(mLauncher, widget);
+ mLauncher.assertEquals(
+ "View is not WidgetCell",
+ "com.android.launcher3.widget.WidgetCell",
+ widget.getClassName());
+ UiObject2 preview = widget.findObject(previewSelector);
+ mLauncher.assertTrue("Can't find widget preview", preview != null);
+ Rect previewRect = new Rect(preview.getVisibleBounds());
+ boolean intersected = searchBar.getVisibleBounds().intersect(previewRect);
+ if (intersected) {
+ Rect scrollUp = new Rect(/* left= */ 0, /* top= */0, /* right*/ 0,
+ /* bottom= */ searchBar.getVisibleBounds().height());
+ mLauncher.scroll(
+ fullWidgetsPicker,
+ Direction.UP,
+ scrollUp,
+ /* steps= */ 2,
+ /* slowDown= */ true);
+ }
+ preview = widget.findObject(previewSelector);
+ return new Widget(mLauncher, preview);
}
}
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 "
@@ -161,14 +157,27 @@
}
}
+ private UiObject2 findSearchBar() {
+ final BySelector searchBarContainerSelector = By.res(mLauncher.getLauncherPackageName(),
+ "search_and_recommendations_container");
+ final BySelector searchBarSelector = By.res(mLauncher.getLauncherPackageName(),
+ "widgets_search_bar");
+ final UiObject2 searchBarContainer = mLauncher.waitForLauncherObject(
+ searchBarContainerSelector);
+ mLauncher.assertTrue("Can't find a search bar container", searchBarContainer != null);
+ UiObject2 searchBar = searchBarContainer.findObject(searchBarSelector);
+ mLauncher.assertTrue("Can't find a search bar", searchBar != null);
+ return searchBar;
+ }
+
/** Finds the widgets list of this test app from the collapsed full widgets picker. */
- private UiObject2 findTestAppWidgetsScrollContainer() {
+ private UiObject2 findTestAppWidgetsTableContainer(final UiObject2 searchBar) {
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_cell_list");
+ "widgets_table");
boolean hasHeaderExpanded = false;
for (int i = 0; i < 40; i++) {
@@ -180,6 +189,18 @@
// Look for a header that has the test app name.
UiObject2 headerTitle = fullWidgetsPicker.findObject(targetAppSelector);
if (headerTitle != null) {
+ Rect headerTitleRect = new Rect(headerTitle.getVisibleBounds());
+ boolean intersected = searchBar.getVisibleBounds().intersect(headerTitleRect);
+ if (intersected) {
+ Rect scrollUp = new Rect(/* left= */ 0, /* top= */0, /* right*/ 0,
+ /* bottom= */ searchBar.getVisibleBounds().height());
+ mLauncher.scroll(
+ fullWidgetsPicker,
+ Direction.UP,
+ scrollUp,
+ /* steps= */ 2,
+ /* slowDown= */ true);
+ }
// If we find the header and it has not been expanded, let's click it to see the
// widgets list.
if (!hasHeaderExpanded) {
@@ -196,14 +217,12 @@
// Look for a widgets list.
UiObject2 widgetsContainer = fullWidgetsPicker.findObject(widgetsContainerSelector);
if (widgetsContainer != null) {
- // Make sure the widgets list is fully visible on the screen.
- mLauncher.scrollToLastVisibleRow(fullWidgetsPicker,
- widgetsContainer.getChildren(), 0);
return widgetsContainer;
}
mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, List.of(headerTitle), 0);
} else {
- mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, header.getChildren(), 0);
+ mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, fullWidgetsPicker.getChildren(),
+ 0);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index f0e686f..d43e235 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -110,7 +110,8 @@
TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT).
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
LauncherInstrumentation.log(
- "switchToAllApps: swipeHeight = " + swipeHeight + ", slop = "
+ "switchToAllApps: deviceHeight = " + deviceHeight + ", startY = " + startY
+ + ", swipeHeight = " + swipeHeight + ", slop = "
+ mLauncher.getTouchSlop());
mLauncher.swipeToState(