mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-01 08:16:49 +00:00
499 lines
18 KiB
Java
499 lines
18 KiB
Java
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.android.launcher3.allapps;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Rect;
|
|
import android.os.Process;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.v7.widget.LinearLayoutManager;
|
|
import android.support.v7.widget.RecyclerView;
|
|
import android.text.Selection;
|
|
import android.text.SpannableStringBuilder;
|
|
import android.util.AttributeSet;
|
|
import android.view.KeyEvent;
|
|
import android.view.LayoutInflater;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.View.OnLongClickListener;
|
|
import android.view.ViewGroup;
|
|
import android.widget.RelativeLayout;
|
|
|
|
import com.android.launcher3.AppInfo;
|
|
import com.android.launcher3.BubbleTextView;
|
|
import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
|
|
import com.android.launcher3.ClickShadowView;
|
|
import com.android.launcher3.DeviceProfile;
|
|
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
|
import com.android.launcher3.DragSource;
|
|
import com.android.launcher3.DropTarget;
|
|
import com.android.launcher3.DropTarget.DragObject;
|
|
import com.android.launcher3.Insettable;
|
|
import com.android.launcher3.InsettableFrameLayout;
|
|
import com.android.launcher3.ItemInfo;
|
|
import com.android.launcher3.Launcher;
|
|
import com.android.launcher3.LauncherState;
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.anim.SpringAnimationHandler;
|
|
import com.android.launcher3.config.FeatureFlags;
|
|
import com.android.launcher3.dragndrop.DragController;
|
|
import com.android.launcher3.dragndrop.DragOptions;
|
|
import com.android.launcher3.keyboard.FocusedItemDecorator;
|
|
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
|
|
import com.android.launcher3.util.ItemInfoMatcher;
|
|
import com.android.launcher3.views.BottomUserEducationView;
|
|
|
|
/**
|
|
* The all apps view container.
|
|
*/
|
|
public class AllAppsContainerView extends RelativeLayout implements DragSource,
|
|
OnLongClickListener, Insettable, BubbleTextShadowHandler, OnDeviceProfileChangeListener {
|
|
|
|
private final Launcher mLauncher;
|
|
private final AdapterHolder[] mAH;
|
|
private final ClickShadowView mTouchFeedbackView;
|
|
private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
|
|
private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
|
|
private final AllAppsStore mAllAppsStore = new AllAppsStore();
|
|
|
|
private SearchUiManager mSearchUiManager;
|
|
private View mSearchContainer;
|
|
private AllAppsPagedView mViewPager;
|
|
private FloatingHeaderView mHeader;
|
|
|
|
private SpannableStringBuilder mSearchQueryBuilder = null;
|
|
|
|
private boolean mUsingTabs;
|
|
private boolean mSearchModeWhileUsingTabs = false;
|
|
|
|
public AllAppsContainerView(Context context) {
|
|
this(context, null);
|
|
}
|
|
|
|
public AllAppsContainerView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
super(context, attrs, defStyleAttr);
|
|
|
|
mLauncher = Launcher.getLauncher(context);
|
|
mLauncher.addOnDeviceProfileChangeListener(this);
|
|
|
|
mSearchQueryBuilder = new SpannableStringBuilder();
|
|
|
|
Selection.setSelection(mSearchQueryBuilder, 0);
|
|
|
|
mTouchFeedbackView = new ClickShadowView(context);
|
|
// Make the feedback view large enough to hold the blur bitmap.
|
|
int size = mLauncher.getDeviceProfile().allAppsIconSizePx
|
|
+ mTouchFeedbackView.getExtraSize();
|
|
addView(mTouchFeedbackView, size, size);
|
|
|
|
mAH = new AdapterHolder[2];
|
|
mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
|
|
mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
|
|
|
|
mAllAppsStore.addUpdateListener(this::onAppsUpdated);
|
|
}
|
|
|
|
@Override
|
|
protected void onAttachedToWindow() {
|
|
super.onAttachedToWindow();
|
|
applyTouchDelegate();
|
|
}
|
|
|
|
private void applyTouchDelegate() {
|
|
// TODO: Reimplement once fast scroller is fixed.
|
|
}
|
|
|
|
public AllAppsStore getAppsStore() {
|
|
return mAllAppsStore;
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
super.onLayout(changed, left, top, right, bottom);
|
|
applyTouchDelegate();
|
|
}
|
|
|
|
@Override
|
|
public void onDeviceProfileChanged(DeviceProfile dp) {
|
|
for (AdapterHolder holder : mAH) {
|
|
if (holder.recyclerView != null) {
|
|
// Remove all views and clear the pool, while keeping the data same. After this
|
|
// call, all the viewHolders will be recreated.
|
|
holder.recyclerView.swapAdapter(holder.recyclerView.getAdapter(), true);
|
|
holder.recyclerView.getRecycledViewPool().clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void onAppsUpdated() {
|
|
if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
|
|
boolean hasWorkApps = false;
|
|
for (AppInfo app : mAllAppsStore.getApps()) {
|
|
if (mWorkMatcher.matches(app, null)) {
|
|
hasWorkApps = true;
|
|
break;
|
|
}
|
|
}
|
|
rebindAdapters(hasWorkApps);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setPressedIcon(BubbleTextView icon, Bitmap background) {
|
|
mTouchFeedbackView.setPressedIcon(icon, background);
|
|
}
|
|
|
|
/**
|
|
* Returns whether the view itself will handle the touch event or not.
|
|
*/
|
|
public boolean shouldContainerScroll(MotionEvent ev) {
|
|
// 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;
|
|
}
|
|
AllAppsRecyclerView rv = getActiveRecyclerView();
|
|
return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
|
|
}
|
|
|
|
public AllAppsRecyclerView getActiveRecyclerView() {
|
|
if (!mUsingTabs || mViewPager.getNextPage() == 0) {
|
|
return mAH[AdapterHolder.MAIN].recyclerView;
|
|
} else {
|
|
return mAH[AdapterHolder.WORK].recyclerView;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the state of AllApps.
|
|
*/
|
|
public void reset() {
|
|
for (int i = 0; i < mAH.length; i++) {
|
|
if (mAH[i].recyclerView != null) {
|
|
mAH[i].recyclerView.scrollToTop();
|
|
}
|
|
}
|
|
if (isHeaderVisible()) {
|
|
mHeader.reset();
|
|
}
|
|
// Reset the search bar and base recycler view after transitioning home
|
|
mSearchUiManager.reset();
|
|
}
|
|
|
|
@Override
|
|
protected void onFinishInflate() {
|
|
super.onFinishInflate();
|
|
|
|
// 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.
|
|
setOnFocusChangeListener((v, hasFocus) -> {
|
|
if (hasFocus && getActiveRecyclerView() != null) {
|
|
getActiveRecyclerView().requestFocus();
|
|
}
|
|
});
|
|
|
|
mHeader = findViewById(R.id.all_apps_header);
|
|
rebindAdapters(mUsingTabs, true /* force */);
|
|
|
|
mSearchContainer = findViewById(R.id.search_container_all_apps);
|
|
mSearchUiManager = (SearchUiManager) mSearchContainer;
|
|
mSearchUiManager.initialize(this);
|
|
}
|
|
|
|
public SearchUiManager getSearchUiManager() {
|
|
return mSearchUiManager;
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
mSearchUiManager.preDispatchKeyEvent(event);
|
|
return super.dispatchKeyEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public boolean onLongClick(final View v) {
|
|
// When we have exited all apps or are in transition, disregard long clicks
|
|
if (!mLauncher.isInState(LauncherState.ALL_APPS) ||
|
|
mLauncher.getWorkspace().isSwitchingState()) return false;
|
|
// Return if global dragging is not enabled or we are already dragging
|
|
if (!mLauncher.isDraggingEnabled()) return false;
|
|
if (mLauncher.getDragController().isDragging()) return false;
|
|
|
|
// Start the drag
|
|
final DragController dragController = mLauncher.getDragController();
|
|
dragController.addDragListener(new DragController.DragListener() {
|
|
@Override
|
|
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
|
|
v.setVisibility(INVISIBLE);
|
|
}
|
|
|
|
@Override
|
|
public void onDragEnd() {
|
|
v.setVisibility(VISIBLE);
|
|
dragController.removeDragListener(this);
|
|
}
|
|
});
|
|
|
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
|
DragOptions options = new DragOptions();
|
|
options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
|
|
mLauncher.getWorkspace().beginDragShared(v, this, options);
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void onDropCompleted(View target, DragObject d, boolean success) { }
|
|
|
|
@Override
|
|
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
|
|
// This is filled in {@link AllAppsRecyclerView}
|
|
}
|
|
|
|
@Override
|
|
public void setInsets(Rect insets) {
|
|
DeviceProfile grid = mLauncher.getDeviceProfile();
|
|
int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
|
|
+ grid.cellLayoutPaddingLeftRightPx;
|
|
|
|
for (int i = 0; i < mAH.length; i++) {
|
|
mAH[i].padding.bottom = insets.bottom;
|
|
mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
|
|
mAH[i].applyPadding();
|
|
}
|
|
|
|
ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
|
|
if (grid.isVerticalBarLayout()) {
|
|
mlp.leftMargin = insets.left;
|
|
mlp.topMargin = insets.top;
|
|
mlp.rightMargin = insets.right;
|
|
setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
|
|
} else {
|
|
mlp.leftMargin = mlp.rightMargin = mlp.topMargin = 0;
|
|
setPadding(0, 0, 0, 0);
|
|
}
|
|
setLayoutParams(mlp);
|
|
|
|
View navBarBg = findViewById(R.id.nav_bar_bg);
|
|
ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
|
|
navBarBgLp.height = insets.bottom;
|
|
navBarBg.setLayoutParams(navBarBgLp);
|
|
|
|
InsettableFrameLayout.dispatchInsets(this, insets);
|
|
}
|
|
|
|
public SpringAnimationHandler getSpringAnimationHandler() {
|
|
return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
|
|
}
|
|
|
|
private void rebindAdapters(boolean showTabs) {
|
|
rebindAdapters(showTabs, false /* force */);
|
|
}
|
|
|
|
private void rebindAdapters(boolean showTabs, boolean force) {
|
|
if (showTabs == mUsingTabs && !force) {
|
|
return;
|
|
}
|
|
replaceRVContainer(showTabs);
|
|
mUsingTabs = showTabs;
|
|
|
|
mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
|
|
mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
|
|
|
|
if (mUsingTabs) {
|
|
mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
|
|
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
|
|
onTabChanged(mViewPager.getNextPage());
|
|
} else {
|
|
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
|
|
mAH[AdapterHolder.WORK].recyclerView = null;
|
|
}
|
|
setupHeader();
|
|
|
|
mAllAppsStore.registerIconContainer(mAH[AdapterHolder.MAIN].recyclerView);
|
|
mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
|
|
|
|
applyTouchDelegate();
|
|
}
|
|
|
|
private void replaceRVContainer(boolean showTabs) {
|
|
for (int i = 0; i < mAH.length; i++) {
|
|
if (mAH[i].recyclerView != null) {
|
|
mAH[i].recyclerView.setLayoutManager(null);
|
|
}
|
|
}
|
|
View oldView = getRecyclerViewContainer();
|
|
int index = indexOfChild(oldView);
|
|
removeView(oldView);
|
|
int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
|
|
View newView = LayoutInflater.from(getContext()).inflate(layout, this, false);
|
|
addView(newView, index);
|
|
if (showTabs) {
|
|
mViewPager = (AllAppsPagedView) newView;
|
|
mViewPager.initParentViews(this);
|
|
mViewPager.getPageIndicator().setContainerView(this);
|
|
} else {
|
|
mViewPager = null;
|
|
}
|
|
}
|
|
|
|
public View getRecyclerViewContainer() {
|
|
return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
|
|
}
|
|
|
|
public void onTabChanged(int pos) {
|
|
mHeader.setMainActive(pos == 0);
|
|
reset();
|
|
applyTouchDelegate();
|
|
if (mAH[pos].recyclerView != null) {
|
|
mAH[pos].recyclerView.bindFastScrollbar();
|
|
|
|
findViewById(R.id.tab_personal)
|
|
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
|
|
findViewById(R.id.tab_work)
|
|
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
|
|
|
|
}
|
|
if (pos == AdapterHolder.WORK) {
|
|
BottomUserEducationView.showIfNeeded(mLauncher);
|
|
}
|
|
}
|
|
|
|
public AlphabeticalAppsList getApps() {
|
|
return mAH[AdapterHolder.MAIN].appsList;
|
|
}
|
|
|
|
public FloatingHeaderView getFloatingHeaderView() {
|
|
return mHeader;
|
|
}
|
|
|
|
public void setupHeader() {
|
|
mHeader.setVisibility(View.VISIBLE);
|
|
mHeader.setup(mAH, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView == null);
|
|
|
|
int padding = mHeader.getMaxTranslation();
|
|
for (int i = 0; i < mAH.length; i++) {
|
|
mAH[i].padding.top = padding;
|
|
mAH[i].applyPadding();
|
|
}
|
|
}
|
|
|
|
public void setLastSearchQuery(String query) {
|
|
for (int i = 0; i < mAH.length; i++) {
|
|
mAH[i].adapter.setLastSearchQuery(query);
|
|
}
|
|
if (mUsingTabs) {
|
|
mSearchModeWhileUsingTabs = true;
|
|
rebindAdapters(false); // hide tabs
|
|
}
|
|
}
|
|
|
|
public void onClearSearchResult() {
|
|
if (mSearchModeWhileUsingTabs) {
|
|
rebindAdapters(true); // show tabs
|
|
mSearchModeWhileUsingTabs = false;
|
|
}
|
|
}
|
|
|
|
public void onSearchResultsChanged() {
|
|
for (int i = 0; i < mAH.length; i++) {
|
|
if (mAH[i].recyclerView != null) {
|
|
mAH[i].recyclerView.onSearchResultsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
|
|
for (int i = 0; i < mAH.length; i++) {
|
|
mAH[i].applyVerticalFadingEdgeEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
public void addElevationController(RecyclerView.OnScrollListener scrollListener) {
|
|
if (!mUsingTabs) {
|
|
mAH[AdapterHolder.MAIN].recyclerView.addOnScrollListener(scrollListener);
|
|
}
|
|
}
|
|
|
|
public boolean isHeaderVisible() {
|
|
return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
|
|
}
|
|
|
|
public void onScrollUpEnd() {
|
|
if (mUsingTabs) {
|
|
((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs)).highlightWorkTabIfNecessary();
|
|
}
|
|
}
|
|
|
|
public class AdapterHolder {
|
|
public static final int MAIN = 0;
|
|
public static final int WORK = 1;
|
|
|
|
public final AllAppsGridAdapter adapter;
|
|
final LinearLayoutManager layoutManager;
|
|
final SpringAnimationHandler animationHandler;
|
|
final AlphabeticalAppsList appsList;
|
|
final Rect padding = new Rect();
|
|
AllAppsRecyclerView recyclerView;
|
|
boolean verticalFadingEdge;
|
|
|
|
AdapterHolder(boolean isWork) {
|
|
appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
|
|
adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
|
|
AllAppsContainerView.this, true);
|
|
appsList.setAdapter(adapter);
|
|
animationHandler = adapter.getSpringAnimationHandler();
|
|
layoutManager = adapter.getLayoutManager();
|
|
}
|
|
|
|
void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
|
|
appsList.updateItemFilter(matcher);
|
|
recyclerView = (AllAppsRecyclerView) rv;
|
|
recyclerView.setApps(appsList, mUsingTabs);
|
|
recyclerView.setLayoutManager(layoutManager);
|
|
recyclerView.setAdapter(adapter);
|
|
recyclerView.setHasFixedSize(true);
|
|
// No animations will occur when changes occur to the items in this RecyclerView.
|
|
recyclerView.setItemAnimator(null);
|
|
if (FeatureFlags.LAUNCHER3_PHYSICS && animationHandler != null) {
|
|
recyclerView.setSpringAnimationHandler(animationHandler);
|
|
}
|
|
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
|
|
recyclerView.addItemDecoration(focusedItemDecorator);
|
|
adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
|
|
applyVerticalFadingEdgeEnabled(verticalFadingEdge);
|
|
applyPadding();
|
|
}
|
|
|
|
void applyPadding() {
|
|
if (recyclerView != null) {
|
|
recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
|
|
}
|
|
}
|
|
|
|
public void applyVerticalFadingEdgeEnabled(boolean enabled) {
|
|
verticalFadingEdge = enabled;
|
|
mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
|
|
&& verticalFadingEdge);
|
|
}
|
|
}
|
|
}
|