summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMario Bertschler <bmario@google.com>2017-10-18 10:31:36 -0700
committerMario Bertschler <bmario@google.com>2017-11-14 10:28:54 -0800
commitac9408a5cd7744a8dbc66a61114665ab6e4051de (patch)
treeedb48150310dde96c444ec64952fd9678d825a13
parentf405f507b70c0bd54adcfd69cc2c7a2b245d10d6 (diff)
downloadandroid_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
-rw-r--r--res/layout/all_apps.xml53
-rw-r--r--res/layout/all_apps_rv_layout.xml26
-rw-r--r--res/layout/all_apps_tabs.xml34
-rw-r--r--res/values/dimens.xml1
-rw-r--r--res/values/strings.xml6
-rw-r--r--src/com/android/launcher3/BaseRecyclerView.java8
-rw-r--r--src/com/android/launcher3/DeviceProfile.java6
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java472
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java12
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java32
-rw-r--r--src/com/android/launcher3/allapps/AlphabeticalAppsList.java120
-rw-r--r--src/com/android/launcher3/allapps/FloatingHeaderHandler.java139
-rw-r--r--src/com/android/launcher3/allapps/InterceptingViewPager.java91
-rw-r--r--src/com/android/launcher3/allapps/PredictionRowView.java139
-rw-r--r--src/com/android/launcher3/allapps/SearchUiManager.java2
-rw-r--r--src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java26
-rw-r--r--src/com/android/launcher3/config/BaseFlags.java2
-rw-r--r--src/com/android/launcher3/util/ItemInfoMatcher.java13
-rw-r--r--src/com/android/launcher3/views/RecyclerViewFastScroller.java6
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;