Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d07acba0 authored by Alina Zaidi's avatar Alina Zaidi
Browse files

Add a WidgetsSearchBar(Launcher3) and a WidgetsSearchController.

- Make WidgetsSearchBar in Launcher3 initialize WidgetsSearchController with SimpleWidgetsSearchPipeline
- Modify SimpleWidgetsSearchPipeline to filter widgets entries on widgets/shortcut labels also.

Test: Tested prototype locally. Also added robolectric test.
Bug: b/157286785
Change-Id: I65f5fa0240ffb6d22023167e4e86d94d83bbd9f7
parent 334e6593
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="?android:attr/textColorTertiary"
        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
</vector>
+1 −12
Original line number Diff line number Diff line
@@ -34,16 +34,5 @@
        android:textSize="24sp"
        android:layout_marginTop="16dp"
        android:text="@string/widget_button_text"/>
    <!-- Disable the search bar because it has not been implemented. -->
    <EditText
        android:id="@+id/widgets_search_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:layout_marginTop="16dp"
        android:background="@drawable/bg_widgets_searchbox"
        android:drawablePadding="8dp"
        android:drawableStart="@drawable/ic_allapps_search"
        android:hint="@string/widgets_full_sheet_search_bar_hint"
        android:padding="12dp" />
    <include layout="@layout/widgets_search_bar"/>
</LinearLayout>
+33 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<com.android.launcher3.widget.picker.search.WidgetsSearchBar
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widgets_search_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:layout_marginTop="16dp"
    android:background="@drawable/bg_widgets_searchbox"
    android:padding="12dp"
    android:visibility="gone">

    <EditText
        android:id="@+id/widgets_search_bar_edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="8dp"
        android:drawableStart="@drawable/ic_allapps_search"
        android:background="@null"
        android:hint="@string/widgets_full_sheet_search_bar_hint"
        android:maxLines="1"
        android:layout_weight="1"
        android:inputType="text"/>

    <ImageButton
        android:id="@+id/widgets_search_cancel_button"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:src="@drawable/ic_gm_close_24"
        android:background="?android:selectableItemBackground"
        android:layout_gravity="center"
        android:visibility="gone"/>
</com.android.launcher3.widget.picker.search.WidgetsSearchBar>
 No newline at end of file
+45 −42
Original line number Diff line number Diff line
@@ -19,8 +19,6 @@ package com.android.launcher3.widget.picker.search;
import static android.os.Looper.getMainLooper;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.robolectric.Shadows.shadowOf;
@@ -40,6 +38,7 @@ import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;

import org.junit.Before;
import org.junit.Test;
@@ -56,9 +55,6 @@ import java.util.List;

@RunWith(RobolectricTestRunner.class)
public class SimpleWidgetsSearchPipelineTest {
    private static final SimpleWidgetsSearchPipeline.StringMatcher MATCHER =
            SimpleWidgetsSearchPipeline.StringMatcher.getInstance();

    @Mock private IconCache mIconCache;

    private InvariantDeviceProfile mTestProfile;
@@ -73,9 +69,10 @@ public class SimpleWidgetsSearchPipelineTest {
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
                .getComponent().getPackageName())
                .when(mIconCache).getTitleNoCache(any());
        doAnswer(invocation -> {
            ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
            return componentWithLabel.getComponent().getShortClassName();
        }).when(mIconCache).getTitleNoCache(any());
        mTestProfile = new InvariantDeviceProfile();
        mTestProfile.numRows = 5;
        mTestProfile.numColumns = 5;
@@ -85,54 +82,60 @@ public class SimpleWidgetsSearchPipelineTest {
                createWidgetsHeaderEntry("com.example.android.Calendar", "Calendar", 2);
        mCalendarContentEntry =
                createWidgetsContentEntry("com.example.android.Calendar", "Calendar", 2);
        mCameraHeaderEntry = createWidgetsHeaderEntry("com.example.android.Camera", "Camera", 5);
        mCameraContentEntry = createWidgetsContentEntry("com.example.android.Camera", "Camera", 5);
        mCameraHeaderEntry = createWidgetsHeaderEntry("com.example.android.Camera", "Camera", 11);
        mCameraContentEntry = createWidgetsContentEntry("com.example.android.Camera", "Camera", 11);
        mClockHeaderEntry = createWidgetsHeaderEntry("com.example.android.Clock", "Clock", 3);
        mClockContentEntry = createWidgetsContentEntry("com.example.android.Clock", "Clock", 3);
    }

    @Test
    public void query_shouldInformCallbackWithResultsMatchedOnAppName() {
    public void query_shouldMatchOnAppName() {
        SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
                List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
                        mCameraContentEntry, mClockHeaderEntry, mClockContentEntry));

        pipeline.query("Ca", results ->
                assertEquals(results, List.of(mCalendarHeaderEntry, mCalendarContentEntry,
                        mCameraHeaderEntry, mCameraContentEntry)));
                assertEquals(results,
                        List.of(
                                new WidgetsListSearchHeaderEntry(
                                        mCalendarHeaderEntry.mPkgItem,
                                        mCalendarHeaderEntry.mTitleSectionName,
                                        mCalendarHeaderEntry.mWidgets),
                                mCalendarContentEntry,
                                new WidgetsListSearchHeaderEntry(
                                        mCameraHeaderEntry.mPkgItem,
                                        mCameraHeaderEntry.mTitleSectionName,
                                        mCameraHeaderEntry.mWidgets),
                                mCameraContentEntry)));
        shadowOf(getMainLooper()).idle();
    }

    @Test
    public void testMatches() {
        assertTrue(MATCHER.matches("q", "Q"));
        assertTrue(MATCHER.matches("q", "  Q"));
        assertTrue(MATCHER.matches("e", "elephant"));
        assertTrue(MATCHER.matches("eL", "Elephant"));
        assertTrue(MATCHER.matches("elephant ", "elephant"));
        assertTrue(MATCHER.matches("whitec", "white cow"));
        assertTrue(MATCHER.matches("white  c", "white cow"));
        assertTrue(MATCHER.matches("white ", "white cow"));
        assertTrue(MATCHER.matches("white c", "white cow"));
        assertTrue(MATCHER.matches("电", "电子邮件"));
        assertTrue(MATCHER.matches("电子", "电子邮件"));
        assertTrue(MATCHER.matches("다", "다운로드"));
        assertTrue(MATCHER.matches("드", "드라이브"));
        assertTrue(MATCHER.matches("åbç", "abc"));
        assertTrue(MATCHER.matches("ål", "Alpha"));

        assertFalse(MATCHER.matches("phant", "elephant"));
        assertFalse(MATCHER.matches("elephants", "elephant"));
        assertFalse(MATCHER.matches("cow", "white cow"));
        assertFalse(MATCHER.matches("cow", "whiteCow"));
        assertFalse(MATCHER.matches("dog", "cats&Dogs"));
        assertFalse(MATCHER.matches("ba", "Bot"));
        assertFalse(MATCHER.matches("ba", "bot"));
        assertFalse(MATCHER.matches("子", "电子邮件"));
        assertFalse(MATCHER.matches("邮件", "电子邮件"));
        assertFalse(MATCHER.matches("ㄷ", "다운로드 드라이브"));
        assertFalse(MATCHER.matches("ㄷㄷ", "다운로드 드라이브"));
        assertFalse(MATCHER.matches("åç", "abc"));
    public void query_shouldMatchOnWidgetLabel() {
        SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
                List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
                        mCameraContentEntry));

        pipeline.query("Widget1", results ->
                assertEquals(results,
                        List.of(
                                new WidgetsListSearchHeaderEntry(
                                        mCalendarHeaderEntry.mPkgItem,
                                        mCalendarHeaderEntry.mTitleSectionName,
                                        mCalendarHeaderEntry.mWidgets.subList(1, 2)),
                                new WidgetsListContentEntry(
                                        mCalendarHeaderEntry.mPkgItem,
                                        mCalendarHeaderEntry.mTitleSectionName,
                                        mCalendarHeaderEntry.mWidgets.subList(1, 2)),
                                new WidgetsListSearchHeaderEntry(
                                        mCameraHeaderEntry.mPkgItem,
                                        mCameraHeaderEntry.mTitleSectionName,
                                        mCameraHeaderEntry.mWidgets.subList(1, 3)),
                                new WidgetsListContentEntry(
                                        mCameraHeaderEntry.mPkgItem,
                                        mCameraHeaderEntry.mTitleSectionName,
                                        mCameraHeaderEntry.mWidgets.subList(1, 3)))));
        shadowOf(getMainLooper()).idle();
    }

    private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
+140 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.widget.picker.search;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.content.Context;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;

import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

import java.util.ArrayList;

@RunWith(RobolectricTestRunner.class)
public class WidgetsSearchBarControllerTest {

    private WidgetsSearchBarController mController;
    private Context mContext;
    private EditText mEditText;
    private ImageButton mCancelButton;
    @Mock
    private SearchModeListener mSearchModeListener;
    @Mock
    private SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
        mEditText = new EditText(mContext);
        mCancelButton = new ImageButton(mContext);
        mController = new WidgetsSearchBarController(
                mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener);
    }

    @Test
    public void onSearchResult_shouldInformSearchModeListener() {
        ArrayList<WidgetsListBaseEntry> entries = new ArrayList<>();
        mController.onSearchResult("abc", entries);

        verify(mSearchModeListener).onSearchResults(entries);
    }

    @Test
    public void afterTextChanged_shouldInformSearchModeListenerToEnterSearch() {
        mEditText.setText("abc");

        verify(mSearchModeListener).enterSearchMode();
        verifyNoMoreInteractions(mSearchModeListener);
    }

    @Test
    public void afterTextChanged_shouldDoSearch() {
        mEditText.setText("abc");

        verify(mSearchAlgorithm).doSearch(eq("abc"), any());
    }

    @Test
    public void afterTextChanged_shouldShowCancelButton() {
        mEditText.setText("abc");

        assertEquals(mCancelButton.getVisibility(), View.VISIBLE);
    }

    @Test
    public void afterTextChanged_empty_shouldInformSearchModeListenerToExitSearch() {
        mEditText.setText("");

        verify(mSearchModeListener).exitSearchMode();
        verifyNoMoreInteractions(mSearchModeListener);
    }

    @Test
    public void afterTextChanged_empty_shouldCancelSearch() {
        mEditText.setText("");

        verify(mSearchAlgorithm).cancel(true);
        verifyNoMoreInteractions(mSearchAlgorithm);
    }

    @Test
    public void afterTextChanged_empty_shouldHideCancelButton() {
        mEditText.setText("");

        assertEquals(mCancelButton.getVisibility(), View.GONE);
    }

    @Test
    public void cancelSearch_shouldInformSearchModeListenerToExitSearch() {
        mCancelButton.performClick();

        verify(mSearchModeListener).exitSearchMode();
        verifyNoMoreInteractions(mSearchModeListener);
    }

    @Test
    public void cancelSearch_shouldCancelSearch() {
        mCancelButton.performClick();

        verify(mSearchAlgorithm).cancel(true);
        verifyNoMoreInteractions(mSearchAlgorithm);
    }

    @Test
    public void cancelSearch_shouldClearSearchBar() {
        mCancelButton.performClick();

        assertEquals(mEditText.getText().toString(), "");
    }
}
Loading