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