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.Utilities.prefixTextWithIcon;
23 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
24 
25 import android.content.Context;
26 import android.graphics.Rect;
27 import android.text.Selection;
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.view.ViewGroup.MarginLayoutParams;
34 
35 import com.android.launcher3.BaseDraggingActivity;
36 import com.android.launcher3.DeviceProfile;
37 import com.android.launcher3.ExtendedEditText;
38 import com.android.launcher3.Insettable;
39 import com.android.launcher3.R;
40 import com.android.launcher3.allapps.AllAppsContainerView;
41 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
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.search.SearchCallback;
46 
47 import java.util.ArrayList;
48 
49 /**
50  * Layout to contain the All-apps search UI.
51  */
52 public class AppsSearchContainerLayout extends ExtendedEditText
53         implements SearchUiManager, SearchCallback<AdapterItem>,
54         AllAppsStore.OnUpdateListener, Insettable {
55 
56     private final BaseDraggingActivity mLauncher;
57     private final AllAppsSearchBarController mSearchBarController;
58     private final SpannableStringBuilder mSearchQueryBuilder;
59 
60     private AlphabeticalAppsList mApps;
61     private AllAppsContainerView mAppsView;
62 
63     // The amount of pixels to shift down and overlap with the rest of the content.
64     private final int mContentOverlap;
65 
AppsSearchContainerLayout(Context context)66     public AppsSearchContainerLayout(Context context) {
67         this(context, null);
68     }
69 
AppsSearchContainerLayout(Context context, AttributeSet attrs)70     public AppsSearchContainerLayout(Context context, AttributeSet attrs) {
71         this(context, attrs, 0);
72     }
73 
AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr)74     public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
75         super(context, attrs, defStyleAttr);
76 
77         mLauncher = BaseDraggingActivity.fromContext(context);
78         mSearchBarController = new AllAppsSearchBarController();
79 
80         mSearchQueryBuilder = new SpannableStringBuilder();
81         Selection.setSelection(mSearchQueryBuilder, 0);
82         setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint()));
83 
84         mContentOverlap =
85                 getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height) / 2;
86     }
87 
88     @Override
onAttachedToWindow()89     protected void onAttachedToWindow() {
90         super.onAttachedToWindow();
91         mAppsView.getAppsStore().addUpdateListener(this);
92     }
93 
94     @Override
onDetachedFromWindow()95     protected void onDetachedFromWindow() {
96         super.onDetachedFromWindow();
97         mAppsView.getAppsStore().removeUpdateListener(this);
98     }
99 
100     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)101     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
102         // Update the width to match the grid padding
103         DeviceProfile dp = mLauncher.getDeviceProfile();
104         int myRequestedWidth = getSize(widthMeasureSpec);
105         int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
106                 - mAppsView.getActiveRecyclerView().getPaddingRight();
107 
108         int cellWidth = DeviceProfile.calculateCellWidth(rowWidth,
109                 dp.cellLayoutBorderSpacePx.x, dp.numShownHotseatIcons);
110         int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx);
111         int iconPadding = cellWidth - iconVisibleSize;
112 
113         int myWidth = rowWidth - iconPadding + getPaddingLeft() + getPaddingRight();
114         super.onMeasure(makeMeasureSpec(myWidth, EXACTLY), heightMeasureSpec);
115     }
116 
117     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)118     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
119         super.onLayout(changed, left, top, right, bottom);
120 
121         // Shift the widget horizontally so that its centered in the parent (b/63428078)
122         View parent = (View) getParent();
123         int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight();
124         int myWidth = right - left;
125         int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2;
126         int shift = expectedLeft - left;
127         setTranslationX(shift);
128 
129         offsetTopAndBottom(mContentOverlap);
130     }
131 
132     @Override
initializeSearch(AllAppsContainerView appsView)133     public void initializeSearch(AllAppsContainerView appsView) {
134         mApps = appsView.getApps();
135         mAppsView = appsView;
136         mSearchBarController.initialize(
137                 new DefaultAppSearchAlgorithm(mLauncher),
138                 this, mLauncher, this);
139     }
140 
141     @Override
onAppsUpdated()142     public void onAppsUpdated() {
143         mSearchBarController.refreshSearchResult();
144     }
145 
146     @Override
resetSearch()147     public void resetSearch() {
148         mSearchBarController.reset();
149     }
150 
151     @Override
preDispatchKeyEvent(KeyEvent event)152     public void preDispatchKeyEvent(KeyEvent event) {
153         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
154         // the key normally so that it can process this key event
155         if (!mSearchBarController.isSearchFieldFocused() &&
156                 event.getAction() == KeyEvent.ACTION_DOWN) {
157             final int unicodeChar = event.getUnicodeChar();
158             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
159                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
160             if (isKeyNotWhitespace) {
161                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
162                         event.getKeyCode(), event);
163                 if (gotKey && mSearchQueryBuilder.length() > 0) {
164                     mSearchBarController.focusSearchField();
165                 }
166             }
167         }
168     }
169 
170     @Override
onSearchResult(String query, ArrayList<AdapterItem> items)171     public void onSearchResult(String query, ArrayList<AdapterItem> items) {
172         if (items != null) {
173             mApps.setSearchResults(items);
174             notifyResultChanged();
175             mAppsView.setLastSearchQuery(query);
176         }
177     }
178 
179     @Override
onAppendSearchResult(String query, ArrayList<AdapterItem> items)180     public void onAppendSearchResult(String query, ArrayList<AdapterItem> items) {
181         if (items != null) {
182             mApps.appendSearchResults(items);
183             notifyResultChanged();
184         }
185     }
186 
187     @Override
clearSearchResult()188     public void clearSearchResult() {
189         if (mApps.setSearchResults(null)) {
190             notifyResultChanged();
191         }
192 
193         // Clear the search query
194         mSearchQueryBuilder.clear();
195         mSearchQueryBuilder.clearSpans();
196         Selection.setSelection(mSearchQueryBuilder, 0);
197         mAppsView.onClearSearchResult();
198     }
199 
notifyResultChanged()200     private void notifyResultChanged() {
201         mAppsView.onSearchResultsChanged();
202     }
203 
204     @Override
setInsets(Rect insets)205     public void setInsets(Rect insets) {
206         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
207         mlp.topMargin = insets.top;
208         requestLayout();
209     }
210 
211     @Override
getEditText()212     public ExtendedEditText getEditText() {
213         return this;
214     }
215 }
216