Merge "Remove widget panel" into sc-v2-dev
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 85daeff..5777fb9 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -274,7 +274,7 @@
public void onOverlayScrollChanged(float progress) {
// Round out the progress to dedupe frequent, non-perceptable updates
int progressI = (int) (progress * 256);
- float progressF = progressI / 256f;
+ float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f);
if (Float.compare(mOverlayScrollProgress, progressF) == 0) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
index d14e8ef..0e12e30 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
@@ -14,35 +14,18 @@
package com.android.launcher3.uioverrides.plugins;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
import android.content.Context;
-import android.os.Looper;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.plugins.PluginInitializer;
public class PluginInitializerImpl implements PluginInitializer {
@Override
- public Looper getBgLooper() {
- return MODEL_EXECUTOR.getLooper();
- }
-
- @Override
- public void onPluginManagerInit() {
- }
-
- @Override
- public String[] getWhitelistedPlugins(Context context) {
+ public String[] getPrivilegedPlugins(Context context) {
return new String[0];
}
@Override
- public PluginEnablerImpl getPluginEnabler(Context context) {
- return new PluginEnablerImpl(context);
- }
-
- @Override
public void handleWtfs() {
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 2e422b7..f653906 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -16,6 +16,8 @@
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -24,6 +26,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.shared.plugins.PluginInstanceManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
@@ -31,6 +34,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
public class PluginManagerWrapper {
@@ -47,8 +51,14 @@
private PluginManagerWrapper(Context c) {
mContext = c;
PluginInitializerImpl pluginInitializer = new PluginInitializerImpl();
- mPluginManager = new PluginManagerImpl(c, pluginInitializer);
- mPluginEnabler = pluginInitializer.getPluginEnabler(c);
+ mPluginEnabler = new PluginEnablerImpl(c);
+ PluginInstanceManager.Factory instanceManagerFactory = new PluginInstanceManager.Factory(
+ c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR, pluginInitializer);
+
+ mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
+ pluginInitializer.isDebuggable(),
+ Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
+ new PluginPrefs(c), pluginInitializer.getPrivilegedPlugins(c));
}
public PluginEnablerImpl getPluginEnabler() {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3aed7cc..d77c8ec 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -125,6 +125,7 @@
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
@@ -1277,7 +1278,8 @@
+ " taskId: " + getTaskIdsForTaskViewId(taskViewId)[0]
+ " for taskView: " + taskView + "\n");
}
- Log.d(TASK_VIEW_ID_CRASH, sb.toString());
+ Log.d(TASK_VIEW_ID_CRASH, "taskViewCount: " + getTaskViewCount()
+ + " " + sb.toString());
}
mRunningTaskViewId = newRunningTaskView.getTaskViewId();
}
@@ -2017,6 +2019,8 @@
* Sets the running task id, cleaning up the old running task if necessary.
*/
public void setCurrentTask(int runningTaskViewId) {
+ Log.d(TASK_VIEW_ID_CRASH, "currentRunningTaskViewId: " + mRunningTaskViewId
+ + " requestedTaskViewId: " + runningTaskViewId);
if (mRunningTaskViewId == runningTaskViewId) {
return;
}
@@ -2087,23 +2091,39 @@
}
}
- /** Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
+ /**
+ * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
* layout.
* This method is used when no task dismissal has occurred.
*/
private void updateGridProperties() {
- updateGridProperties(false);
+ updateGridProperties(false, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
+ * layout.
+ *
+ * This method is used when task dismissal has occurred, but rebalance is not needed.
+ *
+ * @param isTaskDismissal indicates if update was called due to task dismissal
+ */
+ private void updateGridProperties(boolean isTaskDismissal) {
+ updateGridProperties(isTaskDismissal, Integer.MAX_VALUE);
}
/**
* 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.
*
- * @param isTaskDismissal indicates if update was called due to task dismissal
+ * @param isTaskDismissal indicates if update was called due to task dismissal
+ * @param startRebalanceAfter which view index to start rebalancing from. Use Integer.MAX_VALUE
+ * to skip rebalance
*/
- private void updateGridProperties(boolean isTaskDismissal) {
+ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAfter) {
int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
@@ -2170,8 +2190,20 @@
focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
}
int taskViewId = taskView.getTaskViewId();
- boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskViewId)
- : topRowWidth <= bottomRowWidth;
+
+ // Rebalance the grid starting after a certain index
+ boolean isTopRow;
+ if (isTaskDismissal) {
+ if (i > startRebalanceAfter) {
+ mTopRowIdSet.remove(taskViewId);
+ isTopRow = topRowWidth <= bottomRowWidth;
+ } else {
+ isTopRow = mTopRowIdSet.contains(taskViewId);
+ }
+ } else {
+ isTopRow = topRowWidth <= bottomRowWidth;
+ }
+
if (isTopRow) {
if (homeTaskView != null && nextFocusedTaskView == null) {
// TaskView will be focused when swipe up, don't count towards row width.
@@ -2677,9 +2709,44 @@
mTopRowIdSet.remove(mFocusedTaskViewId);
finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
}
- updateTaskSize(true);
+ updateTaskSize(/*isTaskDismissal=*/ true);
// Update scroll and snap to page.
updateScrollSynchronously();
+
+ int highestVisibleTaskIndex = getHighestVisibleTaskIndex();
+ if (highestVisibleTaskIndex < Integer.MAX_VALUE) {
+ TaskView taskView = getTaskViewAt(highestVisibleTaskIndex);
+
+ boolean shouldRebalance = false;
+ int screenStart = mOrientationHandler.getPrimaryScroll(
+ RecentsView.this);
+ int taskStart = mOrientationHandler.getChildStart(taskView)
+ + (int) taskView.getOffsetAdjustment(
+ /*fullscreenEnabled=*/ false,
+ /*gridEnabled=*/ true);
+
+ // Rebalance only if there is a maximum gap between the task and the
+ // screen's edge; this ensures that rebalanced tasks are outside the
+ // visible screen.
+ if (mIsRtl) {
+ shouldRebalance = taskStart <= screenStart + mPageSpacing;
+ } else {
+ int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(
+ RecentsView.this);
+ int taskSize = (int) (mOrientationHandler.getMeasuredSize(taskView)
+ * taskView.getSizeAdjustment(/*fullscreenEnabled=*/ false));
+ int taskEnd = taskStart + taskSize;
+
+ shouldRebalance = taskEnd >= screenEnd - mPageSpacing;
+ }
+
+ if (shouldRebalance) {
+ updateGridProperties(/*isTaskDismissal=*/ true,
+ highestVisibleTaskIndex);
+ updateScrollSynchronously();
+ }
+ }
+
setCurrentPage(pageToSnapTo);
dispatchScrollChanged();
}
@@ -2691,6 +2758,52 @@
return anim;
}
+ /**
+ * Returns all the tasks in the bottom row, without the focused task
+ */
+ private IntArray getBottomRowIdArray() {
+ IntArray bottomArray = new IntArray();
+ int taskViewCount = getTaskViewCount();
+ for (int i = 0; i < taskViewCount; i++) {
+ int taskViewId = getTaskViewAt(i).getTaskViewId();
+ if (!mTopRowIdSet.contains(taskViewId) && taskViewId != mFocusedTaskViewId) {
+ bottomArray.add(taskViewId);
+ }
+ }
+ return bottomArray;
+ }
+
+ /**
+ * Iterate the grid by columns instead of by TaskView index, starting after the focused task and
+ * up to the last balanced column.
+ *
+ * @return the highest visible TaskView index between both rows
+ */
+ private int getHighestVisibleTaskIndex() {
+ if (mTopRowIdSet.isEmpty()) return Integer.MAX_VALUE; // return earlier
+
+ int lastVisibleIndex = Integer.MAX_VALUE;
+ IntArray topRowIdArray = mTopRowIdSet.getArray();
+ IntArray bottomRowIdArray = getBottomRowIdArray();
+ int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size());
+
+ for (int i = 0; i < balancedColumns; i++) {
+ TaskView topTask = getTaskViewFromTaskViewId(topRowIdArray.get(i));
+
+ if (isTaskViewVisible(topTask)) {
+ TaskView bottomTask = getTaskViewFromTaskViewId(bottomRowIdArray.get(i));
+ lastVisibleIndex = Math.max(
+ indexOfChild(topTask) - mTaskViewStartIndex,
+ indexOfChild(bottomTask) - mTaskViewStartIndex
+ );
+ } else if (lastVisibleIndex < Integer.MAX_VALUE) {
+ break;
+ }
+ }
+
+ return lastVisibleIndex;
+ }
+
private void removeTaskInternal(int dismissedTaskId) {
UI_HELPER_EXECUTOR.getHandler().postDelayed(
() -> ActivityManagerWrapper.getInstance().removeTask(dismissedTaskId),
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 67840d1..bc8602c 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -49,6 +49,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Test rule that allows executing a test with Quickstep on and then Quickstep off.
@@ -182,17 +183,12 @@
};
targetContext.getMainExecutor().execute(() ->
SYS_UI_NAVIGATION_MODE.addModeChangeListener(listener));
- // b/139137636
-// latch.await(60, TimeUnit.SECONDS);
+ latch.await(60, TimeUnit.SECONDS);
targetContext.getMainExecutor().execute(() ->
SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
- Wait.atMost(() -> "Navigation mode didn't change to " + expectedMode,
- () -> currentSysUiNavigationMode() == expectedMode, WAIT_TIME_MS,
- launcher);
- // b/139137636
-// assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
-// currentSysUiNavigationMode() == expectedMode, description);
+ assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
+ currentSysUiNavigationMode() == expectedMode, description);
}
diff --git a/res/drawable-v28/widgets_bottom_sheet_background.xml b/res/drawable-v28/widgets_bottom_sheet_background.xml
deleted file mode 100644
index 7fb8681..0000000
--- a/res/drawable-v28/widgets_bottom_sheet_background.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/surface" />
- <corners
- android:topLeftRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"
- />
-</shape>
\ No newline at end of file
diff --git a/res/drawable/widgets_bottom_sheet_background.xml b/res/drawable/bg_widgets_full_sheet.xml
similarity index 65%
rename from res/drawable/widgets_bottom_sheet_background.xml
rename to res/drawable/bg_widgets_full_sheet.xml
index b877546..dfcd354 100644
--- a/res/drawable/widgets_bottom_sheet_background.xml
+++ b/res/drawable/bg_widgets_full_sheet.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
+<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,13 +13,11 @@
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="@color/surface" />
+ android:shape="rectangle" >
+ <solid android:color="?android:attr/colorBackground" />
<corners
- android:topLeftRadius="@dimen/default_dialog_corner_radius"
- android:topRightRadius="@dimen/default_dialog_corner_radius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"
- />
+ android:topLeftRadius="@dimen/dialogCornerRadius"
+ android:topRightRadius="@dimen/dialogCornerRadius" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_picker_handle.xml b/res/drawable/bg_widgets_picker_handle.xml
deleted file mode 100644
index 68681a6..0000000
--- a/res/drawable/bg_widgets_picker_handle.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <shape android:shape="rectangle">
- <solid android:color="?android:attr/colorBackground" />
- <padding android:top="16dp"/>
- </shape>
- </item>
- <item android:gravity="center">
- <shape android:shape="rectangle">
- <solid android:color="?android:attr/textColorSecondary" />
- <size android:width="48dp" android:height="2dp" />
- </shape>
- </item>
-</layer-list>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
index 3d330dc..1a2cfc6 100644
--- a/res/layout/widgets_bottom_sheet_content.xml
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -18,7 +18,7 @@
android:id="@+id/widgets_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/widgets_bottom_sheet_background"
+ android:background="@drawable/bg_rounded_corner_bottom_sheet"
android:paddingTop="16dp"
android:orientation="vertical">
<View
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 96b73c2..8afd40b 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -19,13 +19,21 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:theme="?attr/widgetsTheme" >
+ android:theme="?attr/widgetsTheme">
- <com.android.launcher3.views.TopRoundedCornerView
+ <com.android.launcher3.views.SpringRelativeLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?android:attr/colorBackground">
+ android:background="@drawable/bg_widgets_full_sheet">
+
+ <View
+ android:id="@+id/collapse_handle"
+ android:layout_width="48dp"
+ android:layout_height="2dp"
+ android:layout_marginTop="16dp"
+ android:layout_centerHorizontal="true"
+ android:background="?android:attr/textColorSecondary"/>
<TextView
android:id="@+id/no_widgets_text"
@@ -35,6 +43,7 @@
android:visibility="gone"
android:fontFamily="sans-serif-medium"
android:textSize="20sp"
+ android:layout_below="@id/search_and_recommendations_container"
tools:text="No widgets available" />
<!-- Fast scroller popup -->
@@ -57,9 +66,10 @@
android:id="@+id/search_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_below="@id/collapse_handle"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:visibility="gone"
android:clipToPadding="false" />
- </com.android.launcher3.views.TopRoundedCornerView>
+ </com.android.launcher3.views.SpringRelativeLayout>
</com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index fefad19..85f14cd 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -22,7 +22,7 @@
android:layout_height="match_parent"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToPadding="false"
- android:paddingTop="@dimen/widget_picker_view_pager_top_padding"
+ android:layout_below="@id/collapse_handle"
android:descendantFocusability="afterDescendants"
launcher:pageIndicator="@+id/tabs">
@@ -40,5 +40,84 @@
</com.android.launcher3.workprofile.PersonalWorkPagedView>
- <include layout="@layout/widgets_personal_work_tabs"/>
+ <!-- SearchAndRecommendationsView contains the tab layout as well -->
+ <com.android.launcher3.widget.picker.SearchAndRecommendationsView
+ android:id="@+id/search_and_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:layout_below="@id/collapse_handle"
+ android:paddingBottom="0dp"
+ android:orientation="vertical">
+
+ <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="24dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/widget_button_text"/>
+
+ <FrameLayout
+ android:id="@+id/search_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="0.1dp"
+ android:background="?android:attr/colorBackground"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false">
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <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="8dp"
+ android:background="@drawable/widgets_recommendation_background"
+ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+ android:visibility="gone" />
+
+ <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal"
+ android:paddingVertical="8dp"
+ android:paddingLeft="@dimen/widget_tabs_horizontal_padding"
+ android:paddingRight="@dimen/widget_tabs_horizontal_padding"
+ android:background="?android:attr/colorBackground"
+ style="@style/TextHeadline">
+
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
+ android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
+ android:layout_weight="1"
+ android:background="@drawable/all_apps_tabs_background"
+ android:text="@string/widgets_full_sheet_personal_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp"
+ style="?android:attr/borderlessButtonStyle" />
+
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
+ android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
+ android:layout_weight="1"
+ android:background="@drawable/all_apps_tabs_background"
+ android:text="@string/widgets_full_sheet_work_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp"
+ style="?android:attr/borderlessButtonStyle" />
+ </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
+
+ </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
</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
index 0f7e020..dde82ea 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -13,10 +13,54 @@
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:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
- android:clipToPadding="false" />
\ No newline at end of file
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.android.launcher3.widget.picker.WidgetsRecyclerView
+ android:id="@+id/primary_widgets_list_view"
+ android:layout_below="@id/collapse_handle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:clipToPadding="false" />
+
+ <!-- SearchAndRecommendationsView without the tab layout as well -->
+ <com.android.launcher3.widget.picker.SearchAndRecommendationsView
+ android:id="@+id/search_and_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:layout_below="@id/collapse_handle"
+ android:paddingBottom="16dp"
+ android:orientation="vertical">
+
+ <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="24dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/widget_button_text"/>
+
+ <FrameLayout
+ android:id="@+id/search_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="0.1dp"
+ android:background="?android:attr/colorBackground"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false">
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <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="8dp"
+ android:background="@drawable/widgets_recommendation_background"
+ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+ android:visibility="gone" />
+ </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
+
+</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_search_and_recommendations.xml b/res/layout/widgets_full_sheet_search_and_recommendations.xml
deleted file mode 100644
index 938c8ed..0000000
--- a/res/layout/widgets_full_sheet_search_and_recommendations.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.widget.picker.SearchAndRecommendationsView
- 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:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
- android:layout_marginBottom="16dp"
- android:orientation="vertical">
-
- <View
- android:id="@+id/collapse_handle"
- android:layout_width="match_parent"
- android:layout_height="18dp"
- android:elevation="0.1dp"
- android:background="@drawable/bg_widgets_picker_handle"/>
-
- <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="24dp"
- android:textColor="?android:attr/textColorSecondary"
- android:text="@string/widget_button_text"/>
-
- <FrameLayout
- android:id="@+id/search_bar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:elevation="0.1dp"
- android:background="?android:attr/colorBackground"
- android:paddingBottom="8dp"
- android:clipToPadding="false">
- <include layout="@layout/widgets_search_bar" />
- </FrameLayout>
-
- <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="8dp"
- android:background="@drawable/widgets_recommendation_background"
- android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
- android:visibility="gone" />
-</com.android.launcher3.widget.picker.SearchAndRecommendationsView>
diff --git a/res/layout/widgets_personal_work_tabs.xml b/res/layout/widgets_personal_work_tabs.xml
deleted file mode 100644
index 532c422..0000000
--- a/res/layout/widgets_personal_work_tabs.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
--->
-
-<com.android.launcher3.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_pill_height"
- android:gravity="center_horizontal"
- android:orientation="horizontal"
- android:layout_marginHorizontal="@dimen/widget_tabs_horizontal_margin"
- style="@style/TextHeadline">
-
- <Button
- android:id="@+id/tab_personal"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
- android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
- android:layout_weight="1"
- android:background="@drawable/all_apps_tabs_background"
- android:text="@string/widgets_full_sheet_personal_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp"
- style="?android:attr/borderlessButtonStyle" />
-
- <Button
- android:id="@+id/tab_work"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
- android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
- android:layout_weight="1"
- android:background="@drawable/all_apps_tabs_background"
- android:text="@string/widgets_full_sheet_work_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp"
- style="?android:attr/borderlessButtonStyle" />
-</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index da37250..8457bd8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -143,8 +143,7 @@
<dimen name="widget_cell_font_size">14sp</dimen>
<dimen name="widget_tabs_button_horizontal_padding">4dp</dimen>
- <dimen name="widget_tabs_horizontal_margin">32dp</dimen>
- <dimen name="widget_apps_header_pill_height">48dp</dimen>
+ <dimen name="widget_tabs_horizontal_padding">16dp</dimen>
<dimen name="widget_apps_tabs_vertical_padding">6dp</dimen>
<dimen name="recommended_widgets_table_vertical_padding">8dp</dimen>
@@ -178,8 +177,6 @@
<dimen name="widget_picker_education_tip_max_width">308dp</dimen>
<dimen name="widget_picker_education_tip_min_margin">4dp</dimen>
- <dimen name="widget_picker_view_pager_top_padding">10dp</dimen>
-
<!-- Padding applied to shortcut previews -->
<dimen name="shortcut_preview_padding_left">0dp</dimen>
<dimen name="shortcut_preview_padding_right">0dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 0e2dff0..ebc4075 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -17,6 +17,7 @@
<resources>
<item type="id" name="apps_list_view_work" />
<item type="id" name="tag_widget_entry" />
+ <item type="id" name="view_type_widgets_space" />
<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" />
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 c730fc0..fb44ca1 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -82,7 +82,7 @@
mTestProfile.numColumns = 5;
mUserHandle = Process.myUserHandle();
mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
- mIconCache, null, null);
+ mIconCache, () -> 0, null, null);
mAdapter.registerAdapterDataObserver(mListener);
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
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 81b0c5f..b7d7788 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -41,7 +41,6 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -79,8 +78,6 @@
@Mock
private DeviceProfile mDeviceProfile;
@Mock
- private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- @Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
@@ -99,18 +96,10 @@
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
-
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
LayoutInflater.from(mTestActivity),
mOnHeaderClickListener,
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
+ new WidgetsListDrawableFactory(mTestActivity));
}
@After
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index a0ba7c3..2b4cea0 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -41,7 +41,6 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
@@ -79,8 +78,6 @@
@Mock
private DeviceProfile mDeviceProfile;
@Mock
- private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- @Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
@@ -99,18 +96,10 @@
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
-
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
LayoutInflater.from(mTestActivity),
mOnHeaderClickListener,
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
+ new WidgetsListDrawableFactory(mTestActivity));
}
@After
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 8f9d132..9f66fb7 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -107,19 +107,12 @@
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListTableViewHolderBinder(
LayoutInflater.from(mTestActivity),
mOnIconClickListener,
mOnLongClickListener,
new CachingWidgetPreviewLoader(mWidgetPreviewLoader),
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
+ new WidgetsListDrawableFactory(mTestActivity));
}
@After
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
index 5b8d5bc..6215827 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -17,8 +17,12 @@
import android.view.ViewGroup;
+import androidx.annotation.IntDef;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Creates and populates views with data
*
@@ -26,6 +30,15 @@
* @param <V> A subclass of {@link ViewHolder} which holds references to views.
*/
public interface ViewHolderBinder<T, V extends ViewHolder> {
+
+ int POSITION_DEFAULT = 0;
+ int POSITION_FIRST = 1 << 0;
+ int POSITION_LAST = 1 << 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {POSITION_DEFAULT, POSITION_FIRST, POSITION_LAST}, flag = true)
+ @interface ListPosition {}
+
/**
* Creates a new view, and attach it to the parent {@link ViewGroup}. Then, populates UI
* references in a {@link ViewHolder}.
@@ -33,7 +46,7 @@
V newViewHolder(ViewGroup parent);
/** Populate UI references in {@link ViewHolder} with data. */
- void bindViewHolder(V viewHolder, T data, int position);
+ void bindViewHolder(V viewHolder, T data, @ListPosition int position);
/**
* Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 92ca8a1..8ac40b8 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -28,6 +28,7 @@
import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
@@ -68,7 +69,7 @@
protected final SingleAxisSwipeDetector mSwipeDetector;
protected final ObjectAnimator mOpenCloseAnimator;
- protected View mContent;
+ protected ViewGroup mContent;
protected final View mColorScrim;
protected Interpolator mScrollInterpolator;
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 1c2534d..2b0f707 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -40,7 +40,6 @@
import android.view.WindowInsets;
import android.widget.TextView;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.recyclerview.widget.RecyclerView;
@@ -131,7 +130,6 @@
protected BaseRecyclerView mRv;
private RecyclerView.OnScrollListener mOnScrollListener;
- @Nullable private OnFastScrollChangeListener mOnFastScrollChangeListener;
private int mDownX;
private int mDownY;
@@ -208,7 +206,6 @@
int rvCurrentOffsetY = mRv.getCurrentScrollY();
if (mRvOffsetY != rvCurrentOffsetY) {
mRvOffsetY = mRv.getCurrentScrollY();
- notifyScrollChanged();
}
return;
}
@@ -216,7 +213,6 @@
mThumbOffsetY = y;
invalidate();
mRvOffsetY = mRv.getCurrentScrollY();
- notifyScrollChanged();
}
public int getThumbOffsetY() {
@@ -461,23 +457,4 @@
public void setIsRecyclerViewFirstChildInParent(boolean isRecyclerViewFirstChildInParent) {
mIsRecyclerViewFirstChildInParent = isRecyclerViewFirstChildInParent;
}
-
- public void setOnFastScrollChangeListener(
- @Nullable OnFastScrollChangeListener onFastScrollChangeListener) {
- mOnFastScrollChangeListener = onFastScrollChangeListener;
- }
-
- private void notifyScrollChanged() {
- if (mOnFastScrollChangeListener != null) {
- mOnFastScrollChangeListener.onScrollChanged();
- }
- }
-
- /**
- * A callback that is invoked when there is a scroll change in {@link RecyclerViewFastScroller}.
- */
- public interface OnFastScrollChangeListener {
- /** Called when the recycler view scroll has changed. */
- void onScrollChanged();
- }
}
diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java
deleted file mode 100644
index 92cce92..0000000
--- a/src/com/android/launcher3/views/TopRoundedCornerView.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-
-import com.android.launcher3.util.Themes;
-
-/**
- * View with top rounded corners.
- */
-public class TopRoundedCornerView extends SpringRelativeLayout {
-
- private final RectF mRect = new RectF();
- private final Path mClipPath = new Path();
- private float[] mRadii;
-
- public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- float radius = Themes.getDialogCornerRadius(context);
- mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0};
- }
-
- public TopRoundedCornerView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.save();
- canvas.clipPath(mClipPath);
- super.draw(canvas);
- canvas.restore();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- mClipPath.reset();
- mClipPath.addRoundRect(mRect, mRadii, Path.Direction.CW);
- }
-}
diff --git a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
new file mode 100644
index 0000000..e62425f
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
@@ -0,0 +1,37 @@
+/*
+ * 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.data.PackageItemInfo;
+
+import java.util.Collections;
+
+/**
+ * Entry representing the top empty space
+ */
+public class WidgetListSpaceEntry extends WidgetsListBaseEntry {
+
+ public WidgetListSpaceEntry() {
+ super(new PackageItemInfo(""), "", Collections.EMPTY_LIST);
+ mPkgItem.title = "";
+ }
+
+ @Override
+ public int getRank() {
+ return RANK_WIDGETS_TOP_SPACE;
+ }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index abc79ff..1d1c9dc 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -73,11 +73,13 @@
}
@Retention(SOURCE)
- @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT})
+ @IntDef({RANK_WIDGETS_TOP_SPACE, 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_SEARCH_HEADER = 2;
- public static final int RANK_WIDGETS_LIST_CONTENT = 3;
+ public static final int RANK_WIDGETS_TOP_SPACE = 1;
+ public static final int RANK_WIDGETS_LIST_HEADER = 2;
+ public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 3;
+ public static final int RANK_WIDGETS_LIST_CONTENT = 4;
}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index 6643779..716dcf3 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -15,297 +15,148 @@
*/
package com.android.launcher3.widget.picker;
-import android.animation.ValueAnimator;
-import android.graphics.Point;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.widget.RelativeLayout;
+import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.widget.picker.WidgetsFullSheet.SearchAndRecommendationViewHolder;
-import com.android.launcher3.workprofile.PersonalWorkPagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
/**
* A controller which measures & updates {@link WidgetsFullSheet}'s views padding, margin and
* vertical displacement upon scrolling.
*/
final class SearchAndRecommendationsScrollController implements
- RecyclerViewFastScroller.OnFastScrollChangeListener, ValueAnimator.AnimatorUpdateListener {
- private final boolean mHasWorkProfile;
- private final SearchAndRecommendationViewHolder mViewHolder;
- private final View mSearchAndRecommendationViewParent;
- private final WidgetsRecyclerView mPrimaryRecyclerView;
- private final WidgetsRecyclerView mSearchRecyclerView;
- private final TextView mNoWidgetsView;
- private final int mTabsHeight;
- private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
- private final Point mTempOffset = new Point();
- private int mBottomInset;
+ RecyclerView.OnChildAttachStateChangeListener {
- // 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 static final FloatProperty<SearchAndRecommendationsScrollController> SCROLL_OFFSET =
+ new FloatProperty<SearchAndRecommendationsScrollController>("scrollAnimOffset") {
+ @Override
+ public void setValue(SearchAndRecommendationsScrollController controller, float offset) {
+ controller.mScrollOffset = offset;
+ controller.updateHeaderScroll();
+ }
+
+ @Override
+ public Float get(SearchAndRecommendationsScrollController controller) {
+ return controller.mScrollOffset;
+ }
+ };
+
+ private static final MotionEventProxyMethod INTERCEPT_PROXY = ViewGroup::onInterceptTouchEvent;
+ private static final MotionEventProxyMethod TOUCH_PROXY = ViewGroup::onTouchEvent;
+
+ final SearchAndRecommendationsView mContainer;
+ final View mSearchBarContainer;
+ final WidgetsSearchBar mSearchBar;
+ final TextView mHeaderTitle;
+ final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
+ @Nullable final View mTabBar;
private WidgetsRecyclerView mCurrentRecyclerView;
- private int mCurrentRecyclerViewScrollY = 0;
+ private EmptySpaceView mCurrentEmptySpaceView;
- private OnContentChangeListener mOnContentChangeListener = () -> onScrollChanged();
-
- /**
- * 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;
+ private float mLastScroll = 0;
+ private float mScrollOffset = 0;
+ private Animator mOffsetAnimator;
private boolean mShouldForwardToRecyclerView = false;
+ private int mHeaderHeight;
+
SearchAndRecommendationsScrollController(
- boolean hasWorkProfile,
- int tabsHeight,
- SearchAndRecommendationViewHolder viewHolder,
- WidgetsRecyclerView primaryRecyclerView,
- @Nullable WidgetsRecyclerView workRecyclerView,
- WidgetsRecyclerView searchRecyclerView,
- @Nullable View personalWorkTabsView,
- @Nullable PersonalWorkPagedView primaryWorkViewPager,
- TextView noWidgetsView) {
- mHasWorkProfile = hasWorkProfile;
- mViewHolder = viewHolder;
- mViewHolder.mContainer.setSearchAndRecommendationScrollController(this);
- mSearchAndRecommendationViewParent = (View) mViewHolder.mContainer.getParent();
- mPrimaryRecyclerView = primaryRecyclerView;
- mWorkRecyclerView = workRecyclerView;
- mSearchRecyclerView = searchRecyclerView;
- mPrimaryWorkTabsView = personalWorkTabsView;
- mPrimaryWorkViewPager = primaryWorkViewPager;
- mTabsHeight = tabsHeight;
- mNoWidgetsView = noWidgetsView;
- setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false);
+ SearchAndRecommendationsView searchAndRecommendationContainer) {
+ mContainer = searchAndRecommendationContainer;
+ mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
+ mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
+ mHeaderTitle = mContainer.findViewById(R.id.title);
+ mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
+ mTabBar = mContainer.findViewById(R.id.tabs);
+
+ mContainer.setSearchAndRecommendationScrollController(this);
}
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
- setCurrentRecyclerView(currentRecyclerView, /* animateReset= */ true);
- }
-
- /** Sets the current active {@link WidgetsRecyclerView}. */
- private void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView,
- boolean animateReset) {
- if (mCurrentRecyclerView == currentRecyclerView) {
- return;
- }
+ boolean animateReset = mCurrentRecyclerView != null;
if (mCurrentRecyclerView != null) {
- mCurrentRecyclerView.setOnContentChangeListener(null);
+ mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
}
mCurrentRecyclerView = currentRecyclerView;
- mCurrentRecyclerView.setOnContentChangeListener(mOnContentChangeListener);
+ mCurrentRecyclerView.addOnChildAttachStateChangeListener(this);
+ findCurrentEmptyView();
reset(animateReset);
}
- /**
- * Updates padding of {@link WidgetsFullSheet} contents to include {@code bottomInset} wherever
- * necessary.
- */
- public boolean updateBottomInset(int bottomInset) {
- mBottomInset = bottomInset;
- return updateMarginAndPadding();
+ public int getHeaderHeight() {
+ return mHeaderHeight;
+ }
+
+ private void updateHeaderScroll() {
+ mLastScroll = getCurrentScroll();
+ mHeaderTitle.setTranslationY(mLastScroll);
+ mRecommendedWidgetsTable.setTranslationY(mLastScroll);
+
+ float searchYDisplacement = Math.max(mLastScroll, -mSearchBarContainer.getTop());
+ mSearchBarContainer.setTranslationY(searchYDisplacement);
+
+ if (mTabBar != null) {
+ float tabsDisplacement = Math.max(mLastScroll, -mTabBar.getTop()
+ + mSearchBarContainer.getHeight());
+ mTabBar.setTranslationY(tabsDisplacement);
+ }
+ }
+
+ private float getCurrentScroll() {
+ return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY());
}
/**
- * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
+ * Updates the scrollable header height
*
- * @return {@code true} if margins or/and padding of views in the search and recommendations
- * container have been updated.
+ * @return {@code true} if the header height or dependent property changed.
*/
- public boolean updateMarginAndPadding() {
- boolean hasMarginOrPaddingUpdated = false;
- mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
- mCollapsibleHeightForRecommendation =
- measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
- + measureHeightWithVerticalMargins((View) mViewHolder.mSearchBarContainer)
- + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
+ public boolean updateHeaderHeight() {
+ boolean hasSizeUpdated = false;
- int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
- int noWidgetsViewHeight = topContainerHeight - mBottomInset;
-
- 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;
-
- if (mPrimaryWorkTabsView.getVisibility() == View.VISIBLE) {
- noWidgetsViewHeight += mTabsHeight;
- }
-
- 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;
- }
- if (mNoWidgetsView.getPaddingTop() != noWidgetsViewHeight) {
- mNoWidgetsView.setPadding(
- mNoWidgetsView.getPaddingLeft(),
- noWidgetsViewHeight,
- mNoWidgetsView.getPaddingRight(),
- mNoWidgetsView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- return hasMarginOrPaddingUpdated;
- }
-
- @Override
- public void onScrollChanged() {
- int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
- if (recyclerViewYOffset < 0) return;
- mCurrentRecyclerViewScrollY = recyclerViewYOffset;
- if (mAnimator.isStarted()) {
- mAnimator.cancel();
- }
- applyVerticalTransition();
- }
-
- /**
- * Changes the displacement of collapsible views (e.g. title & widget recommendations) and fixed
- * views (e.g. recycler views, tabs) upon scrolling / content changes in the recycler view.
- */
- private void applyVerticalTransition() {
- if (mCollapsibleHeightForRecommendation > 0) {
- int yDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForRecommendation);
- mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
- mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
+ int headerHeight = mContainer.getMeasuredHeight();
+ if (headerHeight != mHeaderHeight) {
+ mHeaderHeight = headerHeight;
+ hasSizeUpdated = true;
}
- if (mCollapsibleHeightForSearch > 0) {
- int searchYDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForSearch);
- mViewHolder.mSearchBarContainer.setTranslationY(searchYDisplacement);
+ if (mCurrentEmptySpaceView != null
+ && mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) {
+ hasSizeUpdated = true;
}
-
- if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
- int yDisplacementForTabs = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForTabs);
- mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
- }
+ return hasSizeUpdated;
}
/** Resets any previous view translation. */
public void reset(boolean animate) {
- if (mCurrentRecyclerViewScrollY == 0) {
- return;
- }
- if (mAnimator.isStarted()) {
- mAnimator.cancel();
+ if (mOffsetAnimator != null) {
+ mOffsetAnimator.cancel();
+ mOffsetAnimator = null;
}
- if (animate) {
- mAnimator.setIntValues(mCurrentRecyclerViewScrollY, 0);
- mAnimator.addUpdateListener(this);
- mAnimator.setDuration(300);
- mAnimator.start();
+ mScrollOffset = 0;
+ if (!animate) {
+ updateHeaderScroll();
} else {
- mCurrentRecyclerViewScrollY = 0;
- applyVerticalTransition();
+ float startValue = mLastScroll - getCurrentScroll();
+ mOffsetAnimator = ObjectAnimator.ofFloat(this, SCROLL_OFFSET, startValue, 0);
+ mOffsetAnimator.addListener(forEndCallback(() -> mOffsetAnimator = null));
+ mOffsetAnimator.start();
}
}
@@ -313,61 +164,60 @@
* Returns {@code true} if a touch event should be intercepted by this controller.
*/
public boolean onInterceptTouchEvent(MotionEvent event) {
- calculateMotionEventOffset(mTempOffset);
- event.offsetLocation(mTempOffset.x, mTempOffset.y);
- try {
- mShouldForwardToRecyclerView = mCurrentRecyclerView.onInterceptTouchEvent(event);
- return mShouldForwardToRecyclerView;
- } finally {
- event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
- }
+ return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY));
}
/**
* Returns {@code true} if this controller has intercepted and consumed a touch event.
*/
public boolean onTouchEvent(MotionEvent event) {
- if (mShouldForwardToRecyclerView) {
- calculateMotionEventOffset(mTempOffset);
- event.offsetLocation(mTempOffset.x, mTempOffset.y);
- try {
- return mCurrentRecyclerView.onTouchEvent(event);
- } finally {
- event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
- }
- }
- return false;
+ return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY);
}
- private void calculateMotionEventOffset(Point p) {
- p.x = mViewHolder.mContainer.getLeft() - mCurrentRecyclerView.getLeft()
- - mSearchAndRecommendationViewParent.getLeft();
- p.y = mViewHolder.mContainer.getTop() - mCurrentRecyclerView.getTop()
- - mSearchAndRecommendationViewParent.getTop();
- }
-
- /** 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;
+ private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) {
+ float dx = mCurrentRecyclerView.getLeft() - mContainer.getLeft();
+ float dy = mCurrentRecyclerView.getTop() - mContainer.getTop();
+ event.offsetLocation(dx, dy);
+ try {
+ return method.proxyEvent(mCurrentRecyclerView, event);
+ } finally {
+ event.offsetLocation(-dx, -dy);
}
- MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
- return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
- + marginLayoutParams.topMargin;
}
@Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mCurrentRecyclerViewScrollY = (Integer) animation.getAnimatedValue();
- applyVerticalTransition();
+ public void onChildViewAttachedToWindow(@NonNull View view) {
+ if (view instanceof EmptySpaceView) {
+ findCurrentEmptyView();
+ }
}
- /**
- * A listener to be notified when there is a content change in the recycler view that may affect
- * the relative position of the search and recommendation container.
- */
- public interface OnContentChangeListener {
- /** Notifies a content change in the recycler view. */
- void onContentChanged();
+ @Override
+ public void onChildViewDetachedFromWindow(@NonNull View view) {
+ if (view == mCurrentEmptySpaceView) {
+ findCurrentEmptyView();
+ }
+ }
+
+ private void findCurrentEmptyView() {
+ if (mCurrentEmptySpaceView != null) {
+ mCurrentEmptySpaceView.setOnYChangeCallback(null);
+ mCurrentEmptySpaceView = null;
+ }
+ int childCount = mCurrentRecyclerView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mCurrentRecyclerView.getChildAt(i);
+ if (view instanceof EmptySpaceView) {
+ mCurrentEmptySpaceView = (EmptySpaceView) view;
+ mCurrentEmptySpaceView.setFixedHeight(getHeaderHeight());
+ mCurrentEmptySpaceView.setOnYChangeCallback(this::updateHeaderScroll);
+ return;
+ }
+ }
+ }
+
+ private interface MotionEventProxyMethod {
+
+ boolean proxyEvent(ViewGroup view, MotionEvent event);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index be83f9a..9dbfa87 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -57,13 +57,12 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.views.TopRoundedCornerView;
+import com.android.launcher3.views.SpringRelativeLayout;
import com.android.launcher3.views.WidgetsEduView;
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.util.WidgetsTableUtils;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -79,7 +78,6 @@
public class WidgetsFullSheet extends BaseWidgetSheet
implements ProviderChangedListener, OnActivePageChangedListener,
WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
- private static final String TAG = WidgetsFullSheet.class.getSimpleName();
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
@@ -149,8 +147,6 @@
};
private final int mTabsHeight;
- private final int mViewPagerTopPadding;
- private final int mSearchAndRecommendationContainerBottomMargin;
private final int mWidgetSheetContentHorizontalPadding;
@Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
@@ -158,10 +154,8 @@
private boolean mIsInSearchMode;
private boolean mIsNoWidgetsViewNeeded;
private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
- private View mTabsView;
private TextView mNoWidgetsView;
- private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
- private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
+ private SearchAndRecommendationsScrollController mSearchScrollController;
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -174,14 +168,6 @@
mTabsHeight = mHasWorkProfile
? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
: 0;
- mViewPagerTopPadding = mHasWorkProfile
- ? getContext().getResources()
- .getDimensionPixelSize(R.dimen.widget_picker_view_pager_top_padding)
- : 0;
- mSearchAndRecommendationContainerBottomMargin = resources.getDimensionPixelSize(
- mHasWorkProfile
- ? R.dimen.search_and_recommended_widgets_container_small_bottom_margin
- : R.dimen.search_and_recommended_widgets_container_bottom_margin);
mWidgetSheetContentHorizontalPadding = 2 * resources.getDimensionPixelSize(
R.dimen.widget_cell_horizontal_padding);
}
@@ -194,12 +180,11 @@
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.container);
- TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
: R.layout.widgets_full_sheet_recyclerview;
- layoutInflater.inflate(contentLayoutRes, springLayout, true);
+ layoutInflater.inflate(contentLayoutRes, mContent, true);
RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
@@ -209,7 +194,6 @@
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)
@@ -220,33 +204,18 @@
mViewPager = null;
}
- layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
- true);
mNoWidgetsView = findViewById(R.id.no_widgets_text);
- mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
+ mSearchScrollController = new SearchAndRecommendationsScrollController(
findViewById(R.id.search_and_recommendations_container));
- TopRoundedCornerView.LayoutParams layoutParams =
- (TopRoundedCornerView.LayoutParams)
- mSearchAndRecommendationViewHolder.mContainer.getLayoutParams();
- layoutParams.bottomMargin = mSearchAndRecommendationContainerBottomMargin;
- mSearchAndRecommendationViewHolder.mContainer.setLayoutParams(layoutParams);
- 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,
- mNoWidgetsView);
- fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
-
+ mSearchScrollController.setCurrentRecyclerView(
+ findViewById(R.id.primary_widgets_list_view));
+ mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
+ mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
onRecommendedWidgetsBound();
onWidgetsBound();
- mSearchAndRecommendationViewHolder.mSearchBar.initialize(
+ mSearchScrollController.mSearchBar.initialize(
mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
setUpEducationViewsIfNeeded();
@@ -270,12 +239,13 @@
reset();
resetExpandedHeaders();
mCurrentWidgetsRecyclerView = recyclerView;
- mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
+ mSearchScrollController.setCurrentRecyclerView(recyclerView);
}
}
private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
- boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
+ // The first item is always an empty space entry. Look for any more items.
+ boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
mNoWidgetsView.setText(
@@ -291,7 +261,7 @@
mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
}
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
- mSearchAndRecommendationsScrollController.reset(/* animate= */ true);
+ mSearchScrollController.reset(/* animate= */ true);
}
@VisibleForTesting
@@ -340,7 +310,8 @@
if (mHasWorkProfile) {
setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
}
- mSearchAndRecommendationsScrollController.updateBottomInset(insets.bottom);
+ ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = insets.bottom;
+
if (insets.bottom > 0) {
setupNavBarColor();
} else {
@@ -360,7 +331,7 @@
@Override
protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
- setContentViewChildHorizontalMargin(mSearchAndRecommendationViewHolder.mContainer,
+ setContentViewChildHorizontalMargin(mSearchScrollController.mContainer,
contentHorizontalMarginInPx);
if (mViewPager == null) {
setContentViewChildHorizontalMargin(
@@ -385,14 +356,14 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
if (updateMaxSpansPerRow()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@@ -455,7 +426,7 @@
if (mHasWorkProfile) {
mViewPager.setVisibility(VISIBLE);
- mTabsView.setVisibility(VISIBLE);
+ mSearchScrollController.mTabBar.setVisibility(VISIBLE);
AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
onActivePageChanged(mViewPager.getCurrentPage());
@@ -465,9 +436,9 @@
// Update recommended widgets section so that it occupies appropriate space on screen to
// leave enough space for presence/absence of mNoWidgetsView.
boolean isNoWidgetsViewNeeded =
- mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.getItemCount() == 0
+ !mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.hasVisibleEntries()
|| (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK)
- .mWidgetsListAdapter.getItemCount() == 0);
+ .mWidgetsListAdapter.hasVisibleEntries());
if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) {
mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded;
onRecommendedWidgetsBound();
@@ -491,8 +462,6 @@
mViewPager.snapToPage(AdapterHolder.PRIMARY);
}
attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
-
- mSearchAndRecommendationsScrollController.updateMarginAndPadding();
}
@Override
@@ -505,10 +474,10 @@
private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
mIsInSearchMode = isInSearchMode;
if (isInSearchMode) {
- mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.setVisibility(GONE);
+ mSearchScrollController.mRecommendedWidgetsTable.setVisibility(GONE);
if (mHasWorkProfile) {
mViewPager.setVisibility(GONE);
- mTabsView.setVisibility(GONE);
+ mSearchScrollController.mTabBar.setVisibility(GONE);
} else {
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.setVisibility(GONE);
}
@@ -536,8 +505,7 @@
}
List<WidgetItem> recommendedWidgets =
mActivityContext.getPopupDataProvider().getRecommendedWidgets();
- WidgetsRecommendationTableLayout table =
- mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
+ WidgetsRecommendationTableLayout table = mSearchScrollController.mRecommendedWidgetsTable;
if (recommendedWidgets.size() > 0) {
float noWidgetsViewHeight = 0;
if (mIsNoWidgetsViewNeeded) {
@@ -554,7 +522,7 @@
makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
MeasureSpec.EXACTLY));
float maxTableHeight = (mContent.getMeasuredHeight()
- - mTabsHeight - mViewPagerTopPadding - getHeaderViewHeight()
+ - mTabsHeight - getHeaderViewHeight()
- noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
@@ -617,10 +585,10 @@
mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
}
- if (mSearchAndRecommendationViewHolder.mSearchBar.isSearchBarFocused()
+ if (mSearchScrollController.mSearchBar.isSearchBarFocused()
&& !getPopupContainer().isEventOverView(
- mSearchAndRecommendationViewHolder.mSearchBarContainer, ev)) {
- mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
+ mSearchScrollController.mSearchBarContainer, ev)) {
+ mSearchScrollController.mSearchBar.clearSearchBarFocus();
}
}
return super.onControllerInterceptTouchEvent(ev);
@@ -661,10 +629,8 @@
@Override
public int getHeaderViewHeight() {
- return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
- + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(
- (View) mSearchAndRecommendationViewHolder.mSearchBarContainer);
+ return measureHeightWithVerticalMargins(mSearchScrollController.mHeaderTitle)
+ + measureHeightWithVerticalMargins(mSearchScrollController.mSearchBarContainer);
}
/** private the height, in pixel, + the vertical margins of a given view. */
@@ -681,14 +647,14 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mIsInSearchMode) {
- mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ mSearchScrollController.mSearchBar.reset();
}
}
@Override
public boolean onBackPressed() {
if (mIsInSearchMode) {
- mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ mSearchScrollController.mSearchBar.reset();
return true;
}
return super.onBackPressed();
@@ -701,11 +667,10 @@
}
@Nullable private View getViewToShowEducationTip() {
- if (mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getVisibility() == VISIBLE
- && mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getChildCount() > 0
- ) {
- return ((ViewGroup) mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
- .getChildAt(0)).getChildAt(0);
+ if (mSearchScrollController.mRecommendedWidgetsTable.getVisibility() == VISIBLE
+ && mSearchScrollController.mRecommendedWidgetsTable.getChildCount() > 0) {
+ return ((ViewGroup) mSearchScrollController.mRecommendedWidgetsTable.getChildAt(0))
+ .getChildAt(0);
}
AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
@@ -782,6 +747,7 @@
LayoutInflater.from(context),
apps.getWidgetCache(),
apps.getIconCache(),
+ this::getEmptySpaceHeight,
/* iconClickListener= */ WidgetsFullSheet.this,
/* iconLongClickListener= */ WidgetsFullSheet.this);
mWidgetsListAdapter.setHasStableIds(true);
@@ -801,13 +767,17 @@
mWidgetsListItemAnimator.setSupportsChangeAnimations(false);
}
+ private int getEmptySpaceHeight() {
+ return mSearchScrollController.getHeaderHeight();
+ }
+
void setup(WidgetsRecyclerView recyclerView) {
mWidgetsRecyclerView = recyclerView;
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
mWidgetsRecyclerView.setEdgeEffectFactory(
- ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
+ ((SpringRelativeLayout) mContent).createEdgeEffectFactory());
// Recycler view binds to fast scroller when it is attached to screen. Make sure
// search recycler view is bound to fast scroller if user is in search mode at the time
// of attachment.
@@ -818,29 +788,4 @@
mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
}
}
-
- final class SearchAndRecommendationViewHolder {
- final SearchAndRecommendationsView mContainer;
- final View mCollapseHandle;
- final View mSearchBarContainer;
- final WidgetsSearchBar mSearchBar;
- final TextView mHeaderTitle;
- final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
-
- SearchAndRecommendationViewHolder(
- SearchAndRecommendationsView searchAndRecommendationContainer) {
- mContainer = searchAndRecommendationContainer;
- mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
- mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
- 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 1125b82..1ad1f7a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -16,6 +16,9 @@
package com.android.launcher3.widget.picker;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_APP_EXPANDED;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_DEFAULT;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_FIRST;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_LAST;
import android.content.Context;
import android.graphics.Rect;
@@ -52,6 +55,7 @@
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -64,6 +68,7 @@
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
+import java.util.function.IntSupplier;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -84,6 +89,7 @@
private static final boolean DEBUG = false;
/** Uniquely identifies widgets list view type within the app. */
+ private static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space;
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;
@@ -97,7 +103,7 @@
private final WidgetListBaseRowEntryComparator mRowComparator =
new WidgetListBaseRowEntryComparator();
- private List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
+ private final List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
@Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null;
@@ -118,6 +124,7 @@
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
+ IntSupplier emptySpaceHeightProvider,
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
mContext = context;
mLauncher = Launcher.getLauncher(context);
@@ -126,22 +133,23 @@
WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
layoutInflater, iconClickListener, iconLongClickListener,
- mCachingPreviewLoader, listDrawableFactory, /* listAdapter= */ this);
+ mCachingPreviewLoader, listDrawableFactory);
mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_HEADER,
new WidgetsListHeaderViewHolderBinder(
layoutInflater,
/* onHeaderClickListener= */ this,
- listDrawableFactory,
- /* listAdapter= */ this));
+ listDrawableFactory));
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_SEARCH_HEADER,
new WidgetsListSearchHeaderViewHolderBinder(
layoutInflater,
/* onHeaderClickListener= */ this,
- listDrawableFactory,
- /* listAdapter= */ this));
+ listDrawableFactory));
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_SPACE,
+ new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
mShortcutPreviewPadding =
2 * context.getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
@@ -205,6 +213,14 @@
return mVisibleEntries.size();
}
+ /**
+ * Returns true if the adapter has entries which will be visible to the user
+ */
+ public boolean hasVisibleEntries() {
+ // Account for the 1st space entry
+ return getItemCount() > 1;
+ }
+
/** Returns all items that will be drawn in a recycler view. */
public List<WidgetsListBaseEntry> getItems() {
return mVisibleEntries;
@@ -218,8 +234,9 @@
/** Updates the widget list based on {@code tempEntries}. */
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
mCachingPreviewLoader.clearAll();
- mAllEntries = tempEntries.stream().sorted(mRowComparator)
- .collect(Collectors.toList());
+ mAllEntries.clear();
+ mAllEntries.add(new WidgetListSpaceEntry());
+ tempEntries.stream().sorted(mRowComparator).forEach(mAllEntries::add);
if (shouldClearVisibleEntries()) {
mVisibleEntries.clear();
}
@@ -247,8 +264,9 @@
getOffsetForPosition(previousPositionForPackageUserKey);
List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
- .filter(entry -> (mFilter == null || mFilter.test(entry))
+ .filter(entry -> ((mFilter == null || mFilter.test(entry))
&& mHeaderAndSelectedContentFilter.test(entry))
+ || entry instanceof WidgetListSpaceEntry)
.map(entry -> {
if (entry instanceof WidgetsListBaseEntry.Header<?>
&& matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
@@ -352,7 +370,13 @@
public void onBindViewHolder(ViewHolder holder, int pos) {
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
- viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
+
+ // The first entry has an empty space, count from second entries.
+ int listPos = (pos > 1) ? POSITION_DEFAULT : POSITION_FIRST;
+ if (pos == (getItemCount() - 1)) {
+ listPos |= POSITION_LAST;
+ }
+ viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos);
holder.itemView.setTag(R.id.tag_widget_entry, entry);
}
@@ -395,6 +419,8 @@
return VIEW_TYPE_WIDGETS_HEADER;
} else if (entry instanceof WidgetsListSearchHeaderEntry) {
return VIEW_TYPE_WIDGETS_SEARCH_HEADER;
+ } else if (entry instanceof WidgetListSpaceEntry) {
+ return VIEW_TYPE_WIDGETS_SPACE;
}
throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index 2f8f1ba..00750bd 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -31,16 +31,13 @@
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
OnHeaderClickListener onHeaderClickListener,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
@Override
@@ -53,14 +50,14 @@
@Override
public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
- int position) {
+ @ListPosition int position) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setListDrawableState(
WidgetsListDrawableState.obtain(
- /* isFirst= */ position == 0,
- /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+ (position & POSITION_FIRST) != 0,
+ (position & POSITION_LAST) != 0,
/* isExpanded= */ data.isWidgetListShown()));
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
mOnHeaderClickListener.onHeaderClicked(
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java b/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java
deleted file mode 100644
index 2b7f544..0000000
--- a/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java
+++ /dev/null
@@ -1,51 +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 androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.widget.picker.SearchAndRecommendationsScrollController.OnContentChangeListener;
-
-/**
- * A layout manager for the {@link WidgetsRecyclerView}.
- *
- * {@link #setOnContentChangeListener(OnContentChangeListener)} can be used to register a callback
- * for when the content of the layout manager has changed, following measurement and animation.
- */
-public final class WidgetsListLayoutManager extends LinearLayoutManager {
- @Nullable
- private OnContentChangeListener mOnContentChangeListener;
-
- public WidgetsListLayoutManager(Context context) {
- super(context);
- }
-
- @Override
- public void onLayoutCompleted(RecyclerView.State state) {
- super.onLayoutCompleted(state);
- if (mOnContentChangeListener != null) {
- mOnContentChangeListener.onContentChanged();
- }
- }
-
- public void setOnContentChangeListener(@Nullable OnContentChangeListener listener) {
- mOnContentChangeListener = listener;
- }
-}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
index 31dd9ee..1e2a3bf 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -32,16 +32,13 @@
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListSearchHeaderViewHolderBinder(LayoutInflater layoutInflater,
OnHeaderClickListener onHeaderClickListener,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
@Override
@@ -54,14 +51,14 @@
@Override
public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
- WidgetsListSearchHeaderEntry data, int position) {
+ WidgetsListSearchHeaderEntry data, @ListPosition int position) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setListDrawableState(
WidgetsListDrawableState.obtain(
- /* isFirst= */ position == 0,
- /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+ (position & POSITION_FIRST) != 0,
+ (position & POSITION_LAST) != 0,
/* isExpanded= */ data.isWidgetListShown()));
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
mOnHeaderClickListener.onHeaderClicked(isExpanded,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 9c06558..804b0ae 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -54,7 +54,6 @@
private final OnLongClickListener mIconLongClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
private final CachingWidgetPreviewLoader mWidgetPreviewLoader;
- private final WidgetsListAdapter mWidgetsListAdapter;
private boolean mApplyBitmapDeferred = false;
public WidgetsListTableViewHolderBinder(
@@ -62,14 +61,12 @@
OnClickListener iconClickListener,
OnLongClickListener iconLongClickListener,
CachingWidgetPreviewLoader widgetPreviewLoader,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
mWidgetPreviewLoader = widgetPreviewLoader;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
/**
@@ -97,15 +94,13 @@
@Override
public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
- int position) {
+ @ListPosition int position) {
WidgetsListTableView table = holder.mTableContainer;
if (DEBUG) {
Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
entry.mWidgets.size(), table.getChildCount()));
}
-
- table.setListDrawableState(
- position == mWidgetsListAdapter.getItemCount() - 1 ? LAST : MIDDLE);
+ table.setListDrawableState(((position & POSITION_LAST) != 0) ? LAST : MIDDLE);
List<ArrayList<WidgetItem>> widgetItemsTable =
WidgetsTableUtils.groupWidgetItemsIntoTable(
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 0b8ca34..60dfebe 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -53,7 +53,6 @@
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);
@@ -79,11 +78,6 @@
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}.
@@ -129,7 +123,6 @@
WidgetCell widget = (WidgetCell) LayoutInflater.from(
getContext()).inflate(R.layout.widget_cell, parent, false);
- widget.setOnTouchListener(mWidgetCellOnTouchListener);
View previewContainer = widget.findViewById(R.id.widget_preview_container);
previewContainer.setOnClickListener(mWidgetCellOnClickListener);
previewContainer.setOnLongClickListener(mWidgetCellOnLongClickListener);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 7671841..f780f03 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -23,7 +23,6 @@
import android.view.View;
import android.widget.TableLayout;
-import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
@@ -32,11 +31,12 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
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.SearchAndRecommendationsScrollController.OnContentChangeListener;
+import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
/**
* The widgets recycler view.
@@ -50,10 +50,13 @@
private final Point mFastScrollerOffset = new Point();
private boolean mTouchDownOnScroller;
private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
+
+ // Cached sizes
private int mLastVisibleWidgetContentTableHeight = 0;
private int mWidgetHeaderHeight = 0;
+ private int mWidgetEmptySpaceHeight = 0;
+
private final int mSpacingBetweenEntries;
- @Nullable private OnContentChangeListener mOnContentChangeListener;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -82,9 +85,7 @@
super.onFinishInflate();
// create a layout manager with Launcher's context so that scroll position
// can be preserved during screen rotation.
- WidgetsListLayoutManager layoutManager = new WidgetsListLayoutManager(getContext());
- layoutManager.setOnContentChangeListener(mOnContentChangeListener);
- setLayoutManager(layoutManager);
+ setLayoutManager(new LinearLayoutManager(getContext()));
}
@Override
@@ -169,10 +170,12 @@
// This assumes there is ever only one content shown in this recycler view.
mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
} else if (view instanceof WidgetsListHeader
- && mLastVisibleWidgetContentTableHeight == 0
+ && mWidgetHeaderHeight == 0
&& view.getMeasuredHeight() > 0) {
// This assumes all header views are of the same height.
mWidgetHeaderHeight = view.getMeasuredHeight();
+ } else if (view instanceof EmptySpaceView && view.getMeasuredHeight() > 0) {
+ mWidgetEmptySpaceHeight = view.getMeasuredHeight();
}
}
@@ -251,14 +254,6 @@
scrollToPosition(0);
}
- public void setOnContentChangeListener(@Nullable OnContentChangeListener listener) {
- mOnContentChangeListener = listener;
- WidgetsListLayoutManager layoutManager = (WidgetsListLayoutManager) getLayoutManager();
- if (layoutManager != null) {
- layoutManager.setOnContentChangeListener(listener);
- }
- }
-
/**
* Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
* {@code untilIndex}.
@@ -283,6 +278,8 @@
}
} else if (entry instanceof WidgetsListContentEntry) {
totalItemsHeight += mLastVisibleWidgetContentTableHeight;
+ } else if (entry instanceof WidgetListSpaceEntry) {
+ totalItemsHeight += mWidgetEmptySpaceHeight;
} else {
throw new UnsupportedOperationException("Can't estimate height for " + entry);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
new file mode 100644
index 0000000..f33c2fa
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
+
+import java.util.function.IntSupplier;
+
+/**
+ * {@link ViewHolderBinder} for binding the top empty space
+ */
+public class WidgetsSpaceViewHolderBinder
+ implements ViewHolderBinder<WidgetListSpaceEntry, ViewHolder> {
+
+ private final IntSupplier mEmptySpaceHeightProvider;
+
+ public WidgetsSpaceViewHolderBinder(IntSupplier emptySpaceHeightProvider) {
+ mEmptySpaceHeightProvider = emptySpaceHeightProvider;
+ }
+
+ @Override
+ public ViewHolder newViewHolder(ViewGroup parent) {
+ return new ViewHolder(new EmptySpaceView(parent.getContext())) { };
+ }
+
+ @Override
+ public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data, int position) {
+ ((EmptySpaceView) holder.itemView).setFixedHeight(mEmptySpaceHeightProvider.getAsInt());
+ }
+
+ /**
+ * Empty view which allows listening for 'Y' changes
+ */
+ public static class EmptySpaceView extends View {
+
+ private Runnable mOnYChangeCallback;
+ private int mHeight = 0;
+
+ private EmptySpaceView(Context context) {
+ super(context);
+ animate().setUpdateListener(v -> notifyYChanged());
+ }
+
+ /**
+ * Sets the height for the empty view
+ * @return true if the height changed, false otherwise
+ */
+ public boolean setFixedHeight(int height) {
+ if (mHeight != height) {
+ mHeight = height;
+ requestLayout();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, makeMeasureSpec(mHeight, EXACTLY));
+ }
+
+ public void setOnYChangeCallback(Runnable callback) {
+ mOnYChangeCallback = callback;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ notifyYChanged();
+ }
+
+ @Override
+ public void offsetTopAndBottom(int offset) {
+ super.offsetTopAndBottom(offset);
+ notifyYChanged();
+ }
+
+ @Override
+ public void setTranslationY(float translationY) {
+ super.setTranslationY(translationY);
+ notifyYChanged();
+ }
+
+ private void notifyYChanged() {
+ if (mOnYChangeCallback != null) {
+ mOnYChangeCallback.run();
+ }
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 522b185..2ca40d8 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -66,6 +66,7 @@
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
+import com.android.systemui.shared.system.ContextUtils;
import com.android.systemui.shared.system.QuickStepContract;
import org.junit.Assert;
@@ -245,12 +246,16 @@
ComponentName cn = new ComponentName(pi.packageName, pi.name);
if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
- mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
- android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
- try {
+ if (TestHelpers.isInLauncherProcess()) {
pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
- } finally {
- mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ } else {
+ try {
+ final int userId = ContextUtils.getUserId(getContext());
+ mDevice.executeShellCommand(
+ "pm enable --user " + userId + " " + cn.flattenToString());
+ } catch (IOException e) {
+ fail(e.toString());
+ }
}
}
}
@@ -300,7 +305,7 @@
public boolean isTwoPanels() {
return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS)
- .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
private void setForcePauseTimeout(long timeout) {
@@ -617,9 +622,7 @@
}
private String getNavigationButtonResPackage() {
- return isTablet() && getNavigationModel() == NavigationModel.THREE_BUTTON ?
- getLauncherPackageName() :
- SYSTEMUI_PACKAGE;
+ return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
}
private UiObject2 verifyContainerType(ContainerType containerType) {
@@ -750,11 +753,11 @@
boolean gestureStartFromLauncher = isTablet()
? !isLauncher3() || hasLauncherObject(WORKSPACE_RES_ID)
: isLauncherVisible();
- GestureScope gestureScope = gestureStartFromLauncher
- ? GestureScope.INSIDE_TO_OUTSIDE
- : GestureScope.OUTSIDE_WITH_PILFER;
if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
+ GestureScope gestureScope = gestureStartFromLauncher
+ ? (isTablet()? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
+ : GestureScope.OUTSIDE_WITH_PILFER;
linearGesture(
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
@@ -777,7 +780,8 @@
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
- gestureScope);
+ gestureStartFromLauncher ? GestureScope.INSIDE_TO_OUTSIDE
+ : GestureScope.OUTSIDE_WITH_PILFER);
}
} else {
log("Hierarchy before clicking home:");
@@ -1327,13 +1331,6 @@
}
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
- // b/190748682
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_UP:
- log("b/190748682: injecting " + event);
- break;
- }
assertTrue("injectInputEvent failed",
mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
event.recycle();