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 import android.view.animation.Interpolator; 35 import android.widget.EditText; 36 37 import com.android.launcher3.BaseDraggingActivity; 38 import com.android.launcher3.DeviceProfile; 39 import com.android.launcher3.ExtendedEditText; 40 import com.android.launcher3.Insettable; 41 import com.android.launcher3.R; 42 import com.android.launcher3.allapps.AllAppsContainerView; 43 import com.android.launcher3.allapps.AllAppsStore; 44 import com.android.launcher3.allapps.AlphabeticalAppsList; 45 import com.android.launcher3.allapps.SearchUiManager; 46 import com.android.launcher3.anim.PropertySetter; 47 import com.android.launcher3.util.ComponentKey; 48 49 import java.util.ArrayList; 50 51 /** 52 * Layout to contain the All-apps search UI. 53 */ 54 public class AppsSearchContainerLayout extends ExtendedEditText 55 implements SearchUiManager, AllAppsSearchBarController.Callbacks, 56 AllAppsStore.OnUpdateListener, Insettable { 57 58 private final BaseDraggingActivity mLauncher; 59 private final AllAppsSearchBarController mSearchBarController; 60 private final SpannableStringBuilder mSearchQueryBuilder; 61 62 private AlphabeticalAppsList mApps; 63 private AllAppsContainerView mAppsView; 64 65 // The amount of pixels to shift down and overlap with the rest of the content. 66 private final int mContentOverlap; 67 AppsSearchContainerLayout(Context context)68 public AppsSearchContainerLayout(Context context) { 69 this(context, null); 70 } 71 AppsSearchContainerLayout(Context context, AttributeSet attrs)72 public AppsSearchContainerLayout(Context context, AttributeSet attrs) { 73 this(context, attrs, 0); 74 } 75 AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr)76 public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) { 77 super(context, attrs, defStyleAttr); 78 79 mLauncher = BaseDraggingActivity.fromContext(context); 80 mSearchBarController = new AllAppsSearchBarController(); 81 82 mSearchQueryBuilder = new SpannableStringBuilder(); 83 Selection.setSelection(mSearchQueryBuilder, 0); 84 setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint())); 85 86 mContentOverlap = 87 getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height) / 2; 88 } 89 90 @Override onAttachedToWindow()91 protected void onAttachedToWindow() { 92 super.onAttachedToWindow(); 93 mAppsView.getAppsStore().addUpdateListener(this); 94 } 95 96 @Override onDetachedFromWindow()97 protected void onDetachedFromWindow() { 98 super.onDetachedFromWindow(); 99 mAppsView.getAppsStore().removeUpdateListener(this); 100 } 101 102 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)103 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 104 // Update the width to match the grid padding 105 DeviceProfile dp = mLauncher.getDeviceProfile(); 106 int myRequestedWidth = getSize(widthMeasureSpec); 107 int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft() 108 - mAppsView.getActiveRecyclerView().getPaddingRight(); 109 110 int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.inv.numHotseatIcons); 111 int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx); 112 int iconPadding = cellWidth - iconVisibleSize; 113 114 int myWidth = rowWidth - iconPadding + getPaddingLeft() + getPaddingRight(); 115 super.onMeasure(makeMeasureSpec(myWidth, EXACTLY), heightMeasureSpec); 116 } 117 118 @Override onLayout(boolean changed, int left, int top, int right, int bottom)119 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 120 super.onLayout(changed, left, top, right, bottom); 121 122 // Shift the widget horizontally so that its centered in the parent (b/63428078) 123 View parent = (View) getParent(); 124 int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(); 125 int myWidth = right - left; 126 int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2; 127 int shift = expectedLeft - left; 128 setTranslationX(shift); 129 130 offsetTopAndBottom(mContentOverlap); 131 } 132 133 @Override initialize(AllAppsContainerView appsView)134 public void initialize(AllAppsContainerView appsView) { 135 mApps = appsView.getApps(); 136 mAppsView = appsView; 137 mSearchBarController.initialize( 138 new DefaultAppSearchAlgorithm(mApps.getApps()), 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<ComponentKey> apps)171 public void onSearchResult(String query, ArrayList<ComponentKey> apps) { 172 if (apps != null) { 173 mApps.setOrderedFilter(apps); 174 notifyResultChanged(); 175 mAppsView.setLastSearchQuery(query); 176 } 177 } 178 179 @Override clearSearchResult()180 public void clearSearchResult() { 181 if (mApps.setOrderedFilter(null)) { 182 notifyResultChanged(); 183 } 184 185 // Clear the search query 186 mSearchQueryBuilder.clear(); 187 mSearchQueryBuilder.clearSpans(); 188 Selection.setSelection(mSearchQueryBuilder, 0); 189 mAppsView.onClearSearchResult(); 190 } 191 notifyResultChanged()192 private void notifyResultChanged() { 193 mAppsView.onSearchResultsChanged(); 194 } 195 196 @Override setInsets(Rect insets)197 public void setInsets(Rect insets) { 198 MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); 199 mlp.topMargin = insets.top; 200 requestLayout(); 201 } 202 203 @Override getScrollRangeDelta(Rect insets)204 public float getScrollRangeDelta(Rect insets) { 205 if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { 206 return 0; 207 } else { 208 return insets.bottom + insets.top; 209 } 210 } 211 212 @Override setContentVisibility(int visibleElements, PropertySetter setter, Interpolator interpolator)213 public void setContentVisibility(int visibleElements, PropertySetter setter, 214 Interpolator interpolator) { 215 setter.setViewAlpha(this, isQsbVisible(visibleElements) ? 1 : 0, interpolator); 216 } 217 218 @Override setTextSearchEnabled(boolean isEnabled)219 public EditText setTextSearchEnabled(boolean isEnabled) { 220 return this; 221 } 222 } 223