• 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 static android.view.View.MeasureSpec.EXACTLY;
19 import static android.view.View.MeasureSpec.getSize;
20 import static android.view.View.MeasureSpec.makeMeasureSpec;
21 
22 import static com.android.launcher3.graphics.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
23 
24 import android.content.Context;
25 import android.graphics.Rect;
26 import android.text.Selection;
27 import android.text.Spannable;
28 import android.text.SpannableString;
29 import android.text.SpannableStringBuilder;
30 import android.text.method.TextKeyListener;
31 import android.util.AttributeSet;
32 import android.view.KeyEvent;
33 import android.view.View;
34 import android.view.ViewGroup.MarginLayoutParams;
35 
36 import com.android.launcher3.DeviceProfile;
37 import com.android.launcher3.ExtendedEditText;
38 import com.android.launcher3.Insettable;
39 import com.android.launcher3.Launcher;
40 import com.android.launcher3.R;
41 import com.android.launcher3.allapps.AllAppsContainerView;
42 import com.android.launcher3.allapps.AllAppsStore;
43 import com.android.launcher3.allapps.AlphabeticalAppsList;
44 import com.android.launcher3.allapps.SearchUiManager;
45 import com.android.launcher3.graphics.TintedDrawableSpan;
46 import com.android.launcher3.util.ComponentKey;
47 
48 import java.util.ArrayList;
49 
50 /**
51  * Layout to contain the All-apps search UI.
52  */
53 public class AppsSearchContainerLayout extends ExtendedEditText
54         implements SearchUiManager, AllAppsSearchBarController.Callbacks,
55         AllAppsStore.OnUpdateListener, Insettable {
56 
57 
58     private final Launcher mLauncher;
59     private final AllAppsSearchBarController mSearchBarController;
60     private final SpannableStringBuilder mSearchQueryBuilder;
61 
62     private AlphabeticalAppsList mApps;
63     private AllAppsContainerView mAppsView;
64 
65     // This value was used to position the QSB. We store it here for translationY animations.
66     private final float mFixedTranslationY;
67     private final float mMarginTopAdjusting;
68 
AppsSearchContainerLayout(Context context)69     public AppsSearchContainerLayout(Context context) {
70         this(context, null);
71     }
72 
AppsSearchContainerLayout(Context context, AttributeSet attrs)73     public AppsSearchContainerLayout(Context context, AttributeSet attrs) {
74         this(context, attrs, 0);
75     }
76 
AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr)77     public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
78         super(context, attrs, defStyleAttr);
79 
80         mLauncher = Launcher.getLauncher(context);
81         mSearchBarController = new AllAppsSearchBarController();
82 
83         mSearchQueryBuilder = new SpannableStringBuilder();
84         Selection.setSelection(mSearchQueryBuilder, 0);
85 
86         mFixedTranslationY = getTranslationY();
87         mMarginTopAdjusting = mFixedTranslationY - getPaddingTop();
88 
89         // Update the hint to contain the icon.
90         // Prefix the original hint with two spaces. The first space gets replaced by the icon
91         // using span. The second space is used for a singe space character between the hint
92         // and the icon.
93         SpannableString spanned = new SpannableString("  " + getHint());
94         spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
95                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
96         setHint(spanned);
97     }
98 
99     @Override
onAttachedToWindow()100     protected void onAttachedToWindow() {
101         super.onAttachedToWindow();
102         mLauncher.getAppsView().getAppsStore().addUpdateListener(this);
103     }
104 
105     @Override
onDetachedFromWindow()106     protected void onDetachedFromWindow() {
107         super.onDetachedFromWindow();
108         mLauncher.getAppsView().getAppsStore().removeUpdateListener(this);
109     }
110 
111     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)112     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
113         // Update the width to match the grid padding
114         DeviceProfile dp = mLauncher.getDeviceProfile();
115         int myRequestedWidth = getSize(widthMeasureSpec);
116         int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
117                 - mAppsView.getActiveRecyclerView().getPaddingRight();
118 
119         int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.inv.numHotseatIcons);
120         int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx);
121         int iconPadding = cellWidth - iconVisibleSize;
122 
123         int myWidth = rowWidth - iconPadding + getPaddingLeft() + getPaddingRight();
124         super.onMeasure(makeMeasureSpec(myWidth, EXACTLY), heightMeasureSpec);
125     }
126 
127     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)128     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
129         super.onLayout(changed, left, top, right, bottom);
130 
131         // Shift the widget horizontally so that its centered in the parent (b/63428078)
132         View parent = (View) getParent();
133         int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight();
134         int myWidth = right - left;
135         int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2;
136         int shift = expectedLeft - left;
137         setTranslationX(shift);
138     }
139 
140     @Override
initialize(AllAppsContainerView appsView)141     public void initialize(AllAppsContainerView appsView) {
142         mApps = appsView.getApps();
143         mAppsView = appsView;
144         mSearchBarController.initialize(
145                 new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this);
146     }
147 
148     @Override
onAppsUpdated()149     public void onAppsUpdated() {
150         mSearchBarController.refreshSearchResult();
151     }
152 
153     @Override
resetSearch()154     public void resetSearch() {
155         mSearchBarController.reset();
156     }
157 
158     @Override
preDispatchKeyEvent(KeyEvent event)159     public void preDispatchKeyEvent(KeyEvent event) {
160         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
161         // the key normally so that it can process this key event
162         if (!mSearchBarController.isSearchFieldFocused() &&
163                 event.getAction() == KeyEvent.ACTION_DOWN) {
164             final int unicodeChar = event.getUnicodeChar();
165             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
166                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
167             if (isKeyNotWhitespace) {
168                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
169                         event.getKeyCode(), event);
170                 if (gotKey && mSearchQueryBuilder.length() > 0) {
171                     mSearchBarController.focusSearchField();
172                 }
173             }
174         }
175     }
176 
177     @Override
onSearchResult(String query, ArrayList<ComponentKey> apps)178     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
179         if (apps != null) {
180             mApps.setOrderedFilter(apps);
181             notifyResultChanged();
182             mAppsView.setLastSearchQuery(query);
183         }
184     }
185 
186     @Override
clearSearchResult()187     public void clearSearchResult() {
188         if (mApps.setOrderedFilter(null)) {
189             notifyResultChanged();
190         }
191 
192         // Clear the search query
193         mSearchQueryBuilder.clear();
194         mSearchQueryBuilder.clearSpans();
195         Selection.setSelection(mSearchQueryBuilder, 0);
196         mAppsView.onClearSearchResult();
197     }
198 
notifyResultChanged()199     private void notifyResultChanged() {
200         mAppsView.onSearchResultsChanged();
201     }
202 
203     @Override
setInsets(Rect insets)204     public void setInsets(Rect insets) {
205         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
206         mlp.topMargin = Math.round(Math.max(-mFixedTranslationY, insets.top - mMarginTopAdjusting));
207         requestLayout();
208 
209         DeviceProfile dp = mLauncher.getDeviceProfile();
210         if (dp.isVerticalBarLayout()) {
211             mLauncher.getAllAppsController().setScrollRangeDelta(0);
212         } else {
213             mLauncher.getAllAppsController().setScrollRangeDelta(
214                     insets.bottom + mlp.topMargin + mFixedTranslationY);
215         }
216     }
217 }
218