diff --git a/Android.mk b/Android.mk
index 5267469b3e..385380861f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -37,6 +37,9 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
LOCAL_SDK_VERSION := 21
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
+ android-support-v7-recyclerview
LOCAL_PACKAGE_NAME := Launcher3
#LOCAL_CERTIFICATE := shared
diff --git a/res/drawable/apps_list_fastscroll_bg.xml b/res/drawable/apps_list_fastscroll_bg.xml
new file mode 100644
index 0000000000..4ec18488b2
--- /dev/null
+++ b/res/drawable/apps_list_fastscroll_bg.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/apps_list_scrollbar_thumb.xml b/res/drawable/apps_list_scrollbar_thumb.xml
new file mode 100644
index 0000000000..ddd65b2319
--- /dev/null
+++ b/res/drawable/apps_list_scrollbar_thumb.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/apps_list_search_bg.xml b/res/drawable/apps_list_search_bg.xml
new file mode 100644
index 0000000000..eda33a9189
--- /dev/null
+++ b/res/drawable/apps_list_search_bg.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout-sw600dp/apps_view.xml b/res/layout-sw600dp/apps_view.xml
index 1f773b3073..3bb6ec5057 100644
--- a/res/layout-sw600dp/apps_view.xml
+++ b/res/layout-sw600dp/apps_view.xml
@@ -23,12 +23,12 @@
android:descendantFocusability="afterDescendants">
\ No newline at end of file
diff --git a/res/layout/apps_grid_row_icon_view.xml b/res/layout/apps_grid_row_icon_view.xml
index 11c8eeb4d8..81e74b985a 100644
--- a/res/layout/apps_grid_row_icon_view.xml
+++ b/res/layout/apps_grid_row_icon_view.xml
@@ -13,10 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+ android:background="@drawable/focusable_view_bg"
+ launcher:deferShadowGeneration="true" />
+
diff --git a/res/layout/apps_grid_row_view.xml b/res/layout/apps_grid_row_view.xml
deleted file mode 100644
index bce43bc1b4..0000000000
--- a/res/layout/apps_grid_row_view.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/res/layout/apps_list_reveal_view.xml b/res/layout/apps_list_reveal_view.xml
index 4a26787c84..19e462beee 100644
--- a/res/layout/apps_list_reveal_view.xml
+++ b/res/layout/apps_list_reveal_view.xml
@@ -15,7 +15,7 @@
-->
+ launcher:layoutHorizontal="true"
+ launcher:deferShadowGeneration="true" />
diff --git a/res/layout/apps_list_row_view.xml b/res/layout/apps_list_row_view.xml
index c4dcd00188..83c175bb89 100644
--- a/res/layout/apps_list_row_view.xml
+++ b/res/layout/apps_list_row_view.xml
@@ -26,9 +26,8 @@
android:layout_width="64dp"
android:layout_height="match_parent"
android:paddingLeft="16dp"
- android:gravity="left|center_vertical"
- android:textColor="#009688"
- android:textSize="24sp"
- android:textAllCaps="true"
+ android:gravity="start|center_vertical"
+ android:textColor="@color/apps_view_section_text_color"
+ android:textSize="@dimen/apps_view_section_text_size"
android:focusable="false" />
\ No newline at end of file
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index b1b0f310b6..dfb2fc42f2 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -13,18 +13,41 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
\ No newline at end of file
+ android:visibility="gone">
+
+
+
\ No newline at end of file
diff --git a/res/layout/apps_view.xml b/res/layout/apps_view.xml
index 19ad3d2c9a..c1bae63f66 100644
--- a/res/layout/apps_view.xml
+++ b/res/layout/apps_view.xml
@@ -19,7 +19,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
- android:background="#22000000"
+ android:background="@drawable/apps_customize_bg"
android:descendantFocusability="afterDescendants">
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index f7ad0c4cd4..d9075872a7 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -18,6 +18,7 @@
64dp
+ 480dp
76dp
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4e7c592809..845b182306 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -24,6 +24,7 @@
+
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 2daf9fe123..590a8872b3 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,4 +36,8 @@
#FFFFFFFF
#FF374248
+
+ #009688
+ #009688
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 013bd925b5..9b4c560944 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -47,7 +47,13 @@
12dip
+ 0dp
+ 52dp
64dp
+ 24sp
+ 48dp
+ 64dp
+ 48dp
Choose widget to create
+
+
+ Search Apps
+
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
new file mode 100644
index 0000000000..2847afc891
--- /dev/null
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -0,0 +1,242 @@
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+
+/**
+ * The alphabetically sorted list of applications.
+ */
+public class AlphabeticalAppsList {
+
+ /**
+ * Info about a section in the alphabetic list
+ */
+ public class SectionInfo {
+ public String sectionName;
+ public int numAppsInSection;
+ }
+
+ /**
+ * A filter interface to limit the set of applications in the apps list.
+ */
+ public interface Filter {
+ public boolean retainApp(AppInfo info);
+ }
+
+ // Hack to force RecyclerView to break sections
+ public static final AppInfo SECTION_BREAK_INFO = null;
+
+ private List mApps = new ArrayList<>();
+ private List mFilteredApps = new ArrayList<>();
+ private List mSectionedFilteredApps = new ArrayList<>();
+ private List mSections = new ArrayList<>();
+ private RecyclerView.Adapter mAdapter;
+ private Filter mFilter;
+ private AlphabeticIndexCompat mIndexer;
+
+ public AlphabeticalAppsList(Context context) {
+ mIndexer = new AlphabeticIndexCompat(context);
+ }
+
+ /**
+ * Sets the adapter to notify when this dataset changes.
+ */
+ public void setAdapter(RecyclerView.Adapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Returns sections of all the current filtered applications.
+ */
+ public List getSections() {
+ return mSections;
+ }
+
+ /**
+ * Returns the current filtered list of applications broken down into their sections.
+ */
+ public List getApps() {
+ return mSectionedFilteredApps;
+ }
+
+ /**
+ * Returns the current filtered list of applications.
+ */
+ public List getAppsWithoutSectionBreaks() {
+ return mFilteredApps;
+ }
+
+ /**
+ * Returns the section name for the application.
+ */
+ public String getSectionNameForApp(AppInfo info) {
+ String title = info.title.toString();
+ String sectionName = mIndexer.getBucketLabel(mIndexer.getBucketIndex(title));
+ return sectionName;
+ }
+
+ /**
+ * Returns the indexer for this locale.
+ */
+ public AlphabeticIndexCompat getIndexer() {
+ return mIndexer;
+ }
+
+ /**
+ * Sets the current filter for this list of apps.
+ */
+ public void setFilter(Filter f) {
+ mFilter = f;
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Sets the current set of apps.
+ */
+ public void setApps(List apps) {
+ Collections.sort(apps, LauncherModel.getAppNameComparator());
+ mApps.clear();
+ mApps.addAll(apps);
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Adds new apps to the list.
+ */
+ public void addApps(List apps) {
+ // We add it in place, in alphabetical order
+ for (AppInfo info : apps) {
+ addApp(info);
+ }
+ }
+
+ /**
+ * Updates existing apps in the list
+ */
+ public void updateApps(List apps) {
+ for (AppInfo info : apps) {
+ int index = mApps.indexOf(info);
+ if (index != -1) {
+ mApps.set(index, info);
+ onAppsUpdated();
+ mAdapter.notifyItemChanged(index);
+ } else {
+ addApp(info);
+ }
+ }
+ }
+
+ /**
+ * Removes some apps from the list.
+ */
+ public void removeApps(List apps) {
+ for (AppInfo info : apps) {
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex != -1) {
+ int sectionedIndex = mSectionedFilteredApps.indexOf(info);
+ int numAppsInSection = numAppsInSection(info);
+ mApps.remove(removeIndex);
+ onAppsUpdated();
+ if (numAppsInSection == 1) {
+ // Remove the section and the icon
+ mAdapter.notifyItemRemoved(sectionedIndex - 1);
+ mAdapter.notifyItemRemoved(sectionedIndex - 1);
+ } else {
+ mAdapter.notifyItemRemoved(sectionedIndex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds the index of an app given a target AppInfo.
+ */
+ private int findAppByComponent(List apps, AppInfo targetInfo) {
+ ComponentName targetComponent = targetInfo.intent.getComponent();
+ int length = apps.size();
+ for (int i = 0; i < length; ++i) {
+ AppInfo info = apps.get(i);
+ if (info.user.equals(info.user)
+ && info.intent.getComponent().equals(targetComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Implementation to actually add an app to the alphabetic list
+ */
+ private void addApp(AppInfo info) {
+ Comparator appNameComparator = LauncherModel.getAppNameComparator();
+ int index = Collections.binarySearch(mApps, info, appNameComparator);
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ onAppsUpdated();
+
+ int sectionedIndex = mSectionedFilteredApps.indexOf(info);
+ int numAppsInSection = numAppsInSection(info);
+ if (numAppsInSection == 1) {
+ // New section added along with icon
+ mAdapter.notifyItemInserted(sectionedIndex - 1);
+ mAdapter.notifyItemInserted(sectionedIndex - 1);
+ } else {
+ mAdapter.notifyItemInserted(sectionedIndex);
+ }
+ }
+ }
+
+ /**
+ * Returns the number of apps in the section that the given info is in.
+ */
+ private int numAppsInSection(AppInfo info) {
+ int appIndex = mFilteredApps.indexOf(info);
+ int appCount = 0;
+ for (SectionInfo section : mSections) {
+ if (appCount + section.numAppsInSection > appIndex) {
+ return section.numAppsInSection;
+ }
+ appCount += section.numAppsInSection;
+ }
+ return 1;
+ }
+
+ /**
+ * Updates internals when the set of apps are updated.
+ */
+ private void onAppsUpdated() {
+ // Recreate the filtered apps
+ mFilteredApps.clear();
+ for (AppInfo info : mApps) {
+ if (mFilter == null || mFilter.retainApp(info)) {
+ mFilteredApps.add(info);
+ }
+ }
+
+ // Section the apps (for convenience for the grid layout)
+ mSections.clear();
+ mSectionedFilteredApps.clear();
+ SectionInfo lastSectionInfo = null;
+ for (AppInfo info : mFilteredApps) {
+ String sectionName = getSectionNameForApp(info);
+ if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
+ lastSectionInfo = new SectionInfo();
+ lastSectionInfo.sectionName = sectionName;
+ mSectionedFilteredApps.add(SECTION_BREAK_INFO);
+ mSections.add(lastSectionInfo);
+ }
+ lastSectionInfo.numAppsInSection++;
+ mSectionedFilteredApps.add(info);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
new file mode 100644
index 0000000000..2280e99ef7
--- /dev/null
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -0,0 +1,271 @@
+/*
+ * 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;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import java.util.List;
+
+/**
+ * A RecyclerView with custom fastscroll support. This is the main container for the all apps
+ * icons.
+ */
+public class AppsContainerRecyclerView extends RecyclerView
+ implements RecyclerView.OnItemTouchListener {
+
+ private AlphabeticalAppsList mApps;
+ private int mNumAppsPerRow;
+
+ private Drawable mFastScrollerBg;
+ private boolean mDraggingFastScroller;
+ private String mFastScrollSectionName;
+ private Paint mFastScrollTextPaint;
+ private Rect mFastScrollTextBounds = new Rect();
+ private float mFastScrollAlpha;
+ private int mDownX;
+ private int mDownY;
+ private int mLastX;
+ private int mLastY;
+ private int mGutterSize;
+
+ public AppsContainerRecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public AppsContainerRecyclerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr);
+
+ Resources res = context.getResources();
+ int fastScrollerSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_popup_size);
+ mFastScrollerBg = res.getDrawable(R.drawable.apps_list_fastscroll_bg);
+ mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
+ mFastScrollTextPaint = new Paint();
+ mFastScrollTextPaint.setColor(Color.WHITE);
+ mFastScrollTextPaint.setAntiAlias(true);
+ mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.apps_view_fast_scroll_text_size));
+ mGutterSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_gutter_size);
+ setFastScrollerAlpha(getFastScrollerAlpha());
+ }
+
+ /**
+ * Sets the list of apps in this view, used to determine the fastscroll position.
+ */
+ public void setApps(AlphabeticalAppsList apps) {
+ mApps = apps;
+ }
+
+ /**
+ * Sets the number of apps per row in this recycler view.
+ */
+ public void setNumAppsPerRow(int rowSize) {
+ mNumAppsPerRow = rowSize;
+ }
+
+ /**
+ * Sets the fast scroller alpha.
+ */
+ public void setFastScrollerAlpha(float alpha) {
+ mFastScrollAlpha = alpha;
+ invalidateFastScroller();
+ }
+
+ /**
+ * Gets the fast scroller alpha.
+ */
+ public float getFastScrollerAlpha() {
+ return mFastScrollAlpha;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ addOnItemTouchListener(this);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (mFastScrollAlpha > 0f) {
+ boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+ LAYOUT_DIRECTION_RTL);
+ Rect bgBounds = mFastScrollerBg.getBounds();
+ int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ int x;
+ if (isRtl) {
+ x = getPaddingLeft() + getScrollBarSize();
+ } else {
+ x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
+ }
+ int y = mLastY - bgBounds.height() / 2;
+ y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
+ bgBounds.height()));
+ canvas.translate(x, y);
+ mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollerBg.draw(canvas);
+ mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
+ mFastScrollSectionName.length(), mFastScrollTextBounds);
+ canvas.drawText(mFastScrollSectionName,
+ (bgBounds.width() - mFastScrollTextBounds.width()) / 2,
+ bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
+ mFastScrollTextPaint);
+ canvas.restoreToCount(restoreCount);
+ }
+ }
+
+ /**
+ * We intercept the touch handling only to support fast scrolling when initiated from the
+ * gutter. Otherwise, we fall back to the default RecyclerView touch handling.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+ handleTouchEvent(ev);
+ }
+
+ /**
+ * Handles the touch event and determines whether to show the fast scroller (or updates it if
+ * it is already showing).
+ */
+ private boolean handleTouchEvent(MotionEvent ev) {
+ ViewConfiguration config = ViewConfiguration.get(getContext());
+
+ int action = ev.getAction();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ // Keep track of the down positions
+ mDownX = mLastX = x;
+ mDownY = mLastY = y;
+ stopScroll();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Check if we are scrolling
+ boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+ LAYOUT_DIRECTION_RTL);
+ boolean isInGutter;
+ if (isRtl) {
+ isInGutter = mDownX < mGutterSize;
+ } else {
+ isInGutter = mDownX >= (getWidth() - mGutterSize);
+ }
+ if (!mDraggingFastScroller && isInGutter &&
+ Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ mDraggingFastScroller = true;
+ animateFastScrollerVisibility(true);
+ }
+ if (mDraggingFastScroller) {
+ mLastX = x;
+ mLastY = y;
+
+ // Scroll to the right position, and update the section name
+ int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2);
+ int bottom = getHeight() - getPaddingBottom() -
+ (mFastScrollerBg.getBounds().height() / 2);
+ float boundedY = (float) Math.max(top, Math.min(bottom, y));
+ mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) /
+ (bottom - top));
+ invalidateFastScroller();
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mDraggingFastScroller = false;
+ animateFastScrollerVisibility(false);
+ break;
+ }
+ return mDraggingFastScroller;
+
+ }
+
+ /**
+ * Animates the visibility of the fast scroller popup.
+ */
+ private void animateFastScrollerVisibility(boolean visible) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
+ anim.setDuration(visible ? 200 : 150);
+ anim.start();
+ }
+
+ /**
+ * Invalidates the fast scroller popup.
+ */
+ private void invalidateFastScroller() {
+ invalidate(getWidth() - getPaddingRight() - getScrollBarSize() -
+ mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight());
+ }
+
+ /**
+ * Maps the progress (from 0..1) to the position that should be visible
+ */
+ private String scrollToPositionAtProgress(float progress) {
+ List sections = mApps.getSections();
+ // Get the total number of rows
+ int rowCount = 0;
+ for (AlphabeticalAppsList.SectionInfo info : sections) {
+ int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
+ rowCount += numRowsInSection;
+ }
+
+ // Find the index of the first app in that row and scroll to that position
+ int rowAtProgress = (int) (progress * rowCount);
+ int appIndex = 0;
+ rowCount = 0;
+ for (AlphabeticalAppsList.SectionInfo info : sections) {
+ int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
+ if (rowCount + numRowsInSection > rowAtProgress) {
+ appIndex += (rowAtProgress - rowCount) * mNumAppsPerRow;
+ break;
+ }
+ rowCount += numRowsInSection;
+ appIndex += info.numAppsInSection;
+ }
+ appIndex = Math.max(0, Math.min(mApps.getAppsWithoutSectionBreaks().size() - 1, appIndex));
+ AppInfo appInfo = mApps.getAppsWithoutSectionBreaks().get(appIndex);
+ int sectionedAppIndex = mApps.getApps().indexOf(appInfo);
+ scrollToPosition(sectionedAppIndex);
+
+ // Returns the section name of the row
+ return mApps.getSectionNameForApp(appInfo);
+ }
+}
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index cabacec3c1..cc31e20fa9 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -1,409 +1,64 @@
+/*
+ * 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;
-import android.content.ComponentName;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.SectionIndexer;
import android.widget.TextView;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
-/**
- * Represents a row in the apps list view.
- */
-class AppsRow {
- int sectionId;
- String sectionDescription;
- List apps;
-
- public AppsRow(int sId, String sc, List ai) {
- sectionId = sId;
- sectionDescription = sc;
- apps = ai;
- }
-
- public AppsRow(int sId, List ai) {
- sectionId = sId;
- apps = ai;
- }
-}
-
-/**
- * An interface to an algorithm that generates app rows.
- */
-interface AppRowAlgorithm {
- public List computeAppRows(List sortedApps, int appsPerRow);
- public int getIconViewLayoutId();
- public int getRowViewLayoutId();
- public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info);
-}
-
-/**
- * Computes the rows in the apps list view.
- */
-class SectionedAppsAlgorithm implements AppRowAlgorithm {
-
- @Override
- public List computeAppRows(List sortedApps, int appsPerRow) {
- List rows = new ArrayList<>();
- LinkedHashMap> sections = computeSectionedApps(sortedApps);
- int sectionId = 0;
- for (Map.Entry> sectionEntry : sections.entrySet()) {
- String section = sectionEntry.getKey();
- List apps = sectionEntry.getValue();
- int numRows = (int) Math.ceil((float) apps.size() / appsPerRow);
- for (int i = 0; i < numRows; i++) {
- List appsInRow = new ArrayList<>();
- int offset = i * appsPerRow;
- for (int j = 0; j < appsPerRow; j++) {
- if (offset + j < apps.size()) {
- appsInRow.add(apps.get(offset + j));
- }
- }
- if (i == 0) {
- rows.add(new AppsRow(sectionId, section, appsInRow));
- } else {
- rows.add(new AppsRow(sectionId, appsInRow));
- }
- }
- sectionId++;
- }
- return rows;
- }
-
- @Override
- public int getIconViewLayoutId() {
- return R.layout.apps_grid_row_icon_view;
- }
-
- @Override
- public int getRowViewLayoutId() {
- return R.layout.apps_grid_row_view;
- }
-
- private LinkedHashMap> computeSectionedApps(List sortedApps) {
- LinkedHashMap> sections = new LinkedHashMap<>();
- for (AppInfo info : sortedApps) {
- String section = getSection(info);
- List sectionApps = sections.get(section);
- if (sectionApps == null) {
- sectionApps = new ArrayList<>();
- sections.put(section, sectionApps);
- }
- sectionApps.add(info);
- }
- return sections;
- }
-
- @Override
- public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info) {
- icon.applyFromApplicationInfo(info);
- }
-
- private String getSection(AppInfo app) {
- return app.title.toString().substring(0, 1).toLowerCase();
- }
-}
-
-/**
- * Computes the rows in the apps grid view.
- */
-class ListedAppsAlgorithm implements AppRowAlgorithm {
-
- @Override
- public List computeAppRows(List sortedApps, int appsPerRow) {
- List rows = new ArrayList<>();
- int sectionId = -1;
- String prevSection = "";
- for (AppInfo info : sortedApps) {
- List appsInRow = new ArrayList<>();
- appsInRow.add(info);
- String section = getSection(info);
- if (!prevSection.equals(section)) {
- prevSection = section;
- sectionId++;
- rows.add(new AppsRow(sectionId, section, appsInRow));
- } else {
- rows.add(new AppsRow(sectionId, appsInRow));
- }
- }
- return rows;
- }
-
- @Override
- public int getIconViewLayoutId() {
- return R.layout.apps_list_row_icon_view;
- }
-
- @Override
- public int getRowViewLayoutId() {
- return R.layout.apps_list_row_view;
- }
-
- @Override
- public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info) {
- icon.applyFromApplicationInfo(info);
- }
-
- private String getSection(AppInfo app) {
- return app.title.toString().substring(0, 1).toLowerCase();
- }
-}
-
-/**
- * The adapter of all the apps
- */
-class AppsListAdapter extends BaseAdapter implements SectionIndexer {
-
- private LayoutInflater mLayoutInflater;
- private List mAppRows = new ArrayList<>();
- private View.OnTouchListener mTouchListener;
- private View.OnClickListener mIconClickListener;
- private View.OnLongClickListener mIconLongClickListener;
- private AppRowAlgorithm mRowAlgorithm;
- private int mAppsPerRow;
-
- public AppsListAdapter(Context context, View.OnTouchListener touchListener,
- View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
- mLayoutInflater = LayoutInflater.from(context);
- mTouchListener = touchListener;
- mIconClickListener = iconClickListener;
- mIconLongClickListener = iconLongClickListener;
- }
-
- void setApps(List apps, int appsPerRow, AppRowAlgorithm algo) {
- mAppsPerRow = appsPerRow;
- mRowAlgorithm = algo;
- mAppRows.clear();
- mAppRows.addAll(apps);
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return mAppRows.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mAppRows.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- AppsRow info = mAppRows.get(position);
- ViewGroup row = (ViewGroup) convertView;
- if (row == null) {
- // Inflate the row and all the icon children necessary
- row = (ViewGroup) mLayoutInflater.inflate(mRowAlgorithm.getRowViewLayoutId(),
- parent, false);
- for (int i = 0; i < mAppsPerRow; i++) {
- BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
- mRowAlgorithm.getIconViewLayoutId(), 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);
- }
- }
- // Bind the section header
- TextView tv = (TextView) row.findViewById(R.id.section);
- if (info.sectionDescription != null) {
- tv.setText(info.sectionDescription);
- tv.setVisibility(View.VISIBLE);
- } else {
- tv.setVisibility(View.INVISIBLE);
- }
- // Bind the icons
- for (int i = 0; i < mAppsPerRow; i++) {
- BubbleTextView icon = (BubbleTextView) row.getChildAt(i + 1);
- if (i < info.apps.size()) {
- mRowAlgorithm.bindRowViewIconToInfo(icon, info.apps.get(i));
- icon.setVisibility(View.VISIBLE);
- } else {
- icon.setVisibility(View.INVISIBLE);
- }
- }
- return row;
- }
-
- @Override
- public Object[] getSections() {
- ArrayList