Home | History | Annotate | Download | only in search
      1 /*
      2  * Copyright (C) 2017 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 android.content.Context;
     19 import android.graphics.Rect;
     20 import android.support.animation.FloatValueHolder;
     21 import android.support.animation.SpringAnimation;
     22 import android.support.animation.SpringForce;
     23 import android.support.annotation.NonNull;
     24 import android.support.annotation.Nullable;
     25 import android.text.Selection;
     26 import android.text.Spannable;
     27 import android.text.SpannableString;
     28 import android.text.SpannableStringBuilder;
     29 import android.text.method.TextKeyListener;
     30 import android.util.AttributeSet;
     31 import android.view.KeyEvent;
     32 import android.view.View;
     33 import android.widget.FrameLayout;
     34 import com.android.launcher3.DeviceProfile;
     35 import com.android.launcher3.ExtendedEditText;
     36 import com.android.launcher3.Launcher;
     37 import com.android.launcher3.R;
     38 import com.android.launcher3.allapps.AllAppsGridAdapter;
     39 import com.android.launcher3.allapps.AllAppsRecyclerView;
     40 import com.android.launcher3.allapps.AlphabeticalAppsList;
     41 import com.android.launcher3.allapps.SearchUiManager;
     42 import com.android.launcher3.config.FeatureFlags;
     43 import com.android.launcher3.discovery.AppDiscoveryItem;
     44 import com.android.launcher3.discovery.AppDiscoveryUpdateState;
     45 import com.android.launcher3.graphics.TintedDrawableSpan;
     46 import com.android.launcher3.util.ComponentKey;
     47 import java.util.ArrayList;
     48 
     49 /**
     50  * Layout to contain the All-apps search UI.
     51  */
     52 public class AppsSearchContainerLayout extends FrameLayout
     53         implements SearchUiManager, AllAppsSearchBarController.Callbacks {
     54 
     55     private final Launcher mLauncher;
     56     private final int mMinHeight;
     57     private final int mSearchBoxHeight;
     58     private final AllAppsSearchBarController mSearchBarController;
     59     private final SpannableStringBuilder mSearchQueryBuilder;
     60 
     61     private ExtendedEditText mSearchInput;
     62     private AlphabeticalAppsList mApps;
     63     private AllAppsRecyclerView mAppsRecyclerView;
     64     private AllAppsGridAdapter mAdapter;
     65     private View mDivider;
     66     private HeaderElevationController mElevationController;
     67 
     68     private SpringAnimation mSpring;
     69 
     70     public AppsSearchContainerLayout(Context context) {
     71         this(context, null);
     72     }
     73 
     74     public AppsSearchContainerLayout(Context context, AttributeSet attrs) {
     75         this(context, attrs, 0);
     76     }
     77 
     78     public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
     79         super(context, attrs, defStyleAttr);
     80 
     81         mLauncher = Launcher.getLauncher(context);
     82         mMinHeight = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
     83         mSearchBoxHeight = getResources()
     84                 .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height);
     85         mSearchBarController = new AllAppsSearchBarController();
     86 
     87         mSearchQueryBuilder = new SpannableStringBuilder();
     88         Selection.setSelection(mSearchQueryBuilder, 0);
     89 
     90         // Note: This spring does nothing.
     91         mSpring = new SpringAnimation(new FloatValueHolder()).setSpring(new SpringForce(0));
     92     }
     93 
     94     @Override
     95     protected void onFinishInflate() {
     96         super.onFinishInflate();
     97         mSearchInput = findViewById(R.id.search_box_input);
     98         mDivider = findViewById(R.id.search_divider);
     99         mElevationController = new HeaderElevationController(mDivider);
    100 
    101         // Update the hint to contain the icon.
    102         // Prefix the original hint with two spaces. The first space gets replaced by the icon
    103         // using span. The second space is used for a singe space character between the hint
    104         // and the icon.
    105         SpannableString spanned = new SpannableString("  " + mSearchInput.getHint());
    106         spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
    107                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
    108         mSearchInput.setHint(spanned);
    109 
    110         DeviceProfile dp = mLauncher.getDeviceProfile();
    111         if (!dp.isVerticalBarLayout()) {
    112             LayoutParams lp = (LayoutParams) mDivider.getLayoutParams();
    113             lp.leftMargin = lp.rightMargin = dp.edgeMarginPx;
    114         }
    115     }
    116 
    117     @Override
    118     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    119         if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
    120             getLayoutParams().height = mLauncher.getDragLayer().getInsets().top + mMinHeight;
    121         }
    122         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    123     }
    124 
    125 
    126     @Override
    127     public void initialize(
    128             AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView) {
    129         mApps = appsList;
    130         mAppsRecyclerView = recyclerView;
    131         mAppsRecyclerView.addOnScrollListener(mElevationController);
    132         mAdapter = (AllAppsGridAdapter) mAppsRecyclerView.getAdapter();
    133         mSearchBarController.initialize(
    134                 new DefaultAppSearchAlgorithm(appsList.getApps()), mSearchInput, mLauncher, this);
    135     }
    136 
    137     @Override
    138     public @NonNull SpringAnimation getSpringForFling() {
    139         return mSpring;
    140     }
    141 
    142     @Override
    143     public void refreshSearchResult() {
    144         mSearchBarController.refreshSearchResult();
    145     }
    146 
    147     @Override
    148     public void reset() {
    149         mElevationController.reset();
    150         mSearchBarController.reset();
    151     }
    152 
    153     @Override
    154     public void preDispatchKeyEvent(KeyEvent event) {
    155         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
    156         // the key normally so that it can process this key event
    157         if (!mSearchBarController.isSearchFieldFocused() &&
    158                 event.getAction() == KeyEvent.ACTION_DOWN) {
    159             final int unicodeChar = event.getUnicodeChar();
    160             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
    161                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
    162             if (isKeyNotWhitespace) {
    163                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
    164                         event.getKeyCode(), event);
    165                 if (gotKey && mSearchQueryBuilder.length() > 0) {
    166                     mSearchBarController.focusSearchField();
    167                 }
    168             }
    169         }
    170     }
    171 
    172     @Override
    173     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
    174         if (apps != null) {
    175             mApps.setOrderedFilter(apps);
    176             notifyResultChanged();
    177             mAdapter.setLastSearchQuery(query);
    178         }
    179     }
    180 
    181     @Override
    182     public void clearSearchResult() {
    183         if (mApps.setOrderedFilter(null)) {
    184             notifyResultChanged();
    185         }
    186 
    187         // Clear the search query
    188         mSearchQueryBuilder.clear();
    189         mSearchQueryBuilder.clearSpans();
    190         Selection.setSelection(mSearchQueryBuilder, 0);
    191     }
    192 
    193     @Override
    194     public void onAppDiscoverySearchUpdate(
    195             @Nullable AppDiscoveryItem app, @NonNull AppDiscoveryUpdateState state) {
    196         if (!mLauncher.isDestroyed()) {
    197             mApps.onAppDiscoverySearchUpdate(app, state);
    198             notifyResultChanged();
    199         }
    200     }
    201 
    202     private void notifyResultChanged() {
    203         mElevationController.reset();
    204         mAppsRecyclerView.onSearchResultsChanged();
    205     }
    206 
    207     @Override
    208     public void addOnScrollRangeChangeListener(final OnScrollRangeChangeListener listener) {
    209         mLauncher.getHotseat().addOnLayoutChangeListener(new OnLayoutChangeListener() {
    210             @Override
    211             public void onLayoutChange(View v, int left, int top, int right, int bottom,
    212                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
    213                 DeviceProfile dp = mLauncher.getDeviceProfile();
    214                 if (!dp.isVerticalBarLayout()) {
    215                     Rect insets = mLauncher.getDragLayer().getInsets();
    216                     int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom;
    217                     int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight)
    218                             + ((MarginLayoutParams) getLayoutParams()).bottomMargin;
    219                     listener.onScrollRangeChanged(hotseatBottom - searchTopMargin);
    220                 } else {
    221                     listener.onScrollRangeChanged(bottom);
    222                 }
    223             }
    224         });
    225     }
    226 }
    227