Moving apps search related logic into a custom layout file

This will allow derivative projects to easily change the search behavior
by simply overriding the xml file

Bug: 37616877
Change-Id: Ib8d6a2dab06819a52611e9a3d97c70c5a49bbf97
This commit is contained in:
Sunny Goyal 2017-05-07 11:56:00 -07:00
parent b73fa5d7a4
commit 161f96bc77
19 changed files with 355 additions and 320 deletions

View File

@ -64,33 +64,9 @@
android:layout_alignParentEnd="true"
android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />
<FrameLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="center|top"
android:gravity="center|bottom"
android:orientation="horizontal"
android:saveEnabled="false">
<com.android.launcher3.ExtendedEditText
android:id="@+id/search_box_input"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:background="@android:color/transparent"
android:layout_gravity="bottom"
android:focusableInTouchMode="true"
android:gravity="center"
android:imeOptions="actionSearch|flagNoExtractUi"
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:hint="@string/all_apps_search_bar_hint"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp" />
</FrameLayout>
<include
layout="@layout/all_apps_search_container"
android:id="@+id/search_container" />
</com.android.launcher3.allapps.AllAppsRecyclerViewContainerView>
<View

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<com.android.launcher3.allapps.search.AppsSearchContainerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="center|top"
android:gravity="center|bottom"
android:id="@+id/search_container"
android:saveEnabled="false">
<com.android.launcher3.ExtendedEditText
android:id="@+id/search_box_input"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:background="@android:color/transparent"
android:layout_gravity="bottom"
android:focusableInTouchMode="true"
android:gravity="center"
android:imeOptions="actionSearch|flagNoExtractUi"
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:hint="@string/all_apps_search_bar_hint"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp" />
</com.android.launcher3.allapps.search.AppsSearchContainerLayout>

View File

@ -51,7 +51,6 @@
<!-- Fast scroller popup -->
<TextView
style="@style/FastScrollerPopup"
android:layout_below="@+id/search_container"
android:id="@+id/fast_scroller_popup"
android:layout_gravity="top|end"
android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />

View File

@ -13,6 +13,10 @@
easily override the app name without providing all translations -->
<string name="derived_app_name" translatable="false">@string/app_name</string>
<!-- String representing the intent for search on the apps market. To specify a query, add
q=<query> to the data to the intent -->
<string name="market_search_intent" translatable="false">market://search?c=apps</string>
<!-- Values for icon shape overrides. These should correspond to entries defined
in icon_shape_override_paths_names -->
<string-array name="icon_shape_override_paths_values">

View File

@ -82,10 +82,6 @@ public abstract class BaseRecyclerView extends RecyclerView
}
}
public void reset() {
mScrollbar.reattachThumbToScroll();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();

View File

@ -85,7 +85,6 @@ import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DefaultAppSearchController;
import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
@ -560,47 +559,6 @@ public class Launcher extends BaseActivity
public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
mLauncherCallbacks = callbacks;
mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() {
private boolean mWorkspaceImportanceStored = false;
private boolean mHotseatImportanceStored = false;
private int mWorkspaceImportanceForAccessibility =
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
private int mHotseatImportanceForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
@Override
public void onSearchOverlayOpened() {
if (mWorkspaceImportanceStored || mHotseatImportanceStored) {
return;
}
// The underlying workspace and hotseat are temporarily suppressed by the search
// overlay. So they shouldn't be accessible.
if (mWorkspace != null) {
mWorkspaceImportanceForAccessibility =
mWorkspace.getImportantForAccessibility();
mWorkspace.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
mWorkspaceImportanceStored = true;
}
if (mHotseat != null) {
mHotseatImportanceForAccessibility = mHotseat.getImportantForAccessibility();
mHotseat.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
mHotseatImportanceStored = true;
}
}
@Override
public void onSearchOverlayClosed() {
if (mWorkspaceImportanceStored && mWorkspace != null) {
mWorkspace.setImportantForAccessibility(mWorkspaceImportanceForAccessibility);
}
if (mHotseatImportanceStored && mHotseat != null) {
mHotseat.setImportantForAccessibility(mHotseatImportanceForAccessibility);
}
mWorkspaceImportanceStored = false;
mHotseatImportanceStored = false;
}
});
return true;
}
@ -1140,18 +1098,6 @@ public class Launcher extends BaseActivity
public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
}
public interface LauncherSearchCallbacks {
/**
* Called when the search overlay is shown.
*/
public void onSearchOverlayOpened();
/**
* Called when the search overlay is dismissed.
*/
public void onSearchOverlayClosed();
}
public interface LauncherOverlayCallbacks {
public void onScrollChanged(float progress);
}
@ -1344,11 +1290,6 @@ public class Launcher extends BaseActivity
// Setup Apps and Widgets
mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {
mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
} else {
mAppsView.setSearchBarController(new DefaultAppSearchController());
}
// Setup the drag controller (drop targets have to be added in reverse order in priority)
mDragController.setMoveTarget(mWorkspace);
@ -1777,7 +1718,7 @@ public class Launcher extends BaseActivity
// Reset the apps view
if (!alreadyOnHome && mAppsView != null) {
mAppsView.scrollToTop();
mAppsView.reset();
}
// Reset the widgets view

View File

@ -21,7 +21,6 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import com.android.launcher3.allapps.AllAppsSearchBarController;
import com.android.launcher3.util.ComponentKey;
import java.io.FileDescriptor;
@ -92,20 +91,11 @@ public interface LauncherCallbacks {
*/
boolean shouldMoveToDefaultScreenOnHomeIntent();
boolean hasSettings();
AllAppsSearchBarController getAllAppsSearchBarController();
List<ComponentKey> getPredictedApps();
int SEARCH_BAR_HEIGHT_NORMAL = 0, SEARCH_BAR_HEIGHT_TALL = 1;
/** Must return one of {@link #SEARCH_BAR_HEIGHT_NORMAL} or {@link #SEARCH_BAR_HEIGHT_TALL} */
int getSearchBarHeight();
/**
* Sets the callbacks to allow reacting the actions of search overlays of the launcher.
*
* @param callbacks A set of callbacks to the Launcher, is actually a LauncherSearchCallback,
* but for implementation purposes is passed around as an object.
*/
void setLauncherSearchCallback(Object callbacks);
boolean shouldShowDiscoveryBounce();
void onExtractedColorsChanged();

View File

@ -20,15 +20,9 @@ import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.InsetDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
@ -42,7 +36,6 @@ import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
@ -50,18 +43,14 @@ import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.discovery.AppDiscoveryItem;
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -69,7 +58,7 @@ import java.util.Set;
* The all apps view container.
*/
public class AllAppsContainerView extends BaseContainerView implements DragSource,
View.OnLongClickListener, AllAppsSearchBarController.Callbacks, Insettable {
View.OnLongClickListener, Insettable {
private final Launcher mLauncher;
private final AlphabeticalAppsList mApps;
@ -77,12 +66,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
private final RecyclerView.LayoutManager mLayoutManager;
private AllAppsRecyclerView mAppsRecyclerView;
private AllAppsSearchBarController mSearchBarController;
private SearchUiManager mSearchUiManager;
private View mSearchContainer;
private int mSearchContainerMinHeight;
private ExtendedEditText mSearchInput;
private HeaderElevationController mElevationController;
private SpannableStringBuilder mSearchQueryBuilder = null;
@ -106,8 +91,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
mSearchQueryBuilder = new SpannableStringBuilder();
mSearchContainerMinHeight
= getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
Selection.setSelection(mSearchQueryBuilder, 0);
}
@ -149,7 +132,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void addApps(List<AppInfo> apps) {
mApps.addApps(apps);
mSearchBarController.refreshSearchResult();
mSearchUiManager.refreshSearchResult();
}
/**
@ -157,7 +140,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void updateApps(List<AppInfo> apps) {
mApps.updateApps(apps);
mSearchBarController.refreshSearchResult();
mSearchUiManager.refreshSearchResult();
}
public void updatePromiseAppProgress(PromiseAppInfo app) {
@ -176,34 +159,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void removeApps(List<AppInfo> apps) {
mApps.removeApps(apps);
mSearchBarController.refreshSearchResult();
}
public void setSearchBarVisible(boolean visible) {
if (visible) {
mSearchBarController.setVisibility(View.VISIBLE);
} else {
mSearchBarController.setVisibility(View.INVISIBLE);
}
}
/**
* Sets the search bar that shows above the a-z list.
*/
public void setSearchBarController(AllAppsSearchBarController searchController) {
if (mSearchBarController != null) {
throw new RuntimeException("Expected search bar controller to only be set once");
}
mSearchBarController = searchController;
mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this);
mAdapter.setSearchController(mSearchBarController);
}
/**
* Scrolls this list view to the top.
*/
public void scrollToTop() {
mAppsRecyclerView.scrollToTop();
mSearchUiManager.refreshSearchResult();
}
/**
@ -238,9 +194,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
* Focuses the search field and begins an app search.
*/
public void startAppsSearch() {
if (mSearchBarController != null) {
mSearchBarController.focusSearchField();
}
mSearchUiManager.startAppsSearch();
}
/**
@ -248,9 +202,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
*/
public void reset() {
// Reset the search bar and base recycler view after transitioning home
scrollToTop();
mSearchBarController.reset();
mAppsRecyclerView.reset();
mAppsRecyclerView.scrollToTop();
mSearchUiManager.reset();
}
@Override
@ -268,28 +221,17 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
});
mSearchContainer = findViewById(R.id.search_container);
mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
// 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);
mElevationController = new HeaderElevationController(mSearchContainer);
// Load the all apps recycler view
mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
mAppsRecyclerView.setApps(mApps);
mAppsRecyclerView.setLayoutManager(mLayoutManager);
mAppsRecyclerView.setAdapter(mAdapter);
mAppsRecyclerView.setHasFixedSize(true);
mAppsRecyclerView.addOnScrollListener(mElevationController);
mAppsRecyclerView.setElevationController(mElevationController);
mSearchContainer = findViewById(R.id.search_container);
mSearchUiManager = (SearchUiManager) mSearchContainer;
mSearchUiManager.initialize(mApps, mAppsRecyclerView);
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
@ -308,13 +250,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
return mAppsRecyclerView;
}
@Override
public void onBoundsChanged(Rect newBounds) { }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
DeviceProfile grid = mLauncher.getDeviceProfile();
// Update the number of items in the grid before we measure the view
grid.updateAppsViewNumCols();
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
if (mNumAppsPerRow != grid.inv.numColumns ||
mNumPredictedAppsPerRow != grid.inv.numColumns) {
@ -325,22 +266,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
}
if (!grid.isVerticalBarLayout()) {
MarginLayoutParams searchContainerLp =
(MarginLayoutParams) mSearchContainer.getLayoutParams();
searchContainerLp.height = mLauncher.getDragLayer().getInsets().top
+ mSearchContainerMinHeight;
mSearchContainer.setLayoutParams(searchContainerLp);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
// --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
// Update the number of items in the grid before we measure the view
grid.updateAppsViewNumCols();
if (mNumAppsPerRow != grid.allAppsNumCols ||
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
mNumAppsPerRow = grid.allAppsNumCols;
@ -357,22 +287,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public boolean dispatchKeyEvent(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();
}
}
}
mSearchUiManager.preDispatchKeyEvent(event);
return super.dispatchKeyEvent(event);
}
@ -439,43 +354,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
}
@Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
mApps.setOrderedFilter(apps);
mAppsRecyclerView.onSearchResultsChanged();
mAdapter.setLastSearchQuery(query);
}
}
@Override
public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
@NonNull AppDiscoveryUpdateState state) {
if (!mLauncher.isDestroyed()) {
mApps.onAppDiscoverySearchUpdate(app, state);
mAppsRecyclerView.onSearchResultsChanged();
}
}
@Override
public void clearSearchResult() {
if (mApps.setOrderedFilter(null)) {
mAppsRecyclerView.onSearchResultsChanged();
}
// Clear the search query
mSearchQueryBuilder.clear();
mSearchQueryBuilder.clearSpans();
Selection.setSelection(mSearchQueryBuilder, 0);
}
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
targetParent.containerType = mAppsRecyclerView.getContainerType(v);
}
public boolean shouldRestoreImeState() {
return !TextUtils.isEmpty(mSearchInput.getText());
return mSearchUiManager.shouldRestoreImeState();
}
@Override

View File

@ -40,6 +40,7 @@ import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.discovery.AppDiscoveryAppInfo;
import com.android.launcher3.discovery.AppDiscoveryItemView;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.List;
@ -199,7 +200,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
private int mAppsPerRow;
private BindViewCallback mBindViewCallback;
private AllAppsSearchBarController mSearchController;
private OnFocusChangeListener mIconFocusListener;
// The text to show when there are no search results and no market search handler.
@ -241,10 +241,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
mGridLayoutMgr.setSpanCount(appsPerRow);
}
public void setSearchController(AllAppsSearchBarController searchController) {
mSearchController = searchController;
}
public void setIconFocusListener(OnFocusChangeListener focusListener) {
mIconFocusListener = focusListener;
}
@ -256,7 +252,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public void setLastSearchQuery(String query) {
Resources res = mLauncher.getResources();
mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query);
mMarketSearchIntent = mSearchController.createMarketSearchIntent(query);
mMarketSearchIntent = PackageManagerHelper.getMarketSearchIntent(mLauncher, query);
}
/**

View File

@ -53,8 +53,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
private AllAppsBackgroundDrawable mEmptySearchBackground;
private int mEmptySearchBackgroundTopOffset;
private HeaderElevationController mElevationController;
public AllAppsRecyclerView(Context context) {
this(context, null);
}
@ -85,10 +83,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
}
public void setElevationController(HeaderElevationController elevationController) {
mElevationController = elevationController;
}
/**
* Sets the number of apps per row in this recycler view.
*/
@ -152,13 +146,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
*/
public void scrollToTop() {
// Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
if (mScrollbar.isThumbDetached()) {
mScrollbar.reattachThumbToScroll();
}
mScrollbar.reattachThumbToScroll();
scrollToPosition(0);
if (mElevationController != null) {
mElevationController.reset();
}
}
@Override

View File

@ -1,26 +0,0 @@
/*
* Copyright (C) 2015 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;
/**
* The default search controller.
*/
public class DefaultAppSearchController extends AllAppsSearchBarController {
public DefaultAppSearchAlgorithm onInitializeSearch() {
return new DefaultAppSearchAlgorithm(mApps.getApps());
}
}

View File

@ -0,0 +1,58 @@
/*
* 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;
import android.view.KeyEvent;
/**
* Interface for controlling the Apps search UI.
*/
public interface SearchUiManager {
/**
* Initializes the search manager.
*/
void initialize(AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView);
/**
* Notifies the search manager that the apps-list has changed and the search UI should be
* updated accordingly.
*/
void refreshSearchResult();
/**
* Notifies the search manager to close any active search session.
*/
void reset();
/**
* Called before dispatching a key event, in case the search manager wants to initialize
* some UI beforehand.
*/
void preDispatchKeyEvent(KeyEvent keyEvent);
/**
* Returns true if the IME should be brought back.
* TODO: Remove when removing support for opening all-apps in search mode.
*/
boolean shouldRestoreImeState();
/**
* Starts the search UI
* TODO: Remove when removing support for opening all-apps in search mode.
*/
void startAppsSearch();
}

View File

@ -13,12 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.allapps;
package com.android.launcher3.allapps.search;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Editable;
@ -34,16 +31,18 @@ import android.widget.TextView.OnEditorActionListener;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.discovery.AppDiscoveryItem;
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
/**
* An interface to a search box that AllApps can command.
*/
public abstract class AllAppsSearchBarController
public class AllAppsSearchBarController
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener {
protected Launcher mLauncher;
@ -88,9 +87,11 @@ public abstract class AllAppsSearchBarController
}
/**
* To be implemented by subclasses. This method will get called when the controller is set.
* This method will get called when the controller is set.
*/
protected abstract DefaultAppSearchAlgorithm onInitializeSearch();
public DefaultAppSearchAlgorithm onInitializeSearch() {
return new DefaultAppSearchAlgorithm(mApps.getApps());
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@ -114,7 +115,7 @@ public abstract class AllAppsSearchBarController
}
}
protected void refreshSearchResult() {
public void refreshSearchResult() {
if (TextUtils.isEmpty(mQuery)) {
return;
}
@ -135,7 +136,8 @@ public abstract class AllAppsSearchBarController
if (query.isEmpty()) {
return false;
}
return mLauncher.startActivitySafely(v, createMarketSearchIntent(query), null);
return mLauncher.startActivitySafely(v,
PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null);
}
@Override
@ -185,29 +187,11 @@ public abstract class AllAppsSearchBarController
return mInput.isFocused();
}
/**
* Creates a new market search intent.
*/
public Intent createMarketSearchIntent(String query) {
Uri marketSearchUri = Uri.parse("market://search")
.buildUpon()
.appendQueryParameter("c", "apps")
.appendQueryParameter("q", query)
.build();
return new Intent(Intent.ACTION_VIEW).setData(marketSearchUri);
}
/**
* Callback for getting search results.
*/
public interface Callbacks {
/**
* Called when the bounds of the search bar has changed.
*/
@Deprecated
void onBoundsChanged(Rect newBounds);
/**
* Called when the search is complete.
*
@ -220,7 +204,6 @@ public abstract class AllAppsSearchBarController
*/
void clearSearchResult();
/**
* Called when the app discovery is providing an update of search, which can either be
* START for starting a new discovery,

View File

@ -0,0 +1,195 @@
/*
* 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.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.TextUtils;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.FrameLayout;
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 AllAppsSearchBarController mSearchBarController;
private final SpannableStringBuilder mSearchQueryBuilder;
private final HeaderElevationController mElevationController;
private ExtendedEditText mSearchInput;
private AlphabeticalAppsList mApps;
private AllAppsRecyclerView mAppsRecyclerView;
private AllAppsGridAdapter mAdapter;
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);
mSearchBarController = new AllAppsSearchBarController();
mElevationController = new HeaderElevationController(this);
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSearchInput = findViewById(R.id.search_box_input);
// 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);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
!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(appsList, 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 boolean shouldRestoreImeState() {
return !TextUtils.isEmpty(mSearchInput.getText());
}
@Override
public void startAppsSearch() {
if (mApps != null) {
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();
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.allapps;
package com.android.launcher3.allapps.search;
import android.os.Handler;

View File

@ -1,4 +1,4 @@
package com.android.launcher3.allapps;
package com.android.launcher3.allapps.search;
import android.content.res.Resources;
import android.graphics.Outline;

View File

@ -10,7 +10,6 @@ import android.widget.FrameLayout;
import com.android.launcher3.AppInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherCallbacks;
import com.android.launcher3.allapps.AllAppsSearchBarController;
import com.android.launcher3.util.ComponentKey;
import java.io.FileDescriptor;
@ -197,11 +196,6 @@ public class LauncherExtension extends Launcher {
return false;
}
@Override
public AllAppsSearchBarController getAllAppsSearchBarController() {
return null;
}
@Override
public List<ComponentKey> getPredictedApps() {
// To debug app predictions, enable AlphabeticalAppsList#DEBUG_PREDICTIONS
@ -213,11 +207,6 @@ public class LauncherExtension extends Launcher {
return SEARCH_BAR_HEIGHT_NORMAL;
}
@Override
public void setLauncherSearchCallback(Object callbacks) {
// Do nothing
}
@Override
public void onAttachedToWindow() {
}

View File

@ -30,9 +30,11 @@ import android.os.UserHandle;
import android.text.TextUtils;
import com.android.launcher3.AppInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
import java.net.URISyntaxException;
import java.util.List;
/**
@ -149,4 +151,20 @@ public class PackageManagerHelper {
.appendQueryParameter("id", packageName)
.build());
}
/**
* Creates a new market search intent.
*/
public static Intent getMarketSearchIntent(Context context, String query) {
try {
Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0);
if (!TextUtils.isEmpty(query)) {
intent.setData(
intent.getData().buildUpon().appendQueryParameter("q", query).build());
}
return intent;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.allapps;
package com.android.launcher3.allapps.search;
import android.content.ComponentName;
import android.test.InstrumentationTestCase;