/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.allapps.search; import android.content.Context; import android.graphics.Rect; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.Selection; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.method.TextKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; import android.widget.FrameLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsGridAdapter; import com.android.launcher3.allapps.AllAppsRecyclerView; import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.allapps.SearchUiManager; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.discovery.AppDiscoveryItem; import com.android.launcher3.discovery.AppDiscoveryUpdateState; import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.util.ComponentKey; import java.util.ArrayList; /** * Layout to contain the All-apps search UI. */ public class AppsSearchContainerLayout extends FrameLayout implements SearchUiManager, AllAppsSearchBarController.Callbacks { private final Launcher mLauncher; private final int mMinHeight; private final int mSearchBoxHeight; private final AllAppsSearchBarController mSearchBarController; private final SpannableStringBuilder mSearchQueryBuilder; private ExtendedEditText mSearchInput; private AlphabeticalAppsList mApps; private AllAppsRecyclerView mAppsRecyclerView; private AllAppsGridAdapter mAdapter; private View mDivider; private HeaderElevationController mElevationController; public AppsSearchContainerLayout(Context context) { this(context, null); } public AppsSearchContainerLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mLauncher = Launcher.getLauncher(context); mMinHeight = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height); mSearchBoxHeight = getResources() .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height); mSearchBarController = new AllAppsSearchBarController(); mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); } @Override protected void onFinishInflate() { super.onFinishInflate(); mSearchInput = findViewById(R.id.search_box_input); mDivider = findViewById(R.id.search_divider); mElevationController = new HeaderElevationController(mDivider); // Update the hint to contain the icon. // Prefix the original hint with two spaces. The first space gets replaced by the icon // using span. The second space is used for a singe space character between the hint // and the icon. SpannableString spanned = new SpannableString(" " + mSearchInput.getHint()); spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search), 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); mSearchInput.setHint(spanned); DeviceProfile dp = mLauncher.getDeviceProfile(); if (!dp.isVerticalBarLayout()) { LayoutParams lp = (LayoutParams) mDivider.getLayoutParams(); lp.leftMargin = lp.rightMargin = dp.edgeMarginPx; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { getLayoutParams().height = mLauncher.getDragLayer().getInsets().top + mMinHeight; } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override public void initialize( AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView) { mApps = appsList; mAppsRecyclerView = recyclerView; mAppsRecyclerView.addOnScrollListener(mElevationController); mAdapter = (AllAppsGridAdapter) mAppsRecyclerView.getAdapter(); mSearchBarController.initialize( new DefaultAppSearchAlgorithm(appsList.getApps()), mSearchInput, mLauncher, this); } @Override public void refreshSearchResult() { mSearchBarController.refreshSearchResult(); } @Override public void reset() { mElevationController.reset(); mSearchBarController.reset(); } @Override public void preDispatchKeyEvent(KeyEvent event) { // Determine if the key event was actual text, if so, focus the search bar and then dispatch // the key normally so that it can process this key event if (!mSearchBarController.isSearchFieldFocused() && event.getAction() == KeyEvent.ACTION_DOWN) { final int unicodeChar = event.getUnicodeChar(); final boolean isKeyNotWhitespace = unicodeChar > 0 && !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar); if (isKeyNotWhitespace) { boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder, event.getKeyCode(), event); if (gotKey && mSearchQueryBuilder.length() > 0) { mSearchBarController.focusSearchField(); } } } } @Override public void onSearchResult(String query, ArrayList apps) { if (apps != null) { mApps.setOrderedFilter(apps); notifyResultChanged(); mAdapter.setLastSearchQuery(query); } } @Override public void clearSearchResult() { if (mApps.setOrderedFilter(null)) { notifyResultChanged(); } // Clear the search query mSearchQueryBuilder.clear(); mSearchQueryBuilder.clearSpans(); Selection.setSelection(mSearchQueryBuilder, 0); } @Override public void onAppDiscoverySearchUpdate( @Nullable AppDiscoveryItem app, @NonNull AppDiscoveryUpdateState state) { if (!mLauncher.isDestroyed()) { mApps.onAppDiscoverySearchUpdate(app, state); notifyResultChanged(); } } private void notifyResultChanged() { mElevationController.reset(); mAppsRecyclerView.onSearchResultsChanged(); } @Override public void addOnScrollRangeChangeListener(final OnScrollRangeChangeListener listener) { mLauncher.getHotseat().addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { DeviceProfile dp = mLauncher.getDeviceProfile(); if (!dp.isVerticalBarLayout()) { Rect insets = mLauncher.getDragLayer().getInsets(); int hotseatBottom = bottom - dp.hotseatBarBottomPaddingPx - insets.bottom; int searchTopMargin = insets.top + (mMinHeight - mSearchBoxHeight) + ((MarginLayoutParams) getLayoutParams()).bottomMargin; listener.onScrollRangeChanged(hotseatBottom - searchTopMargin); } else { listener.onScrollRangeChanged(bottom); } } }); } }