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-05-22 11:12:27 -07:00
|
|
|
package com.android.launcher3.allapps;
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-05-14 00:07:08 -07:00
|
|
|
import android.annotation.SuppressLint;
|
|
|
|
|
import android.annotation.TargetApi;
|
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-05-14 00:07:08 -07:00
|
|
|
import android.os.Build;
|
2015-05-28 17:33:40 -07:00
|
|
|
import android.os.Bundle;
|
2015-03-10 16:28:47 -07:00
|
|
|
import android.support.v7.widget.RecyclerView;
|
2015-06-04 17:18:17 -07:00
|
|
|
import android.text.Selection;
|
|
|
|
|
import android.text.SpannableStringBuilder;
|
|
|
|
|
import android.text.method.TextKeyListener;
|
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-05-15 12:03:37 -07:00
|
|
|
import android.view.ViewTreeObserver;
|
2015-05-12 19:05:30 -07:00
|
|
|
import android.widget.FrameLayout;
|
2015-06-04 17:18:17 -07:00
|
|
|
import android.widget.LinearLayout;
|
2015-06-16 11:36:19 -07:00
|
|
|
|
2015-05-22 11:12:27 -07:00
|
|
|
import com.android.launcher3.AppInfo;
|
|
|
|
|
import com.android.launcher3.BaseContainerView;
|
|
|
|
|
import com.android.launcher3.BubbleTextView;
|
|
|
|
|
import com.android.launcher3.CellLayout;
|
|
|
|
|
import com.android.launcher3.CheckLongPressHelper;
|
|
|
|
|
import com.android.launcher3.DeleteDropTarget;
|
|
|
|
|
import com.android.launcher3.DeviceProfile;
|
|
|
|
|
import com.android.launcher3.DragSource;
|
|
|
|
|
import com.android.launcher3.DropTarget;
|
|
|
|
|
import com.android.launcher3.Folder;
|
|
|
|
|
import com.android.launcher3.ItemInfo;
|
|
|
|
|
import com.android.launcher3.Launcher;
|
|
|
|
|
import com.android.launcher3.LauncherTransitionable;
|
|
|
|
|
import com.android.launcher3.R;
|
2015-05-28 17:33:40 -07:00
|
|
|
import com.android.launcher3.Stats;
|
2015-05-22 11:12:27 -07:00
|
|
|
import com.android.launcher3.Utilities;
|
|
|
|
|
import com.android.launcher3.Workspace;
|
2015-06-16 11:36:19 -07:00
|
|
|
import com.android.launcher3.util.ComponentKey;
|
2015-03-18 14:16:05 -07:00
|
|
|
import com.android.launcher3.util.Thunk;
|
|
|
|
|
|
2015-06-12 14:18:55 -07:00
|
|
|
import java.nio.charset.Charset;
|
|
|
|
|
import java.nio.charset.CharsetEncoder;
|
2015-05-29 14:54:40 -07:00
|
|
|
import java.util.ArrayList;
|
2015-03-02 11:51:23 -08:00
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
|
2015-05-15 12:03:37 -07:00
|
|
|
|
2015-06-12 14:18:55 -07:00
|
|
|
/**
|
|
|
|
|
* A merge algorithm that merges every section indiscriminately.
|
|
|
|
|
*/
|
|
|
|
|
final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
|
|
|
|
|
AlphabeticalAppsList.SectionInfo withSection,
|
|
|
|
|
int sectionAppCount, int numAppsPerRow, int mergeCount) {
|
|
|
|
|
// Merge EVERYTHING
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The logic we use to merge multiple sections. We only merge sections when their final row
|
|
|
|
|
* contains less than a certain number of icons, and stop at a specified max number of merges.
|
|
|
|
|
* In addition, we will try and not merge sections that identify apps from different scripts.
|
|
|
|
|
*/
|
|
|
|
|
final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
|
|
|
|
|
|
|
|
|
|
private int mMinAppsPerRow;
|
|
|
|
|
private int mMinRowsInMergedSection;
|
|
|
|
|
private int mMaxAllowableMerges;
|
|
|
|
|
private CharsetEncoder mAsciiEncoder;
|
|
|
|
|
|
|
|
|
|
public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
|
|
|
|
|
mMinAppsPerRow = minAppsPerRow;
|
|
|
|
|
mMinRowsInMergedSection = minRowsInMergedSection;
|
|
|
|
|
mMaxAllowableMerges = maxNumMerges;
|
|
|
|
|
mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
|
|
|
|
|
AlphabeticalAppsList.SectionInfo withSection,
|
|
|
|
|
int sectionAppCount, int numAppsPerRow, int mergeCount) {
|
|
|
|
|
// Continue merging if the number of hanging apps on the final row is less than some
|
|
|
|
|
// fixed number (ragged), the merged rows has yet to exceed some minimum row count,
|
|
|
|
|
// and while the number of merged sections is less than some fixed number of merges
|
|
|
|
|
int rows = sectionAppCount / numAppsPerRow;
|
|
|
|
|
int cols = sectionAppCount % numAppsPerRow;
|
|
|
|
|
|
|
|
|
|
// Ensure that we do not merge across scripts, currently we only allow for english and
|
|
|
|
|
// native scripts so we can test if both can just be ascii encoded
|
|
|
|
|
boolean isCrossScript = false;
|
|
|
|
|
if (section.firstAppItem != null && withSection.firstAppItem != null) {
|
|
|
|
|
isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
|
|
|
|
|
mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
|
|
|
|
|
}
|
|
|
|
|
return (0 < cols && cols < mMinAppsPerRow) &&
|
|
|
|
|
rows < mMinRowsInMergedSection &&
|
|
|
|
|
mergeCount < mMaxAllowableMerges &&
|
|
|
|
|
!isCrossScript;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-06-04 17:18:17 -07:00
|
|
|
public class AllAppsContainerView extends BaseContainerView implements DragSource,
|
|
|
|
|
LauncherTransitionable, AlphabeticalAppsList.AdapterChangedCallback,
|
|
|
|
|
AllAppsGridAdapter.PredictionBarSpacerCallbacks, View.OnTouchListener,
|
|
|
|
|
View.OnLongClickListener, ViewTreeObserver.OnPreDrawListener,
|
|
|
|
|
AllAppsSearchBarController.Callbacks, Stats.LaunchSourceProvider {
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-06-12 14:18:55 -07:00
|
|
|
private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
|
|
|
|
|
private static final int MAX_NUM_MERGES_PHONE = 2;
|
2015-03-10 16:28:47 -07:00
|
|
|
|
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-22 11:12:27 -07:00
|
|
|
private AllAppsGridAdapter 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-06-04 17:18:17 -07:00
|
|
|
@Thunk View mContent;
|
|
|
|
|
@Thunk View mContainerView;
|
|
|
|
|
@Thunk View mRevealView;
|
2015-05-22 11:12:27 -07:00
|
|
|
@Thunk AllAppsRecyclerView mAppsRecyclerView;
|
2015-06-02 09:38:28 -07:00
|
|
|
@Thunk ViewGroup mPredictionBarView;
|
2015-06-04 17:18:17 -07:00
|
|
|
@Thunk AllAppsSearchBarController mSearchBarController;
|
|
|
|
|
private ViewGroup mSearchBarContainerView;
|
|
|
|
|
private View mSearchBarView;
|
2015-05-13 17:40:42 -07:00
|
|
|
|
2015-06-12 14:18:55 -07:00
|
|
|
private int mSectionNamesMargin;
|
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
|
2015-05-14 09:55:17 -07:00
|
|
|
private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
|
2015-05-13 15:44:26 -07:00
|
|
|
// This coordinate is relative to its parent
|
2015-05-14 09:55:17 -07:00
|
|
|
private final Point mIconLastTouchPos = new Point();
|
|
|
|
|
// This coordinate is used to proxy click and long-click events to the prediction bar icons
|
|
|
|
|
private final Point mPredictionIconTouchDownPos = new Point();
|
2015-04-08 10:27:49 -07:00
|
|
|
// Normal container insets
|
2015-05-12 19:05:30 -07:00
|
|
|
private int mPredictionBarHeight;
|
2015-05-15 12:03:37 -07:00
|
|
|
private int mLastRecyclerViewScrollPos = -1;
|
2015-06-02 09:38:28 -07:00
|
|
|
@Thunk boolean mFocusPredictionBarOnFirstBind;
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
private SpannableStringBuilder mSearchQueryBuilder = null;
|
|
|
|
|
|
2015-05-13 15:44:26 -07:00
|
|
|
private CheckLongPressHelper mPredictionIconCheckForLongPress;
|
|
|
|
|
private View mPredictionIconUnderTouch;
|
|
|
|
|
|
2015-05-22 11:12:27 -07:00
|
|
|
public AllAppsContainerView(Context context) {
|
2015-03-02 11:51:23 -08:00
|
|
|
this(context, null);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-22 11:12:27 -07:00
|
|
|
public AllAppsContainerView(Context context, AttributeSet attrs) {
|
2015-03-02 11:51:23 -08:00
|
|
|
this(context, attrs, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-22 11:12:27 -07:00
|
|
|
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
2015-03-24 16:28:44 -07:00
|
|
|
super(context, attrs, defStyleAttr);
|
2015-03-13 11:14:16 -07:00
|
|
|
Resources res = context.getResources();
|
2015-03-02 11:51:23 -08:00
|
|
|
|
2015-05-06 11:42:25 -07:00
|
|
|
mLauncher = (Launcher) context;
|
2015-06-04 17:18:17 -07:00
|
|
|
mLayoutInflater = LayoutInflater.from(context);
|
2015-05-06 11:42:25 -07:00
|
|
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
2015-06-01 14:38:24 -07:00
|
|
|
mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx +
|
|
|
|
|
Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) +
|
|
|
|
|
2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) +
|
2015-06-04 17:18:17 -07:00
|
|
|
2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding));
|
2015-06-12 14:18:55 -07:00
|
|
|
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
|
2015-06-04 17:18:17 -07:00
|
|
|
mApps = new AlphabeticalAppsList(context);
|
2015-05-21 15:45:24 -07:00
|
|
|
mApps.setAdapterChangedCallback(this);
|
2015-06-04 17:18:17 -07:00
|
|
|
mAdapter = new AllAppsGridAdapter(context, mApps, this, this, mLauncher, this);
|
2015-05-22 11:12:27 -07:00
|
|
|
mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
|
2015-05-12 19:05:30 -07:00
|
|
|
mAdapter.setPredictionRowHeight(mPredictionBarHeight);
|
2015-06-04 17:18:17 -07:00
|
|
|
mApps.setAdapter(mAdapter);
|
2015-05-05 17:21:58 -07:00
|
|
|
mLayoutManager = mAdapter.getLayoutManager();
|
|
|
|
|
mItemDecoration = mAdapter.getItemDecoration();
|
2015-05-06 11:42:25 -07:00
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
mSearchQueryBuilder = new SpannableStringBuilder();
|
|
|
|
|
Selection.setSelection(mSearchQueryBuilder, 0);
|
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-06-04 17:18:17 -07:00
|
|
|
* Sets the search bar that shows above the a-z list.
|
2015-04-08 10:27:49 -07:00
|
|
|
*/
|
2015-06-04 17:18:17 -07:00
|
|
|
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, this);
|
|
|
|
|
|
|
|
|
|
// Add the new search view to the layout
|
|
|
|
|
View searchBarView = searchController.getView(mSearchBarContainerView);
|
|
|
|
|
mSearchBarContainerView.addView(searchBarView);
|
|
|
|
|
mSearchBarContainerView.setVisibility(View.VISIBLE);
|
|
|
|
|
mSearchBarView = searchBarView;
|
|
|
|
|
setHasSearchBar();
|
|
|
|
|
|
|
|
|
|
updateBackgroundAndPaddings();
|
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-06-04 17:18:17 -07:00
|
|
|
return mContainerView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the all apps search view.
|
|
|
|
|
*/
|
|
|
|
|
public View getSearchBarView() {
|
|
|
|
|
return mSearchBarView;
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the reveal view used for the launcher transitions.
|
|
|
|
|
*/
|
|
|
|
|
public View getRevealView() {
|
2015-06-04 17:18:17 -07:00
|
|
|
return mRevealView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns an new instance of the default app search controller.
|
|
|
|
|
*/
|
|
|
|
|
public AllAppsSearchBarController newDefaultAppSearchController() {
|
|
|
|
|
return new DefaultAppSearchController(getContext(), this, mAppsRecyclerView);
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onFinishInflate() {
|
2015-06-04 17:18:17 -07:00
|
|
|
super.onFinishInflate();
|
2015-05-14 00:07:08 -07:00
|
|
|
boolean isRtl = Utilities.isRtl(getResources());
|
2015-05-05 17:21:58 -07:00
|
|
|
mAdapter.setRtl(isRtl);
|
2015-06-04 17:18:17 -07:00
|
|
|
mContent = findViewById(R.id.content);
|
2015-04-10 10:19:58 -07:00
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
// This is a focus listener that proxies focus from a view into the list view. This is to
|
|
|
|
|
// work around the search box from getting first focus and showing the cursor.
|
|
|
|
|
View.OnFocusChangeListener focusProxyListener = new View.OnFocusChangeListener() {
|
2015-04-10 10:19:58 -07:00
|
|
|
@Override
|
|
|
|
|
public void onFocusChange(View v, boolean hasFocus) {
|
2015-06-04 17:18:17 -07:00
|
|
|
if (hasFocus) {
|
2015-05-20 15:03:13 -07:00
|
|
|
if (!mApps.getPredictedApps().isEmpty()) {
|
|
|
|
|
// If the prediction bar is going to be bound, then defer focusing until
|
|
|
|
|
// it is first bound
|
|
|
|
|
if (mPredictionBarView.getChildCount() == 0) {
|
|
|
|
|
mFocusPredictionBarOnFirstBind = true;
|
|
|
|
|
} else {
|
|
|
|
|
mPredictionBarView.requestFocus();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
mAppsRecyclerView.requestFocus();
|
|
|
|
|
}
|
2015-04-10 10:19:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
2015-06-04 17:18:17 -07:00
|
|
|
};
|
|
|
|
|
mSearchBarContainerView = (ViewGroup) findViewById(R.id.search_box_container);
|
|
|
|
|
mSearchBarContainerView.setOnFocusChangeListener(focusProxyListener);
|
|
|
|
|
mContainerView = findViewById(R.id.all_apps_container);
|
|
|
|
|
mContainerView.setOnFocusChangeListener(focusProxyListener);
|
|
|
|
|
mRevealView = findViewById(R.id.all_apps_reveal);
|
|
|
|
|
|
|
|
|
|
// Load the all apps recycler view
|
|
|
|
|
mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.collection);
|
2015-04-10 10:19:58 -07:00
|
|
|
mAppsRecyclerView.setApps(mApps);
|
2015-05-12 19:05:30 -07:00
|
|
|
mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight);
|
2015-04-10 10:19:58 -07:00
|
|
|
mAppsRecyclerView.setLayoutManager(mLayoutManager);
|
|
|
|
|
mAppsRecyclerView.setAdapter(mAdapter);
|
|
|
|
|
mAppsRecyclerView.setHasFixedSize(true);
|
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-06-04 17:18:17 -07:00
|
|
|
|
|
|
|
|
// Fix the prediction bar height
|
|
|
|
|
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
|
|
|
|
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
|
|
|
|
lp.height = mPredictionBarHeight;
|
|
|
|
|
|
|
|
|
|
updateBackgroundAndPaddings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onBoundsChanged(Rect newBounds) {
|
|
|
|
|
mLauncher.updateOverlayBounds(newBounds);
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
@Override
|
|
|
|
|
public void onBindPredictionBar() {
|
2015-05-15 12:03:37 -07:00
|
|
|
updatePredictionBarVisibility();
|
2015-05-12 19:05:30 -07:00
|
|
|
|
|
|
|
|
List<AppInfo> predictedApps = mApps.getPredictedApps();
|
2015-06-04 17:18:17 -07:00
|
|
|
|
|
|
|
|
// Remove extra prediction icons
|
|
|
|
|
while (mPredictionBarView.getChildCount() > mNumPredictedAppsPerRow) {
|
|
|
|
|
mPredictionBarView.removeViewAt(mPredictionBarView.getChildCount() - 1);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
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(
|
2015-05-22 11:12:27 -07:00
|
|
|
R.layout.all_apps_prediction_bar_icon, mPredictionBarView, false);
|
2015-05-12 19:05:30 -07:00
|
|
|
icon.setFocusable(true);
|
2015-06-02 11:24:28 -07:00
|
|
|
icon.setLongPressTimeout(ViewConfiguration.get(getContext()).getLongPressTimeout());
|
2015-05-12 19:05:30 -07:00
|
|
|
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-05-20 15:03:13 -07:00
|
|
|
|
|
|
|
|
if (mFocusPredictionBarOnFirstBind) {
|
|
|
|
|
mFocusPredictionBarOnFirstBind = false;
|
|
|
|
|
mPredictionBarView.requestFocus();
|
|
|
|
|
}
|
2015-05-12 19:05:30 -07:00
|
|
|
}
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
@Override
|
2015-06-04 17:18:17 -07:00
|
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
|
|
|
// Update the number of items in the grid before we measure the view
|
|
|
|
|
int availableWidth = !mContentBounds.isEmpty() ? mContentBounds.width() :
|
|
|
|
|
MeasureSpec.getSize(widthMeasureSpec);
|
2015-05-06 11:42:25 -07:00
|
|
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
2015-06-04 17:18:17 -07:00
|
|
|
grid.updateAppsViewNumCols(getResources(), availableWidth);
|
|
|
|
|
if (mNumAppsPerRow != grid.allAppsNumCols ||
|
|
|
|
|
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
|
2015-05-22 11:12:27 -07:00
|
|
|
mNumAppsPerRow = grid.allAppsNumCols;
|
|
|
|
|
mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
|
2015-06-12 14:18:55 -07:00
|
|
|
|
|
|
|
|
// If there is a start margin to draw section names, determine how we are going to merge
|
|
|
|
|
// app sections
|
|
|
|
|
boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
|
|
|
|
|
AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
|
|
|
|
|
new FullMergeAlgorithm() :
|
|
|
|
|
new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
|
|
|
|
|
MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
|
|
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
|
2015-05-08 13:06:44 -07:00
|
|
|
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
2015-06-12 14:18:55 -07:00
|
|
|
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
2015-06-04 17:18:17 -07:00
|
|
|
|
|
|
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
2015-04-08 10:27:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2015-06-04 17:18:17 -07:00
|
|
|
* Update the background and padding of the Apps view and children. Instead of insetting the
|
|
|
|
|
* container view, we inset the background and padding of the recycler view to allow for the
|
|
|
|
|
* recycler view to handle touch events (for fast scrolling) all the way to the edge.
|
2015-04-08 10:27:49 -07:00
|
|
|
*/
|
2015-05-08 13:06:44 -07:00
|
|
|
@Override
|
2015-06-04 17:18:17 -07:00
|
|
|
protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) {
|
2015-05-14 00:07:08 -07:00
|
|
|
boolean isRtl = Utilities.isRtl(getResources());
|
2015-04-08 10:27:49 -07:00
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
// TODO: Use quantum_panel instead of quantum_panel_shape.
|
|
|
|
|
InsetDrawable background = new InsetDrawable(
|
|
|
|
|
getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0,
|
|
|
|
|
padding.right, 0);
|
|
|
|
|
mContainerView.setBackground(background);
|
|
|
|
|
mRevealView.setBackground(background.getConstantState().newDrawable());
|
|
|
|
|
mAppsRecyclerView.updateBackgroundPadding(padding);
|
|
|
|
|
mAdapter.updateBackgroundPadding(padding);
|
|
|
|
|
|
|
|
|
|
// Hack: We are going to let the recycler view take the full width, so reset the padding on
|
|
|
|
|
// the container to zero after setting the background and apply the top-bottom padding to
|
|
|
|
|
// the content view instead so that the launcher transition clips correctly.
|
|
|
|
|
mContent.setPadding(0, padding.top, 0, padding.bottom);
|
|
|
|
|
mContainerView.setPadding(0, 0, 0, 0);
|
|
|
|
|
|
|
|
|
|
// Pad the recycler view by the background padding plus the start margin (for the section
|
|
|
|
|
// names)
|
2015-06-12 14:18:55 -07:00
|
|
|
int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getScrollbarWidth());
|
2015-05-08 13:06:44 -07:00
|
|
|
if (isRtl) {
|
2015-06-04 17:18:17 -07:00
|
|
|
mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0,
|
2015-06-12 14:18:55 -07:00
|
|
|
padding.right + startInset, 0);
|
2015-05-08 13:06:44 -07:00
|
|
|
} else {
|
2015-06-12 14:18:55 -07:00
|
|
|
mAppsRecyclerView.setPadding(padding.left + startInset, 0,
|
2015-06-04 17:18:17 -07:00
|
|
|
padding.right + mAppsRecyclerView.getScrollbarWidth(), 0);
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
|
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
// Inset the search bar to fit its bounds above the container
|
|
|
|
|
if (mSearchBarView != null) {
|
|
|
|
|
Rect backgroundPadding = new Rect();
|
|
|
|
|
if (mSearchBarView.getBackground() != null) {
|
|
|
|
|
mSearchBarView.getBackground().getPadding(backgroundPadding);
|
|
|
|
|
}
|
|
|
|
|
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
|
|
|
|
|
mSearchBarContainerView.getLayoutParams();
|
|
|
|
|
lp.leftMargin = searchBarBounds.left - backgroundPadding.left;
|
|
|
|
|
lp.topMargin = searchBarBounds.top - backgroundPadding.top;
|
|
|
|
|
lp.rightMargin = (getMeasuredWidth() - searchBarBounds.right) - backgroundPadding.right;
|
|
|
|
|
mSearchBarContainerView.requestLayout();
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
2015-05-12 19:05:30 -07:00
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
// Update the prediction bar insets as well
|
|
|
|
|
mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
|
2015-05-12 19:05:30 -07:00
|
|
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
|
2015-06-04 17:18:17 -07:00
|
|
|
lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth();
|
|
|
|
|
lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth();
|
2015-05-12 19:05:30 -07:00
|
|
|
mPredictionBarView.requestLayout();
|
2015-05-08 13:06:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2015-06-04 17:18:17 -07:00
|
|
|
public boolean onPreDraw() {
|
|
|
|
|
if (mNumAppsPerRow > 0) {
|
|
|
|
|
// Update the position of the prediction bar to match the scroll of the all apps list
|
|
|
|
|
synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition());
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
2015-05-15 12:03:37 -07:00
|
|
|
@Override
|
2015-06-04 17:18:17 -07:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return super.dispatchKeyEvent(event);
|
2015-05-15 12:03:37 -07:00
|
|
|
}
|
|
|
|
|
|
2015-04-17 12:11:01 -07:00
|
|
|
@Override
|
|
|
|
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
|
|
|
return handleTouchEvent(ev);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-14 00:07:08 -07:00
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
2015-04-17 12:11:01 -07:00
|
|
|
@Override
|
|
|
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
|
|
|
return handleTouchEvent(ev);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-14 00:07:08 -07:00
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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() {
|
2015-05-06 11:42:25 -07:00
|
|
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
2015-03-02 11:51:23 -08:00
|
|
|
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
|
|
|
|
2015-05-12 19:05:30 -07:00
|
|
|
@Override
|
2015-05-20 16:07:46 -07:00
|
|
|
public void onAdapterItemsChanged() {
|
2015-05-12 19:05:30 -07:00
|
|
|
updatePredictionBarVisibility();
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-10 16:28:47 -07:00
|
|
|
@Override
|
|
|
|
|
public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
|
2015-05-15 12:03:37 -07:00
|
|
|
// Register for a pre-draw listener to synchronize the recycler view scroll to other views
|
|
|
|
|
// in this container
|
|
|
|
|
if (!toWorkspace) {
|
|
|
|
|
getViewTreeObserver().addOnPreDrawListener(this);
|
|
|
|
|
}
|
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-15 12:03:37 -07:00
|
|
|
if (toWorkspace) {
|
|
|
|
|
getViewTreeObserver().removeOnPreDrawListener(this);
|
|
|
|
|
mLastRecyclerViewScrollPos = -1;
|
2015-06-04 17:18:17 -07:00
|
|
|
|
|
|
|
|
// Reset the search bar after transitioning home
|
|
|
|
|
mSearchBarController.reset();
|
2015-05-15 12:03:37 -07:00
|
|
|
}
|
2015-05-05 17:21:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates the container when the recycler view is scrolled.
|
|
|
|
|
*/
|
2015-05-14 00:07:08 -07:00
|
|
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
2015-05-15 12:03:37 -07:00
|
|
|
private void synchronizeToRecyclerViewScrollPosition(int scrollY) {
|
|
|
|
|
if (mLastRecyclerViewScrollPos != scrollY) {
|
|
|
|
|
mLastRecyclerViewScrollPos = scrollY;
|
2015-05-12 19:05:30 -07:00
|
|
|
|
2015-05-15 12:03:37 -07:00
|
|
|
// Scroll the prediction bar with the contents of the recycler view
|
|
|
|
|
mPredictionBarView.setTranslationY(-scrollY + 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) {
|
2015-05-06 11:42:25 -07:00
|
|
|
DeviceProfile grid = mLauncher.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.
|
2015-05-15 06:02:44 -07:00
|
|
|
if (mPredictionBarView != null && mPredictionBarView.getVisibility() == View.VISIBLE) {
|
|
|
|
|
mPredictionIconTouchDownPos.set(x, y);
|
|
|
|
|
mPredictionIconUnderTouch = findPredictedAppAtCoordinate(x, y);
|
|
|
|
|
if (mPredictionIconUnderTouch != null) {
|
|
|
|
|
mPredictionIconCheckForLongPress =
|
|
|
|
|
new CheckLongPressHelper(mPredictionIconUnderTouch, this);
|
|
|
|
|
mPredictionIconCheckForLongPress.postCheckForLongPress();
|
|
|
|
|
}
|
2015-05-13 15:44:26 -07:00
|
|
|
}
|
|
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
if (!mContentBounds.isEmpty()) {
|
2015-04-17 12:11:01 -07:00
|
|
|
// Outset the fixed bounds and check if the touch is outside all apps
|
2015-06-04 17:18:17 -07:00
|
|
|
Rect tmpRect = new Rect(mContentBounds);
|
2015-04-17 12:11:01 -07:00
|
|
|
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;
|
2015-05-14 17:24:58 -07:00
|
|
|
case MotionEvent.ACTION_MOVE:
|
|
|
|
|
if (mPredictionIconUnderTouch != null) {
|
|
|
|
|
float dist = (float) Math.hypot(x - mPredictionIconTouchDownPos.x,
|
|
|
|
|
y - mPredictionIconTouchDownPos.y);
|
|
|
|
|
if (dist > ViewConfiguration.get(getContext()).getScaledTouchSlop()) {
|
|
|
|
|
if (mPredictionIconCheckForLongPress != null) {
|
|
|
|
|
mPredictionIconCheckForLongPress.cancelLongPress();
|
|
|
|
|
}
|
|
|
|
|
mPredictionIconCheckForLongPress = null;
|
|
|
|
|
mPredictionIconUnderTouch = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2015-04-17 12:11:01 -07:00
|
|
|
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-06-04 17:18:17 -07:00
|
|
|
@Override
|
2015-06-16 11:36:19 -07:00
|
|
|
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
|
2015-06-04 17:18:17 -07:00
|
|
|
if (apps != null) {
|
|
|
|
|
if (apps.isEmpty()) {
|
|
|
|
|
String formatStr = getResources().getString(R.string.all_apps_no_search_results);
|
|
|
|
|
mAdapter.setEmptySearchText(String.format(formatStr, query));
|
|
|
|
|
} else {
|
|
|
|
|
mAppsRecyclerView.scrollToTop();
|
|
|
|
|
}
|
|
|
|
|
mApps.setOrderedFilter(apps);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void clearSearchResult() {
|
|
|
|
|
mApps.setOrderedFilter(null);
|
|
|
|
|
|
|
|
|
|
// Clear the search query
|
|
|
|
|
mSearchQueryBuilder.clear();
|
|
|
|
|
mSearchQueryBuilder.clearSpans();
|
|
|
|
|
Selection.setSelection(mSearchQueryBuilder, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-28 17:33:40 -07:00
|
|
|
@Override
|
|
|
|
|
public void fillInLaunchSourceData(Bundle sourceData) {
|
|
|
|
|
// Since the other cases are caught by the AllAppsRecyclerView LaunchSourceProvider, we just
|
|
|
|
|
// handle the prediction bar icons here
|
|
|
|
|
sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
|
|
|
|
|
sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
|
|
|
|
|
Stats.SUB_CONTAINER_ALL_APPS_PREDICTION);
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
|
|
|
|
Rect hitRect = new Rect();
|
2015-05-14 17:24:58 -07:00
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
// Ensure that are touching in the recycler view
|
2015-05-14 17:24:58 -07:00
|
|
|
int[] coord = {x, y};
|
2015-06-04 17:18:17 -07:00
|
|
|
Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, coord);
|
|
|
|
|
mAppsRecyclerView.getHitRect(hitRect);
|
|
|
|
|
if (!hitRect.contains(coord[0], coord[1])) {
|
2015-05-14 17:24:58 -07:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check against the children of the prediction bar
|
|
|
|
|
coord[0] = x;
|
|
|
|
|
coord[1] = y;
|
2015-05-13 15:44:26 -07:00
|
|
|
Utilities.mapCoordInSelfToDescendent(mPredictionBarView, this, coord);
|
|
|
|
|
for (int i = 0; i < mPredictionBarView.getChildCount(); i++) {
|
|
|
|
|
View child = mPredictionBarView.getChildAt(i);
|
2015-05-15 06:02:44 -07:00
|
|
|
if (child.getVisibility() != View.VISIBLE) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-05-13 15:44:26 -07:00
|
|
|
child.getHitRect(hitRect);
|
|
|
|
|
if (hitRect.contains(coord[0], coord[1])) {
|
|
|
|
|
return child;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
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() {
|
2015-06-04 17:18:17 -07:00
|
|
|
boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() &&
|
|
|
|
|
(!mApps.hasFilter() || mSearchBarController.shouldShowPredictionBar());
|
2015-05-12 19:05:30 -07:00
|
|
|
if (showPredictionBar) {
|
|
|
|
|
mPredictionBarView.setVisibility(View.VISIBLE);
|
|
|
|
|
} else if (!showPredictionBar) {
|
|
|
|
|
mPredictionBarView.setVisibility(View.INVISIBLE);
|
|
|
|
|
}
|
|
|
|
|
return showPredictionBar;
|
|
|
|
|
}
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|