1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.allapps.search;
17 
18 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME;
19 
20 import android.text.Editable;
21 import android.text.SpannableStringBuilder;
22 import android.text.TextUtils;
23 import android.text.TextWatcher;
24 import android.text.style.SuggestionSpan;
25 import android.view.KeyEvent;
26 import android.view.View;
27 import android.view.View.OnFocusChangeListener;
28 import android.view.inputmethod.EditorInfo;
29 import android.widget.TextView;
30 import android.widget.TextView.OnEditorActionListener;
31 
32 import com.android.launcher3.BaseDraggingActivity;
33 import com.android.launcher3.ExtendedEditText;
34 import com.android.launcher3.Launcher;
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
37 import com.android.launcher3.config.FeatureFlags;
38 import com.android.launcher3.search.SearchAlgorithm;
39 import com.android.launcher3.search.SearchCallback;
40 
41 /**
42  * An interface to a search box that AllApps can command.
43  */
44 public class AllAppsSearchBarController
45         implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
46         OnFocusChangeListener {
47 
48     protected BaseDraggingActivity mLauncher;
49     protected SearchCallback<AdapterItem> mCallback;
50     protected ExtendedEditText mInput;
51     protected String mQuery;
52     private String[] mTextConversions;
53 
54     protected SearchAlgorithm<AdapterItem> mSearchAlgorithm;
55 
setVisibility(int visibility)56     public void setVisibility(int visibility) {
57         mInput.setVisibility(visibility);
58     }
59 
60     /**
61      * Sets the references to the apps model and the search result callback.
62      */
initialize( SearchAlgorithm<AdapterItem> searchAlgorithm, ExtendedEditText input, BaseDraggingActivity launcher, SearchCallback<AdapterItem> callback)63     public final void initialize(
64             SearchAlgorithm<AdapterItem> searchAlgorithm, ExtendedEditText input,
65             BaseDraggingActivity launcher, SearchCallback<AdapterItem> callback) {
66         mCallback = callback;
67         mLauncher = launcher;
68 
69         mInput = input;
70         mInput.addTextChangedListener(this);
71         mInput.setOnEditorActionListener(this);
72         mInput.setOnBackKeyListener(this);
73         mInput.setOnFocusChangeListener(this);
74         mSearchAlgorithm = searchAlgorithm;
75     }
76 
77     @Override
beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)78     public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
79         // Do nothing
80     }
81 
82     @Override
onTextChanged(CharSequence s, int start, int before, int count)83     public void onTextChanged(CharSequence s, int start, int before, int count) {
84         mTextConversions = extractTextConversions(s);
85     }
86 
extractTextConversions(CharSequence text)87     private static String[] extractTextConversions(CharSequence text) {
88         if (text instanceof SpannableStringBuilder) {
89             SpannableStringBuilder spanned = (SpannableStringBuilder) text;
90             SuggestionSpan[] suggestionSpans =
91                 spanned.getSpans(0, text.length(), SuggestionSpan.class);
92             if (suggestionSpans != null && suggestionSpans.length > 0) {
93                 spanned.removeSpan(suggestionSpans[0]);
94                 return suggestionSpans[0].getSuggestions();
95             }
96         }
97         return null;
98     }
99 
100     @Override
afterTextChanged(final Editable s)101     public void afterTextChanged(final Editable s) {
102         mQuery = s.toString();
103         if (mQuery.isEmpty()) {
104             mSearchAlgorithm.cancel(true);
105             mCallback.clearSearchResult();
106         } else {
107             mSearchAlgorithm.cancel(false);
108             mSearchAlgorithm.doSearch(mQuery, mTextConversions, mCallback);
109         }
110     }
111 
refreshSearchResult()112     public void refreshSearchResult() {
113         if (TextUtils.isEmpty(mQuery)) {
114             return;
115         }
116         // If play store continues auto updating an app, we want to show partial result.
117         mSearchAlgorithm.cancel(false);
118         mSearchAlgorithm.doSearch(mQuery, mCallback);
119     }
120 
121     @Override
onEditorAction(TextView v, int actionId, KeyEvent event)122     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
123 
124         if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
125             mLauncher.getStatsLogManager().logger()
126                     .log(LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME);
127             // selectFocusedView should return SearchTargetEvent that is passed onto onClick
128             return Launcher.getLauncher(mLauncher).getAppsView().launchHighlightedItem();
129         }
130         return false;
131     }
132 
133     @Override
onBackKey()134     public boolean onBackKey() {
135         // Only hide the search field if there is no query
136         String query = Utilities.trim(mInput.getEditableText().toString());
137         if (query.isEmpty()) {
138             reset();
139             return true;
140         }
141         return false;
142     }
143 
144     @Override
onFocusChange(View view, boolean hasFocus)145     public void onFocusChange(View view, boolean hasFocus) {
146         if (!hasFocus && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
147             mInput.hideKeyboard();
148         }
149     }
150 
151     /**
152      * Resets the search bar state.
153      */
reset()154     public void reset() {
155         mCallback.clearSearchResult();
156         mInput.reset();
157         mQuery = null;
158     }
159 
160     /**
161      * Focuses the search field to handle key events.
162      */
focusSearchField()163     public void focusSearchField() {
164         mInput.showKeyboard();
165     }
166 
167     /**
168      * Returns whether the search field is focused.
169      */
isSearchFieldFocused()170     public boolean isSearchFieldFocused() {
171         return mInput.isFocused();
172     }
173 }
174