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