• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
17 
18 import android.content.Context;
19 import android.text.Editable;
20 import android.text.TextWatcher;
21 import android.view.KeyEvent;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.inputmethod.EditorInfo;
26 import android.view.inputmethod.InputMethodManager;
27 import android.widget.TextView;
28 import com.android.launcher3.ExtendedEditText;
29 import com.android.launcher3.R;
30 import com.android.launcher3.Utilities;
31 import com.android.launcher3.util.Thunk;
32 
33 import java.util.List;
34 
35 
36 /**
37  * The default search controller.
38  */
39 final class DefaultAppSearchController extends AllAppsSearchBarController
40         implements TextWatcher, TextView.OnEditorActionListener, View.OnClickListener {
41 
42     private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
43 
44     private static final int FADE_IN_DURATION = 175;
45     private static final int FADE_OUT_DURATION = 100;
46     private static final int SEARCH_TRANSLATION_X_DP = 18;
47 
48     private final Context mContext;
49     @Thunk final InputMethodManager mInputMethodManager;
50 
51     private DefaultAppSearchAlgorithm mSearchManager;
52 
53     private ViewGroup mContainerView;
54     private View mSearchView;
55     @Thunk View mSearchBarContainerView;
56     private View mSearchButtonView;
57     private View mDismissSearchButtonView;
58     @Thunk
59     ExtendedEditText mSearchBarEditView;
60     @Thunk AllAppsRecyclerView mAppsRecyclerView;
61     @Thunk Runnable mFocusRecyclerViewRunnable = new Runnable() {
62         @Override
63         public void run() {
64             mAppsRecyclerView.requestFocus();
65         }
66     };
67 
DefaultAppSearchController(Context context, ViewGroup containerView, AllAppsRecyclerView appsRecyclerView)68     public DefaultAppSearchController(Context context, ViewGroup containerView,
69             AllAppsRecyclerView appsRecyclerView) {
70         mContext = context;
71         mInputMethodManager = (InputMethodManager)
72                 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
73         mContainerView = containerView;
74         mAppsRecyclerView = appsRecyclerView;
75     }
76 
77     @Override
getView(ViewGroup parent)78     public View getView(ViewGroup parent) {
79         LayoutInflater inflater = LayoutInflater.from(parent.getContext());
80         mSearchView = inflater.inflate(R.layout.all_apps_search_bar, parent, false);
81         mSearchView.setOnClickListener(this);
82 
83         mSearchButtonView = mSearchView.findViewById(R.id.search_button);
84         mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
85         mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
86         mDismissSearchButtonView.setOnClickListener(this);
87         mSearchBarEditView = (ExtendedEditText)
88                 mSearchBarContainerView.findViewById(R.id.search_box_input);
89         mSearchBarEditView.addTextChangedListener(this);
90         mSearchBarEditView.setOnEditorActionListener(this);
91         mSearchBarEditView.setOnBackKeyListener(
92                 new ExtendedEditText.OnBackKeyListener() {
93                     @Override
94                     public boolean onBackKey() {
95                         // Only hide the search field if there is no query, or if there
96                         // are no filtered results
97                         String query = Utilities.trim(
98                                 mSearchBarEditView.getEditableText().toString());
99                         if (query.isEmpty() || mApps.hasNoFilteredResults()) {
100                             hideSearchField(true, mFocusRecyclerViewRunnable);
101                             return true;
102                         }
103                         return false;
104                     }
105                 });
106         return mSearchView;
107     }
108 
109     @Override
focusSearchField()110     public void focusSearchField() {
111         mSearchBarEditView.requestFocus();
112         showSearchField();
113     }
114 
115     @Override
isSearchFieldFocused()116     public boolean isSearchFieldFocused() {
117         return mSearchBarEditView.isFocused();
118     }
119 
120     @Override
onInitialize()121     protected void onInitialize() {
122         mSearchManager = new DefaultAppSearchAlgorithm(mApps.getApps());
123     }
124 
125     @Override
reset()126     public void reset() {
127         hideSearchField(false, null);
128     }
129 
130     @Override
shouldShowPredictionBar()131     public boolean shouldShowPredictionBar() {
132         return false;
133     }
134 
135     @Override
onClick(View v)136     public void onClick(View v) {
137         if (v == mSearchView) {
138             showSearchField();
139         } else if (v == mDismissSearchButtonView) {
140             hideSearchField(true, mFocusRecyclerViewRunnable);
141         }
142     }
143 
144     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)145     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
146         // Do nothing
147     }
148 
149     @Override
onTextChanged(CharSequence s, int start, int before, int count)150     public void onTextChanged(CharSequence s, int start, int before, int count) {
151         // Do nothing
152     }
153 
154     @Override
afterTextChanged(final Editable s)155     public void afterTextChanged(final Editable s) {
156         String query = s.toString();
157         if (query.isEmpty()) {
158             mSearchManager.cancel(true);
159             mCb.clearSearchResult();
160         } else {
161             mSearchManager.cancel(false);
162             mSearchManager.doSearch(query, mCb);
163         }
164     }
165 
166     @Override
onEditorAction(TextView v, int actionId, KeyEvent event)167     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
168         // Skip if we disallow app-launch-on-enter
169         if (!ALLOW_SINGLE_APP_LAUNCH) {
170             return false;
171         }
172         // Skip if it's not the right action
173         if (actionId != EditorInfo.IME_ACTION_SEARCH) {
174             return false;
175         }
176         // Skip if there are more than one icon
177         if (mApps.getNumFilteredApps() > 1) {
178             return false;
179         }
180         // Otherwise, find the first icon, or fallback to the search-market-view and launch it
181         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
182         for (int i = 0; i < items.size(); i++) {
183             AlphabeticalAppsList.AdapterItem item = items.get(i);
184             switch (item.viewType) {
185                 case AllAppsGridAdapter.ICON_VIEW_TYPE:
186                 case AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE:
187                     mAppsRecyclerView.getChildAt(i).performClick();
188                     mInputMethodManager.hideSoftInputFromWindow(
189                             mContainerView.getWindowToken(), 0);
190                     return true;
191             }
192         }
193         return false;
194     }
195 
196     /**
197      * Focuses the search field.
198      */
showSearchField()199     private void showSearchField() {
200         // Show the search bar and focus the search
201         final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
202                 mContext.getResources().getDisplayMetrics());
203         mSearchBarContainerView.setVisibility(View.VISIBLE);
204         mSearchBarContainerView.setAlpha(0f);
205         mSearchBarContainerView.setTranslationX(translationX);
206         mSearchBarContainerView.animate()
207                 .alpha(1f)
208                 .translationX(0)
209                 .setDuration(FADE_IN_DURATION)
210                 .withLayer()
211                 .withEndAction(new Runnable() {
212                     @Override
213                     public void run() {
214                         mSearchBarEditView.requestFocus();
215                         mInputMethodManager.showSoftInput(mSearchBarEditView,
216                                 InputMethodManager.SHOW_IMPLICIT);
217                     }
218                 });
219         mSearchButtonView.animate()
220                 .alpha(0f)
221                 .translationX(-translationX)
222                 .setDuration(FADE_OUT_DURATION)
223                 .withLayer();
224     }
225 
226     /**
227      * Unfocuses the search field.
228      */
hideSearchField(boolean animated, final Runnable postAnimationRunnable)229     @Thunk void hideSearchField(boolean animated, final Runnable postAnimationRunnable) {
230         mSearchManager.cancel(true);
231 
232         final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
233         final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP,
234                 mContext.getResources().getDisplayMetrics());
235         if (animated) {
236             // Hide the search bar and focus the recycler view
237             mSearchBarContainerView.animate()
238                     .alpha(0f)
239                     .translationX(0)
240                     .setDuration(FADE_IN_DURATION)
241                     .withLayer()
242                     .withEndAction(new Runnable() {
243                         @Override
244                         public void run() {
245                             mSearchBarContainerView.setVisibility(View.INVISIBLE);
246                             if (resetTextField) {
247                                 mSearchBarEditView.setText("");
248                             }
249                             mCb.clearSearchResult();
250                             if (postAnimationRunnable != null) {
251                                 postAnimationRunnable.run();
252                             }
253                         }
254                     });
255             mSearchButtonView.setTranslationX(-translationX);
256             mSearchButtonView.animate()
257                     .alpha(1f)
258                     .translationX(0)
259                     .setDuration(FADE_OUT_DURATION)
260                     .withLayer();
261         } else {
262             mSearchBarContainerView.setVisibility(View.INVISIBLE);
263             if (resetTextField) {
264                 mSearchBarEditView.setText("");
265             }
266             mCb.clearSearchResult();
267             mSearchButtonView.setAlpha(1f);
268             mSearchButtonView.setTranslationX(0f);
269             if (postAnimationRunnable != null) {
270                 postAnimationRunnable.run();
271             }
272         }
273         mInputMethodManager.hideSoftInputFromWindow(mContainerView.getWindowToken(), 0);
274     }
275 }
276