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
|
|
|
|
|
|
|
|
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.Rect;
|
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;
|
2016-07-21 10:42:30 -07:00
|
|
|
import android.text.Spannable;
|
|
|
|
|
import android.text.SpannableString;
|
2015-06-04 17:18:17 -07:00
|
|
|
import android.text.SpannableStringBuilder;
|
2016-09-09 15:02:20 -07:00
|
|
|
import android.text.TextUtils;
|
2015-06-04 17:18:17 -07:00
|
|
|
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-03-02 11:51:23 -08:00
|
|
|
import android.view.MotionEvent;
|
|
|
|
|
import android.view.View;
|
2016-07-06 15:10:14 -07:00
|
|
|
import android.view.ViewGroup;
|
2016-01-29 13:14:14 -08:00
|
|
|
|
2015-05-22 11:12:27 -07:00
|
|
|
import com.android.launcher3.AppInfo;
|
|
|
|
|
import com.android.launcher3.BaseContainerView;
|
|
|
|
|
import com.android.launcher3.CellLayout;
|
|
|
|
|
import com.android.launcher3.DeleteDropTarget;
|
|
|
|
|
import com.android.launcher3.DeviceProfile;
|
|
|
|
|
import com.android.launcher3.DragSource;
|
|
|
|
|
import com.android.launcher3.DropTarget;
|
2016-01-29 13:14:14 -08:00
|
|
|
import com.android.launcher3.ExtendedEditText;
|
2016-09-26 14:01:56 -07:00
|
|
|
import com.android.launcher3.Insettable;
|
2015-05-22 11:12:27 -07:00
|
|
|
import com.android.launcher3.ItemInfo;
|
|
|
|
|
import com.android.launcher3.Launcher;
|
|
|
|
|
import com.android.launcher3.LauncherTransitionable;
|
|
|
|
|
import com.android.launcher3.R;
|
|
|
|
|
import com.android.launcher3.Utilities;
|
|
|
|
|
import com.android.launcher3.Workspace;
|
2016-04-28 17:39:03 -07:00
|
|
|
import com.android.launcher3.config.FeatureFlags;
|
2016-09-28 12:49:25 -07:00
|
|
|
import com.android.launcher3.dragndrop.DragController;
|
2016-08-16 15:36:48 -07:00
|
|
|
import com.android.launcher3.dragndrop.DragOptions;
|
2016-04-28 17:39:03 -07:00
|
|
|
import com.android.launcher3.folder.Folder;
|
2016-07-21 10:42:30 -07:00
|
|
|
import com.android.launcher3.graphics.TintedDrawableSpan;
|
2016-05-09 20:43:21 -07:00
|
|
|
import com.android.launcher3.keyboard.FocusedItemDecorator;
|
2016-09-01 12:47:12 -07:00
|
|
|
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
|
2015-06-16 11:36:19 -07:00
|
|
|
import com.android.launcher3.util.ComponentKey;
|
2015-03-18 14:16:05 -07:00
|
|
|
|
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-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,
|
2016-06-30 13:52:36 -07:00
|
|
|
AlphabeticalAppsList.SectionInfo withSection,
|
|
|
|
|
int sectionAppCount, int numAppsPerRow, int mergeCount) {
|
2015-07-06 17:14:51 -07:00
|
|
|
// Don't merge the predicted apps
|
2016-07-07 14:47:05 -07:00
|
|
|
if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
|
2015-07-06 17:14:51 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, merge every other section
|
2015-06-12 14:18:55 -07:00
|
|
|
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,
|
2016-06-30 13:52:36 -07:00
|
|
|
AlphabeticalAppsList.SectionInfo withSection,
|
|
|
|
|
int sectionAppCount, int numAppsPerRow, int mergeCount) {
|
2015-07-06 17:14:51 -07:00
|
|
|
// Don't merge the predicted apps
|
2016-07-07 14:47:05 -07:00
|
|
|
if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
|
2015-07-06 17:14:51 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-12 14:18:55 -07:00
|
|
|
// 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,
|
2016-09-26 14:01:56 -07:00
|
|
|
LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks,
|
|
|
|
|
Insettable {
|
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
|
|
|
|
2016-01-29 13:14:14 -08:00
|
|
|
private final Launcher mLauncher;
|
|
|
|
|
private final AlphabeticalAppsList mApps;
|
|
|
|
|
private final AllAppsGridAdapter mAdapter;
|
|
|
|
|
private final RecyclerView.LayoutManager mLayoutManager;
|
|
|
|
|
private final RecyclerView.ItemDecoration mItemDecoration;
|
|
|
|
|
|
|
|
|
|
private AllAppsRecyclerView mAppsRecyclerView;
|
|
|
|
|
private AllAppsSearchBarController mSearchBarController;
|
|
|
|
|
|
|
|
|
|
private View mSearchContainer;
|
|
|
|
|
private ExtendedEditText mSearchInput;
|
|
|
|
|
private HeaderElevationController mElevationController;
|
|
|
|
|
|
2015-07-10 12:38:30 -07:00
|
|
|
private SpannableStringBuilder mSearchQueryBuilder = null;
|
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;
|
2016-09-22 16:50:17 -07:00
|
|
|
|
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
|
|
|
|
2016-06-19 12:49:00 -07:00
|
|
|
mLauncher = Launcher.getLauncher(context);
|
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);
|
2016-04-28 17:39:03 -07:00
|
|
|
mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
|
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-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.
|
|
|
|
|
*/
|
2015-06-18 11:38:42 -07:00
|
|
|
public void setPredictedApps(List<ComponentKey> apps) {
|
2015-05-08 17:34:17 -07:00
|
|
|
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);
|
2016-07-25 14:24:08 -07:00
|
|
|
mSearchBarController.refreshSearchResult();
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates existing apps in the list
|
|
|
|
|
*/
|
|
|
|
|
public void updateApps(List<AppInfo> apps) {
|
|
|
|
|
mApps.updateApps(apps);
|
2016-07-25 14:24:08 -07:00
|
|
|
mSearchBarController.refreshSearchResult();
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes some apps from the list.
|
|
|
|
|
*/
|
|
|
|
|
public void removeApps(List<AppInfo> apps) {
|
|
|
|
|
mApps.removeApps(apps);
|
2016-07-25 14:24:08 -07:00
|
|
|
mSearchBarController.refreshSearchResult();
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|
|
|
|
|
|
2016-06-06 14:19:02 -07:00
|
|
|
public void setSearchBarVisible(boolean visible) {
|
|
|
|
|
if (visible) {
|
|
|
|
|
mSearchBarController.setVisibility(View.VISIBLE);
|
|
|
|
|
} else {
|
|
|
|
|
mSearchBarController.setVisibility(View.INVISIBLE);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-30 13:52:36 -07:00
|
|
|
|
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;
|
2016-03-02 11:32:18 -08:00
|
|
|
mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this);
|
2016-03-03 11:35:59 -08:00
|
|
|
mAdapter.setSearchController(mSearchBarController);
|
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
|
|
|
}
|
|
|
|
|
|
2016-06-10 12:00:02 -07:00
|
|
|
/**
|
|
|
|
|
* Returns whether the view itself will handle the touch event or not.
|
|
|
|
|
*/
|
2016-08-05 13:57:21 -07:00
|
|
|
public boolean shouldContainerScroll(MotionEvent ev) {
|
2016-06-10 12:00:02 -07:00
|
|
|
int[] point = new int[2];
|
2016-08-05 13:57:21 -07:00
|
|
|
point[0] = (int) ev.getX();
|
|
|
|
|
point[1] = (int) ev.getY();
|
2016-06-10 12:00:02 -07:00
|
|
|
Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
|
|
|
|
|
|
2016-08-05 13:57:21 -07:00
|
|
|
// IF the MotionEvent is inside the search box, and the container keeps on receiving
|
|
|
|
|
// touch input, container should move down.
|
|
|
|
|
if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IF the MotionEvent is inside the thumb, container should not be pulled down.
|
2016-06-30 13:52:36 -07:00
|
|
|
if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) {
|
|
|
|
|
return false;
|
2016-06-10 12:00:02 -07:00
|
|
|
}
|
2016-09-08 14:20:48 -07:00
|
|
|
|
|
|
|
|
// IF a shortcuts container is open, container should not be pulled down.
|
|
|
|
|
if (mLauncher.getOpenShortcutsContainer() != null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-12 17:10:38 -07:00
|
|
|
// IF scroller is at the very top OR there is no scroll bar because there is probably not
|
|
|
|
|
// enough items to scroll, THEN it's okay for the container to be pulled down.
|
|
|
|
|
if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) {
|
2016-06-10 12:00:02 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2016-06-06 14:19:02 -07:00
|
|
|
}
|
2016-06-30 13:52:36 -07:00
|
|
|
|
2015-07-10 14:33:23 -07:00
|
|
|
/**
|
|
|
|
|
* Focuses the search field and begins an app search.
|
|
|
|
|
*/
|
|
|
|
|
public void startAppsSearch() {
|
|
|
|
|
if (mSearchBarController != null) {
|
|
|
|
|
mSearchBarController.focusSearchField();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-03 11:36:19 -07:00
|
|
|
/**
|
|
|
|
|
* Resets the state of AllApps.
|
|
|
|
|
*/
|
|
|
|
|
public void reset() {
|
|
|
|
|
// Reset the search bar and base recycler view after transitioning home
|
2016-07-12 15:34:14 -07:00
|
|
|
scrollToTop();
|
2015-09-03 11:36:19 -07:00
|
|
|
mSearchBarController.reset();
|
|
|
|
|
mAppsRecyclerView.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
@Override
|
|
|
|
|
protected void onFinishInflate() {
|
2015-06-04 17:18:17 -07:00
|
|
|
super.onFinishInflate();
|
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.
|
2016-01-29 13:14:14 -08:00
|
|
|
getContentView().setOnFocusChangeListener(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-07-06 17:14:51 -07:00
|
|
|
mAppsRecyclerView.requestFocus();
|
2015-04-10 10:19:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-01-29 13:14:14 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mSearchContainer = findViewById(R.id.search_container);
|
|
|
|
|
mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
|
2016-07-21 10:42:30 -07:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2016-01-29 13:14:14 -08:00
|
|
|
mElevationController = Utilities.ATLEAST_LOLLIPOP
|
|
|
|
|
? new HeaderElevationController.ControllerVL(mSearchContainer)
|
|
|
|
|
: new HeaderElevationController.ControllerV16(mSearchContainer);
|
2015-06-04 17:18:17 -07:00
|
|
|
|
|
|
|
|
// Load the all apps recycler view
|
2015-06-18 15:05:56 -07:00
|
|
|
mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
|
2015-04-10 10:19:58 -07:00
|
|
|
mAppsRecyclerView.setApps(mApps);
|
|
|
|
|
mAppsRecyclerView.setLayoutManager(mLayoutManager);
|
|
|
|
|
mAppsRecyclerView.setAdapter(mAdapter);
|
|
|
|
|
mAppsRecyclerView.setHasFixedSize(true);
|
2016-01-29 13:14:14 -08:00
|
|
|
mAppsRecyclerView.addOnScrollListener(mElevationController);
|
|
|
|
|
mAppsRecyclerView.setElevationController(mElevationController);
|
|
|
|
|
|
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
|
|
|
|
2016-05-09 20:43:21 -07:00
|
|
|
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
|
|
|
|
|
mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
|
2016-07-11 18:59:18 -07:00
|
|
|
mAppsRecyclerView.preMeasureViews(mAdapter);
|
2016-05-09 20:43:21 -07:00
|
|
|
mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
|
|
|
|
|
|
2016-06-15 16:45:48 -07:00
|
|
|
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
|
|
|
|
getRevealView().setVisibility(View.VISIBLE);
|
2016-07-27 17:08:38 -07:00
|
|
|
getContentView().setVisibility(View.VISIBLE);
|
|
|
|
|
getContentView().setBackground(null);
|
2016-06-15 16:45:48 -07:00
|
|
|
}
|
2016-09-26 14:01:56 -07:00
|
|
|
|
|
|
|
|
int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
|
|
|
|
|
int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
|
|
|
|
|
if (Utilities.isRtl(getResources())) {
|
|
|
|
|
mAppsRecyclerView.setPadding(maxScrollBarWidth, 0, startInset, 0);
|
|
|
|
|
} else {
|
|
|
|
|
mAppsRecyclerView.setPadding(startInset, 0, maxScrollBarWidth, 0);
|
|
|
|
|
}
|
2015-06-04 17:18:17 -07:00
|
|
|
}
|
|
|
|
|
|
2016-09-21 14:27:41 -07:00
|
|
|
@Override
|
|
|
|
|
public View getTouchDelegateTargetView() {
|
|
|
|
|
return mAppsRecyclerView;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
@Override
|
2016-03-17 11:57:24 -07:00
|
|
|
public void onBoundsChanged(Rect newBounds) { }
|
2015-03-02 11:51:23 -08:00
|
|
|
|
|
|
|
|
@Override
|
2015-06-04 17:18:17 -07:00
|
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
2016-06-06 14:19:02 -07:00
|
|
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
2016-07-18 17:18:02 -07:00
|
|
|
grid.updateAppsViewNumCols();
|
2016-06-06 14:19:02 -07:00
|
|
|
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
|
|
|
|
if (mNumAppsPerRow != grid.inv.numColumns ||
|
|
|
|
|
mNumPredictedAppsPerRow != grid.inv.numColumns) {
|
|
|
|
|
mNumAppsPerRow = grid.inv.numColumns;
|
|
|
|
|
mNumPredictedAppsPerRow = grid.inv.numColumns;
|
|
|
|
|
|
|
|
|
|
mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
|
|
|
|
|
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
|
|
|
|
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
|
2016-09-21 14:27:41 -07:00
|
|
|
}
|
|
|
|
|
if (!grid.isVerticalBarLayout()) {
|
2016-09-26 14:01:56 -07:00
|
|
|
MarginLayoutParams searchContainerLp =
|
|
|
|
|
(MarginLayoutParams) mSearchContainer.getLayoutParams();
|
|
|
|
|
searchContainerLp.height = grid.hotseatBarHeightPx;
|
|
|
|
|
mSearchContainer.setLayoutParams(searchContainerLp);
|
2016-06-06 14:19:02 -07:00
|
|
|
}
|
|
|
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
|
|
|
|
|
|
2015-06-04 17:18:17 -07:00
|
|
|
// Update the number of items in the grid before we measure the view
|
2016-01-29 13:14:14 -08:00
|
|
|
// TODO: mSectionNamesMargin is currently 0, but also account for it,
|
|
|
|
|
// if it's enabled in the future.
|
2016-07-18 17:18:02 -07:00
|
|
|
grid.updateAppsViewNumCols();
|
2015-06-04 17:18:17 -07:00
|
|
|
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-06-25 16:46:47 -07:00
|
|
|
mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
|
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
|
|
|
|
2016-06-06 14:19:02 -07:00
|
|
|
// --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
|
2015-06-04 17:18:17 -07:00
|
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
2015-04-08 10:27:49 -07: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-03-02 11:51:23 -08:00
|
|
|
@Override
|
2016-09-28 12:49:25 -07:00
|
|
|
public boolean onLongClick(final View v) {
|
2015-03-02 11:51:23 -08:00
|
|
|
// 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
|
2016-06-06 14:19:02 -07:00
|
|
|
|
2015-03-02 11:51:23 -08:00
|
|
|
if (!mLauncher.isAppsViewVisible() ||
|
|
|
|
|
mLauncher.getWorkspace().isSwitchingState()) return false;
|
Refactor shortcuts drag and drop.
- Instead of creating our own drag view within the container, and
handling logic to determine when to start a real drag, we start
the drag immediately and just defer onDragStart().
- To determine when the deferred drag should start, we add a
DeferDragCondition to DragOptions. The default DeferDragCondition
never defers a drag, but is overridden for apps with shortcuts
to defer until the icon is dragged a given distance.
- Because the drag is handled in DragController, including checking
when to start the deferred drag, DeepShortcutsContainer no longer
needs to handle touch events and ShortcutsContainerListener has
been removed.
This change has several immediate benefits:
- The code is much cleaner, because it allows touch handling to be
done by the DragController through the normal drag flow, without
recreating logic in ShortcutsContainerListener/DeepShortcutContainer.
- The janky second haptic feedback has been removed (now it vibrates
when you long press, like everywhere else, but not again when the
shortcuts close after dragging a distance).
- Drops are animated, instead of just popping the icon back into place.
Bug: 30769920
Bug: 30465972
Bug: 31533078
Change-Id: I679b412b72fbf6c3895d76963311eb5010c8e8db
2016-09-19 14:06:31 -07:00
|
|
|
// Return if global dragging is not enabled or we are already dragging
|
2015-03-02 11:51:23 -08:00
|
|
|
if (!mLauncher.isDraggingEnabled()) return false;
|
Refactor shortcuts drag and drop.
- Instead of creating our own drag view within the container, and
handling logic to determine when to start a real drag, we start
the drag immediately and just defer onDragStart().
- To determine when the deferred drag should start, we add a
DeferDragCondition to DragOptions. The default DeferDragCondition
never defers a drag, but is overridden for apps with shortcuts
to defer until the icon is dragged a given distance.
- Because the drag is handled in DragController, including checking
when to start the deferred drag, DeepShortcutsContainer no longer
needs to handle touch events and ShortcutsContainerListener has
been removed.
This change has several immediate benefits:
- The code is much cleaner, because it allows touch handling to be
done by the DragController through the normal drag flow, without
recreating logic in ShortcutsContainerListener/DeepShortcutContainer.
- The janky second haptic feedback has been removed (now it vibrates
when you long press, like everywhere else, but not again when the
shortcuts close after dragging a distance).
- Drops are animated, instead of just popping the icon back into place.
Bug: 30769920
Bug: 30465972
Bug: 31533078
Change-Id: I679b412b72fbf6c3895d76963311eb5010c8e8db
2016-09-19 14:06:31 -07:00
|
|
|
if (mLauncher.getDragController().isDragging()) return false;
|
2015-03-02 11:51:23 -08:00
|
|
|
|
|
|
|
|
// Start the drag
|
2016-09-28 12:49:25 -07:00
|
|
|
final DragController dragController = mLauncher.getDragController();
|
|
|
|
|
dragController.addDragListener(new DragController.DragListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
|
|
|
|
|
v.setVisibility(INVISIBLE);
|
Refactor shortcuts drag and drop.
- Instead of creating our own drag view within the container, and
handling logic to determine when to start a real drag, we start
the drag immediately and just defer onDragStart().
- To determine when the deferred drag should start, we add a
DeferDragCondition to DragOptions. The default DeferDragCondition
never defers a drag, but is overridden for apps with shortcuts
to defer until the icon is dragged a given distance.
- Because the drag is handled in DragController, including checking
when to start the deferred drag, DeepShortcutsContainer no longer
needs to handle touch events and ShortcutsContainerListener has
been removed.
This change has several immediate benefits:
- The code is much cleaner, because it allows touch handling to be
done by the DragController through the normal drag flow, without
recreating logic in ShortcutsContainerListener/DeepShortcutContainer.
- The janky second haptic feedback has been removed (now it vibrates
when you long press, like everywhere else, but not again when the
shortcuts close after dragging a distance).
- Drops are animated, instead of just popping the icon back into place.
Bug: 30769920
Bug: 30465972
Bug: 31533078
Change-Id: I679b412b72fbf6c3895d76963311eb5010c8e8db
2016-09-19 14:06:31 -07:00
|
|
|
}
|
2016-09-28 12:49:25 -07:00
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onDragEnd() {
|
|
|
|
|
v.setVisibility(VISIBLE);
|
|
|
|
|
dragController.removeDragListener(this);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
|
Refactor shortcuts drag and drop.
- Instead of creating our own drag view within the container, and
handling logic to determine when to start a real drag, we start
the drag immediately and just defer onDragStart().
- To determine when the deferred drag should start, we add a
DeferDragCondition to DragOptions. The default DeferDragCondition
never defers a drag, but is overridden for apps with shortcuts
to defer until the icon is dragged a given distance.
- Because the drag is handled in DragController, including checking
when to start the deferred drag, DeepShortcutsContainer no longer
needs to handle touch events and ShortcutsContainerListener has
been removed.
This change has several immediate benefits:
- The code is much cleaner, because it allows touch handling to be
done by the DragController through the normal drag flow, without
recreating logic in ShortcutsContainerListener/DeepShortcutContainer.
- The janky second haptic feedback has been removed (now it vibrates
when you long press, like everywhere else, but not again when the
shortcuts close after dragging a distance).
- Drops are animated, instead of just popping the icon back into place.
Bug: 30769920
Bug: 30465972
Bug: 31533078
Change-Id: I679b412b72fbf6c3895d76963311eb5010c8e8db
2016-09-19 14:06:31 -07:00
|
|
|
if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
|
|
|
|
|
// Enter spring loaded mode (the new workspace does this in
|
|
|
|
|
// onDragStart(), so we don't want to do it here)
|
|
|
|
|
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;
|
2016-09-28 12:49:25 -07:00
|
|
|
if (target instanceof Workspace) {
|
2015-03-02 11:51:23 -08:00
|
|
|
int currentScreen = mLauncher.getCurrentWorkspaceScreen();
|
|
|
|
|
Workspace workspace = (Workspace) target;
|
|
|
|
|
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
|
2015-06-12 20:04:41 -07:00
|
|
|
ItemInfo itemInfo = d.dragInfo;
|
2015-03-02 11:51:23 -08:00
|
|
|
if (layout != null) {
|
|
|
|
|
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
|
2016-04-05 18:36:36 -07:00
|
|
|
public void onLauncherTransitionPrepare(Launcher l, boolean animated,
|
|
|
|
|
boolean multiplePagesVisible) {
|
2015-07-06 17:14:51 -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-15 12:03:37 -07:00
|
|
|
if (toWorkspace) {
|
2015-09-03 11:36:19 -07:00
|
|
|
reset();
|
2015-05-15 12:03:37 -07:00
|
|
|
}
|
2015-05-05 17:21:58 -07:00
|
|
|
}
|
|
|
|
|
|
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) {
|
2016-02-11 10:07:36 -08:00
|
|
|
if (mApps.setOrderedFilter(apps)) {
|
|
|
|
|
mAppsRecyclerView.onSearchResultsChanged();
|
|
|
|
|
}
|
2015-07-10 12:38:30 -07:00
|
|
|
mAdapter.setLastSearchQuery(query);
|
2015-06-04 17:18:17 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void clearSearchResult() {
|
2016-02-11 10:07:36 -08:00
|
|
|
if (mApps.setOrderedFilter(null)) {
|
|
|
|
|
mAppsRecyclerView.onSearchResultsChanged();
|
|
|
|
|
}
|
2015-06-04 17:18:17 -07:00
|
|
|
|
|
|
|
|
// Clear the search query
|
|
|
|
|
mSearchQueryBuilder.clear();
|
|
|
|
|
mSearchQueryBuilder.clearSpans();
|
|
|
|
|
Selection.setSelection(mSearchQueryBuilder, 0);
|
|
|
|
|
}
|
2016-09-01 12:47:12 -07:00
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
|
|
|
|
|
targetParent.containerType = mAppsRecyclerView.getContainerType(v);
|
|
|
|
|
}
|
2016-09-09 15:02:20 -07:00
|
|
|
|
|
|
|
|
public boolean shouldRestoreImeState() {
|
|
|
|
|
return !TextUtils.isEmpty(mSearchInput.getText());
|
|
|
|
|
}
|
2016-09-26 14:01:56 -07:00
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void setInsets(Rect insets) {
|
|
|
|
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
|
|
|
|
if (grid.isVerticalBarLayout()) {
|
|
|
|
|
ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
|
|
|
|
|
mlp.leftMargin = insets.left;
|
|
|
|
|
mlp.topMargin = insets.top;
|
|
|
|
|
mlp.rightMargin = insets.right;
|
|
|
|
|
setLayoutParams(mlp);
|
|
|
|
|
} else {
|
|
|
|
|
View navBarBg = findViewById(R.id.nav_bar_bg);
|
|
|
|
|
ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
|
|
|
|
|
navBarBgLp.height = insets.bottom;
|
|
|
|
|
navBarBg.setLayoutParams(navBarBgLp);
|
|
|
|
|
navBarBg.setVisibility(View.VISIBLE);
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-02 11:51:23 -08:00
|
|
|
}
|