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