diff --git a/res/drawable/apps_list_bg.xml b/res/drawable/apps_list_bg.xml
new file mode 100644
index 0000000000..61f1c083a1
--- /dev/null
+++ b/res/drawable/apps_list_bg.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 6f95bd506e..b13984a265 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -62,6 +62,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 960ccf330d..a3d502cf41 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -71,6 +71,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
+
+
+
+
+
diff --git a/res/layout/apps_grid_row_view.xml b/res/layout/apps_grid_row_view.xml
new file mode 100644
index 0000000000..bce43bc1b4
--- /dev/null
+++ b/res/layout/apps_grid_row_view.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/apps_list_reveal_view.xml b/res/layout/apps_list_reveal_view.xml
new file mode 100644
index 0000000000..4a26787c84
--- /dev/null
+++ b/res/layout/apps_list_reveal_view.xml
@@ -0,0 +1,25 @@
+
+
+
\ No newline at end of file
diff --git a/res/layout/apps_list_row_icon_view.xml b/res/layout/apps_list_row_icon_view.xml
new file mode 100644
index 0000000000..607af9b0bb
--- /dev/null
+++ b/res/layout/apps_list_row_icon_view.xml
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/res/layout/apps_list_row_view.xml b/res/layout/apps_list_row_view.xml
new file mode 100644
index 0000000000..c4dcd00188
--- /dev/null
+++ b/res/layout/apps_list_row_view.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
new file mode 100644
index 0000000000..b1b0f310b6
--- /dev/null
+++ b/res/layout/apps_list_view.xml
@@ -0,0 +1,30 @@
+
+
+
\ No newline at end of file
diff --git a/res/layout/apps_view.xml b/res/layout/apps_view.xml
new file mode 100644
index 0000000000..19ad3d2c9a
--- /dev/null
+++ b/res/layout/apps_view.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 28679be2ed..f7ad0c4cd4 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -17,6 +17,9 @@
64dp
+
+ 76dp
+
60dp
8dp
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 3331cdec4d..4e7c592809 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -18,6 +18,14 @@
+
+
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d6fc508d12..013bd925b5 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -46,6 +46,9 @@
4dip
12dip
+
+ 64dp
+
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
new file mode 100644
index 0000000000..cabacec3c1
--- /dev/null
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -0,0 +1,609 @@
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+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