diff options
author | Mario Bertschler <bmario@google.com> | 2017-10-18 10:31:36 -0700 |
---|---|---|
committer | Mario Bertschler <bmario@google.com> | 2017-11-14 10:28:54 -0800 |
commit | ac9408a5cd7744a8dbc66a61114665ab6e4051de (patch) | |
tree | edb48150310dde96c444ec64952fd9678d825a13 | |
parent | f405f507b70c0bd54adcfd69cc2c7a2b245d10d6 (diff) | |
download | android_packages_apps_Trebuchet-ac9408a5cd7744a8dbc66a61114665ab6e4051de.tar.gz android_packages_apps_Trebuchet-ac9408a5cd7744a8dbc66a61114665ab6e4051de.tar.bz2 android_packages_apps_Trebuchet-ac9408a5cd7744a8dbc66a61114665ab6e4051de.zip |
Enable work profile tab in all apps.
This CL will bring two tabs to all apps: Personal and Work,
currently only if the user has a workfile set up and
behind a feature flag defaulting to disabled.
Bug: 68713881
Change-Id: Ib5a558281ef3593359db3ad593ee1d0cf279f547
19 files changed, 1004 insertions, 184 deletions
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml index 6f837aeca..05f509f13 100644 --- a/res/layout/all_apps.xml +++ b/res/layout/all_apps.xml @@ -27,25 +27,52 @@ android:focusableInTouchMode="true" android:saveEnabled="false" > - <!-- DO NOT CHANGE THE ID --> - <com.android.launcher3.allapps.AllAppsRecyclerView - android:id="@+id/apps_list_view" + <include layout="@layout/all_apps_rv_layout" /> + + <include layout="@layout/all_apps_fast_scroller" /> + + <RelativeLayout + android:id="@+id/all_apps_header" android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_below="@id/search_container_all_apps" - android:layout_gravity="center_horizontal|top" - android:clipToPadding="false" - android:descendantFocusability="afterDescendants" - android:focusable="true" - android:overScrollMode="never" /> + android:layout_height="wrap_content" + android:clickable="true" + android:paddingTop="30dp" + android:layout_below="@id/search_container_all_apps" > + + <com.android.launcher3.allapps.PredictionRowView + android:id="@+id/header_content" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <LinearLayout + android:id="@+id/tab_layout" + android:layout_width="match_parent" + android:layout_height="@dimen/all_apps_header_tab_height" + android:layout_below="@id/header_content" + android:orientation="horizontal"> + <Button + android:id="@+id/tab_personal" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:text="@string/all_apps_personal_tab" + android:background="?android:attr/selectableItemBackground"/> + <Button + android:id="@+id/tab_work" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:text="@string/all_apps_work_tab" + android:background="?android:attr/selectableItemBackground"/> + </LinearLayout> + + </RelativeLayout> <!-- Note: we are reusing/repurposing a system attribute for search layout, because of a platform bug, which prevents using custom attributes in <include> tag --> <include android:id="@id/search_container_all_apps" - layout="?android:attr/keyboardLayout" /> - - <include layout="@layout/all_apps_fast_scroller" /> + layout="?android:attr/keyboardLayout"/> <View android:id="@+id/nav_bar_bg" diff --git a/res/layout/all_apps_rv_layout.xml b/res/layout/all_apps_rv_layout.xml new file mode 100644 index 000000000..3c19f8c5b --- /dev/null +++ b/res/layout/all_apps_rv_layout.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.launcher3.allapps.AllAppsRecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/apps_list_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_below="@id/search_container_all_apps" + android:clipToPadding="false" + android:descendantFocusability="afterDescendants" + android:focusable="true" + android:overScrollMode="never" /> diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml new file mode 100644 index 000000000..fa1d5914c --- /dev/null +++ b/res/layout/all_apps_tabs.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.launcher3.allapps.InterceptingViewPager + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/all_apps_tabs_view_pager" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_below="@id/search_container_all_apps" + android:layout_gravity="center_horizontal|top" + android:layout_marginTop="@dimen/all_apps_header_tab_height" + android:clipChildren="false" + android:clipToPadding="false" + android:descendantFocusability="afterDescendants" + android:paddingTop="30dp"> + + <include layout="@layout/all_apps_rv_layout" /> + + <include layout="@layout/all_apps_rv_layout" /> + +</com.android.launcher3.allapps.InterceptingViewPager>
\ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 59736d8fd..94db0cc9d 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -90,6 +90,7 @@ <dimen name="all_apps_background_canvas_width">700dp</dimen> <dimen name="all_apps_background_canvas_height">475dp</dimen> <dimen name="all_apps_caret_workspace_offset">18dp</dimen> + <dimen name="all_apps_header_tab_height">50dp</dimen> <!-- Search bar in All Apps --> <dimen name="all_apps_header_max_elevation">3dp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index e9b00f6fa..fdd4d8d4d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -318,4 +318,10 @@ <!-- Accessibility confirmation for notification being dismissed. --> <string name="notification_dismissed">Notification dismissed</string> + <!-- Label of tab to indicate personal apps --> + <string name="all_apps_personal_tab">Personal</string> + + <!-- Label of tab to indicate work apps --> + <string name="all_apps_work_tab">Work</string> + </resources> diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index afb83be58..b315980e8 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -61,9 +61,13 @@ public abstract class BaseRecyclerView extends RecyclerView @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - ViewGroup parent = (ViewGroup) getParent(); + bindFastScrollbar(); + } + + public void bindFastScrollbar() { + ViewGroup parent = (ViewGroup) getParent().getParent(); mScrollbar = parent.findViewById(R.id.fast_scroller); - mScrollbar.setRecyclerView(this, (TextView) parent.findViewById(R.id.fast_scroller_popup)); + mScrollbar.setRecyclerView(this, parent.findViewById(R.id.fast_scroller_popup)); } /** diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 6ddaf29db..c6226f447 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -31,6 +31,7 @@ import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import com.android.launcher3.CellLayout.ContainerType; +import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.badge.BadgeRenderer; import java.util.ArrayList; @@ -668,10 +669,9 @@ public class DeviceProfile { } // Layout the AllAppsRecyclerView - View view = launcher.findViewById(R.id.apps_list_view); + AllAppsContainerView appsView = launcher.findViewById(R.id.apps_view); int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx; - view.setPadding(paddingLeftRight, view.getPaddingTop(), paddingLeftRight, - view.getPaddingBottom()); + appsView.setRecyclerViewSidePadding(paddingLeftRight, paddingLeftRight); if (notifyListeners) { for (int i = mListeners.size() - 1; i >= 0; i--) { diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 81f58423f..271a133ea 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -18,11 +18,18 @@ 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.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; 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.ViewGroup; @@ -47,9 +54,14 @@ 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.ComponentKey; +import com.android.launcher3.util.ComponentKeyMapper; +import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.Themes; import com.android.launcher3.util.TransformingTouchDelegate; +import java.util.HashMap; import java.util.List; import java.util.Set; @@ -63,23 +75,26 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, protected final Rect mBasePadding = new Rect(); private final Launcher mLauncher; - private final AlphabeticalAppsList mApps; - private final AllAppsGridAdapter mAdapter; - private final LinearLayoutManager mLayoutManager; + private final AdapterHolder[] mAH; private final ClickShadowView mTouchFeedbackView; + private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle()); + private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher); - private AllAppsRecyclerView mAppsRecyclerView; private SearchUiManager mSearchUiManager; private View mSearchContainer; + private InterceptingViewPager mViewPager; + private ViewGroup mHeader; + private FloatingHeaderHandler mFloatingHeaderHandler; private SpannableStringBuilder mSearchQueryBuilder = null; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; - private SpringAnimationHandler mSpringAnimationHandler; - private TransformingTouchDelegate mTouchDelegate; + private boolean mUsingTabs; + + private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>(); public AllAppsContainerView(Context context) { this(context, null); @@ -93,11 +108,7 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, super(context, attrs, defStyleAttr); mLauncher = Launcher.getLauncher(context); - mApps = new AlphabeticalAppsList(context); - mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this); - mSpringAnimationHandler = mAdapter.getSpringAnimationHandler(); - mApps.setAdapter(mAdapter); - mLayoutManager = mAdapter.getLayoutManager(); + mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); @@ -107,6 +118,10 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, int size = mLauncher.getDeviceProfile().allAppsIconSizePx + mTouchFeedbackView.getExtraSize(); addView(mTouchFeedbackView, size, size); + + mAH = new AdapterHolder[2]; + mAH[AdapterHolder.MAIN] = new AdapterHolder(); + mAH[AdapterHolder.WORK] = new AdapterHolder(); } @Override @@ -116,7 +131,17 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); grid.addLauncherLayoutChangedListener(this); - mTouchDelegate = new TransformingTouchDelegate(mAppsRecyclerView); + applyTouchDelegate(); + } + + private void applyTouchDelegate() { + RecyclerView rv = getActiveRecyclerView(); + mTouchDelegate = new TransformingTouchDelegate(rv); + mTouchDelegate.setBounds( + rv.getLeft() - mBasePadding.left, + rv.getTop() - mBasePadding.top, + rv.getRight() + mBasePadding.right, + rv.getBottom() + mBasePadding.bottom); setTouchDelegate(mTouchDelegate); } @@ -148,11 +173,7 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - mTouchDelegate.setBounds( - mAppsRecyclerView.getLeft() - mBasePadding.left, - mAppsRecyclerView.getTop() - mBasePadding.top, - mAppsRecyclerView.getRight() + mBasePadding.right, - mAppsRecyclerView.getBottom() + mBasePadding.bottom); + applyTouchDelegate(); } @Override @@ -164,21 +185,62 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, * Sets the current set of apps. */ public void setApps(List<AppInfo> apps) { - mApps.setApps(apps); + boolean hasWorkProfileApp = hasWorkProfileApp(apps); + if (mUsingTabs != hasWorkProfileApp) { + rebindAdapters(hasWorkProfileApp); + } + mComponentToAppMap.clear(); + addOrUpdateApps(apps); } /** * Adds or updates existing apps in the list */ public void addOrUpdateApps(List<AppInfo> apps) { - mApps.addOrUpdateApps(apps); + for (AppInfo app : apps) { + mComponentToAppMap.put(app.toComponentKey(), app); + } + onAppsUpdated(); mSearchUiManager.refreshSearchResult(); } + /** + * Removes some apps from the list. + */ + public void removeApps(List<AppInfo> apps) { + for (AppInfo app : apps) { + mComponentToAppMap.remove(app.toComponentKey()); + } + onAppsUpdated(); + mSearchUiManager.refreshSearchResult(); + } + + private void onAppsUpdated() { + for (int i = 0; i < getNumOfAdapters(); i++) { + mAH[i].appsList.onAppsUpdated(); + } + } + + private int getNumOfAdapters() { + return mUsingTabs ? mAH.length : 1; + } + public void updatePromiseAppProgress(PromiseAppInfo app) { - int childCount = mAppsRecyclerView.getChildCount(); + for (int i = 0; i < mAH.length; i++) { + updatePromiseAppProgress(app, mAH[i].recyclerView); + } + if (mFloatingHeaderHandler != null) { + updatePromiseAppProgress(app, mFloatingHeaderHandler.getContentView()); + } + } + + private void updatePromiseAppProgress(PromiseAppInfo app, ViewGroup parent) { + if (parent == null) { + return; + } + int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { - View child = mAppsRecyclerView.getChildAt(i); + View child = parent.getChildAt(i); if (child instanceof BubbleTextView && child.getTag() == app) { BubbleTextView bubbleTextView = (BubbleTextView) child; bubbleTextView.applyProgressLevel(app.level); @@ -187,14 +249,6 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, } /** - * Removes some apps from the list. - */ - public void removeApps(List<AppInfo> apps) { - mApps.removeApps(apps); - mSearchUiManager.refreshSearchResult(); - } - - /** * Returns whether the view itself will handle the touch event or not. */ public boolean shouldContainerScroll(MotionEvent ev) { @@ -203,15 +257,31 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { return true; } - return mAppsRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer()); + if (mUsingTabs && mLauncher.getDragLayer().isEventOverView(mHeader, ev)) { + return true; + } + AllAppsRecyclerView rv = getActiveRecyclerView(); + return rv == null || rv.shouldContainerScroll(ev, mLauncher.getDragLayer()); + } + + public AllAppsRecyclerView getActiveRecyclerView() { + if (!mUsingTabs || mViewPager.getCurrentItem() == 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(); + } + } // Reset the search bar and base recycler view after transitioning home - mAppsRecyclerView.scrollToTop(); mSearchUiManager.reset(); } @@ -224,33 +294,18 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - mAppsRecyclerView.requestFocus(); + if (hasFocus && getActiveRecyclerView() != null) { + getActiveRecyclerView().requestFocus(); } } }); - // Load the all apps recycler view - mAppsRecyclerView = findViewById(R.id.apps_list_view); - mAppsRecyclerView.setApps(mApps); - mAppsRecyclerView.setLayoutManager(mLayoutManager); - mAppsRecyclerView.setAdapter(mAdapter); - mAppsRecyclerView.setHasFixedSize(true); - // No animations will occur when changes occur to the items in this RecyclerView. - mAppsRecyclerView.setItemAnimator(null); - if (FeatureFlags.LAUNCHER3_PHYSICS) { - mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler); - } + mHeader = findViewById(R.id.all_apps_header); + rebindAdapters(mUsingTabs); mSearchContainer = findViewById(R.id.search_container_all_apps); mSearchUiManager = (SearchUiManager) mSearchContainer; - mSearchUiManager.initialize(mApps, mAppsRecyclerView); - - - FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView); - mAppsRecyclerView.addItemDecoration(focusedItemDecorator); - mAppsRecyclerView.preMeasureViews(mAdapter); - mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); + mSearchUiManager.initialize(this); onLauncherLayoutChanged(); } @@ -269,10 +324,9 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, mNumPredictedAppsPerRow != grid.inv.numColumns) { mNumAppsPerRow = grid.inv.numColumns; mNumPredictedAppsPerRow = grid.inv.numColumns; - - mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); - mAdapter.setNumAppsPerRow(mNumAppsPerRow); - mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); + for (int i = 0; i < mAH.length; i++) { + mAH[i].applyNumsPerRow(); + } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @@ -325,10 +379,10 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, @Override public void setInsets(Rect insets) { DeviceProfile grid = mLauncher.getDeviceProfile(); - mAppsRecyclerView.setPadding( - mAppsRecyclerView.getPaddingLeft(), mAppsRecyclerView.getPaddingTop(), - mAppsRecyclerView.getPaddingRight(), insets.bottom); - + for (int i = 0; i < mAH.length; i++) { + mAH[i].padding.bottom = insets.bottom; + mAH[i].applyPadding(); + } if (grid.isVerticalBarLayout()) { ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); mlp.leftMargin = insets.left; @@ -345,20 +399,300 @@ public class AllAppsContainerView extends RelativeLayout implements DragSource, public void updateIconBadges(Set<PackageUserKey> updatedBadges) { final PackageUserKey packageUserKey = new PackageUserKey(null, null); - final int n = mAppsRecyclerView.getChildCount(); - for (int i = 0; i < n; i++) { - View child = mAppsRecyclerView.getChildAt(i); - if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) { - continue; - } - ItemInfo info = (ItemInfo) child.getTag(); - if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) { - ((BubbleTextView) child).applyBadgeState(info, true /* animate */); + for (int j = 0; j < mAH.length; j++) { + if (mAH[j].recyclerView != null) { + final int n = mAH[j].recyclerView.getChildCount(); + for (int i = 0; i < n; i++) { + View child = mAH[j].recyclerView.getChildAt(i); + if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) { + continue; + } + ItemInfo info = (ItemInfo) child.getTag(); + if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) { + ((BubbleTextView) child).applyBadgeState(info, true /* animate */); + } + } } } } public SpringAnimationHandler getSpringAnimationHandler() { - return mSpringAnimationHandler; + return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler; + } + + private void rebindAdapters(boolean showTabs) { + if (showTabs != mUsingTabs) { + replaceRVContainer(showTabs); + } + mUsingTabs = showTabs; + + if (mUsingTabs) { + mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher); + mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher); + setupWorkProfileTabs(); + setupHeader(); + mHeader.setVisibility(View.VISIBLE); + } else { + mHeader.setVisibility(View.GONE); + mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null); + } + + applyTouchDelegate(); + } + + private boolean hasWorkProfileApp(List<AppInfo> apps) { + if (FeatureFlags.ALL_APPS_TABS_ENABLED) { + for (AppInfo app : apps) { + if (mWorkMatcher.matches(app, null)) { + return true; + } + } + } + return false; } + + 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); + mViewPager = showTabs ? (InterceptingViewPager) newView : null; + } + + public View getRecyclerViewContainer() { + return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view); + } + + private void setupWorkProfileTabs() { + mViewPager.setAdapter(new TabsPagerAdapter()); + mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + + boolean mVisible = true; + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + if (positionOffset == 0 && !mVisible || positionOffset > 0 && mVisible) { + mVisible = positionOffset == 0; + for (int i = 0; i < mAH.length; i++) { + if (mAH[i].recyclerView != null) { + mAH[i].recyclerView.getScrollbar().setAlpha(mVisible ? 1 : 0); + } + } + } + } + + @Override + public void onPageSelected(int pos) { + mFloatingHeaderHandler.setMainActive(pos == 0); + applyTouchDelegate(); + if (mAH[pos].recyclerView != null) { + mAH[pos].recyclerView.bindFastScrollbar(); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + } + }); + + findViewById(R.id.tab_personal) + .setOnClickListener((View view) -> mViewPager.setCurrentItem(0)); + findViewById(R.id.tab_work) + .setOnClickListener((View view) -> mViewPager.setCurrentItem(1)); + } + + public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) { + if (mUsingTabs) { + mFloatingHeaderHandler.getContentView().setPredictedApps(apps); + } + mAH[AdapterHolder.MAIN].appsList.setPredictedApps(apps); + } + + public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) { + return mapper.getItem(mComponentToAppMap); + } + + public AlphabeticalAppsList getApps() { + return mAH[AdapterHolder.MAIN].appsList; + } + + public boolean isUsingTabs() { + return mUsingTabs; + } + + public FloatingHeaderHandler getFloatingHeaderHandler() { + return mFloatingHeaderHandler; + } + + private void setupHeader() { + int contentHeight = mLauncher.getDeviceProfile().allAppsCellHeightPx; + RecyclerView mainRV = mAH[AdapterHolder.MAIN].recyclerView; + RecyclerView workRV = mAH[AdapterHolder.WORK] != null + ? mAH[AdapterHolder.WORK].recyclerView : null; + mFloatingHeaderHandler = new FloatingHeaderHandler(mHeader, mainRV, workRV, contentHeight); + mFloatingHeaderHandler.getContentView().setNumAppsPerRow(mNumPredictedAppsPerRow); + mFloatingHeaderHandler.getContentView().setComponentToAppMap(mComponentToAppMap); + for (int i = 0; i < mAH.length; i++) { + mAH[i].paddingTopForTabs = contentHeight; + mAH[i].applyPadding(); + } + } + + public void setLastSearchQuery(String query) { + for (int i = 0; i < mAH.length; i++) { + mAH[i].adapter.setLastSearchQuery(query); + } + } + + public void onSearchResultsChanged() { + for (int i = 0; i < mAH.length; i++) { + mAH[i].recyclerView.onSearchResultsChanged(); + } + } + + public void setRecyclerViewPaddingTop(int top) { + for (int i = 0; i < mAH.length; i++) { + mAH[i].padding.top = top; + mAH[i].applyPadding(); + } + } + + public void setRecyclerViewSidePadding(int left, int right) { + for (int i = 0; i < mAH.length; i++) { + mAH[i].padding.left = left; + mAH[i].padding.right = right; + mAH[i].applyPadding(); + } + } + + 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 List<AppInfo> getPredictedApps() { + if (mUsingTabs) { + return mFloatingHeaderHandler.getContentView().getPredictedApps(); + } else { + return mAH[AdapterHolder.MAIN].appsList.getPredictedApps(); + } + } + + public class AdapterHolder { + public static final int MAIN = 0; + public static final int WORK = 1; + + final AllAppsGridAdapter adapter; + final LinearLayoutManager layoutManager; + final SpringAnimationHandler animationHandler; + final AlphabeticalAppsList appsList; + final Rect padding = new Rect(); + int paddingTopForTabs; + AllAppsRecyclerView recyclerView; + boolean verticalFadingEdge; + + AdapterHolder() { + appsList = new AlphabeticalAppsList(mLauncher, mComponentToAppMap); + 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); + recyclerView.preMeasureViews(adapter); + adapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); + applyVerticalFadingEdgeEnabled(verticalFadingEdge); + applyPadding(); + applyNumsPerRow(); + } + + void applyPadding() { + if (recyclerView != null) { + int paddingTop = mUsingTabs ? paddingTopForTabs : padding.top; + recyclerView.setPadding(padding.left, paddingTop, padding.right, padding.bottom); + } + } + + void applyNumsPerRow() { + if (mNumAppsPerRow > 0) { + if (recyclerView != null) { + recyclerView.setNumAppsPerRow(mLauncher.getDeviceProfile(), mNumAppsPerRow); + } + adapter.setNumAppsPerRow(mNumAppsPerRow); + appsList.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); + if (mUsingTabs && mFloatingHeaderHandler != null) { + mFloatingHeaderHandler.getContentView() + .setNumAppsPerRow(mNumPredictedAppsPerRow); + } + } + } + + public void applyVerticalFadingEdgeEnabled(boolean enabled) { + verticalFadingEdge = enabled; + mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs + && verticalFadingEdge); + } + } + + private class TabsPagerAdapter extends PagerAdapter { + @Override + public int getCount() { + return 2; + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + if (position == 0) { + return mAH[AdapterHolder.MAIN].recyclerView; + } else { + return mAH[AdapterHolder.WORK].recyclerView; + } + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + if (position == 0) { + return getResources().getString(R.string.all_apps_personal_tab); + } else { + return getResources().getString(R.string.all_apps_work_tab); + } + } + } + } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index ed5bf9f8b..ac8d36715 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -206,10 +206,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. // The intent to send off to the market app, updated each time the search query changes. private Intent mMarketSearchIntent; - private SpringAnimationHandler<ViewHolder> mSpringAnimationHandler; + private final SpringAnimationHandler<ViewHolder> mSpringAnimationHandler; public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener - iconClickListener, View.OnLongClickListener iconLongClickListener) { + iconClickListener, View.OnLongClickListener iconLongClickListener, boolean springAnim) { Resources res = launcher.getResources(); mLauncher = launcher; mApps = apps; @@ -220,9 +220,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. mLayoutInflater = LayoutInflater.from(launcher); mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; - if (FeatureFlags.LAUNCHER3_PHYSICS) { + if (FeatureFlags.LAUNCHER3_PHYSICS && springAnim) { mSpringAnimationHandler = new SpringAnimationHandler<>( SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory()); + } else { + mSpringAnimationHandler = null; } } @@ -377,7 +379,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. @Override public void onViewAttachedToWindow(ViewHolder holder) { int type = holder.getItemViewType(); - if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { + if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { mSpringAnimationHandler.add(holder.itemView, holder); } } @@ -385,7 +387,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. @Override public void onViewDetachedFromWindow(ViewHolder holder) { int type = holder.getItemViewType(); - if (FeatureFlags.LAUNCHER3_PHYSICS && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { + if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) { mSpringAnimationHandler.remove(holder.itemView); } } diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index 494cd5ac5..09357f786 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -31,6 +31,7 @@ import com.android.launcher3.BaseRecyclerView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.anim.SpringAnimationHandler; import com.android.launcher3.config.FeatureFlags; @@ -40,6 +41,7 @@ import com.android.launcher3.touch.OverScroll; import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; +import com.android.launcher3.views.RecyclerViewFastScroller; import java.util.List; @@ -51,6 +53,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine private AlphabeticalAppsList mApps; private AllAppsFastScrollHelper mFastScrollHelper; private int mNumAppsPerRow; + private int mUserProfileTabContentHeight; // The specific view heights that we use to calculate scroll private SparseIntArray mViewHeights = new SparseIntArray(); @@ -63,7 +66,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine private SpringAnimationHandler mSpringAnimationHandler; private OverScrollHelper mOverScrollHelper; private SwipeDetector mPullDetector; - private float mContentTranslationY = 0; public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y = new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") { @@ -122,9 +124,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine /** * Sets the list of apps in this view, used to determine the fastscroll position. */ - public void setApps(AlphabeticalAppsList apps) { + public void setApps(AlphabeticalAppsList apps, boolean usingTabs) { mApps = apps; mFastScrollHelper = new AllAppsFastScrollHelper(this, apps); + mUserProfileTabContentHeight = usingTabs + ? Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx : 0;; } public AlphabeticalAppsList getApps() { @@ -136,7 +140,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine */ public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) { mNumAppsPerRow = numAppsPerRow; - RecyclerView.RecycledViewPool pool = getRecycledViewPool(); int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx); pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1); @@ -169,7 +172,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET); putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec, AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH); - if (FeatureFlags.DISCOVERY_ENABLED) { putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec, AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER); @@ -485,8 +487,23 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine */ @Override protected int getAvailableScrollHeight() { - return getPaddingTop() + getCurrentScrollY(mApps.getAdapterItems().size(), 0) - - getHeight() + getPaddingBottom(); + return getPaddingTop() + getCurrentScrollY(getAdapter().getItemCount(), 0) + - getHeight() + getPaddingBottom() + mUserProfileTabContentHeight; + } + + public int getScrollBarTop() { + return super.getScrollBarTop() + mUserProfileTabContentHeight; + } + + /** + * Returns the height of the fast scroll bar + */ + public int getScrollbarTrackHeight() { + return super.getScrollbarTrackHeight() + mUserProfileTabContentHeight; + } + + public RecyclerViewFastScroller getScrollbar() { + return mScrollbar; } /** @@ -587,7 +604,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine private void reset(boolean shouldSpring) { float y = getContentTranslationY(); if (Float.compare(y, 0) != 0) { - if (FeatureFlags.LAUNCHER3_PHYSICS && shouldSpring) { + if (mSpringAnimationHandler != null && shouldSpring) { // We calculate our own velocity to give the springs the desired effect. float velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY; // We want to negate the velocity because we are moving to 0 from -1 due to the @@ -614,4 +631,5 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine return OverScroll.dampedScroll(y, getHeight()); } } + } diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index f0b650bd2..f9dde2f97 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -30,6 +30,7 @@ import com.android.launcher3.discovery.AppDiscoveryItem; import com.android.launcher3.discovery.AppDiscoveryUpdateState; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ComponentKeyMapper; +import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LabelComparator; import java.util.ArrayList; @@ -165,7 +166,7 @@ public class AlphabeticalAppsList { // The set of apps from the system not including predictions private final List<AppInfo> mApps = new ArrayList<>(); - private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>(); + private final HashMap<ComponentKey, AppInfo> mComponentToAppMap; // The set of filtered apps with the current filter private final List<AppInfo> mFilteredApps = new ArrayList<>(); @@ -188,13 +189,20 @@ public class AlphabeticalAppsList { private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; private int mNumAppRowsInAdapter; + private ItemInfoMatcher mItemFilter; - public AlphabeticalAppsList(Context context) { + public AlphabeticalAppsList(Context context, HashMap<ComponentKey, AppInfo> componentToAppMap) { + mComponentToAppMap = componentToAppMap; mLauncher = Launcher.getLauncher(context); mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppInfoComparator(context); } + public void updateItemFilter(ItemInfoMatcher itemFilter) { + this.mItemFilter = itemFilter; + onAppsUpdated(); + } + /** * Sets the number of apps per row. */ @@ -374,40 +382,18 @@ public class AlphabeticalAppsList { } /** - * Sets the current set of apps. - */ - public void setApps(List<AppInfo> apps) { - mComponentToAppMap.clear(); - addOrUpdateApps(apps); - } - - /** - * Adds or updates existing apps in the list - */ - public void addOrUpdateApps(List<AppInfo> apps) { - for (AppInfo app : apps) { - mComponentToAppMap.put(app.toComponentKey(), app); - } - onAppsUpdated(); - } - - /** - * Removes some apps from the list. - */ - public void removeApps(List<AppInfo> apps) { - for (AppInfo app : apps) { - mComponentToAppMap.remove(app.toComponentKey()); - } - onAppsUpdated(); - } - - /** * Updates internals when the set of apps are updated. */ - private void onAppsUpdated() { + void onAppsUpdated() { // Sort the list of apps mApps.clear(); - mApps.addAll(mComponentToAppMap.values()); + + for (AppInfo app : mComponentToAppMap.values()) { + if (mItemFilter == null || mItemFilter.matches(app, null)) { + mApps.add(app); + } + } + Collections.sort(mApps, mAppNameComparator); // As a special case for some languages (currently only Simplified Chinese), we may need to @@ -474,42 +460,45 @@ public class AlphabeticalAppsList { mFastScrollerSections.clear(); mAdapterItems.clear(); - if (DEBUG_PREDICTIONS) { - if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) { - mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, - Process.myUserHandle()))); - mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, - Process.myUserHandle()))); - mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, - Process.myUserHandle()))); - mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, - Process.myUserHandle()))); + if (!FeatureFlags.ALL_APPS_TABS_ENABLED) { + if (DEBUG_PREDICTIONS) { + if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) { + mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, + Process.myUserHandle()))); + mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, + Process.myUserHandle()))); + mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, + Process.myUserHandle()))); + mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, + Process.myUserHandle()))); + } } - } - - // Process the predicted app components - mPredictedApps.clear(); - if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) { - mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents)); - if (!mPredictedApps.isEmpty()) { - // Add a section for the predictions - lastFastScrollerSectionInfo = new FastScrollSectionInfo(""); - mFastScrollerSections.add(lastFastScrollerSectionInfo); - - // Add the predicted app items - for (AppInfo info : mPredictedApps) { - AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info, - appIndex++); - if (lastFastScrollerSectionInfo.fastScrollToItem == null) { - lastFastScrollerSectionInfo.fastScrollToItem = appItem; + // Process the predicted app components + mPredictedApps.clear(); + if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) { + mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents)); + + if (!mPredictedApps.isEmpty()) { + // Add a section for the predictions + lastFastScrollerSectionInfo = new FastScrollSectionInfo(""); + mFastScrollerSections.add(lastFastScrollerSectionInfo); + + // Add the predicted app items + for (AppInfo info : mPredictedApps) { + AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info, + appIndex++); + if (lastFastScrollerSectionInfo.fastScrollToItem == null) { + lastFastScrollerSectionInfo.fastScrollToItem = appItem; + } + mAdapterItems.add(appItem); + mFilteredApps.add(info); } - mAdapterItems.add(appItem); - mFilteredApps.add(info); - } - mAdapterItems.add(AdapterItem.asPredictionDivider(position++)); + mAdapterItems.add(AdapterItem.asPredictionDivider(position++)); + } } + } // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the @@ -626,7 +615,6 @@ public class AlphabeticalAppsList { if (mSearchResults == null) { return mApps; } - ArrayList<AppInfo> result = new ArrayList<>(); for (ComponentKey key : mSearchResults) { AppInfo match = mComponentToAppMap.get(key); @@ -648,10 +636,6 @@ public class AlphabeticalAppsList { return result; } - public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) { - return mapper.getItem(mComponentToAppMap); - } - /** * Returns the cached section name for the given title, recomputing and updating the cache if * the title has no cached section name. diff --git a/src/com/android/launcher3/allapps/FloatingHeaderHandler.java b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java new file mode 100644 index 000000000..984966bbb --- /dev/null +++ b/src/com/android/launcher3/allapps/FloatingHeaderHandler.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps; + +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.android.launcher3.R; + +public class FloatingHeaderHandler extends RecyclerView.OnScrollListener { + + private final int mMaxTranslation; + private final View mHeaderView; + private final PredictionRowView mContentView; + private final RecyclerView mMainRV; + private final RecyclerView mWorkRV; + private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE); + + private boolean mHeaderHidden; + private int mSnappedScrolledY; + private int mTranslationY; + private int mMainScrolledY; + private int mWorkScrolledY; + private boolean mMainRVActive; + + public FloatingHeaderHandler(@NonNull View header, @NonNull RecyclerView personalRV, + @Nullable RecyclerView workRV, int contentHeight) { + mHeaderView = header; + mContentView = mHeaderView.findViewById(R.id.header_content); + mContentView.getLayoutParams().height = contentHeight; + mMaxTranslation = contentHeight; + mMainRV = personalRV; + mMainRV.addOnScrollListener(this); + mWorkRV = workRV; + if (workRV != null) { + workRV.addOnScrollListener(this); + } + setMainActive(true); + } + + public void setMainActive(boolean active) { + mMainRVActive = active; + mSnappedScrolledY = getCurrentScroll() - mMaxTranslation; + setExpanded(true); + } + + public View getHeaderView() { + return mHeaderView; + } + + public PredictionRowView getContentView() { + return mContentView; + } + + @Override + public void onScrolled(RecyclerView rv, int dx, int dy) { + boolean isMainRV = rv == mMainRV; + if (isMainRV != mMainRVActive) { + return; + } + + int current = isMainRV + ? (mMainScrolledY -= dy) + : (mWorkScrolledY -= dy); + + if (dy == 0) { + setExpanded(true); + } else { + moved(current); + apply(); + } + } + + private void moved(final int currentScrollY) { + if (mHeaderHidden) { + if (currentScrollY <= mSnappedScrolledY) { + mSnappedScrolledY = currentScrollY; + } else { + mHeaderHidden = false; + } + mTranslationY = currentScrollY; + } else { + mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation; + + // update state vars + if (mTranslationY >= 0) { // expanded: must not move down further + mTranslationY = 0; + mSnappedScrolledY = currentScrollY - mMaxTranslation; + } else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden + mHeaderHidden = true; + mSnappedScrolledY = currentScrollY; + } + } + } + + private void apply() { + mTranslationY = Math.max(mTranslationY, -mMaxTranslation); + mHeaderView.setTranslationY(mTranslationY); + mClip.top = mMaxTranslation + mTranslationY; + mMainRV.setClipBounds(mClip); + if (mWorkRV != null) { + mWorkRV.setClipBounds(mClip); + } + } + + private void setExpanded(boolean expand) { + int translateTo = expand ? 0 : -mMaxTranslation; + mTranslationY = translateTo; + apply(); + + mHeaderHidden = !expand; + mSnappedScrolledY = expand ? getCurrentScroll() - mMaxTranslation : getCurrentScroll(); + } + + public boolean isExpanded() { + return !mHeaderHidden; + } + + private int getCurrentScroll() { + return mMainRVActive ? mMainScrolledY : mWorkScrolledY; + } + +} diff --git a/src/com/android/launcher3/allapps/InterceptingViewPager.java b/src/com/android/launcher3/allapps/InterceptingViewPager.java new file mode 100644 index 000000000..3524ca9ba --- /dev/null +++ b/src/com/android/launcher3/allapps/InterceptingViewPager.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps; + +import android.content.Context; +import android.graphics.PointF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.launcher3.touch.SwipeDetector; + +import static android.view.MotionEvent.INVALID_POINTER_ID; + + +public class InterceptingViewPager extends ViewPager { + + + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + private final int mSlop; + private int mActivePointerId = INVALID_POINTER_ID; + + public InterceptingViewPager(@NonNull Context context) { + super(context); + mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + } + + public InterceptingViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + boolean result = super.onInterceptTouchEvent(ev); + if (!result) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + break; + case MotionEvent.ACTION_POINTER_UP: + int ptrIdx = ev.getActionIndex(); + int ptrId = ev.getPointerId(ptrIdx); + if (ptrId == mActivePointerId) { + final int newPointerIdx = ptrIdx == 0 ? 1 : 0; + mDownPos.set( + ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), + ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); + mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); + mActivePointerId = ev.getPointerId(newPointerIdx); + } + break; + case MotionEvent.ACTION_MOVE: + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER_ID) { + break; + } + float deltaX = ev.getX() - mDownPos.x; + float deltaY = ev.getY() - mDownPos.y; + if (Math.abs(deltaX) > mSlop && Math.abs(deltaX) > Math.abs(deltaY)) { + return true; + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + break; + default: + break; + } + } + return result; + } + +}
\ No newline at end of file diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java new file mode 100644 index 000000000..5551f0746 --- /dev/null +++ b/src/com/android/launcher3/allapps/PredictionRowView.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.allapps; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.LinearLayout; + +import com.android.launcher3.AppInfo; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.ComponentKeyMapper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +public class PredictionRowView extends LinearLayout { + + private static final String TAG = "PredictionRowView"; + + private HashMap<ComponentKey, AppInfo> mComponentToAppMap; + private int mNumPredictedAppsPerRow; + // The set of predicted app component names + private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>(); + // The set of predicted apps resolved from the component names and the current set of apps + private final List<AppInfo> mPredictedApps = new ArrayList<>(); + + public PredictionRowView(@NonNull Context context) { + this(context, null); + } + + public PredictionRowView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setOrientation(LinearLayout.HORIZONTAL); + } + + public void setComponentToAppMap(HashMap<ComponentKey, AppInfo> componentToAppMap) { + this.mComponentToAppMap = componentToAppMap; + } + + /** + * Sets the number of apps per row. + */ + public void setNumAppsPerRow(int numPredictedAppsPerRow) { + mNumPredictedAppsPerRow = numPredictedAppsPerRow; + } + + public void onAppsUpdated() { + // TODO + } + + /** + * Returns the predicted apps. + */ + public List<AppInfo> getPredictedApps() { + return mPredictedApps; + } + + /** + * Sets the current set of predicted apps. + * + * This can be called before we get the full set of applications, we should merge the results + * only in onAppsUpdated() which is idempotent. + * + * If the number of predicted apps is the same as the previous list of predicted apps, + * we can optimize by swapping them in place. + */ + public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) { + mPredictedAppComponents.clear(); + mPredictedAppComponents.addAll(apps); + + List<AppInfo> newPredictedApps = processPredictedAppComponents(apps); + // We only need to do work if any of the visible predicted apps have changed. + if (!newPredictedApps.equals(mPredictedApps)) { + if (newPredictedApps.size() == mPredictedApps.size()) { + swapInNewPredictedApps(newPredictedApps); + } else { + // We need to update the appIndex of all the items. + onAppsUpdated(); + } + } + } + + private List<AppInfo> processPredictedAppComponents( + List<ComponentKeyMapper<AppInfo>> components) { + if (mComponentToAppMap.isEmpty()) { + // Apps have not been bound yet. + return Collections.emptyList(); + } + + List<AppInfo> predictedApps = new ArrayList<>(); + for (ComponentKeyMapper<AppInfo> mapper : components) { + AppInfo info = mapper.getItem(mComponentToAppMap); + if (info != null) { + predictedApps.add(info); + } else { + if (FeatureFlags.IS_DOGFOOD_BUILD) { + Log.e(TAG, "Predicted app not found: " + mapper); + } + } + // Stop at the number of predicted apps + if (predictedApps.size() == mNumPredictedAppsPerRow) { + break; + } + } + return predictedApps; + } + + /** + * Swaps out the old predicted apps with the new predicted apps, in place. This optimization + * allows us to skip an entire relayout that would otherwise be called by notifyDataSetChanged. + * + * Note: This should only be called if the # of predicted apps is the same. + * This method assumes that predicted apps are the first items in the adapter. + */ + private void swapInNewPredictedApps(List<AppInfo> apps) { + // TODO + } + +} diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java index 34230e046..f562b6aa0 100644 --- a/src/com/android/launcher3/allapps/SearchUiManager.java +++ b/src/com/android/launcher3/allapps/SearchUiManager.java @@ -27,7 +27,7 @@ public interface SearchUiManager { /** * Initializes the search manager. */ - void initialize(AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView); + void initialize(AllAppsContainerView containerView); /** * A {@link SpringAnimation} that will be used when the user flings. diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index ddf6e5849..e65a2c4ea 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -31,19 +31,19 @@ import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; import android.widget.FrameLayout; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.allapps.AllAppsGridAdapter; -import com.android.launcher3.allapps.AllAppsRecyclerView; +import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.allapps.SearchUiManager; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.discovery.AppDiscoveryItem; import com.android.launcher3.discovery.AppDiscoveryUpdateState; import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.util.ComponentKey; + import java.util.ArrayList; /** @@ -60,11 +60,9 @@ public class AppsSearchContainerLayout extends FrameLayout private ExtendedEditText mSearchInput; private AlphabeticalAppsList mApps; - private AllAppsRecyclerView mAppsRecyclerView; - private AllAppsGridAdapter mAdapter; private View mDivider; private HeaderElevationController mElevationController; - + private AllAppsContainerView mAppsView; private SpringAnimation mSpring; public AppsSearchContainerLayout(Context context) { @@ -124,14 +122,12 @@ public class AppsSearchContainerLayout extends FrameLayout @Override - public void initialize( - AlphabeticalAppsList appsList, AllAppsRecyclerView recyclerView) { - mApps = appsList; - mAppsRecyclerView = recyclerView; - mAppsRecyclerView.addOnScrollListener(mElevationController); - mAdapter = (AllAppsGridAdapter) mAppsRecyclerView.getAdapter(); + public void initialize(AllAppsContainerView appsView) { + mApps = appsView.getApps(); + mAppsView = appsView; + appsView.addElevationController(mElevationController); mSearchBarController.initialize( - new DefaultAppSearchAlgorithm(appsList.getApps()), mSearchInput, mLauncher, this); + new DefaultAppSearchAlgorithm(mApps.getApps()), mSearchInput, mLauncher, this); } @Override @@ -174,7 +170,7 @@ public class AppsSearchContainerLayout extends FrameLayout if (apps != null) { mApps.setOrderedFilter(apps); notifyResultChanged(); - mAdapter.setLastSearchQuery(query); + mAppsView.setLastSearchQuery(query); } } @@ -201,7 +197,7 @@ public class AppsSearchContainerLayout extends FrameLayout private void notifyResultChanged() { mElevationController.reset(); - mAppsRecyclerView.onSearchResultsChanged(); + mAppsView.onSearchResultsChanged(); } @Override diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index a03dabb2b..192471069 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -59,4 +59,6 @@ abstract class BaseFlags { // Features to control Launcher3Go behavior public static final boolean GO_DISABLE_WIDGETS = false; + // When enabled shows a work profile tab in all apps + public static final boolean ALL_APPS_TABS_ENABLED = false; } diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java index 18787b6a2..daedaef91 100644 --- a/src/com/android/launcher3/util/ItemInfoMatcher.java +++ b/src/com/android/launcher3/util/ItemInfoMatcher.java @@ -93,6 +93,19 @@ public abstract class ItemInfoMatcher { }; } + /** + * Returns a new matcher which returns the opposite boolean value of the provided + * {@param matcher}. + */ + public static ItemInfoMatcher not(final ItemInfoMatcher matcher) { + return new ItemInfoMatcher() { + @Override + public boolean matches(ItemInfo info, ComponentName cn) { + return !matcher.matches(info, cn); + } + }; + } + public static ItemInfoMatcher ofUser(final UserHandle user) { return new ItemInfoMatcher() { @Override diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index 8c9a44186..8f20a8dda 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -98,6 +98,7 @@ public class RecyclerViewFastScroller extends View { private String mPopupSectionName; protected BaseRecyclerView mRv; + private RecyclerView.OnScrollListener mOnScrollListener; private int mDownX; private int mDownY; @@ -141,7 +142,10 @@ public class RecyclerViewFastScroller extends View { public void setRecyclerView(BaseRecyclerView rv, TextView popupView) { mRv = rv; - mRv.addOnScrollListener(new RecyclerView.OnScrollListener() { + if (mOnScrollListener != null) { + mRv.removeOnScrollListener(mOnScrollListener); + } + mRv.addOnScrollListener(mOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { mDy = dy; |