2015-03-10 16:28:47 -07:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2015-03-02 11:51:23 -08:00
|
|
|
package com.android.launcher3;
|
|
|
|
|
|
2015-05-08 17:34:17 -07:00
|
|
|
import android.content.ComponentName;
|
2015-03-02 11:51:23 -08:00
|
|
|
import android.content.Context;
|
2015-03-13 11:14:16 -07:00
|
|
|
import android.content.res.Resources;
|
2015-03-02 11:51:23 -08:00
|
|
|
import android.graphics.Point;
|
|
|
|
|
import android.graphics.Rect;
|
2015-04-08 10:27:49 -07:00
|
|
|
import android.graphics.drawable.InsetDrawable;
|
2015-03-10 16:28:47 -07:00
|
|
|
import android.support.v7.widget.RecyclerView;
|
|
|
|
|
import android.text.Editable;
|
|
|
|
|
import android.text.TextWatcher;
|
2015-03-02 11:51:23 -08:00
|
|
|
import android.util.AttributeSet;
|
2015-03-10 16:28:47 -07:00
|
|
|
import android.view.KeyEvent;
|
2015-05-12 19:05:30 -07:00
|
|
|
import android.view.LayoutInflater;
|
2015-03-02 11:51:23 -08:00
|
|
|
import android.view.MotionEvent;
|
|
|
|
|
import android.view.View;
|
2015-04-08 10:27:49 -07:00
|
|
|
import android.view.ViewConfiguration;
|
2015-05-12 19:05:30 -07:00
|
|
|
import android.view.ViewGroup;
|
2015-03-10 16:28:47 -07:00
|
|
|
import android.view.inputmethod.EditorInfo;
|
|
|
|
|
import android.view.inputmethod.InputMethodManager;
|
2015-05-12 19:05:30 -07:00
|
|
|
import android.widget.FrameLayout;
|
2015-03-02 11:51:23 -08:00
|
|
|
import android.widget.TextView;
|
2015-03-18 14:16:05 -07:00
|
|
|
import com.android.launcher3.util.Thunk;
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
import java.util.List;
|
2015-05-08 17:00:10 -07:00
|
|
|
import java.util.regex.Pattern;
|
2015-03-02 11:51:23 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-05-08 13:06:44 -07:00
|
|
|
* The all apps view container.
|
2015-03-02 11:51:23 -08:00
|
|
|
*/
|
2015-05-08 13:06:44 -07:00
|
|
|
public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
|
2015-05-12 19:05:30 -07:00
|
|
|
TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
|
|
|
|
|
AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
|
|
|
|
|
View.OnTouchListener, View.OnClickListener, View.OnLongClickListener {
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-05-05 17:21:58 -07:00
|
|
|
public static final boolean GRID_MERGE_SECTIONS = true;
|
2015-03-10 16:28:47 -07:00
|
|
|
|
2015-05-05 17:21:58 -07:00
|
|
|
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
|
2015-05-12 13:01:54 -07:00
|
|
|
private static final boolean DYNAMIC_HEADER_ELEVATION = true;
|
2015-05-07 18:21:28 -07:00
|
|
|
private static final boolean DISMISS_SEARCH_ON_BACK = true;
|
2015-05-05 17:21:58 -07:00
|
|
|
private static final float HEADER_ELEVATION_DP = 4;
|
2015-05-12 13:01:54 -07:00
|
|
|
// How far the user has to scroll in order to reach the full elevation
|
|
|
|
|
private static final float HEADER_SCROLL_TO_ELEVATION_DP = 16;
|
2015-05-05 17:21:58 -07:00
|
|
|
private static final int FADE_IN_DURATION = 175;
|
2015-05-07 18:21:28 -07:00
|
|
|
private static final int FADE_OUT_DURATION = 100;
|
|
|
|
|
private static final int SEARCH_TRANSLATION_X_DP = 18;
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-05-08 17:00:10 -07:00
|
|
|
private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
|
|
|
|
|
|
2015-03-18 14:16:05 -07:00
|
|
|
@Thunk Launcher mLauncher;
|
|
|
|
|
@Thunk AlphabeticalAppsList mApps;
|
2015-05-12 19:05:30 -07:00
|
|
|
private LayoutInflater mLayoutInflater;
|
2015-05-05 17:21:58 -07:00
|
|
|
private AppsGridAdapter mAdapter;
|
2015-03-10 16:28:47 -07:00
|
|
|
private RecyclerView.LayoutManager mLayoutManager;
|
|
|
|
|
private RecyclerView.ItemDecoration mItemDecoration;
|
2015-04-10 10:19:58 -07:00
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
private FrameLayout mContentView;
|
2015-04-10 10:19:58 -07:00
|
|
|
@Thunk AppsContainerRecyclerView mAppsRecyclerView;
|
2015-05-12 19:05:30 -07:00
|
|
|
private ViewGroup mPredictionBarView;
|
2015-05-05 17:21:58 -07:00
|
|
|
private View mHeaderView;
|
|
|
|
|
private View mSearchBarContainerView;
|
|
|
|
|
private View mSearchButtonView;
|
|
|
|
|
private View mDismissSearchButtonView;
|
2015-05-07 18:21:28 -07:00
|
|
|
private AppsContainerSearchEditTextView mSearchBarEditView;
|
2015-05-05 17:21:58 -07:00
|
|
|
|
2015-03-10 16:28:47 -07:00
|
|
|
private int mNumAppsPerRow;
|
2015-05-12 19:05:30 -07:00
|
|
|
private int mNumPredictedAppsPerRow;
|
2015-05-13 15:44:26 -07:00
|
|
|
// This coordinate is relative to this container view
|
|
|
|
|
private Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
|
|
|
|
|
// This coordinate is relative to its parent
|
|
|
|
|
private Point mIconLastTouchPos = new Point();
|
|
|
|
|
// This coordinate is used to proxy click and long-click events
|
|
|
|
|
private Point mPredictionIconTouchDownPos = new Point();
|
2015-03-10 16:28:47 -07:00
|
|
|
private int mContentMarginStart;
|
2015-04-08 10:27:49 -07:00
|
|
|
// Normal container insets
|
|
|
|
|
private int mContainerInset;
|
2015-05-12 19:05:30 -07:00
|
|
|
private int mPredictionBarHeight;
|
2015-05-05 17:21:58 -07:00
|
|
|
// RecyclerView scroll position
|
|
|
|
|
@Thunk int mRecyclerViewScrollY;
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-05-13 15:44:26 -07:00
|
|
|
private CheckLongPressHelper mPredictionIconCheckForLongPress;
|
|
|
|
|
private View mPredictionIconUnderTouch;
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
public AppsContainerView(Context context) {
|
|
|
|
|
this(context, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AppsContainerView(Context context, AttributeSet attrs) {
|
|
|
|
|
this(context, attrs, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
2015-03-24 16:28:44 -07:00
|
|
|
super(context, attrs, defStyleAttr);
|
2015-03-02 11:51:23 -08:00
|
|
|
LauncherAppState app = LauncherAppState.getInstance();
|
|
|
|
|
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
2015-03-13 11:14:16 -07:00
|
|
|
Resources res = context.getResources();
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-04-08 10:27:49 -07:00
|
|
|
mContainerInset = context.getResources().getDimensionPixelSize(
|
|
|
|
|
R.dimen.apps_container_inset);
|
2015-05-12 19:05:30 -07:00
|
|
|
mPredictionBarHeight = grid.allAppsCellHeightPx +
|
|
|
|
|
2 * res.getDimensionPixelSize(R.dimen.apps_prediction_icon_top_bottom_padding);
|
2015-03-02 11:51:23 -08:00
|
|
|
mLauncher = (Launcher) context;
|
2015-05-12 19:05:30 -07:00
|
|
|
mLayoutInflater = LayoutInflater.from(context);
|
2015-05-05 17:21:58 -07:00
|
|
|
mNumAppsPerRow = grid.appsViewNumCols;
|
2015-05-12 19:05:30 -07:00
|
|
|
mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols;
|
|
|
|
|
mApps = new AlphabeticalAppsList(context, this, mNumAppsPerRow, mNumPredictedAppsPerRow);
|
|
|
|
|
mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this);
|
2015-05-05 17:21:58 -07:00
|
|
|
mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
|
|
|
|
|
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
2015-05-12 19:05:30 -07:00
|
|
|
mAdapter.setPredictionRowHeight(mPredictionBarHeight);
|
2015-05-05 17:21:58 -07:00
|
|
|
mLayoutManager = mAdapter.getLayoutManager();
|
|
|
|
|
mItemDecoration = mAdapter.getItemDecoration();
|
|
|
|
|
mContentMarginStart = mAdapter.getContentMarginStart();
|
2015-03-10 16:28:47 -07:00
|
|
|
mApps.setAdapter(mAdapter);
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
2015-05-08 17:34:17 -07:00
|
|
|
/**
|
|
|
|
|
* Sets the current set of predicted apps.
|
|
|
|
|
*/
|
|
|
|
|
public void setPredictedApps(List<ComponentName> apps) {
|
|
|
|
|
mApps.setPredictedApps(apps);
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
/**
|
|
|
|
|
* Sets the current set of apps.
|
|
|
|
|
*/
|
|
|
|
|
public void setApps(List<AppInfo> apps) {
|
|
|
|
|
mApps.setApps(apps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds new apps to the list.
|
|
|
|
|
*/
|
|
|
|
|
public void addApps(List<AppInfo> apps) {
|
|
|
|
|
mApps.addApps(apps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates existing apps in the list
|
|
|
|
|
*/
|
|
|
|
|
public void updateApps(List<AppInfo> apps) {
|
|
|
|
|
mApps.updateApps(apps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes some apps from the list.
|
|
|
|
|
*/
|
|
|
|
|
public void removeApps(List<AppInfo> apps) {
|
|
|
|
|
mApps.removeApps(apps);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-08 10:27:49 -07:00
|
|
|
/**
|
2015-05-05 17:21:58 -07:00
|
|
|
* Hides the header bar
|
2015-04-08 10:27:49 -07:00
|
|
|
*/
|
2015-05-05 17:21:58 -07:00
|
|
|
public void hideHeaderBar() {
|
|
|
|
|
mHeaderView.setVisibility(View.GONE);
|
2015-05-08 13:06:44 -07:00
|
|
|
onUpdateBackgrounds();
|
|
|
|
|
onUpdatePaddings();
|
2015-04-08 10:27:49 -07:00
|
|
|
}
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
/**
|
|
|
|
|
* Scrolls this list view to the top.
|
|
|
|
|
*/
|
|
|
|
|
public void scrollToTop() {
|
2015-05-12 13:01:54 -07:00
|
|
|
mAppsRecyclerView.scrollToTop();
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the content view used for the launcher transitions.
|
|
|
|
|
*/
|
|
|
|
|
public View getContentView() {
|
2015-04-10 10:19:58 -07:00
|
|
|
return mContentView;
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the reveal view used for the launcher transitions.
|
|
|
|
|
*/
|
|
|
|
|
public View getRevealView() {
|
2015-03-10 16:28:47 -07:00
|
|
|
return findViewById(R.id.apps_view_transition_overlay);
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onFinishInflate() {
|
2015-03-10 16:28:47 -07:00
|
|
|
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
|
|
|
|
|
LAYOUT_DIRECTION_RTL);
|
2015-05-05 17:21:58 -07:00
|
|
|
mAdapter.setRtl(isRtl);
|
2015-04-10 10:19:58 -07:00
|
|
|
|
|
|
|
|
// Work around the search box getting first focus and showing the cursor by
|
|
|
|
|
// proxying the focus from the content view to the recycler view directly
|
2015-05-12 19:05:30 -07:00
|
|
|
mContentView = (FrameLayout) findViewById(R.id.apps_list);
|
2015-04-10 10:19:58 -07:00
|
|
|
mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onFocusChange(View v, boolean hasFocus) {
|
|
|
|
|
if (v == mContentView && hasFocus) {
|
|
|
|
|
mAppsRecyclerView.requestFocus();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2015-05-12 19:05:30 -07:00
|
|
|
|
|
|
|
|
// Fix the header view elevation if not dynamically calculating it
|
2015-05-05 17:21:58 -07:00
|
|
|
mHeaderView = findViewById(R.id.header);
|
|
|
|
|
mHeaderView.setOnClickListener(this);
|
|
|
|
|
if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) {
|
|
|
|
|
mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
|
|
|
|
|
getContext().getResources().getDisplayMetrics()));
|
|
|
|
|
}
|
2015-05-12 19:05:30 -07:00
|
|
|
|
|
|
|
|
// Fix the prediction bar size
|
|
|
|
|
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
|
|
|
|
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
|
|
|
|
lp.height = mPredictionBarHeight;
|
|
|
|
|
|
2015-05-05 17:21:58 -07:00
|
|
|
mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
|
|
|
|
|
mSearchBarContainerView = findViewById(R.id.app_search_container);
|
|
|
|
|
mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
|
|
|
|
|
mDismissSearchButtonView.setOnClickListener(this);
|
2015-05-07 18:21:28 -07:00
|
|
|
mSearchBarEditView = (AppsContainerSearchEditTextView) findViewById(R.id.app_search_box);
|
2015-05-05 17:21:58 -07:00
|
|
|
if (mSearchBarEditView != null) {
|
|
|
|
|
mSearchBarEditView.addTextChangedListener(this);
|
|
|
|
|
mSearchBarEditView.setOnEditorActionListener(this);
|
2015-05-07 18:21:28 -07:00
|
|
|
if (DISMISS_SEARCH_ON_BACK) {
|
|
|
|
|
mSearchBarEditView.setOnBackKeyListener(
|
|
|
|
|
new AppsContainerSearchEditTextView.OnBackKeyListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onBackKey() {
|
2015-05-11 16:30:13 -07:00
|
|
|
// Only hide the search field if there is no query, or if there
|
|
|
|
|
// are no filtered results
|
|
|
|
|
String query = Utilities.trim(
|
|
|
|
|
mSearchBarEditView.getEditableText().toString());
|
|
|
|
|
if (query.isEmpty() || mApps.hasNoFilteredResults()) {
|
|
|
|
|
hideSearchField(true, true);
|
|
|
|
|
}
|
2015-05-07 18:21:28 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-04-08 10:27:49 -07:00
|
|
|
}
|
2015-04-10 10:19:58 -07:00
|
|
|
mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
|
|
|
|
|
mAppsRecyclerView.setApps(mApps);
|
2015-05-12 19:05:30 -07:00
|
|
|
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
|
|
|
|
mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight);
|
2015-04-10 10:19:58 -07:00
|
|
|
mAppsRecyclerView.setLayoutManager(mLayoutManager);
|
|
|
|
|
mAppsRecyclerView.setAdapter(mAdapter);
|
|
|
|
|
mAppsRecyclerView.setHasFixedSize(true);
|
2015-05-12 13:01:54 -07:00
|
|
|
mAppsRecyclerView.setOnScrollListenerProxy(
|
|
|
|
|
new BaseContainerRecyclerView.OnScrollToListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onScrolledTo(int x, int y) {
|
|
|
|
|
mRecyclerViewScrollY = y;
|
|
|
|
|
onRecyclerViewScrolled();
|
|
|
|
|
}
|
|
|
|
|
});
|
2015-03-10 16:28:47 -07:00
|
|
|
if (mItemDecoration != null) {
|
2015-04-10 10:19:58 -07:00
|
|
|
mAppsRecyclerView.addItemDecoration(mItemDecoration);
|
2015-03-10 16:28:47 -07:00
|
|
|
}
|
2015-05-08 13:06:44 -07:00
|
|
|
onUpdateBackgrounds();
|
|
|
|
|
onUpdatePaddings();
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
@Override
|
|
|
|
|
public void onBindPredictionBar() {
|
|
|
|
|
if (!updatePredictionBarVisibility()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<AppInfo> predictedApps = mApps.getPredictedApps();
|
|
|
|
|
int childCount = mPredictionBarView.getChildCount();
|
|
|
|
|
for (int i = 0; i < mNumPredictedAppsPerRow; i++) {
|
|
|
|
|
BubbleTextView icon;
|
|
|
|
|
if (i < childCount) {
|
|
|
|
|
// If a child at that index exists, then get that child
|
|
|
|
|
icon = (BubbleTextView) mPredictionBarView.getChildAt(i);
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, inflate a new icon
|
|
|
|
|
icon = (BubbleTextView) mLayoutInflater.inflate(
|
|
|
|
|
R.layout.apps_prediction_bar_icon_view, mPredictionBarView, false);
|
|
|
|
|
icon.setFocusable(true);
|
|
|
|
|
mPredictionBarView.addView(icon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Either apply the app info to the child, or hide the view
|
|
|
|
|
if (i < predictedApps.size()) {
|
|
|
|
|
if (icon.getVisibility() != View.VISIBLE) {
|
|
|
|
|
icon.setVisibility(View.VISIBLE);
|
|
|
|
|
}
|
|
|
|
|
icon.applyFromApplicationInfo(predictedApps.get(i));
|
|
|
|
|
} else {
|
|
|
|
|
icon.setVisibility(View.INVISIBLE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
@Override
|
2015-05-08 13:06:44 -07:00
|
|
|
protected void onFixedBoundsUpdated() {
|
|
|
|
|
// Update the number of items in the grid
|
|
|
|
|
LauncherAppState app = LauncherAppState.getInstance();
|
|
|
|
|
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
|
|
|
|
if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
|
|
|
|
|
mNumAppsPerRow = grid.appsViewNumCols;
|
2015-05-12 19:05:30 -07:00
|
|
|
mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols;
|
|
|
|
|
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
2015-05-08 13:06:44 -07:00
|
|
|
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
2015-05-12 19:05:30 -07:00
|
|
|
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
2015-04-08 10:27:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2015-05-08 13:06:44 -07:00
|
|
|
* Update the padding of the Apps view and children. To ensure that the RecyclerView has the
|
|
|
|
|
* full width to handle touches right to the edge of the screen, we only apply the top and
|
|
|
|
|
* bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
|
|
|
|
|
* itself. In particular, the left/right padding is applied to the background of the view,
|
|
|
|
|
* and then additionally inset by the start margin.
|
2015-04-08 10:27:49 -07:00
|
|
|
*/
|
2015-05-08 13:06:44 -07:00
|
|
|
@Override
|
|
|
|
|
protected void onUpdatePaddings() {
|
|
|
|
|
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
|
|
|
|
|
LAYOUT_DIRECTION_RTL);
|
|
|
|
|
boolean hasSearchBar = (mSearchBarEditView != null) &&
|
|
|
|
|
(mSearchBarEditView.getVisibility() == View.VISIBLE);
|
2015-04-08 10:27:49 -07:00
|
|
|
|
2015-05-08 13:06:44 -07:00
|
|
|
if (mFixedBounds.isEmpty()) {
|
|
|
|
|
// If there are no fixed bounds, then use the default padding and insets
|
|
|
|
|
setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
|
|
|
|
|
mContainerInset + mInsets.bottom);
|
|
|
|
|
} else {
|
|
|
|
|
// If there are fixed bounds, then we update the padding to reflect the fixed bounds.
|
|
|
|
|
setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
|
2015-05-11 16:30:13 -07:00
|
|
|
mFixedBounds.bottom);
|
2015-04-08 10:27:49 -07:00
|
|
|
}
|
2015-05-08 13:06:44 -07:00
|
|
|
|
|
|
|
|
// Update the apps recycler view, inset it by the container inset as well
|
2015-05-11 16:30:13 -07:00
|
|
|
DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
|
|
|
|
|
int startMargin = grid.isPhone() ? mContentMarginStart : 0;
|
2015-05-08 13:06:44 -07:00
|
|
|
int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
|
|
|
|
|
if (isRtl) {
|
2015-05-12 13:01:54 -07:00
|
|
|
mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), inset,
|
|
|
|
|
inset + startMargin, inset);
|
2015-05-08 13:06:44 -07:00
|
|
|
} else {
|
2015-05-12 13:01:54 -07:00
|
|
|
mAppsRecyclerView.setPadding(inset + startMargin, inset,
|
|
|
|
|
inset + mAppsRecyclerView.getScrollbarWidth(), inset);
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the header bar
|
|
|
|
|
if (hasSearchBar) {
|
2015-05-12 19:05:30 -07:00
|
|
|
FrameLayout.LayoutParams lp =
|
|
|
|
|
(FrameLayout.LayoutParams) mHeaderView.getLayoutParams();
|
2015-05-08 13:06:44 -07:00
|
|
|
lp.leftMargin = lp.rightMargin = inset;
|
2015-05-12 19:05:30 -07:00
|
|
|
mHeaderView.requestLayout();
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
2015-05-12 19:05:30 -07:00
|
|
|
|
|
|
|
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
|
|
|
|
lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth();
|
|
|
|
|
lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth();
|
|
|
|
|
mPredictionBarView.requestLayout();
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update the background of the Apps view and children.
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
protected void onUpdateBackgrounds() {
|
|
|
|
|
int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
|
|
|
|
|
boolean hasSearchBar = (mSearchBarEditView != null) &&
|
|
|
|
|
(mSearchBarEditView.getVisibility() == View.VISIBLE);
|
|
|
|
|
|
|
|
|
|
// Update the background of the reveal view and list to be inset with the fixed bound
|
|
|
|
|
// insets instead of the default insets
|
|
|
|
|
mAppsRecyclerView.setBackground(new InsetDrawable(
|
|
|
|
|
getContext().getResources().getDrawable(
|
|
|
|
|
hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg),
|
|
|
|
|
inset, 0, inset, 0));
|
|
|
|
|
getRevealView().setBackground(new InsetDrawable(
|
|
|
|
|
getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
|
|
|
|
|
inset, 0, inset, 0));
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
2015-04-17 12:11:01 -07:00
|
|
|
@Override
|
|
|
|
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
|
|
|
return handleTouchEvent(ev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
|
|
|
return handleTouchEvent(ev);
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
@Override
|
2015-03-16 12:39:05 -07:00
|
|
|
public boolean onTouch(View v, MotionEvent ev) {
|
2015-04-08 10:27:49 -07:00
|
|
|
switch (ev.getAction()) {
|
|
|
|
|
case MotionEvent.ACTION_DOWN:
|
|
|
|
|
case MotionEvent.ACTION_MOVE:
|
2015-05-13 15:44:26 -07:00
|
|
|
mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
|
2015-04-08 10:27:49 -07:00
|
|
|
break;
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 17:21:58 -07:00
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
|
|
|
|
if (v == mHeaderView) {
|
|
|
|
|
showSearchField();
|
|
|
|
|
} else if (v == mDismissSearchButtonView) {
|
|
|
|
|
hideSearchField(true, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
@Override
|
|
|
|
|
public boolean onLongClick(View v) {
|
|
|
|
|
// Return early if this is not initiated from a touch
|
|
|
|
|
if (!v.isInTouchMode()) return false;
|
|
|
|
|
// When we have exited all apps or are in transition, disregard long clicks
|
|
|
|
|
if (!mLauncher.isAppsViewVisible() ||
|
|
|
|
|
mLauncher.getWorkspace().isSwitchingState()) return false;
|
|
|
|
|
// Return if global dragging is not enabled
|
|
|
|
|
if (!mLauncher.isDraggingEnabled()) return false;
|
|
|
|
|
|
|
|
|
|
// Start the drag
|
2015-05-13 15:44:26 -07:00
|
|
|
mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
|
2015-04-29 11:03:24 -07:00
|
|
|
// Enter spring loaded mode
|
|
|
|
|
mLauncher.enterSpringLoadedDragMode();
|
2015-03-02 11:51:23 -08:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean supportsFlingToDelete() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean supportsAppInfoDropTarget() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean supportsDeleteDropTarget() {
|
2015-04-10 13:45:42 -07:00
|
|
|
return false;
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public float getIntrinsicIconScaleFactor() {
|
|
|
|
|
LauncherAppState app = LauncherAppState.getInstance();
|
|
|
|
|
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
|
|
|
|
return (float) grid.allAppsIconSizePx / grid.iconSizePx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onFlingToDeleteCompleted() {
|
|
|
|
|
// We just dismiss the drag when we fling, so cleanup here
|
|
|
|
|
mLauncher.exitSpringLoadedDragModeDelayed(true,
|
|
|
|
|
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
|
|
|
|
|
mLauncher.unlockScreenOrientation(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2015-03-10 16:28:47 -07:00
|
|
|
public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
|
|
|
|
|
boolean success) {
|
2015-03-02 11:51:23 -08:00
|
|
|
if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
|
|
|
|
|
!(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
|
|
|
|
|
// Exit spring loaded mode if we have not successfully dropped or have not handled the
|
|
|
|
|
// drop in Workspace
|
|
|
|
|
mLauncher.exitSpringLoadedDragModeDelayed(true,
|
|
|
|
|
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
|
|
|
|
|
}
|
|
|
|
|
mLauncher.unlockScreenOrientation(false);
|
|
|
|
|
|
|
|
|
|
// Display an error message if the drag failed due to there not being enough space on the
|
|
|
|
|
// target layout we were dropping on.
|
|
|
|
|
if (!success) {
|
|
|
|
|
boolean showOutOfSpaceMessage = false;
|
|
|
|
|
if (target instanceof Workspace) {
|
|
|
|
|
int currentScreen = mLauncher.getCurrentWorkspaceScreen();
|
|
|
|
|
Workspace workspace = (Workspace) target;
|
|
|
|
|
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
|
|
|
|
|
ItemInfo itemInfo = (ItemInfo) d.dragInfo;
|
|
|
|
|
if (layout != null) {
|
|
|
|
|
layout.calculateSpans(itemInfo);
|
|
|
|
|
showOutOfSpaceMessage =
|
|
|
|
|
!layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (showOutOfSpaceMessage) {
|
|
|
|
|
mLauncher.showOutOfSpaceMessage(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.deferDragViewCleanupPostAnimation = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-10 16:28:47 -07:00
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
|
|
|
// Do nothing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
|
|
|
// Do nothing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void afterTextChanged(final Editable s) {
|
2015-05-08 17:00:10 -07:00
|
|
|
String queryText = s.toString();
|
|
|
|
|
if (queryText.isEmpty()) {
|
2015-03-10 16:28:47 -07:00
|
|
|
mApps.setFilter(null);
|
|
|
|
|
} else {
|
2015-03-13 11:14:16 -07:00
|
|
|
String formatStr = getResources().getString(R.string.apps_view_no_search_results);
|
2015-05-08 17:00:10 -07:00
|
|
|
mAdapter.setEmptySearchText(String.format(formatStr, queryText));
|
2015-03-13 11:14:16 -07:00
|
|
|
|
2015-05-12 13:01:54 -07:00
|
|
|
// Do an intersection of the words in the query and each title, and filter out all the
|
|
|
|
|
// apps that don't match all of the words in the query.
|
2015-05-08 17:00:10 -07:00
|
|
|
final String queryTextLower = queryText.toLowerCase();
|
2015-05-12 13:01:54 -07:00
|
|
|
final String[] queryWords = SPLIT_PATTERN.split(queryTextLower);
|
2015-03-10 16:28:47 -07:00
|
|
|
mApps.setFilter(new AlphabeticalAppsList.Filter() {
|
|
|
|
|
@Override
|
2015-04-06 15:12:49 -07:00
|
|
|
public boolean retainApp(AppInfo info, String sectionName) {
|
2015-05-08 17:00:10 -07:00
|
|
|
if (sectionName.toLowerCase().contains(queryTextLower)) {
|
2015-05-05 17:21:58 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
2015-05-08 17:00:10 -07:00
|
|
|
String title = info.title.toString();
|
|
|
|
|
String[] words = SPLIT_PATTERN.split(title.toLowerCase());
|
2015-05-12 13:01:54 -07:00
|
|
|
for (int qi = 0; qi < queryWords.length; qi++) {
|
|
|
|
|
boolean foundMatch = false;
|
|
|
|
|
for (int i = 0; i < words.length; i++) {
|
|
|
|
|
if (words[i].startsWith(queryWords[qi])) {
|
|
|
|
|
foundMatch = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!foundMatch) {
|
|
|
|
|
// If there is a word in the query that does not match any words in this
|
|
|
|
|
// title, so skip it.
|
|
|
|
|
return false;
|
2015-05-05 17:21:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
2015-05-12 13:01:54 -07:00
|
|
|
return true;
|
2015-03-10 16:28:47 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-05-05 17:21:58 -07:00
|
|
|
scrollToTop();
|
2015-03-10 16:28:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
|
|
|
|
if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
|
2015-04-06 15:12:49 -07:00
|
|
|
// Skip the quick-launch if there isn't exactly one item
|
|
|
|
|
if (mApps.getSize() != 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
|
|
|
|
for (int i = 0; i < items.size(); i++) {
|
|
|
|
|
AlphabeticalAppsList.AdapterItem item = items.get(i);
|
2015-05-12 19:05:30 -07:00
|
|
|
if (item.viewType == AppsGridAdapter.ICON_VIEW_TYPE) {
|
2015-04-10 10:19:58 -07:00
|
|
|
mAppsRecyclerView.getChildAt(i).performClick();
|
2015-05-05 17:21:58 -07:00
|
|
|
getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
|
2015-04-06 15:12:49 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
2015-03-10 16:28:47 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
@Override
|
|
|
|
|
public void onFilterChanged() {
|
|
|
|
|
updatePredictionBarVisibility();
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-10 16:28:47 -07:00
|
|
|
@Override
|
|
|
|
|
public View getContent() {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
|
2015-04-10 10:19:58 -07:00
|
|
|
// Do nothing
|
2015-03-10 16:28:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
|
|
|
|
|
// Do nothing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onLauncherTransitionStep(Launcher l, float t) {
|
|
|
|
|
// Do nothing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
|
2015-05-05 17:21:58 -07:00
|
|
|
if (mSearchBarEditView != null) {
|
2015-04-08 10:27:49 -07:00
|
|
|
if (toWorkspace) {
|
2015-05-05 17:21:58 -07:00
|
|
|
hideSearchField(false, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates the container when the recycler view is scrolled.
|
|
|
|
|
*/
|
|
|
|
|
private void onRecyclerViewScrolled() {
|
2015-05-12 13:01:54 -07:00
|
|
|
if (DYNAMIC_HEADER_ELEVATION && Utilities.isLmpOrAbove()) {
|
|
|
|
|
int elevation = DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
|
|
|
|
|
getContext().getResources().getDisplayMetrics());
|
|
|
|
|
int scrollToElevation = DynamicGrid.pxFromDp(HEADER_SCROLL_TO_ELEVATION_DP,
|
|
|
|
|
getContext().getResources().getDisplayMetrics());
|
|
|
|
|
float elevationPct = (float) Math.min(mRecyclerViewScrollY, scrollToElevation) /
|
|
|
|
|
scrollToElevation;
|
|
|
|
|
float newElevation = elevation * elevationPct;
|
|
|
|
|
if (Float.compare(mHeaderView.getElevation(), newElevation) != 0) {
|
|
|
|
|
mHeaderView.setElevation(newElevation);
|
2015-04-08 10:27:49 -07:00
|
|
|
}
|
|
|
|
|
}
|
2015-05-12 19:05:30 -07:00
|
|
|
|
|
|
|
|
mPredictionBarView.setTranslationY(-mRecyclerViewScrollY + mAppsRecyclerView.getPaddingTop());
|
2015-04-08 10:27:49 -07:00
|
|
|
}
|
|
|
|
|
|
2015-05-13 15:44:26 -07:00
|
|
|
@Override
|
|
|
|
|
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
|
|
|
|
// If we were waiting for long-click, cancel the request once a child has started handling
|
|
|
|
|
// the scrolling
|
|
|
|
|
if (mPredictionIconCheckForLongPress != null) {
|
|
|
|
|
mPredictionIconCheckForLongPress.cancelLongPress();
|
|
|
|
|
}
|
|
|
|
|
super.requestDisallowInterceptTouchEvent(disallowIntercept);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-17 12:11:01 -07:00
|
|
|
/**
|
|
|
|
|
* Handles the touch events to dismiss all apps when clicking outside the bounds of the
|
|
|
|
|
* recycler view.
|
|
|
|
|
*/
|
|
|
|
|
private boolean handleTouchEvent(MotionEvent ev) {
|
|
|
|
|
LauncherAppState app = LauncherAppState.getInstance();
|
|
|
|
|
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
|
2015-05-13 15:44:26 -07:00
|
|
|
int x = (int) ev.getX();
|
|
|
|
|
int y = (int) ev.getY();
|
2015-04-17 12:11:01 -07:00
|
|
|
|
|
|
|
|
switch (ev.getAction()) {
|
|
|
|
|
case MotionEvent.ACTION_DOWN:
|
2015-05-13 15:44:26 -07:00
|
|
|
// We workaround the fact that the recycler view needs the touches for the scroll
|
|
|
|
|
// and we want to intercept it for clicks in the prediction bar by handling clicks
|
|
|
|
|
// and long clicks in the prediction bar ourselves.
|
|
|
|
|
mPredictionIconTouchDownPos.set(x, y);
|
|
|
|
|
mPredictionIconUnderTouch = findPredictedAppAtCoordinate(x, y);
|
|
|
|
|
if (mPredictionIconUnderTouch != null) {
|
|
|
|
|
mPredictionIconCheckForLongPress =
|
|
|
|
|
new CheckLongPressHelper(mPredictionIconUnderTouch, this);
|
|
|
|
|
mPredictionIconCheckForLongPress.postCheckForLongPress();
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-17 12:11:01 -07:00
|
|
|
if (!mFixedBounds.isEmpty()) {
|
|
|
|
|
// Outset the fixed bounds and check if the touch is outside all apps
|
|
|
|
|
Rect tmpRect = new Rect(mFixedBounds);
|
|
|
|
|
tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
|
|
|
|
|
if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
|
2015-05-13 15:44:26 -07:00
|
|
|
mBoundsCheckLastTouchDownPos.set(x, y);
|
2015-04-17 12:11:01 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Check if the touch is outside all apps
|
|
|
|
|
if (ev.getX() < getPaddingLeft() ||
|
|
|
|
|
ev.getX() > (getWidth() - getPaddingRight())) {
|
2015-05-13 15:44:26 -07:00
|
|
|
mBoundsCheckLastTouchDownPos.set(x, y);
|
2015-04-17 12:11:01 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MotionEvent.ACTION_UP:
|
2015-05-13 15:44:26 -07:00
|
|
|
if (mBoundsCheckLastTouchDownPos.x > -1) {
|
2015-04-17 12:11:01 -07:00
|
|
|
ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
|
2015-05-13 15:44:26 -07:00
|
|
|
float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
|
|
|
|
|
float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
|
2015-04-17 12:11:01 -07:00
|
|
|
float distance = (float) Math.hypot(dx, dy);
|
|
|
|
|
if (distance < viewConfig.getScaledTouchSlop()) {
|
|
|
|
|
// The background was clicked, so just go home
|
|
|
|
|
Launcher launcher = (Launcher) getContext();
|
|
|
|
|
launcher.showWorkspace(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-05-13 15:44:26 -07:00
|
|
|
|
|
|
|
|
// Trigger the click on the prediction bar icon if that's where we touched
|
|
|
|
|
if (mPredictionIconUnderTouch != null &&
|
|
|
|
|
!mPredictionIconCheckForLongPress.hasPerformedLongPress()) {
|
|
|
|
|
mLauncher.onClick(mPredictionIconUnderTouch);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-17 12:11:01 -07:00
|
|
|
// Fall through
|
|
|
|
|
case MotionEvent.ACTION_CANCEL:
|
2015-05-13 15:44:26 -07:00
|
|
|
mBoundsCheckLastTouchDownPos.set(-1, -1);
|
|
|
|
|
mPredictionIconTouchDownPos.set(-1, -1);
|
|
|
|
|
|
|
|
|
|
// On touch up/cancel, cancel the long press on the prediction bar icon if it has
|
|
|
|
|
// not yet been performed
|
|
|
|
|
if (mPredictionIconCheckForLongPress != null) {
|
|
|
|
|
mPredictionIconCheckForLongPress.cancelLongPress();
|
|
|
|
|
mPredictionIconCheckForLongPress = null;
|
|
|
|
|
}
|
|
|
|
|
mPredictionIconUnderTouch = null;
|
|
|
|
|
|
2015-04-17 12:11:01 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-13 15:44:26 -07:00
|
|
|
/**
|
|
|
|
|
* Returns the predicted app in the prediction bar given a set of local coordinates.
|
|
|
|
|
*/
|
|
|
|
|
private View findPredictedAppAtCoordinate(int x, int y) {
|
|
|
|
|
int[] coord = {x, y};
|
|
|
|
|
Rect hitRect = new Rect();
|
|
|
|
|
Utilities.mapCoordInSelfToDescendent(mPredictionBarView, this, coord);
|
|
|
|
|
for (int i = 0; i < mPredictionBarView.getChildCount(); i++) {
|
|
|
|
|
View child = mPredictionBarView.getChildAt(i);
|
|
|
|
|
child.getHitRect(hitRect);
|
|
|
|
|
if (hitRect.contains(coord[0], coord[1])) {
|
|
|
|
|
return child;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 17:21:58 -07:00
|
|
|
/**
|
|
|
|
|
* Shows the search field.
|
|
|
|
|
*/
|
|
|
|
|
private void showSearchField() {
|
|
|
|
|
// Show the search bar and focus the search
|
2015-05-07 18:21:28 -07:00
|
|
|
final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
|
|
|
|
|
getContext().getResources().getDisplayMetrics());
|
2015-05-05 17:21:58 -07:00
|
|
|
mSearchBarContainerView.setVisibility(View.VISIBLE);
|
|
|
|
|
mSearchBarContainerView.setAlpha(0f);
|
2015-05-07 18:21:28 -07:00
|
|
|
mSearchBarContainerView.setTranslationX(translationX);
|
|
|
|
|
mSearchBarContainerView.animate()
|
|
|
|
|
.alpha(1f)
|
|
|
|
|
.translationX(0)
|
|
|
|
|
.setDuration(FADE_IN_DURATION)
|
|
|
|
|
.withLayer()
|
2015-05-05 17:21:58 -07:00
|
|
|
.withEndAction(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
mSearchBarEditView.requestFocus();
|
|
|
|
|
getInputMethodManager().showSoftInput(mSearchBarEditView,
|
|
|
|
|
InputMethodManager.SHOW_IMPLICIT);
|
|
|
|
|
}
|
|
|
|
|
});
|
2015-05-07 18:21:28 -07:00
|
|
|
mSearchButtonView.animate()
|
|
|
|
|
.alpha(0f)
|
|
|
|
|
.translationX(-translationX)
|
|
|
|
|
.setDuration(FADE_OUT_DURATION)
|
|
|
|
|
.withLayer();
|
2015-05-05 17:21:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hides the search field.
|
|
|
|
|
*/
|
|
|
|
|
private void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
|
2015-05-07 18:21:28 -07:00
|
|
|
final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
|
|
|
|
|
final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
|
|
|
|
|
getContext().getResources().getDisplayMetrics());
|
2015-05-05 17:21:58 -07:00
|
|
|
if (animated) {
|
|
|
|
|
// Hide the search bar and focus the recycler view
|
2015-05-07 18:21:28 -07:00
|
|
|
mSearchBarContainerView.animate()
|
|
|
|
|
.alpha(0f)
|
|
|
|
|
.translationX(0)
|
|
|
|
|
.setDuration(FADE_IN_DURATION)
|
|
|
|
|
.withLayer()
|
2015-05-05 17:21:58 -07:00
|
|
|
.withEndAction(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
mSearchBarContainerView.setVisibility(View.INVISIBLE);
|
2015-05-07 18:21:28 -07:00
|
|
|
if (resetTextField) {
|
|
|
|
|
mSearchBarEditView.setText("");
|
|
|
|
|
}
|
2015-05-05 17:21:58 -07:00
|
|
|
mApps.setFilter(null);
|
|
|
|
|
if (returnFocusToRecyclerView) {
|
|
|
|
|
mAppsRecyclerView.requestFocus();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2015-05-07 18:21:28 -07:00
|
|
|
mSearchButtonView.setTranslationX(-translationX);
|
|
|
|
|
mSearchButtonView.animate()
|
|
|
|
|
.alpha(1f)
|
|
|
|
|
.translationX(0)
|
|
|
|
|
.setDuration(FADE_OUT_DURATION)
|
|
|
|
|
.withLayer();
|
2015-05-05 17:21:58 -07:00
|
|
|
} else {
|
|
|
|
|
mSearchBarContainerView.setVisibility(View.INVISIBLE);
|
2015-05-07 18:21:28 -07:00
|
|
|
if (resetTextField) {
|
|
|
|
|
mSearchBarEditView.setText("");
|
|
|
|
|
}
|
2015-05-05 17:21:58 -07:00
|
|
|
mApps.setFilter(null);
|
|
|
|
|
mSearchButtonView.setAlpha(1f);
|
2015-05-07 18:21:28 -07:00
|
|
|
mSearchButtonView.setTranslationX(0f);
|
2015-05-05 17:21:58 -07:00
|
|
|
if (returnFocusToRecyclerView) {
|
|
|
|
|
mAppsRecyclerView.requestFocus();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
/**
|
|
|
|
|
* Updates the visibility of the prediction bar.
|
|
|
|
|
* @return whether the prediction bar is visible
|
|
|
|
|
*/
|
|
|
|
|
private boolean updatePredictionBarVisibility() {
|
|
|
|
|
boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && (!mApps.hasFilter() ||
|
|
|
|
|
mSearchBarEditView.getEditableText().toString().isEmpty());
|
|
|
|
|
if (showPredictionBar) {
|
|
|
|
|
mPredictionBarView.setVisibility(View.VISIBLE);
|
|
|
|
|
} else if (!showPredictionBar) {
|
|
|
|
|
mPredictionBarView.setVisibility(View.INVISIBLE);
|
|
|
|
|
}
|
|
|
|
|
return showPredictionBar;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 17:21:58 -07:00
|
|
|
/**
|
|
|
|
|
* Returns an input method manager.
|
|
|
|
|
*/
|
|
|
|
|
private InputMethodManager getInputMethodManager() {
|
|
|
|
|
return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
|
|
|
}
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|