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 android.content.Context; 19 import android.graphics.Rect; 20 import android.support.animation.FloatValueHolder; 21 import android.support.animation.SpringAnimation; 22 import android.support.animation.SpringForce; 23 import android.support.annotation.NonNull; 24 import android.support.annotation.Nullable; 25 import android.text.Selection; 26 import android.text.Spannable; 27 import android.text.SpannableString; 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.widget.FrameLayout; 34 import com.android.launcher3.DeviceProfile; 35 import com.android.launcher3.ExtendedEditText; 36 import com.android.launcher3.Launcher; 37 import com.android.launcher3.R; 38 import com.android.launcher3.allapps.AllAppsGridAdapter; 39 import com.android.launcher3.allapps.AllAppsRecyclerView; 40 import com.android.launcher3.allapps.AlphabeticalAppsList; 41 import com.android.launcher3.allapps.SearchUiManager; 42 import com.android.launcher3.config.FeatureFlags; 43 import com.android.launcher3.discovery.AppDiscoveryItem; 44 import com.android.launcher3.discovery.AppDiscoveryUpdateState; 45 import com.android.launcher3.graphics.TintedDrawableSpan; 46 import com.android.launcher3.util.ComponentKey; 47 import java.util.ArrayList; 48 49 /** 50 * Layout to contain the All-apps search UI. 51 */ 52 public class AppsSearchContainerLayout extends FrameLayout 53 implements SearchUiManager, AllAppsSearchBarController.Callbacks { 54 55 private final Launcher mLauncher; 56 private final int mMinHeight; 57 private final int mSearchBoxHeight; 58 private final AllAppsSearchBarController mSearchBarController; 59 private final SpannableStringBuilder mSearchQueryBuilder; 60 61 private ExtendedEditText mSearchInput; 62 private AlphabeticalAppsList mApps; 63 private AllAppsRecyclerView mAppsRecyclerView; 64 private AllAppsGridAdapter mAdapter; 65 private View mDivider; 66 private HeaderElevationController mElevationController; 67 68 private SpringAnimation mSpring; 69 70 public AppsSearchContainerLayout(Context context) { 71 this(context, null); 72 } 73 74 public AppsSearchContainerLayout(Context context, AttributeSet attrs) { 75 this(context, attrs, 0); 76 } 77 78 public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) { 79 super(context, attrs, defStyleAttr); 80 81 mLauncher = Launcher.getLauncher(context); 82 mMinHeight = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height); 83 mSearchBoxHeight = getResources() 84 .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height); 85 mSearchBarController = new AllAppsSearchBarController(); 86 87 mSearchQueryBuilder = new SpannableStringBuilder(); 88 Selection.setSelection(mSearchQueryBuilder, 0); 89 90 // Note: This spring does nothing. 91 mSpring = new SpringAnimation(new FloatValueHolder()).setSpring(new SpringForce(0)); 92 } 93 94 @Override 95 protected void onFinishInflate() { 96 super.onFinishInflate(); 97 mSearchInput = findViewById(R.id.search_box_input); 98 mDivider = findViewById(R.id.search_divider); 99 mElevationController = new HeaderElevationController(mDivider); 100 101 // Update the hint to contain the icon. 102 // Prefix the original hint with two spaces. The first space gets replaced by the icon 103 // using span. The second space is used for a singe space character between the hint 104 // and the icon. 105 SpannableString spanned = new SpannableString(" " + mSearchInput.getHint()); 106 spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search), 107 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 108 mSearchInput.setHint(spanned); 109 110 DeviceProfile dp = mLauncher.getDeviceProfile(); 111 if (!dp.isVerticalBarLayout()) { 112 LayoutParams lp = (LayoutParams) mDivider.getLayoutParams(); 113 lp.leftMargin = lp.rightMargin = dp.edgeMarginPx; 114 } 115 } 116 117 @Override 118 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 119 if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { 120 getLayoutParams().height = mLauncher.getDragLayer().getInsets().top + mMinHeight; 121 } 122 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 123 } 124 125 126 @Override 127 public void initialize( 128 AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView) { 129 mApps = appsList; 130 mAppsRecyclerView = recyclerView; 131 mAppsRecyclerView.addOnScrollListener(mElevationController); 132 mAdapter = (AllAppsGridAdapter) mAppsRecyclerView.getAdapter(); 133 mSearchBarController.initialize( 134 new DefaultAppSearchAlgorithm(appsList.getApps()), mSearchInput, mLauncher, this); 135 } 136 137 @Override 138 public @NonNull SpringAnimation getSpringForFling() { 139 return mSpring; 140 } 141 142 @Override 143 public void refreshSearchResult() { 144 mSearchBarController.refreshSearchResult(); 145 } 146 147 @Override 148 public void reset() { 149 mElevationController.reset(); 150 mSearchBarController.reset(); 151 } 152 153 @Override 154 public void preDispatchKeyEvent(KeyEvent event) { 155 // Determine if the key event was actual text, if so, focus the search bar and then dispatch 156 // the key normally so that it can process this key event 157 if (!mSearchBarController.isSearchFieldFocused() && 158 event.getAction() == KeyEvent.ACTION_DOWN) { 159 final int unicodeChar = event.getUnicodeChar(); 160 final boolean isKeyNotWhitespace = unicodeChar > 0 && 161 !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar); 162 if (isKeyNotWhitespace) { 163 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder, 164 event.getKeyCode(), event); 165 if (gotKey && mSearchQueryBuilder.length() > 0) { 166 mSearchBarController.focusSearchField(); 167 } 168 } 169 } 170 } 171 172 @Override 173 public void onSearchResult(String query, ArrayList<ComponentKey> apps) { 174 if (apps != null) { 175 mApps.setOrderedFilter(apps); 176 notifyResultChanged(); 177 mAdapter.setLastSearchQuery(query); 178 } 179 } 180 181 @Override 182 public void clearSearchResult() { 183 if (mApps.setOrderedFilter(null)) { 184 notifyResultChanged(); 185 } 186 187 // Clear the search query 188 mSearchQueryBuilder.clear(); 189 mSearchQueryBuilder.clearSpans(); 190 Selection.setSelection(mSearchQueryBuilder, 0); 191 } 192 193 @Override 194 public void onAppDiscoverySearchUpdate( 195 @Nullable AppDiscoveryItem app, @NonNull AppDiscoveryUpdateState state) { 196 if (!mLauncher.isDestroyed()) { 197 mApps.onAppDiscoverySearchUpdate(app, state); 198 notifyResultChanged(); 199 } 200 } 201 202 private void notifyResultChanged() { 203 mElevationController.reset(); 204 mAppsRecyclerView.onSearchResultsChanged(); 205 } 206 207 @Override 208 public void addOnScrollRangeChangeListener(final OnScrollRangeChangeListener listener) { 209 mLauncher.getHotseat().addOnLayoutChangeListener(new OnLayoutChangeListener() { 210 @Override 211 public void onLayoutChange(View v, int left, int top, int right, int bottom, 212 int oldLeft, int oldTop, int oldRight, int oldBottom) { 213 DeviceProfile dp = mLauncher.getDeviceProfile(); 214 if (!dp.isVerticalBarLayout()) { 215 Rect insets = mLauncher.getDragLayer().getInsets(); 216 int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom; 217 int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight) 218 + ((MarginLayoutParams) getLayoutParams()).bottomMargin; 219 listener.onScrollRangeChanged(hotseatBottom - searchTopMargin); 220 } else { 221 listener.onScrollRangeChanged(bottom); 222 } 223 } 224 }); 225 } 226 } 227