Merge "Bug fixes for widget picker search." into sc-dev
diff --git a/res/layout/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
index 450e5f1..6f65e2b 100644
--- a/res/layout/widgets_search_bar.xml
+++ b/res/layout/widgets_search_bar.xml
@@ -9,7 +9,7 @@
     android:background="@drawable/bg_widgets_searchbox"
     android:padding="12dp">
 
-    <EditText
+    <com.android.launcher3.ExtendedEditText
         android:id="@+id/widgets_search_bar_edit_text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0600cae..292dfbe 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -84,6 +84,9 @@
     <!-- Text shown when there is no widgets shown in the popup view showing all available widgets
          installed on the device. [CHAR_LIMIT=none] -->
     <string name="no_widgets_available">No widgets available</string>
+    <!-- Text shown when there are no matching widget search results for user's search query.
+         [CHAR_LIMIT=none] -->
+    <string name="no_search_results">No search results</string>
 
     <!-- All Apps -->
     <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
index 7fc9650..4e6f17c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
@@ -24,9 +24,9 @@
 
 import android.content.Context;
 import android.view.View;
-import android.widget.EditText;
 import android.widget.ImageButton;
 
+import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.search.SearchAlgorithm;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
@@ -45,7 +45,7 @@
 
     private WidgetsSearchBarController mController;
     private Context mContext;
-    private EditText mEditText;
+    private ExtendedEditText mEditText;
     private ImageButton mCancelButton;
     @Mock
     private SearchModeListener mSearchModeListener;
@@ -56,7 +56,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
-        mEditText = new EditText(mContext);
+        mEditText = new ExtendedEditText(mContext);
         mCancelButton = new ImageButton(mContext);
         mController = new WidgetsSearchBarController(
                 mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener);
@@ -116,11 +116,10 @@
     }
 
     @Test
-    public void cancelSearch_shouldInformSearchModeListenerToExitSearch() {
+    public void cancelSearch_shouldInformSearchModeListenerToClearResultsAndExitSearch() {
         mCancelButton.performClick();
 
         verify(mSearchModeListener).exitSearchMode();
-        verifyNoMoreInteractions(mSearchModeListener);
     }
 
     @Test
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 92994be..5747690 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -161,8 +162,8 @@
                 mAdapters.get(currentActivePage).mWidgetsRecyclerView;
 
         updateNoWidgetsView(currentAdapterHolder);
-
         attachScrollbarToRecyclerView(currentRecyclerView);
+        resetExpandedHeaders();
     }
 
     private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
@@ -180,6 +181,13 @@
         mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
     }
 
+    private void updateNoSearchResultsView(boolean isVisible) {
+        mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
+        if (isVisible) {
+            mNoWidgetsView.setText(R.string.no_search_results);
+        }
+    }
+
     private void reset() {
         mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
         if (mHasWorkProfile) {
@@ -323,10 +331,12 @@
         if (mIsInSearchMode) return;
         setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
         attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
+        resetExpandedHeaders();
     }
 
     @Override
     public void exitSearchMode() {
+        onSearchResults(new ArrayList<>());
         setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
         if (mHasWorkProfile) {
             mViewPager.snapToPage(AdapterHolder.PRIMARY);
@@ -337,6 +347,8 @@
     @Override
     public void onSearchResults(List<WidgetsListBaseEntry> entries) {
         mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
+        updateNoSearchResultsView(
+                mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.getItemCount() == 0);
     }
 
     private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
@@ -350,6 +362,12 @@
         }
         mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
                 .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
+        mNoWidgetsView.setVisibility(GONE);
+    }
+
+    private void resetExpandedHeaders() {
+        mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
+        mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
     }
 
     private void open(boolean animate) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 4f11d73..cab1e02 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.widget.picker;
 
 import android.content.Context;
+import android.os.Process;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
@@ -81,7 +82,7 @@
             entry instanceof WidgetsListHeaderEntry
                     || entry instanceof WidgetsListSearchHeaderEntry
                     || new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
-                    .equals(mWidgetsContentVisiblePackageUserKey);
+                            .equals(mWidgetsContentVisiblePackageUserKey);
     @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
 
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
@@ -176,6 +177,14 @@
         mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
     }
 
+    /**
+     * Resets any expanded widget header.
+     */
+    public void resetExpandedHeader() {
+        mWidgetsContentVisiblePackageUserKey = null;
+        updateVisibleEntries();
+    }
+
     @Override
     public void onBindViewHolder(ViewHolder holder, int pos) {
         ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
@@ -259,7 +268,14 @@
 
         @Override
         public int compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b) {
-            return mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+            int i = mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+            if (i != 0) {
+                return i;
+            }
+            // Prioritize entries from current user over other users if the entries are same.
+            if (a.mPkgItem.user.equals(b.mPkgItem.user)) return 0;
+            if (a.mPkgItem.user.equals(Process.myUserHandle())) return -1;
+            return 1;
         }
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 12d3f11..b016b4f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -220,8 +220,8 @@
         int totalItemsHeight = 0;
         for (int i = 0; i < untilIndex; i++) {
             WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
-            if (entry instanceof WidgetsListHeaderEntry ||
-                    entry instanceof WidgetsListSearchHeaderEntry) {
+            if (entry instanceof WidgetsListHeaderEntry
+                    || entry instanceof WidgetsListSearchHeaderEntry) {
                 totalItemsHeight += mEstimatedWidgetListHeaderHeight;
             } else if (entry instanceof WidgetsListContentEntry) {
                 totalItemsHeight += mLastVisibleWidgetContentTableHeight;
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
index d68e87e..5520826 100644
--- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -18,13 +18,13 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.widget.EditText;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.R;
 import com.android.launcher3.search.SearchAlgorithm;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -36,7 +36,7 @@
  */
 public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSearchBar {
     private WidgetsSearchBarController mController;
-    private EditText mEditText;
+    private ExtendedEditText mEditText;
     private ImageButton mCancelButton;
 
     public LauncherWidgetsSearchBar(Context context) {
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
index 6c37484..6011097 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -22,9 +22,11 @@
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.util.Log;
-import android.widget.EditText;
+import android.view.KeyEvent;
+import android.view.View;
 import android.widget.ImageButton;
 
+import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.search.SearchAlgorithm;
 import com.android.launcher3.search.SearchCallback;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -35,22 +37,25 @@
  * Controller for a search bar with an edit text and a cancel button.
  */
 public class WidgetsSearchBarController implements TextWatcher,
-        SearchCallback<WidgetsListBaseEntry> {
+        SearchCallback<WidgetsListBaseEntry>,  ExtendedEditText.OnBackKeyListener,
+        View.OnKeyListener {
     private static final String TAG = "WidgetsSearchBarController";
     private static final boolean DEBUG = false;
 
     protected SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
-    protected EditText mInput;
+    protected ExtendedEditText mInput;
     protected ImageButton mCancelButton;
     protected SearchModeListener mSearchModeListener;
     protected String mQuery;
 
     public WidgetsSearchBarController(
-            SearchAlgorithm<WidgetsListBaseEntry> algo, EditText editText, ImageButton cancelButton,
-            SearchModeListener searchModeListener) {
+            SearchAlgorithm<WidgetsListBaseEntry> algo, ExtendedEditText editText,
+            ImageButton cancelButton, SearchModeListener searchModeListener) {
         mSearchAlgorithm = algo;
         mInput = editText;
         mInput.addTextChangedListener(this);
+        mInput.setOnBackKeyListener(this);
+        mInput.setOnKeyListener(this);
         mCancelButton = cancelButton;
         mCancelButton.setOnClickListener(v -> clearSearchResult());
         mSearchModeListener = searchModeListener;
@@ -99,6 +104,7 @@
         mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
         mInput.getText().clear();
         mInput.clearFocus();
+        mInput.hideKeyboard();
         mSearchModeListener.exitSearchMode();
     }
 
@@ -108,4 +114,21 @@
     public void onDestroy() {
         mSearchAlgorithm.destroy();
     }
+
+    @Override
+    public boolean onBackKey() {
+        mInput.clearFocus();
+        mInput.hideKeyboard();
+        return true;
+    }
+
+    @Override
+    public boolean onKey(View view, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
+            mInput.clearFocus();
+            mInput.hideKeyboard();
+            return true;
+        }
+        return false;
+    }
 }