Merge "Removing some dead code in paged view" into ub-launcher3-burnaby
diff --git a/res/drawable-hdpi/ic_arrow_back_grey.png b/res/drawable-hdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..ccd3900
--- /dev/null
+++ b/res/drawable-hdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_search_grey.png b/res/drawable-hdpi/ic_search_grey.png
new file mode 100755
index 0000000..f4c5e27
--- /dev/null
+++ b/res/drawable-hdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_grey.png b/res/drawable-mdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..11996ef
--- /dev/null
+++ b/res/drawable-mdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_search_grey.png b/res/drawable-mdpi/ic_search_grey.png
new file mode 100755
index 0000000..e83891c
--- /dev/null
+++ b/res/drawable-mdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_arrow_back_grey.png b/res/drawable-xhdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..79b9b48
--- /dev/null
+++ b/res/drawable-xhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_search_grey.png b/res/drawable-xhdpi/ic_search_grey.png
new file mode 100755
index 0000000..bd5fdf4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_grey.png b/res/drawable-xxhdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..8e42e09
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_search_grey.png b/res/drawable-xxhdpi/ic_search_grey.png
new file mode 100755
index 0000000..1d5c913
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_grey.png b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..854a9bd
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_search_grey.png b/res/drawable-xxxhdpi/ic_search_grey.png
new file mode 100755
index 0000000..28519fd
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index dfb7b58..e29cac5 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -22,22 +22,55 @@
android:elevation="15dp"
android:visibility="gone"
android:focusableInTouchMode="true">
- <EditText
- android:id="@+id/app_search_box"
+ <FrameLayout
+ android:id="@+id/header"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="16dp"
- android:hint="@string/apps_view_search_bar_hint"
- android:maxLines="1"
- android:singleLine="true"
- android:scrollHorizontally="true"
- android:gravity="fill_horizontal"
- android:textSize="16sp"
- android:textColor="#4c4c4c"
- android:textColorHint="#9c9c9c"
- android:imeOptions="actionDone|flagNoExtractUi"
- android:background="@drawable/apps_search_bg"
- android:elevation="4dp" />
+ android:layout_height="52dp"
+ android:orientation="horizontal"
+ android:background="@drawable/apps_search_bg">
+ <LinearLayout
+ android:id="@+id/app_search_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="invisible">
+ <ImageView
+ android:id="@+id/dismiss_search_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:contentDescription="@string/all_apps_button_label"
+ android:src="@drawable/ic_arrow_back_grey" />
+ <EditText
+ android:id="@+id/app_search_box"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingLeft="8dp"
+ android:hint="@string/apps_view_search_bar_hint"
+ android:maxLines="1"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:gravity="fill_horizontal"
+ android:textSize="16sp"
+ android:textColor="#4c4c4c"
+ android:textColorHint="#9c9c9c"
+ android:imeOptions="actionDone|flagNoExtractUi"
+ android:background="@android:color/transparent" />
+ </LinearLayout>
+ <ImageView
+ android:id="@+id/search_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|center_vertical"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:contentDescription="@string/apps_view_search_bar_hint"
+ android:src="@drawable/ic_search_grey" />
+ </FrameLayout>
<com.android.launcher3.AppsContainerRecyclerView
android:id="@+id/apps_list_view"
android:layout_width="match_parent"
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index 0800f59..5cdf560 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -40,7 +40,7 @@
android:clipChildren="false"
android:orientation="vertical">
- <android.support.v7.widget.RecyclerView
+ <com.android.launcher3.widget.WidgetsContainerRecyclerView
android:id="@+id/widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/res/values/config.xml b/res/values/config.xml
index 21e1d69..6ef8635 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -100,4 +100,6 @@
<item type="id" name="action_add_to_workspace" />
<item type="id" name="action_move" />
<item type="id" name="action_move_to_workspace" />
+ <item type="id" name="action_move_screen_backwards" />
+ <item type="id" name="action_move_screen_forwards" />
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a57ae89..4fbe87e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -53,6 +53,7 @@
<!-- Note: This needs to match the fixed insets for the search box -->
<dimen name="apps_container_fixed_bounds_inset">8dp</dimen>
<dimen name="apps_grid_view_start_margin">52dp</dimen>
+ <dimen name="apps_grid_section_y_offset">8dp</dimen>
<dimen name="apps_view_row_height">64dp</dimen>
<dimen name="apps_view_section_text_size">24sp</dimen>
<dimen name="apps_view_fast_scroll_bar_width">6dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 57f23ae..1681fc6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -243,4 +243,13 @@
<!-- Accessibility action to move an item from folder to workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
<string name="action_move_to_workspace">Move to home screen</string>
+
+ <!-- Accessibility action to move an homescreen to the left. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
+ <string name="action_move_screen_left">Move screen to left</string>
+
+ <!-- Accessibility action to move an homescreen to the right. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
+ <string name="action_move_screen_right">Move screen to right</string>
+
+ <!-- Accessibility confirmation when a screen was moved [DO NOT TRANSLATE] -->
+ <string name="screen_moved">Screen moved</string>
</resources>
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index dd646bb..3b25dca 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -24,6 +24,7 @@
import com.android.launcher3.compat.UserHandleCompat;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
@@ -117,6 +118,16 @@
}
}
+ public void updateIconsAndLabels(HashSet<String> packages, UserHandleCompat user,
+ ArrayList<AppInfo> outUpdates) {
+ for (AppInfo info : data) {
+ if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
+ mIconCache.updateTitleAndIcon(info);
+ outUpdates.add(info);
+ }
+ }
+ }
+
/**
* Add and remove icons for this package which has been updated.
*/
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index c7ee2e9..477c00f 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -86,6 +86,8 @@
public String sectionName;
// The number of applications in this section
public int numAppsInSection;
+ // The section AdapterItem for this section
+ public AdapterItem sectionItem;
// The first app AdapterItem for this section
public AdapterItem firstAppItem;
@@ -137,6 +139,9 @@
public boolean retainApp(AppInfo info, String sectionName);
}
+ // The maximum number of rows allowed in a merged section before we stop merging
+ private static final int MAX_ROWS_IN_MERGED_SECTION = Integer.MAX_VALUE;
+
private List<AppInfo> mApps = new ArrayList<>();
private List<AppInfo> mFilteredApps = new ArrayList<>();
private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
@@ -145,10 +150,23 @@
private Filter mFilter;
private AlphabeticIndexCompat mIndexer;
private AppNameComparator mAppNameComparator;
+ private int mNumAppsPerRow;
+ // The maximum number of section merges we allow at a given time before we stop merging
+ private int mMaxAllowableMerges = Integer.MAX_VALUE;
- public AlphabeticalAppsList(Context context) {
+ public AlphabeticalAppsList(Context context, int numAppsPerRow) {
mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppNameComparator(context);
+ setNumAppsPerRow(numAppsPerRow);
+ }
+
+ /**
+ * Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
+ */
+ public void setNumAppsPerRow(int numAppsPerRow) {
+ mNumAppsPerRow = numAppsPerRow;
+ mMaxAllowableMerges = (int) Math.ceil(numAppsPerRow / 2f);
+ onAppsUpdated();
}
/**
@@ -180,6 +198,13 @@
}
/**
+ * Returns whether there are is a filter set.
+ */
+ public boolean hasFilter() {
+ return (mFilter != null);
+ }
+
+ /**
* Returns whether there are no filtered results.
*/
public boolean hasNoFilteredResults() {
@@ -190,9 +215,11 @@
* Sets the current filter for this list of apps.
*/
public void setFilter(Filter f) {
- mFilter = f;
- onAppsUpdated();
- mAdapter.notifyDataSetChanged();
+ if (mFilter != f) {
+ mFilter = f;
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
}
/**
@@ -298,9 +325,13 @@
lastSectionInfo = new SectionInfo(sectionName);
mSections.add(lastSectionInfo);
- // Create a new section item
+ // Create a new section item, this item is used to break the flow of items in the
+ // list
AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName);
- mSectionedFilteredApps.add(sectionItem);
+ if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) {
+ lastSectionInfo.sectionItem = sectionItem;
+ mSectionedFilteredApps.add(sectionItem);
+ }
}
// Create an app item
@@ -312,5 +343,53 @@
mSectionedFilteredApps.add(appItem);
mFilteredApps.add(info);
}
+
+ if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
+ // Go through each section and try and merge some of the sections
+ int minNumAppsPerRow = (int) Math.ceil(mNumAppsPerRow / 2f);
+ int sectionAppCount = 0;
+ for (int i = 0; i < mSections.size(); i++) {
+ SectionInfo section = mSections.get(i);
+ String mergedSectionName = section.sectionName;
+ sectionAppCount = section.numAppsInSection;
+ int mergeCount = 1;
+ // Merge rows if the last app in this section is in a column that is greater than
+ // 0, but less than the min number of apps per row. In addition, apply the
+ // constraint to stop merging if the number of rows in the section is greater than
+ // some limit, and also if there are no lessons to merge.
+ while (0 < (sectionAppCount % mNumAppsPerRow) &&
+ (sectionAppCount % mNumAppsPerRow) < minNumAppsPerRow &&
+ (int) Math.ceil(sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION &&
+ (i + 1) < mSections.size()) {
+ SectionInfo nextSection = mSections.remove(i + 1);
+ // Merge the section names
+ if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
+ mergedSectionName += nextSection.sectionName;
+ }
+ // Remove the next section break
+ mSectionedFilteredApps.remove(nextSection.sectionItem);
+ if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
+ // Update the section names for the two sections
+ int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
+ for (int j = pos; j < (pos + section.numAppsInSection + nextSection.numAppsInSection); j++) {
+ AdapterItem item = mSectionedFilteredApps.get(j);
+ item.sectionName = mergedSectionName;
+ }
+ }
+ // Update the following adapter items of the removed section
+ int pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
+ for (int j = pos; j < mSectionedFilteredApps.size(); j++) {
+ AdapterItem item = mSectionedFilteredApps.get(j);
+ item.position--;
+ }
+ section.numAppsInSection += nextSection.numAppsInSection;
+ sectionAppCount += nextSection.numAppsInSection;
+ mergeCount++;
+ if (mergeCount >= mMaxAllowableMerges) {
+ break;
+ }
+ }
+ }
+ }
}
}
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index f889712..d91bcea 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -30,6 +30,8 @@
import android.view.View;
import android.view.ViewConfiguration;
+import com.android.launcher3.util.Thunk;
+
import java.util.List;
/**
@@ -40,6 +42,11 @@
implements RecyclerView.OnItemTouchListener {
private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
+ private static final int SCROLL_DELTA_THRESHOLD = 4;
+
+ /** Keeps the last known scrolling delta/velocity along y-axis. */
+ @Thunk int mDy = 0;
+ private float mDeltaThreshold;
private AlphabeticalAppsList mApps;
private int mNumAppsPerRow;
@@ -59,6 +66,7 @@
private int mScrollbarWidth;
private int mScrollbarMinHeight;
private int mScrollbarInset;
+ private RecyclerView.OnScrollListener mScrollListenerProxy;
public AppsContainerRecyclerView(Context context) {
this(context, null);
@@ -92,6 +100,21 @@
mScrollbarInset =
res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
setFastScrollerAlpha(getFastScrollerAlpha());
+ mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD;
+
+ ScrollListener listener = new ScrollListener();
+ setOnScrollListener(listener);
+ }
+
+ private class ScrollListener extends RecyclerView.OnScrollListener {
+ public ScrollListener() {
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mDy = dy;
+ mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
+ }
}
/**
@@ -109,6 +132,13 @@
}
/**
+ * Sets an additional scroll listener, not necessary in master support lib.
+ */
+ public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
+ mScrollListenerProxy = listener;
+ }
+
+ /**
* Sets the fast scroller alpha.
*/
public void setFastScrollerAlpha(float alpha) {
@@ -132,6 +162,7 @@
@Override
protected void onFinishInflate() {
+ super.onFinishInflate();
addOnItemTouchListener(this);
}
@@ -156,10 +187,6 @@
handleTouchEvent(ev);
}
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- // Do nothing
- }
-
/**
* Handles the touch event and determines whether to show the fast scroller (or updates it if
* it is already showing).
@@ -175,7 +202,10 @@
// Keep track of the down positions
mDownX = mLastX = x;
mDownY = mLastY = y;
- stopScroll();
+ if ((Math.abs(mDy) < mDeltaThreshold &&
+ getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
+ stopScroll();
+ }
break;
case MotionEvent.ACTION_MOVE:
// Check if we are scrolling
@@ -297,6 +327,7 @@
// Find the position of the first application in the section that contains the row at the
// current progress
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
int rowAtProgress = (int) (progress * getNumRows());
int rowCount = 0;
AlphabeticalAppsList.SectionInfo lastSectionInfo = null;
@@ -308,7 +339,7 @@
}
rowCount += numRowsInSection;
}
- int position = mApps.getAdapterItems().indexOf(lastSectionInfo.firstAppItem);
+ int position = items.indexOf(lastSectionInfo.firstAppItem);
// Scroll the position into view, anchored at the top of the screen if possible. We call the
// scroll method on the LayoutManager directly since it is not exposed by RecyclerView.
@@ -317,15 +348,17 @@
layoutManager.scrollToPositionWithOffset(position, 0);
// Return the section name of the row
- return mApps.getAdapterItems().get(position).sectionName;
+ return lastSectionInfo.sectionName;
}
/**
* Returns the bounds for the scrollbar.
*/
private void updateVerticalScrollbarBounds() {
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+
// Skip early if there are no items
- if (mApps.getAdapterItems().isEmpty()) {
+ if (items.isEmpty()) {
mVerticalScrollbarBounds.setEmpty();
return;
}
@@ -344,7 +377,7 @@
View child = getChildAt(i);
int position = getChildPosition(child);
if (position != NO_POSITION) {
- AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+ AlphabeticalAppsList.AdapterItem item = items.get(position);
if (!item.isSectionHeader) {
rowIndex = findRowForAppIndex(item.appIndex);
rowTopOffset = getLayoutManager().getDecoratedTop(child);
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index c3cf629..9122427 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -34,7 +34,6 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.launcher3.util.Thunk;
import java.util.List;
@@ -45,24 +44,32 @@
*/
public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher,
TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
- View.OnLongClickListener {
+ View.OnClickListener, View.OnLongClickListener {
+
+ public static final boolean GRID_MERGE_SECTIONS = true;
+ public static final boolean GRID_MERGE_SECTION_HEADERS = false;
+ public static final boolean GRID_HIDE_SECTION_HEADERS = false;
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
-
- private static final int GRID_LAYOUT = 0;
- private static final int LIST_LAYOUT = 1;
- private static final int USE_LAYOUT = GRID_LAYOUT;
+ private static final boolean DYNAMIC_HEADER_ELEVATION = false;
+ private static final float HEADER_ELEVATION_DP = 4;
+ private static final int FADE_IN_DURATION = 175;
+ private static final int FADE_OUT_DURATION = 125;
@Thunk Launcher mLauncher;
@Thunk AlphabeticalAppsList mApps;
- private RecyclerView.Adapter mAdapter;
+ private AppsGridAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private RecyclerView.ItemDecoration mItemDecoration;
private LinearLayout mContentView;
@Thunk AppsContainerRecyclerView mAppsRecyclerView;
- private EditText mSearchBarView;
-
+ private View mHeaderView;
+ private View mSearchBarContainerView;
+ private View mSearchButtonView;
+ private View mDismissSearchButtonView;
+ private EditText mSearchBarEditView;
+
private int mNumAppsPerRow;
private Point mLastTouchDownPos = new Point(-1, -1);
private Point mLastTouchPos = new Point();
@@ -73,6 +80,8 @@
private int mContainerInset;
// Fixed bounds container insets
private int mFixedBoundsContainerInset;
+ // RecyclerView scroll position
+ @Thunk int mRecyclerViewScrollY;
public AppsContainerView(Context context) {
this(context, null);
@@ -93,23 +102,14 @@
mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
R.dimen.apps_container_fixed_bounds_inset);
mLauncher = (Launcher) context;
- mApps = new AlphabeticalAppsList(context);
- if (USE_LAYOUT == GRID_LAYOUT) {
- mNumAppsPerRow = grid.appsViewNumCols;
- AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this,
- mLauncher, this);
- adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
- mLayoutManager = adapter.getLayoutManager(context);
- mItemDecoration = adapter.getItemDecoration();
- mAdapter = adapter;
- mContentMarginStart = adapter.getContentMarginStart();
- } else if (USE_LAYOUT == LIST_LAYOUT) {
- mNumAppsPerRow = 1;
- AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this);
- adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
- mLayoutManager = adapter.getLayoutManager(context);
- mAdapter = adapter;
- }
+ mNumAppsPerRow = grid.appsViewNumCols;
+ mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
+ mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this);
+ mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
+ mAdapter.setNumAppsPerRow(mNumAppsPerRow);
+ mLayoutManager = mAdapter.getLayoutManager();
+ mItemDecoration = mAdapter.getItemDecoration();
+ mContentMarginStart = mAdapter.getContentMarginStart();
mApps.setAdapter(mAdapter);
}
@@ -142,10 +142,10 @@
}
/**
- * Hides the search bar
+ * Hides the header bar
*/
- public void hideSearchBar() {
- mSearchBarView.setVisibility(View.GONE);
+ public void hideHeaderBar() {
+ mHeaderView.setVisibility(View.GONE);
updateBackgrounds();
updatePaddings();
}
@@ -155,6 +155,7 @@
*/
public void scrollToTop() {
mAppsRecyclerView.scrollToPosition(0);
+ mRecyclerViewScrollY = 0;
}
/**
@@ -175,9 +176,7 @@
protected void onFinishInflate() {
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
- if (USE_LAYOUT == GRID_LAYOUT) {
- ((AppsGridAdapter) mAdapter).setRtl(isRtl);
- }
+ mAdapter.setRtl(isRtl);
// Work around the search box getting first focus and showing the cursor by
// proxying the focus from the content view to the recycler view directly
@@ -190,10 +189,20 @@
}
}
});
- mSearchBarView = (EditText) findViewById(R.id.app_search_box);
- if (mSearchBarView != null) {
- mSearchBarView.addTextChangedListener(this);
- mSearchBarView.setOnEditorActionListener(this);
+ mHeaderView = findViewById(R.id.header);
+ mHeaderView.setOnClickListener(this);
+ if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) {
+ mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
+ getContext().getResources().getDisplayMetrics()));
+ }
+ mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
+ mSearchBarContainerView = findViewById(R.id.app_search_container);
+ mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
+ mDismissSearchButtonView.setOnClickListener(this);
+ mSearchBarEditView = (EditText) findViewById(R.id.app_search_box);
+ if (mSearchBarEditView != null) {
+ mSearchBarEditView.addTextChangedListener(this);
+ mSearchBarEditView.setOnEditorActionListener(this);
}
mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
mAppsRecyclerView.setApps(mApps);
@@ -201,6 +210,18 @@
mAppsRecyclerView.setLayoutManager(mLayoutManager);
mAppsRecyclerView.setAdapter(mAdapter);
mAppsRecyclerView.setHasFixedSize(true);
+ mAppsRecyclerView.setOnScrollListenerProxy(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ // Do nothing
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mRecyclerViewScrollY += dy;
+ onRecyclerViewScrolled();
+ }
+ });
if (mItemDecoration != null) {
mAppsRecyclerView.addItemDecoration(mItemDecoration);
}
@@ -225,12 +246,15 @@
if (grid.updateAppsViewNumCols(context.getResources(), fixedBounds.width())) {
mNumAppsPerRow = grid.appsViewNumCols;
mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
- if (USE_LAYOUT == GRID_LAYOUT) {
- ((AppsGridAdapter) mAdapter).setNumAppsPerRow(mNumAppsPerRow);
- }
+ mAdapter.setNumAppsPerRow(mNumAppsPerRow);
+ mApps.setNumAppsPerRow(mNumAppsPerRow);
}
mFixedBounds.set(fixedBounds);
+ if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+ mFixedBounds.top = mInsets.top;
+ mFixedBounds.bottom = getMeasuredHeight();
+ }
}
// Post the updates since they can trigger a relayout, and this call can be triggered from
// a layout pass itself.
@@ -265,6 +289,15 @@
}
@Override
+ public void onClick(View v) {
+ if (v == mHeaderView) {
+ showSearchField();
+ } else if (v == mDismissSearchButtonView) {
+ hideSearchField(true, true);
+ }
+ }
+
+ @Override
public boolean onLongClick(View v) {
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
@@ -363,24 +396,27 @@
mApps.setFilter(null);
} else {
String formatStr = getResources().getString(R.string.apps_view_no_search_results);
- if (USE_LAYOUT == GRID_LAYOUT) {
- ((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
- s.toString()));
- } else {
- ((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
- s.toString()));
- }
+ mAdapter.setEmptySearchText(String.format(formatStr, s.toString()));
final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
mApps.setFilter(new AlphabeticalAppsList.Filter() {
@Override
public boolean retainApp(AppInfo info, String sectionName) {
String title = info.title.toString();
- return sectionName.toLowerCase().contains(filterText) ||
- title.toLowerCase().replaceAll("\\s+", "").contains(filterText);
+ if (sectionName.toLowerCase().contains(filterText)) {
+ return true;
+ }
+ String[] words = title.toLowerCase().split("\\s+");
+ for (int i = 0; i < words.length; i++) {
+ if (words[i].startsWith(filterText)) {
+ return true;
+ }
+ }
+ return false;
}
});
}
+ scrollToTop();
}
@Override
@@ -396,9 +432,7 @@
AlphabeticalAppsList.AdapterItem item = items.get(i);
if (!item.isSectionHeader) {
mAppsRecyclerView.getChildAt(i).performClick();
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
return true;
}
}
@@ -428,10 +462,22 @@
@Override
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
- if (mSearchBarView != null) {
+ if (mSearchBarEditView != null) {
if (toWorkspace) {
- // Clear the search bar
- mSearchBarView.setText("");
+ hideSearchField(false, false);
+ }
+ }
+ }
+
+ /**
+ * Updates the container when the recycler view is scrolled.
+ */
+ private void onRecyclerViewScrolled() {
+ if (DYNAMIC_HEADER_ELEVATION) {
+ int elevation = Math.min(mRecyclerViewScrollY, DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
+ getContext().getResources().getDisplayMetrics()));
+ if (Float.compare(mHeaderView.getElevation(), elevation) != 0) {
+ mHeaderView.setElevation(elevation);
}
}
}
@@ -494,8 +540,8 @@
private void updatePaddings() {
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
- boolean hasSearchBar = (mSearchBarView != null) &&
- (mSearchBarView.getVisibility() == View.VISIBLE);
+ boolean hasSearchBar = (mSearchBarEditView != null) &&
+ (mSearchBarEditView.getVisibility() == View.VISIBLE);
if (mFixedBounds.isEmpty()) {
// If there are no fixed bounds, then use the default padding and insets
@@ -516,10 +562,10 @@
mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset);
}
- // Update the search bar
+ // Update the header
if (hasSearchBar) {
LinearLayout.LayoutParams lp =
- (LinearLayout.LayoutParams) mSearchBarView.getLayoutParams();
+ (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
lp.leftMargin = lp.rightMargin = inset;
}
}
@@ -529,8 +575,8 @@
*/
private void updateBackgrounds() {
int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
- boolean hasSearchBar = (mSearchBarView != null) &&
- (mSearchBarView.getVisibility() == View.VISIBLE);
+ boolean hasSearchBar = (mSearchBarEditView != null) &&
+ (mSearchBarEditView.getVisibility() == View.VISIBLE);
// Update the background of the reveal view and list to be inset with the fixed bound
// insets instead of the default insets
@@ -542,4 +588,63 @@
getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
inset, 0, inset, 0));
}
+
+ /**
+ * Shows the search field.
+ */
+ private void showSearchField() {
+ // Show the search bar and focus the search
+ mSearchBarContainerView.setVisibility(View.VISIBLE);
+ mSearchBarContainerView.setAlpha(0f);
+ mSearchBarContainerView.animate().alpha(1f).setDuration(FADE_IN_DURATION).withLayer()
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mSearchBarEditView.requestFocus();
+ getInputMethodManager().showSoftInput(mSearchBarEditView,
+ InputMethodManager.SHOW_IMPLICIT);
+ }
+ });
+ mSearchButtonView.animate().alpha(0f).setDuration(FADE_OUT_DURATION).withLayer();
+ }
+
+ /**
+ * Hides the search field.
+ */
+ private void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
+ if (animated) {
+ // Hide the search bar and focus the recycler view
+ mSearchBarContainerView.animate().alpha(0f).setDuration(FADE_IN_DURATION).withLayer()
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mSearchBarContainerView.setVisibility(View.INVISIBLE);
+ mSearchBarEditView.setText("");
+ mApps.setFilter(null);
+ if (returnFocusToRecyclerView) {
+ mAppsRecyclerView.requestFocus();
+ }
+ scrollToTop();
+ }
+ });
+ mSearchButtonView.animate().alpha(1f).setDuration(FADE_OUT_DURATION).withLayer();
+ } else {
+ mSearchBarContainerView.setVisibility(View.INVISIBLE);
+ mSearchBarEditView.setText("");
+ mApps.setFilter(null);
+ mSearchButtonView.setAlpha(1f);
+ if (returnFocusToRecyclerView) {
+ mAppsRecyclerView.requestFocus();
+ }
+ scrollToTop();
+ }
+ getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+
+ /**
+ * Returns an input method manager.
+ */
+ private InputMethodManager getInputMethodManager() {
+ return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ }
}
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index 5bc3981..62d9129 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -4,16 +4,18 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Point;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
import com.android.launcher3.util.Thunk;
+import java.util.HashMap;
import java.util.List;
@@ -23,6 +25,7 @@
class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
public static final String TAG = "AppsGridAdapter";
+ private static final boolean DEBUG = false;
private static final int SECTION_BREAK_VIEW_TYPE = 0;
private static final int ICON_VIEW_TYPE = 1;
@@ -48,6 +51,12 @@
* Helper class to size the grid items.
*/
public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
+
+ public GridSpanSizer() {
+ super();
+ setSpanIndexCacheEnabled(true);
+ }
+
@Override
public int getSpanSize(int position) {
if (mApps.hasNoFilteredResults()) {
@@ -57,7 +66,11 @@
if (mApps.getAdapterItems().get(position).isSectionHeader) {
// Section break spans full width
- return mAppsPerRow;
+ if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+ return 0;
+ } else {
+ return mAppsPerRow;
+ }
} else {
return 1;
}
@@ -69,31 +82,88 @@
*/
public class GridItemDecoration extends RecyclerView.ItemDecoration {
+ private static final boolean FADE_OUT_SECTIONS = false;
+
+ private HashMap<String, Point> mCachedSectionBounds = new HashMap<>();
+ private Rect mTmpBounds = new Rect();
+
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ if (mApps.hasFilter()) {
+ return;
+ }
+
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+ String lastSectionName = null;
+ int appIndexInSection = 0;
+ int lastSectionTop = 0;
+ int lastSectionHeight = 0;
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
- if (shouldDrawItemSection(holder, child, items)) {
- // Draw at the parent
- AlphabeticalAppsList.AdapterItem item =
- items.get(holder.getPosition());
- String section = item.sectionName;
- mSectionTextPaint.getTextBounds(section, 0, section.length(),
- mTmpBounds);
- if (mIsRtl) {
- int left = parent.getWidth() - mPaddingStart - mStartMargin;
- c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
- child.getTop() + (2 * child.getPaddingTop()) +
- mTmpBounds.height(), mSectionTextPaint);
- } else {
- int left = mPaddingStart;
- c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
- child.getTop() + (2 * child.getPaddingTop()) +
- mTmpBounds.height(), mSectionTextPaint);
+ if (shouldDrawItemSection(holder, child, i, items)) {
+ int cellTopOffset = (2 * child.getPaddingTop());
+ int pos = holder.getPosition();
+ AlphabeticalAppsList.AdapterItem item = items.get(pos);
+ if (!item.sectionName.equals(lastSectionName)) {
+ lastSectionName = item.sectionName;
+
+ // Find the section code points
+ String sectionBegin = null;
+ String sectionEnd = null;
+ int charOffset = 0;
+ while (charOffset < item.sectionName.length()) {
+ int codePoint = item.sectionName.codePointAt(charOffset);
+ int codePointSize = Character.charCount(codePoint);
+ if (charOffset == 0) {
+ // The first code point
+ sectionBegin = item.sectionName.substring(charOffset, charOffset + codePointSize);
+ } else if ((charOffset + codePointSize) >= item.sectionName.length()) {
+ // The last code point
+ sectionEnd = item.sectionName.substring(charOffset, charOffset + codePointSize);
+ }
+ charOffset += codePointSize;
+ }
+
+ Point sectionBeginBounds = getAndCacheSectionBounds(sectionBegin);
+ int minTop = cellTopOffset + sectionBeginBounds.y;
+ int top = child.getTop() + cellTopOffset + sectionBeginBounds.y;
+ int left = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
+ mPaddingStart;
+ int col = appIndexInSection % mAppsPerRow;
+ int nextRowPos = Math.min(pos - col + mAppsPerRow, items.size() - 1);
+ int alpha = 255;
+ boolean fixedToRow = !items.get(nextRowPos).sectionName.equals(item.sectionName);
+ if (fixedToRow) {
+ alpha = Math.min(255, (int) (255 * (Math.max(0, top) / (float) minTop)));
+ } else {
+ // If we aren't fixed to the current row, then bound into the viewport
+ top = Math.max(minTop, top);
+ }
+ if (lastSectionHeight > 0 && top <= (lastSectionTop + lastSectionHeight)) {
+ top += lastSectionTop - top + lastSectionHeight;
+ }
+ if (FADE_OUT_SECTIONS) {
+ mSectionTextPaint.setAlpha(alpha);
+ }
+ if (sectionEnd != null) {
+ Point sectionEndBounds = getAndCacheSectionBounds(sectionEnd);
+ c.drawText(sectionBegin + "/" + sectionEnd,
+ left + (mStartMargin - sectionBeginBounds.x - sectionEndBounds.x) / 2, top,
+ mSectionTextPaint);
+ } else {
+ c.drawText(sectionBegin, left + (mStartMargin - sectionBeginBounds.x) / 2, top,
+ mSectionTextPaint);
+ }
+ lastSectionTop = top;
+ lastSectionHeight = sectionBeginBounds.y + mSectionHeaderOffset;
}
}
+ if (holder.mIsSectionHeader) {
+ appIndexInSection = 0;
+ } else {
+ appIndexInSection++;
+ }
}
}
@@ -103,7 +173,17 @@
// Do nothing
}
- private boolean shouldDrawItemSection(ViewHolder holder, View child,
+ private Point getAndCacheSectionBounds(String sectionName) {
+ Point bounds = mCachedSectionBounds.get(sectionName);
+ if (bounds == null) {
+ mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds);
+ bounds = new Point(mTmpBounds.width(), mTmpBounds.height());
+ mCachedSectionBounds.put(sectionName, bounds);
+ }
+ return bounds;
+ }
+
+ private boolean shouldDrawItemSection(ViewHolder holder, View child, int childIndex,
List<AlphabeticalAppsList.AdapterItem> items) {
// Ensure item is not already removed
GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
@@ -121,11 +201,19 @@
}
// Ensure we have a holder position
int pos = holder.getPosition();
- if (pos <= 0 || pos >= items.size()) {
+ if (pos < 0 || pos >= items.size()) {
return false;
}
- // Only draw the first item in the section (the first one after the section header)
- return items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader;
+ // Ensure this is not a section header
+ if (items.get(pos).isSectionHeader) {
+ return false;
+ }
+ // Only draw the header for the first item in a section, or whenever the sub-sections
+ // changes (if AppsContainerView.GRID_MERGE_SECTIONS is true, but
+ // AppsContainerView.GRID_MERGE_SECTION_HEADERS is false)
+ return (childIndex == 0) ||
+ items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader ||
+ (!items.get(pos - 1).sectionName.equals(items.get(pos).sectionName));
}
}
@@ -144,8 +232,8 @@
// Section drawing
@Thunk int mPaddingStart;
@Thunk int mStartMargin;
+ @Thunk int mSectionHeaderOffset;
@Thunk Paint mSectionTextPaint;
- @Thunk Rect mTmpBounds = new Rect();
public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
@@ -163,7 +251,10 @@
mTouchListener = touchListener;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+ if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+ mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+ mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.apps_grid_section_y_offset);
+ }
mPaddingStart = res.getDimensionPixelSize(R.dimen.apps_container_inset);
mSectionTextPaint = new Paint();
mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
@@ -197,7 +288,7 @@
/**
* Returns the grid layout manager.
*/
- public GridLayoutManager getLayoutManager(Context context) {
+ public GridLayoutManager getLayoutManager() {
return mGridLayoutMgr;
}
@@ -205,7 +296,11 @@
* Returns the item decoration for the recycler view.
*/
public RecyclerView.ItemDecoration getItemDecoration() {
- return mItemDecoration;
+ // We don't draw any headers when we are uncomfortably dense
+ if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+ return mItemDecoration;
+ }
+ return null;
}
/**
diff --git a/src/com/android/launcher3/AppsListAdapter.java b/src/com/android/launcher3/AppsListAdapter.java
deleted file mode 100644
index ffd3092..0000000
--- a/src/com/android/launcher3/AppsListAdapter.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.android.launcher3;
-
-import android.content.Context;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * The linear list view adapter for all the apps.
- */
-class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> {
-
- /**
- * ViewHolder for each row.
- */
- public static class ViewHolder extends RecyclerView.ViewHolder {
- public View mContent;
-
- public ViewHolder(View v) {
- super(v);
- mContent = v;
- }
- }
-
- private static final int SECTION_BREAK_VIEW_TYPE = 0;
- private static final int ICON_VIEW_TYPE = 1;
- private static final int EMPTY_VIEW_TYPE = 2;
-
- private LayoutInflater mLayoutInflater;
- private AlphabeticalAppsList mApps;
- private View.OnTouchListener mTouchListener;
- private View.OnClickListener mIconClickListener;
- private View.OnLongClickListener mIconLongClickListener;
- private String mEmptySearchText;
-
- public AppsListAdapter(Context context, AlphabeticalAppsList apps,
- View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
- View.OnLongClickListener iconLongClickListener) {
- mApps = apps;
- mLayoutInflater = LayoutInflater.from(context);
- mTouchListener = touchListener;
- mIconClickListener = iconClickListener;
- mIconLongClickListener = iconLongClickListener;
- }
-
- public RecyclerView.LayoutManager getLayoutManager(Context context) {
- return new LinearLayoutManager(context);
- }
-
- /**
- * Sets the text to show when there are no apps.
- */
- public void setEmptySearchText(String query) {
- mEmptySearchText = query;
- }
-
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- switch (viewType) {
- case EMPTY_VIEW_TYPE:
- return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
- false));
- case SECTION_BREAK_VIEW_TYPE:
- return new ViewHolder(new View(parent.getContext()));
- case ICON_VIEW_TYPE:
- // Inflate the row and all the icon children necessary
- ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.apps_list_row_view,
- parent, false);
- BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
- R.layout.apps_list_row_icon_view, row, false);
- LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT, 1);
- lp.gravity = Gravity.CENTER_VERTICAL;
- icon.setLayoutParams(lp);
- icon.setOnTouchListener(mTouchListener);
- icon.setOnClickListener(mIconClickListener);
- icon.setOnLongClickListener(mIconLongClickListener);
- icon.setFocusable(true);
- row.addView(icon);
- return new ViewHolder(row);
- default:
- throw new RuntimeException("Unexpected view type");
- }
- }
-
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- switch (holder.getItemViewType()) {
- case ICON_VIEW_TYPE:
- AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
- ViewGroup content = (ViewGroup) holder.mContent;
- String sectionDescription = item.sectionName;
-
- // Bind the section header
- boolean showSectionHeader = true;
- if (position > 0) {
- AlphabeticalAppsList.AdapterItem prevItem =
- mApps.getAdapterItems().get(position - 1);
- showSectionHeader = prevItem.isSectionHeader;
- }
- TextView tv = (TextView) content.findViewById(R.id.section);
- if (showSectionHeader) {
- tv.setText(sectionDescription);
- tv.setVisibility(View.VISIBLE);
- } else {
- tv.setVisibility(View.INVISIBLE);
- }
-
- // Bind the icon
- BubbleTextView icon = (BubbleTextView) content.getChildAt(1);
- icon.applyFromApplicationInfo(item.appInfo);
- break;
- case EMPTY_VIEW_TYPE:
- TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
- emptyViewText.setText(mEmptySearchText);
- break;
- }
- }
-
- @Override
- public int getItemCount() {
- if (mApps.hasNoFilteredResults()) {
- // For the empty view
- return 1;
- }
- return mApps.getAdapterItems().size();
- }
-
- @Override
- public int getItemViewType(int position) {
- if (mApps.hasNoFilteredResults()) {
- return EMPTY_VIEW_TYPE;
- } else if (mApps.getAdapterItems().get(position).isSectionHeader) {
- return SECTION_BREAK_VIEW_TYPE;
- }
- return ICON_VIEW_TYPE;
- }
-}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index deb8075..918517e 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -428,6 +428,13 @@
}
public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
+ if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+ if (appsViewNumCols != allAppsNumCols) {
+ appsViewNumCols = allAppsNumCols;
+ return true;
+ }
+ return false;
+ }
int appsViewLeftMarginPx =
res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index fd45714..6c2aa39 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -407,6 +407,20 @@
}
/**
+ * Updates {@param application} only if a valid entry is found.
+ */
+ public synchronized void updateTitleAndIcon(AppInfo application) {
+ CacheEntry entry = cacheLocked(application.componentName, null, application.user,
+ false, application.usingLowResIcon);
+ if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
+ application.title = entry.title;
+ application.iconBitmap = entry.icon;
+ application.contentDescription = entry.contentDescription;
+ application.usingLowResIcon = entry.isLowResIcon;
+ }
+ }
+
+ /**
* Returns a high res icon for the given intent and user
*/
public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) {
@@ -655,7 +669,7 @@
}
private static final class IconDB extends SQLiteOpenHelper {
- private final static int DB_VERSION = 3;
+ private final static int DB_VERSION = 4;
private final static String TABLE_NAME = "icons";
private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ee8be62..1de383c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -135,6 +135,9 @@
static final String TAG = "Launcher";
static final boolean LOGD = true;
+ // Temporary flag
+ static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
+
static final boolean PROFILE_STARTUP = false;
static final boolean DEBUG_WIDGETS = true;
static final boolean DEBUG_STRICT_MODE = false;
@@ -530,10 +533,12 @@
@Override
public void dismissAllApps() {
- // Dismiss All Apps if we aren't already paused/invisible
- if (!mPaused) {
- showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
- null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
+ if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+ // Dismiss All Apps if we aren't already paused/invisible
+ if (!mPaused) {
+ showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
+ null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
+ }
}
}
});
@@ -1019,7 +1024,7 @@
mOnResumeState = State.NONE;
// Restore the apps state if we are in all apps
- if (mState == State.APPS) {
+ if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mState == State.APPS) {
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsShown();
}
@@ -1453,8 +1458,8 @@
// Setup Apps
mAppsView = (AppsContainerView) findViewById(R.id.apps_view);
- if (mLauncherCallbacks != null && mLauncherCallbacks.overrideAllAppsSearch()) {
- mAppsView.hideSearchBar();
+ if (isAllAppsSearchOverridden()) {
+ mAppsView.hideHeaderBar();
}
// Setup AppsCustomize
@@ -2877,15 +2882,22 @@
/** Updates the interaction state. */
public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
- // Only update the interacting state if we are transitioning to/from a view without an
+ // Only update the interacting state if we are transitioning to/from a view with an
// overlay
- boolean fromStateWithoutOverlay = fromState != Workspace.State.NORMAL &&
- fromState != Workspace.State.NORMAL_HIDDEN;
- boolean toStateWithoutOverlay = toState != Workspace.State.NORMAL &&
- toState != Workspace.State.NORMAL_HIDDEN;
- if (toStateWithoutOverlay) {
+ boolean fromStateWithOverlay;
+ boolean toStateWithOverlay;
+ if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+ fromStateWithOverlay = fromState != Workspace.State.NORMAL;
+ toStateWithOverlay = toState != Workspace.State.NORMAL;
+ } else {
+ fromStateWithOverlay = fromState != Workspace.State.NORMAL &&
+ fromState != Workspace.State.NORMAL_HIDDEN;
+ toStateWithOverlay = toState != Workspace.State.NORMAL &&
+ toState != Workspace.State.NORMAL_HIDDEN;
+ }
+ if (toStateWithOverlay) {
onInteractionBegin();
- } else if (fromStateWithoutOverlay) {
+ } else if (fromStateWithOverlay) {
onInteractionEnd();
}
}
@@ -3366,7 +3378,7 @@
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
if (notifyLauncherCallbacks) {
// Dismiss all apps when the workspace is shown
- if (mLauncherCallbacks != null) {
+ if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsHidden();
}
}
@@ -3418,7 +3430,7 @@
if (toState == State.APPS) {
mStateTransitionAnimation.startAnimationToAllApps(animated);
- if (mLauncherCallbacks != null) {
+ if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsShown();
}
} else {
@@ -3471,7 +3483,7 @@
if (successfulDrop) {
// We need to trigger all apps hidden to notify search to update itself before the
// delayed call to showWorkspace below
- if (mLauncherCallbacks != null) {
+ if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
mLauncherCallbacks.onAllAppsHidden();
}
}
@@ -4453,9 +4465,12 @@
/**
* Returns whether the launcher callbacks overrides search in all apps.
- * @return
*/
@Thunk boolean isAllAppsSearchOverridden() {
+ if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+ return false;
+ }
+
if (mLauncherCallbacks != null) {
return mLauncherCallbacks.overrideAllAppsSearch();
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 0138a91..7efdf32 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -2767,6 +2767,7 @@
}
if (!mAllAppsLoaded) {
loadAllApps();
+ updateAllAppsIconsCache();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
@@ -2821,9 +2822,6 @@
return;
}
- final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
@@ -2843,42 +2841,6 @@
return;
}
- // Update icon cache
- HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
-
- // If any package icon has changed (app was updated while launcher was dead),
- // update the corresponding shortcuts.
- if (!updatedPackages.isEmpty()) {
- final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo && user.equals(info.user)
- && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- ShortcutInfo si = (ShortcutInfo) info;
- ComponentName cn = si.getTargetComponent();
- if (cn != null && updatedPackages.contains(cn.getPackageName())) {
- si.updateIcon(mIconCache);
- updates.add(si);
- }
- }
- }
- }
-
- if (!updates.isEmpty()) {
- final UserHandleCompat userFinal = user;
- mHandler.post(new Runnable() {
-
- public void run() {
- Callbacks cb = getCallback();
- if (cb != null) {
- cb.bindShortcutsChanged(
- updates, new ArrayList<ShortcutInfo>(), userFinal);
- }
- }
- });
- }
- }
-
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfoCompat app = apps.get(i);
@@ -2929,6 +2891,68 @@
}
}
+ private void updateAllAppsIconsCache() {
+ final ArrayList<AppInfo> updatedApps = new ArrayList<>();
+
+ for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+ // Query for the set of apps
+ final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+ // Fail if we don't have any apps
+ // TODO: Fix this. Only fail for the current user.
+ if (apps == null || apps.isEmpty()) {
+ return;
+ }
+
+ // Update icon cache
+ HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
+
+ // If any package icon has changed (app was updated while launcher was dead),
+ // update the corresponding shortcuts.
+ if (!updatedPackages.isEmpty()) {
+ final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+ synchronized (sBgLock) {
+ for (ItemInfo info : sBgItemsIdMap) {
+ if (info instanceof ShortcutInfo && user.equals(info.user)
+ && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ ComponentName cn = si.getTargetComponent();
+ if (cn != null && updatedPackages.contains(cn.getPackageName())) {
+ si.updateIcon(mIconCache);
+ updatedShortcuts.add(si);
+ }
+ }
+ }
+ mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
+ }
+
+ if (!updatedShortcuts.isEmpty()) {
+ final UserHandleCompat userFinal = user;
+ mHandler.post(new Runnable() {
+
+ public void run() {
+ Callbacks cb = getCallback();
+ if (cb != null) {
+ cb.bindShortcutsChanged(updatedShortcuts,
+ new ArrayList<ShortcutInfo>(), userFinal);
+ }
+ }
+ });
+ }
+ }
+ }
+ if (!updatedApps.isEmpty()) {
+ mHandler.post(new Runnable() {
+
+ public void run() {
+ Callbacks cb = getCallback();
+ if (cb != null) {
+ cb.bindAppsUpdated(updatedApps);
+ }
+ }
+ });
+ }
+ }
+
public void dumpState() {
synchronized (sBgLock) {
Log.d(TAG, "mLoaderTask.mContext=" + mContext);
@@ -3004,6 +3028,10 @@
}
public void run() {
+ if (!mHasLoaderCompletedOnce) {
+ // Loader has not yet run.
+ return;
+ }
final Context context = mApp.getContext();
final String[] packages = mPackages;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index d4e8ab5..aa86567 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -483,7 +483,7 @@
/**
* Sets the current page.
*/
- void setCurrentPage(int currentPage) {
+ public void setCurrentPage(int currentPage) {
if (!mScroller.isFinished()) {
abortScrollerAnimation(true);
}
@@ -2244,7 +2244,7 @@
}
}
- protected void onStartReordering() {
+ public void onStartReordering() {
// Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
mTouchState = TOUCH_STATE_REORDERING;
mIsReordering = true;
@@ -2264,7 +2264,7 @@
}
}
- protected void onEndReordering() {
+ public void onEndReordering() {
mIsReordering = false;
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 3cb2aa8..5c2121a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -63,6 +63,7 @@
import com.android.launcher3.LauncherAccessibilityDelegate.AccessibilityDragSource;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.UninstallDropTarget.UninstallSource;
+import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.Thunk;
@@ -276,6 +277,8 @@
// Handles workspace state transitions
private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
+ private AccessibilityDelegate mPagesAccessibilityDelegate;
+
private final Runnable mBindPages = new Runnable() {
@Override
public void run() {
@@ -1996,14 +1999,14 @@
range[1] = Math.max(0, end);
}
- protected void onStartReordering() {
+ public void onStartReordering() {
super.onStartReordering();
showOutlines();
// Reordering handles its own animations, disable the automatic ones.
disableLayoutTransitions();
}
- protected void onEndReordering() {
+ public void onEndReordering() {
super.onEndReordering();
if (mLauncher.isWorkspaceLoading()) {
@@ -2064,11 +2067,45 @@
return mState;
}
- private void updateAccessibilityFlags() {
- int accessible = mState == State.NORMAL ?
- IMPORTANT_FOR_ACCESSIBILITY_NO :
- IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
- setImportantForAccessibility(accessible);
+ public void updateAccessibilityFlags() {
+ if (Utilities.isLmpOrAbove()) {
+ int total = getPageCount();
+ for (int i = numCustomPages(); i < total; i++) {
+ updateAccessibilityFlags((CellLayout) getPageAt(i), i);
+ }
+ if (mState == State.NORMAL) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ } else {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ } else {
+ int accessible = mState == State.NORMAL ?
+ IMPORTANT_FOR_ACCESSIBILITY_NO :
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+ setImportantForAccessibility(accessible);
+ }
+ }
+
+ private void updateAccessibilityFlags(CellLayout page, int pageNo) {
+ if (mState == State.OVERVIEW) {
+ page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ page.getShortcutsAndWidgets().setImportantForAccessibility(
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ page.setContentDescription(getPageDescription(pageNo));
+
+ if (mPagesAccessibilityDelegate == null) {
+ mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
+ }
+ page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
+ } else {
+ int accessible = mState == State.NORMAL ?
+ IMPORTANT_FOR_ACCESSIBILITY_NO :
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+ page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ page.getShortcutsAndWidgets().setImportantForAccessibility(accessible);
+ page.setContentDescription(null);
+ page.setAccessibilityDelegate(null);
+ }
}
@Override
@@ -4448,11 +4485,15 @@
}
protected String getCurrentPageDescription() {
- int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
- int delta = numCustomPages();
if (hasCustomContent() && getNextPage() == 0) {
return mCustomContentDescription;
}
+ int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ return getPageDescription(page);
+ }
+
+ private String getPageDescription(int page) {
+ int delta = numCustomPages();
return String.format(getContext().getString(R.string.workspace_scroll_format),
page + 1 - delta, getChildCount() - delta);
}
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index a0cedeb..61a64e3 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -24,8 +24,11 @@
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
+
import com.android.launcher3.util.Thunk;
import java.util.HashMap;
@@ -190,7 +193,7 @@
final HashMap<View, Integer> layerViews) {
AccessibilityManager am = (AccessibilityManager)
mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
- boolean accessibilityEnabled = am.isEnabled();
+ final boolean accessibilityEnabled = am.isEnabled();
// Reinitialize animation arrays for the current workspace state
reinitializeAnimationArrays();
@@ -301,7 +304,7 @@
}
final View searchBar = mLauncher.getOrCreateQsbBar();
- final View overviewPanel = mLauncher.getOverviewPanel();
+ final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
final View hotseat = mLauncher.getHotseat();
final View pageIndicator = mWorkspace.getPageIndicator();
if (animated) {
@@ -424,6 +427,11 @@
@Override
public void onAnimationEnd(Animator animation) {
mStateAnimator = null;
+
+ if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
+ overviewPanel.getChildAt(0).performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
}
});
} else {
@@ -443,6 +451,11 @@
mWorkspace.setScaleX(mNewScale);
mWorkspace.setScaleY(mNewScale);
mWorkspace.setTranslationY(finalWorkspaceTranslationY);
+
+ if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
+ overviewPanel.getChildAt(0).performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
}
if (stateIsNormal) {
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
new file mode 100644
index 0000000..d3f5230
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.accessibility;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+
+public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
+
+ private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
+ private static final int MOVE_FORWARD = R.id.action_move_screen_forwards;
+
+ private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
+ private final Workspace mWorkspace;
+
+ public OverviewScreenAccessibilityDelegate(Workspace workspace) {
+ mWorkspace = workspace;
+
+ Context context = mWorkspace.getContext();
+ boolean isRtl = mWorkspace.isLayoutRtl();
+ mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD,
+ context.getText(isRtl ? R.string.action_move_screen_right :
+ R.string.action_move_screen_left)));
+ mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD,
+ context.getText(isRtl ? R.string.action_move_screen_left :
+ R.string.action_move_screen_right)));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (host != null) {
+ if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) {
+ int index = mWorkspace.indexOfChild(host);
+ mWorkspace.setCurrentPage(index);
+ } else if (action == MOVE_FORWARD) {
+ movePage(mWorkspace.indexOfChild(host) + 1, host);
+ return true;
+ } else if (action == MOVE_BACKWARD) {
+ movePage(mWorkspace.indexOfChild(host) - 1, host);
+ return true;
+ }
+ }
+
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ private void movePage(int finalIndex, View view) {
+ mWorkspace.onStartReordering();
+ mWorkspace.removeView(view);
+ mWorkspace.addView(view, finalIndex);
+ mWorkspace.onEndReordering();
+ mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved));
+
+ mWorkspace.updateAccessibilityFlags();
+ view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ int index = mWorkspace.indexOfChild(host);
+ if (index < mWorkspace.getChildCount() - 1) {
+ info.addAction(mActions.get(MOVE_FORWARD));
+ }
+ if (index > mWorkspace.numCustomPages()) {
+ info.addAction(mActions.get(MOVE_BACKWARD));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
new file mode 100644
index 0000000..80e13bc
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.util.Thunk;
+
+/**
+ * The widgets recycler view container.
+ * <p>
+ * Overwritten to NOT intercept a touch sequence that started when the {@link RecycleView}
+ * scrolling slowing down below the internally defined threshold.
+ */
+public class WidgetsContainerRecyclerView extends RecyclerView
+ implements RecyclerView.OnItemTouchListener {
+
+ private static final int SCROLL_DELTA_THRESHOLD = 4;
+
+ /** Keeps the last known scrolling delta/velocity along y-axis. */
+ @Thunk int mDy = 0;
+ private float mDeltaThreshold;
+
+ public WidgetsContainerRecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public WidgetsContainerRecyclerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WidgetsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD;
+
+ ScrollListener listener = new ScrollListener();
+ setOnScrollListener(listener);
+ }
+
+ private class ScrollListener extends RecyclerView.OnScrollListener {
+ public ScrollListener() {
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mDy = dy;
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ addOnItemTouchListener(this);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ if ((Math.abs(mDy) < mDeltaThreshold &&
+ getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
+ // now the touch events are being passed to the {@link WidgetCell} until the
+ // touch sequence goes over the touch slop.
+ stopScroll();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+ // Do nothing.
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsRowView.java b/src/com/android/launcher3/widget/WidgetsRowView.java
deleted file mode 100644
index 5466738..0000000
--- a/src/com/android/launcher3/widget/WidgetsRowView.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.widget;
-
-import android.content.Context;
-import android.view.MotionEvent;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.TextView;
-
-import com.android.launcher3.R;
-
-/**
- * Layout used for widget tray rows for each app. For performance, this view can be replaced with
- * a {@link RecyclerView} in the future if we settle on scrollable single row for the widgets.
- * If we decide on collapsable grid, then HorizontalScrollView can be replaced with a
- * {@link GridLayout}.
- */
-public class WidgetsRowView extends HorizontalScrollView {
- static final String TAG = "WidgetsRow";
-
- private Runnable mOnLayoutListener;
- private String mAppName;
-
- public WidgetsRowView(Context context, String appName) {
- super(context, null, 0);
- mAppName = appName;
- }
-
- /**
- * Clears all the key listeners for the individual widgets.
- */
- public void resetChildrenOnKeyListeners() {
- int childCount = getChildCount();
- for (int j = 0; j < childCount; ++j) {
- getChildAt(j).setOnKeyListener(null);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- TextView tv = (TextView) findViewById(R.id.widget_name);
- tv.setText(mAppName);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mOnLayoutListener = null;
- }
-
- public void setOnLayoutListener(Runnable r) {
- mOnLayoutListener = r;
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mOnLayoutListener != null) {
- mOnLayoutListener.run();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- boolean result = super.onTouchEvent(event);
- return result;
- }
-
- public static class LayoutParams extends FrameLayout.LayoutParams {
- public LayoutParams(int width, int height) {
- super(width, height);
- }
- }
-}