• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AppsSearchContainerLayout(Context context)70     public AppsSearchContainerLayout(Context context) {
71         this(context, null);
72     }
73 
AppsSearchContainerLayout(Context context, AttributeSet attrs)74     public AppsSearchContainerLayout(Context context, AttributeSet attrs) {
75         this(context, attrs, 0);
76     }
77 
AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr)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
onFinishInflate()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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
initialize( AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView)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
getSpringForFling()138     public @NonNull SpringAnimation getSpringForFling() {
139         return mSpring;
140     }
141 
142     @Override
refreshSearchResult()143     public void refreshSearchResult() {
144         mSearchBarController.refreshSearchResult();
145     }
146 
147     @Override
reset()148     public void reset() {
149         mElevationController.reset();
150         mSearchBarController.reset();
151     }
152 
153     @Override
preDispatchKeyEvent(KeyEvent event)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
onSearchResult(String query, ArrayList<ComponentKey> apps)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
clearSearchResult()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
onAppDiscoverySearchUpdate( @ullable AppDiscoveryItem app, @NonNull AppDiscoveryUpdateState state)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 
notifyResultChanged()202     private void notifyResultChanged() {
203         mElevationController.reset();
204         mAppsRecyclerView.onSearchResultsChanged();
205     }
206 
207     @Override
addOnScrollRangeChangeListener(final OnScrollRangeChangeListener listener)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