| /* |
| * 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<ComponentKey> 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); |
| } |
| } |
| }); |
| } |
| } |