summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk3
-rw-r--r--res/drawable/apps_list_fastscroll_bg.xml24
-rw-r--r--res/drawable/apps_list_scrollbar_thumb.xml21
-rw-r--r--res/drawable/apps_list_search_bg.xml23
-rw-r--r--res/layout-sw600dp/apps_view.xml8
-rw-r--r--res/layout/apps_grid_row_icon_view.xml13
-rw-r--r--res/layout/apps_grid_row_view.xml38
-rw-r--r--res/layout/apps_list_reveal_view.xml2
-rw-r--r--res/layout/apps_list_row_icon_view.xml5
-rw-r--r--res/layout/apps_list_row_view.xml7
-rw-r--r--res/layout/apps_list_view.xml41
-rw-r--r--res/layout/apps_view.xml2
-rw-r--r--res/values-sw600dp/dimens.xml1
-rw-r--r--res/values/attrs.xml1
-rw-r--r--res/values/colors.xml4
-rw-r--r--res/values/dimens.xml6
-rw-r--r--res/values/strings.xml4
-rw-r--r--src/com/android/launcher3/AlphabeticalAppsList.java242
-rw-r--r--src/com/android/launcher3/AppsContainerRecyclerView.java271
-rw-r--r--src/com/android/launcher3/AppsContainerView.java564
-rw-r--r--src/com/android/launcher3/AppsGridAdapter.java206
-rw-r--r--src/com/android/launcher3/AppsListAdapter.java119
-rw-r--r--src/com/android/launcher3/BubbleTextView.java9
-rw-r--r--src/com/android/launcher3/DeviceProfile.java28
-rw-r--r--src/com/android/launcher3/compat/AlphabeticIndexCompat.java131
25 files changed, 1295 insertions, 478 deletions
diff --git a/Android.mk b/Android.mk
index 5267469b3..385380861 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 000000000..4ec18488b
--- /dev/null
+++ b/res/drawable/apps_list_fastscroll_bg.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/apps_view_scrollbar_thumb_color" />
+ <size
+ android:width="48dp"
+ android:height="48dp" />
+ <corners android:radius="4dp" />
+</shape> \ 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 000000000..ddd65b231
--- /dev/null
+++ b/res/drawable/apps_list_scrollbar_thumb.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/apps_view_scrollbar_thumb_color" />
+ <size android:width="4dp"/>
+</shape> \ 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 000000000..eda33a918
--- /dev/null
+++ b/res/drawable/apps_list_search_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffffff" />
+ <corners
+ android:topLeftRadius="3dp"
+ android:topRightRadius="3dp" />
+</shape> \ No newline at end of file
diff --git a/res/layout-sw600dp/apps_view.xml b/res/layout-sw600dp/apps_view.xml
index 1f773b307..3bb6ec505 100644
--- a/res/layout-sw600dp/apps_view.xml
+++ b/res/layout-sw600dp/apps_view.xml
@@ -23,12 +23,12 @@
android:descendantFocusability="afterDescendants">
<include
layout="@layout/apps_list_reveal_view"
- android:layout_width="420dp"
- android:layout_height="match_parent"
+ android:layout_width="@dimen/apps_container_width"
+ android:layout_height="540dp"
android:layout_gravity="center" />
<include
layout="@layout/apps_list_view"
- android:layout_width="420dp"
- android:layout_height="match_parent"
+ android:layout_width="@dimen/apps_container_width"
+ android:layout_height="540dp"
android:layout_gravity="center" />
</com.android.launcher3.AppsContainerView> \ 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 11c8eeb4d..81e74b985 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.
-->
-
<com.android.launcher3.BubbleTextView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/WorkspaceIcon.AppsCustomize"
- android:id="@+id/application_icon"
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left|center_vertical"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
android:focusable="true"
- android:background="@drawable/focusable_view_bg" />
+ 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 bce43bc1b..000000000
--- a/res/layout/apps_grid_row_view.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/apps_view_row_height"
- android:paddingTop="12dp"
- android:paddingBottom="12dp"
- android:orientation="horizontal"
- android:focusable="true"
- android:background="@drawable/focusable_view_bg"
- android:descendantFocusability="afterDescendants">
- <TextView
- android:id="@+id/section"
- android:layout_width="48dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingRight="8dp"
- android:paddingBottom="12dp"
- android:gravity="right"
- android:textColor="#1ca195"
- android:textSize="16sp"
- android:textAllCaps="true"
- android:focusable="false" />
-</LinearLayout> \ 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 4a26787c8..19e462bee 100644
--- a/res/layout/apps_list_reveal_view.xml
+++ b/res/layout/apps_list_reveal_view.xml
@@ -15,7 +15,7 @@
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/all_apps_transition_overlay"
+ android:id="@+id/apps_view_transition_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
diff --git a/res/layout/apps_list_row_icon_view.xml b/res/layout/apps_list_row_icon_view.xml
index 607af9b0b..867dbdc99 100644
--- a/res/layout/apps_list_row_icon_view.xml
+++ b/res/layout/apps_list_row_icon_view.xml
@@ -20,9 +20,10 @@
style="@style/WorkspaceIcon.AppsCustomize"
android:id="@+id/application_icon"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:focusable="true"
android:background="@drawable/focusable_view_bg"
launcher:iconPaddingOverride="24dp"
launcher:textSizeOverride="16dp"
- launcher:layoutHorizontal="true" />
+ 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 c4dcd0018..83c175bb8 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" />
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index b1b0f310b..dfb2fc42f 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.
-->
-<ListView
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="center"
- android:paddingTop="12dp"
- android:paddingBottom="12dp"
- android:clipToPadding="false"
- android:scrollbars="vertical"
+ android:orientation="vertical"
android:elevation="15dp"
android:background="@drawable/apps_list_bg"
- android:visibility="gone"
- android:focusable="true"
- android:descendantFocusability="afterDescendants" /> \ No newline at end of file
+ android:visibility="gone">
+ <EditText
+ android:id="@+id/app_search_box"
+ 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="flagNoExtractUi"
+ android:background="@drawable/apps_list_search_bg"
+ android:elevation="4dp" />
+ <com.android.launcher3.AppsContainerRecyclerView
+ android:id="@+id/apps_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:clipToPadding="false"
+ android:scrollbars="vertical"
+ android:scrollbarThumbVertical="@drawable/apps_list_scrollbar_thumb"
+ android:focusable="true"
+ android:descendantFocusability="afterDescendants" />
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/apps_view.xml b/res/layout/apps_view.xml
index 19ad3d2c9..c1bae63f6 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">
<include
layout="@layout/apps_list_reveal_view" />
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index f7ad0c4cd..d9075872a 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -18,6 +18,7 @@
<dimen name="app_icon_size">64dp</dimen>
<!-- Apps view -->
+ <dimen name="apps_container_width">480dp</dimen>
<dimen name="apps_view_row_height">76dp</dimen>
<!-- AppsCustomize -->
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4e7c59280..845b18230 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -24,6 +24,7 @@
<attr name="iconSizeOverride" format="dimension" />
<attr name="iconPaddingOverride" format="dimension" />
<attr name="textSizeOverride" format="dimension" />
+ <attr name="deferShadowGeneration" format="boolean" />
</declare-styleable>
<!-- Page Indicator specific attributes. -->
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 2daf9fe12..590a8872b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,4 +36,8 @@
<color name="outline_color">#FFFFFFFF</color>
<color name="widget_text_panel">#FF374248</color>
+<!-- Apps view -->
+ <color name="apps_view_scrollbar_thumb_color">#009688</color>
+ <color name="apps_view_section_text_color">#009688</color>
+
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 013bd925b..9b4c56094 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -47,7 +47,13 @@
<dimen name="toolbar_button_horizontal_padding">12dip</dimen>
<!-- Apps view -->
+ <dimen name="apps_container_width">0dp</dimen>
+ <dimen name="apps_grid_view_start_margin">52dp</dimen>
<dimen name="apps_view_row_height">64dp</dimen>
+ <dimen name="apps_view_section_text_size">24sp</dimen>
+ <dimen name="apps_view_fast_scroll_gutter_size">48dp</dimen>
+ <dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
+ <dimen name="apps_view_fast_scroll_text_size">48dp</dimen>
<!-- AllApps/Customize/AppsCustomize -->
<!-- The height of the tab bar - if this changes, we should update the
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 74b88148c..0d113dbf7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -71,6 +71,10 @@
drop if there are multiple choices. [CHAR_LIMIT=35] -->
<string name="external_drop_widget_pick_title">Choose widget to create</string>
+ <!-- Apps view -->
+ <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
+ <string name="apps_view_search_bar_hint">Search Apps</string>
+
<!-- Folders -->
<skip />
<!-- Label of Folder name field in Rename folder dialog box -->
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
new file mode 100644
index 000000000..2847afc89
--- /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<AppInfo> mApps = new ArrayList<>();
+ private List<AppInfo> mFilteredApps = new ArrayList<>();
+ private List<AppInfo> mSectionedFilteredApps = new ArrayList<>();
+ private List<SectionInfo> 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<SectionInfo> getSections() {
+ return mSections;
+ }
+
+ /**
+ * Returns the current filtered list of applications broken down into their sections.
+ */
+ public List<AppInfo> getApps() {
+ return mSectionedFilteredApps;
+ }
+
+ /**
+ * Returns the current filtered list of applications.
+ */
+ public List<AppInfo> 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<AppInfo> apps) {
+ Collections.sort(apps, LauncherModel.getAppNameComparator());
+ mApps.clear();
+ mApps.addAll(apps);
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Adds new apps to the list.
+ */
+ public void addApps(List<AppInfo> 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<AppInfo> 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<AppInfo> 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<AppInfo> 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<AppInfo> 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 000000000..2280e99ef
--- /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<AlphabeticalAppsList.SectionInfo> 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 cabacec3c..cc31e20fa 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<AppInfo> apps;
-
- public AppsRow(int sId, String sc, List<AppInfo> ai) {
- sectionId = sId;
- sectionDescription = sc;
- apps = ai;
- }
-
- public AppsRow(int sId, List<AppInfo> ai) {
- sectionId = sId;
- apps = ai;
- }
-}
-
-/**
- * An interface to an algorithm that generates app rows.
- */
-interface AppRowAlgorithm {
- public List<AppsRow> computeAppRows(List<AppInfo> 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<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow) {
- List<AppsRow> rows = new ArrayList<>();
- LinkedHashMap<String, List<AppInfo>> sections = computeSectionedApps(sortedApps);
- int sectionId = 0;
- for (Map.Entry<String, List<AppInfo>> sectionEntry : sections.entrySet()) {
- String section = sectionEntry.getKey();
- List<AppInfo> apps = sectionEntry.getValue();
- int numRows = (int) Math.ceil((float) apps.size() / appsPerRow);
- for (int i = 0; i < numRows; i++) {
- List<AppInfo> 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<String, List<AppInfo>> computeSectionedApps(List<AppInfo> sortedApps) {
- LinkedHashMap<String, List<AppInfo>> sections = new LinkedHashMap<>();
- for (AppInfo info : sortedApps) {
- String section = getSection(info);
- List<AppInfo> 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<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow) {
- List<AppsRow> rows = new ArrayList<>();
- int sectionId = -1;
- String prevSection = "";
- for (AppInfo info : sortedApps) {
- List<AppInfo> 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<AppsRow> 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<AppsRow> 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<Object> sections = new ArrayList<>();
- int prevSectionId = -1;
- for (AppsRow row : mAppRows) {
- if (row.sectionId != prevSectionId) {
- sections.add(row.sectionDescription.toUpperCase());
- prevSectionId = row.sectionId;
- }
- }
- return sections.toArray();
- }
-
- @Override
- public int getPositionForSection(int sectionIndex) {
- for (int i = 0; i < mAppRows.size(); i++) {
- AppsRow row = mAppRows.get(i);
- if (row.sectionId == sectionIndex) {
- return i;
- }
- }
- return 0;
- }
-
- @Override
- public int getSectionForPosition(int position) {
- return mAppRows.get(position).sectionId;
- }
-}
-/**
- * The alphabetically sorted list of applications.
- */
-class AlphabeticalAppList {
-
- /**
- * Callbacks for when this list is modified.
- */
- public interface Callbacks {
- public void onAppsUpdated();
- }
-
- private List<AppInfo> mApps;
- private Callbacks mCb;
-
- public AlphabeticalAppList(Callbacks cb) {
- mCb = cb;
- }
-
- /**
- * Returns the list of applications.
- */
- public List<AppInfo> getApps() {
- return mApps;
- }
-
- /**
- * Sets the current set of apps.
- */
- public void setApps(List<AppInfo> apps) {
- Collections.sort(apps, LauncherModel.getAppNameComparator());
- mApps = apps;
- mCb.onAppsUpdated();
- }
-
- /**
- * Adds new apps to the list.
- */
- public void addApps(List<AppInfo> apps) {
- // We add it in place, in alphabetical order
- Comparator<AppInfo> appNameComparator = LauncherModel.getAppNameComparator();
- for (AppInfo info : apps) {
- // This call will return the exact index of where the item is if >= 0, or the index
- // where it should be inserted if < 0.
- int index = Collections.binarySearch(mApps, info, appNameComparator);
- if (index < 0) {
- mApps.add(-(index + 1), info);
- }
- }
- mCb.onAppsUpdated();
- }
-
- /**
- * Updates existing apps in the list
- */
- public void updateApps(List<AppInfo> apps) {
- Comparator<AppInfo> appNameComparator = LauncherModel.getAppNameComparator();
- for (AppInfo info : apps) {
- int index = mApps.indexOf(info);
- if (index != -1) {
- mApps.set(index, info);
- } else {
- index = Collections.binarySearch(mApps, info, appNameComparator);
- if (index < 0) {
- mApps.add(-(index + 1), info);
- }
- }
- }
- mCb.onAppsUpdated();
- }
-
- /**
- * Removes some apps from the list.
- */
- public void removeApps(List<AppInfo> apps) {
- for (AppInfo info : apps) {
- int removeIndex = findAppByComponent(mApps, info);
- if (removeIndex != -1) {
- mApps.remove(removeIndex);
- }
- }
- mCb.onAppsUpdated();
- }
-
- /**
- * Finds the index of an app given a target AppInfo.
- */
- private int findAppByComponent(List<AppInfo> 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;
- }
-
-}
/**
* The all apps list view container.
*/
public class AppsContainerView extends FrameLayout implements DragSource, View.OnTouchListener,
- View.OnLongClickListener, Insettable, AlphabeticalAppList.Callbacks {
+ View.OnLongClickListener, Insettable, TextWatcher, TextView.OnEditorActionListener,
+ LauncherTransitionable {
- static final int GRID_LAYOUT = 0;
- static final int LIST_LAYOUT = 1;
- static final int USE_LAYOUT = LIST_LAYOUT;
+ 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 Launcher mLauncher;
- private AppRowAlgorithm mAppRowsAlgorithm;
- private AppsListAdapter mAdapter;
- private AlphabeticalAppList mApps;
- private ListView mList;
- private int mAppsRowSize;
+ private AlphabeticalAppsList mApps;
+ private RecyclerView.Adapter mAdapter;
+ private RecyclerView.LayoutManager mLayoutManager;
+ private RecyclerView.ItemDecoration mItemDecoration;
+ private AppsContainerRecyclerView mAppsListView;
+ private EditText mSearchBar;
+ private int mNumAppsPerRow;
private Point mLastTouchDownPos = new Point();
private Rect mPadding = new Rect();
+ private int mContentMarginStart;
public AppsContainerView(Context context) {
this(context, null);
@@ -423,15 +78,22 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
mLauncher = (Launcher) context;
+ mApps = new AlphabeticalAppsList(context);
if (USE_LAYOUT == GRID_LAYOUT) {
- mAppRowsAlgorithm = new SectionedAppsAlgorithm();
- mAppsRowSize = grid.allAppsRowsSize;
+ mNumAppsPerRow = grid.appsViewNumCols;
+ AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this,
+ mLauncher, this);
+ mLayoutManager = adapter.getLayoutManager(context);
+ mItemDecoration = adapter.getItemDecoration();
+ mAdapter = adapter;
+ mContentMarginStart = adapter.getContentMarginStart();
} else if (USE_LAYOUT == LIST_LAYOUT) {
- mAppRowsAlgorithm = new ListedAppsAlgorithm();
- mAppsRowSize = 1;
+ mNumAppsPerRow = 1;
+ AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this);
+ mLayoutManager = adapter.getLayoutManager(context);
+ mAdapter = adapter;
}
- mAdapter = new AppsListAdapter(context, this, mLauncher, this);
- mApps = new AlphabeticalAppList(this);
+ mApps.setAdapter(mAdapter);
}
/**
@@ -466,7 +128,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
* Scrolls this list view to the top.
*/
public void scrollToTop() {
- mList.scrollTo(0, 0);
+ mAppsListView.scrollToPosition(0);
}
/**
@@ -480,23 +142,37 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
* Returns the reveal view used for the launcher transitions.
*/
public View getRevealView() {
- return findViewById(R.id.all_apps_transition_overlay);
- }
-
- @Override
- public void onAppsUpdated() {
- List<AppsRow> rows = mAppRowsAlgorithm.computeAppRows(mApps.getApps(), mAppsRowSize);
- mAdapter.setApps(rows, mAppsRowSize, mAppRowsAlgorithm);
+ return findViewById(R.id.apps_view_transition_overlay);
}
@Override
protected void onFinishInflate() {
- mList = (ListView) findViewById(R.id.apps_list);
- mList.setFastScrollEnabled(true);
- mList.setFastScrollAlwaysVisible(true);
- mList.setItemsCanFocus(true);
- mList.setAdapter(mAdapter);
- mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
+ boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+ LAYOUT_DIRECTION_RTL);
+ if (USE_LAYOUT == GRID_LAYOUT) {
+ ((AppsGridAdapter) mAdapter).setRtl(isRtl);
+ }
+ mSearchBar = (EditText) findViewById(R.id.app_search_box);
+ mSearchBar.addTextChangedListener(this);
+ mSearchBar.setOnEditorActionListener(this);
+ mAppsListView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
+ mAppsListView.setApps(mApps);
+ mAppsListView.setNumAppsPerRow(mNumAppsPerRow);
+ mAppsListView.setLayoutManager(mLayoutManager);
+ mAppsListView.setAdapter(mAdapter);
+ mAppsListView.setHasFixedSize(true);
+ if (isRtl) {
+ mAppsListView.setPadding(mAppsListView.getPaddingLeft(), mAppsListView.getPaddingTop(),
+ mAppsListView.getPaddingRight() + mContentMarginStart, mAppsListView.getPaddingBottom());
+ } else {
+ mAppsListView.setPadding(mAppsListView.getPaddingLeft() + mContentMarginStart, mAppsListView.getPaddingTop(),
+ mAppsListView.getPaddingRight(), mAppsListView.getPaddingBottom());
+ }
+ if (mItemDecoration != null) {
+ mAppsListView.addItemDecoration(mItemDecoration);
+ }
+ mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+ getPaddingBottom());
}
@Override
@@ -574,7 +250,8 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
}
@Override
- public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, boolean success) {
+ public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+ boolean success) {
if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
!(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
// Exit spring loaded mode if we have not successfully dropped or have not handled the
@@ -606,4 +283,83 @@ public class AppsContainerView extends FrameLayout implements DragSource, View.O
d.deferDragViewCleanupPostAnimation = false;
}
}
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ if (s.toString().isEmpty()) {
+ mApps.setFilter(null);
+ } else {
+ final AlphabeticIndexCompat indexer = mApps.getIndexer();
+ final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
+ mApps.setFilter(new AlphabeticalAppsList.Filter() {
+ @Override
+ public boolean retainApp(AppInfo info) {
+ String title = info.title.toString();
+ String sectionName = mApps.getSectionNameForApp(info);
+ return sectionName.toLowerCase().contains(filterText) ||
+ title.toLowerCase().replaceAll("\\s+", "").contains(filterText);
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
+ List<AppInfo> appsWithoutSections = mApps.getAppsWithoutSectionBreaks();
+ List<AppInfo> apps = mApps.getApps();
+ if (appsWithoutSections.size() == 1) {
+ mSearchBar.clearFocus();
+ mAppsListView.getChildAt(apps.indexOf(appsWithoutSections.get(0))).performClick();
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public View getContent() {
+ return null;
+ }
+
+ @Override
+ public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+ if (!toWorkspace) {
+ // Disable the focus so that the search bar doesn't get focus
+ mSearchBar.setFocusableInTouchMode(false);
+ }
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ if (toWorkspace) {
+ // Clear the search bar
+ mSearchBar.setText("");
+ } else {
+ mSearchBar.setFocusableInTouchMode(true);
+ }
+ }
}
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
new file mode 100644
index 000000000..6727e4f09
--- /dev/null
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -0,0 +1,206 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
+
+
+/**
+ * The grid view adapter of all the apps.
+ */
+class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
+
+ public static final String TAG = "AppsGridAdapter";
+
+ private static final int SECTION_BREAK_VIEW_TYPE = 0;
+ private static final int ICON_VIEW_TYPE = 1;
+
+ /**
+ * ViewHolder for each icon.
+ */
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public View mContent;
+ public boolean mIsSectionRow;
+
+ public ViewHolder(View v, boolean isSectionRow) {
+ super(v);
+ mContent = v;
+ mIsSectionRow = isSectionRow;
+ }
+ }
+
+ /**
+ * Helper class to size the grid items.
+ */
+ public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
+ @Override
+ public int getSpanSize(int position) {
+ AppInfo info = mApps.getApps().get(position);
+ if (info == AlphabeticalAppsList.SECTION_BREAK_INFO) {
+ return mAppsPerRow;
+ } else {
+ return 1;
+ }
+ }
+ }
+
+ /**
+ * Helper class to draw the section headers
+ */
+ public class GridItemDecoration extends RecyclerView.ItemDecoration {
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ AlphabeticIndexCompat indexer = mApps.getIndexer();
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ View child = parent.getChildAt(i);
+ ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
+ if (holder != null) {
+ GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
+ child.getLayoutParams();
+ if (!holder.mIsSectionRow && !lp.isItemRemoved()) {
+ if (mApps.getApps().get(holder.getPosition() - 1) ==
+ AlphabeticalAppsList.SECTION_BREAK_INFO) {
+ // Draw at the parent
+ AppInfo info = mApps.getApps().get(holder.getPosition());
+ String section = mApps.getSectionNameForApp(info);
+ mSectionTextPaint.getTextBounds(section, 0, section.length(),
+ mTmpBounds);
+ if (mIsRtl) {
+ c.drawText(section, parent.getWidth() - mStartMargin +
+ (mStartMargin - mTmpBounds.width()) / 2,
+ child.getTop() + (2 * child.getPaddingTop()) +
+ mTmpBounds.height(), mSectionTextPaint);
+ } else {
+ c.drawText(section, (mStartMargin - mTmpBounds.width()) / 2,
+ child.getTop() + (2 * child.getPaddingTop()) +
+ mTmpBounds.height(), mSectionTextPaint);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ // Do nothing
+ }
+ }
+
+ private LayoutInflater mLayoutInflater;
+ private AlphabeticalAppsList mApps;
+ private GridSpanSizer mGridSizer;
+ private GridItemDecoration mItemDecoration;
+ private View.OnTouchListener mTouchListener;
+ private View.OnClickListener mIconClickListener;
+ private View.OnLongClickListener mIconLongClickListener;
+ private int mAppsPerRow;
+ private boolean mIsRtl;
+
+ // Section drawing
+ private int mStartMargin;
+ private Paint mSectionTextPaint;
+ private Rect mTmpBounds = new Rect();
+
+
+ public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
+ View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
+ View.OnLongClickListener iconLongClickListener) {
+ Resources res = context.getResources();
+ mApps = apps;
+ mAppsPerRow = appsPerRow;
+ mGridSizer = new GridSpanSizer();
+ mItemDecoration = new GridItemDecoration();
+ mLayoutInflater = LayoutInflater.from(context);
+ mTouchListener = touchListener;
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+ mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+ mSectionTextPaint = new Paint();
+ mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.apps_view_section_text_size));
+ mSectionTextPaint.setColor(res.getColor(R.color.apps_view_section_text_color));
+ mSectionTextPaint.setAntiAlias(true);
+ }
+
+ /**
+ * Sets whether we are in RTL mode.
+ */
+ public void setRtl(boolean rtl) {
+ mIsRtl = rtl;
+ }
+
+ /**
+ * Returns the grid layout manager.
+ */
+ public GridLayoutManager getLayoutManager(Context context) {
+ GridLayoutManager layoutMgr = new GridLayoutManager(context, mAppsPerRow,
+ GridLayoutManager.VERTICAL, false);
+ layoutMgr.setSpanSizeLookup(mGridSizer);
+ return layoutMgr;
+ }
+
+ /**
+ * Returns the item decoration for the recycler view.
+ */
+ public RecyclerView.ItemDecoration getItemDecoration() {
+ return mItemDecoration;
+ }
+
+ /**
+ * Returns the left padding for the recycler view.
+ */
+ public int getContentMarginStart() {
+ return mStartMargin;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case SECTION_BREAK_VIEW_TYPE:
+ return new ViewHolder(new View(parent.getContext()), true);
+ case ICON_VIEW_TYPE:
+ BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
+ R.layout.apps_grid_row_icon_view, parent, false);
+ icon.setOnTouchListener(mTouchListener);
+ icon.setOnClickListener(mIconClickListener);
+ icon.setOnLongClickListener(mIconLongClickListener);
+ icon.setFocusable(true);
+ return new ViewHolder(icon, false);
+ default:
+ throw new RuntimeException("Unexpected view type");
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ AppInfo info = mApps.getApps().get(position);
+ if (info != AlphabeticalAppsList.SECTION_BREAK_INFO) {
+ BubbleTextView icon = (BubbleTextView) holder.mContent;
+ icon.applyFromApplicationInfo(info);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mApps.getApps().size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) {
+ return SECTION_BREAK_VIEW_TYPE;
+ }
+ return ICON_VIEW_TYPE;
+ }
+}
diff --git a/src/com/android/launcher3/AppsListAdapter.java b/src/com/android/launcher3/AppsListAdapter.java
new file mode 100644
index 000000000..8ac381e79
--- /dev/null
+++ b/src/com/android/launcher3/AppsListAdapter.java
@@ -0,0 +1,119 @@
+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;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
+
+/**
+ * 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 LayoutInflater mLayoutInflater;
+ private AlphabeticalAppsList mApps;
+ private View.OnTouchListener mTouchListener;
+ private View.OnClickListener mIconClickListener;
+ private View.OnLongClickListener mIconLongClickListener;
+
+ 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);
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ 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) {
+ AppInfo info = mApps.getApps().get(position);
+ if (info != AlphabeticalAppsList.SECTION_BREAK_INFO) {
+ ViewGroup content = (ViewGroup) holder.mContent;
+ String sectionDescription = mApps.getSectionNameForApp(info);
+
+ // Bind the section header
+ boolean showSectionHeader = true;
+ if (position > 0) {
+ AppInfo prevInfo = mApps.getApps().get(position - 1);
+ showSectionHeader = (prevInfo == AlphabeticalAppsList.SECTION_BREAK_INFO);
+ }
+ 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(info);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mApps.getApps().size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (mApps.getApps().get(position) == AlphabeticalAppsList.SECTION_BREAK_INFO) {
+ return SECTION_BREAK_VIEW_TYPE;
+ }
+ return ICON_VIEW_TYPE;
+ }
+}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5ea84aeb2..fabae5702 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -63,6 +63,7 @@ public class BubbleTextView extends TextView {
private float mSlop;
+ private final boolean mDeferShadowGenerationOnTouch;
private final boolean mCustomShadowsEnabled;
private final boolean mLayoutHorizontal;
private final int mIconSize;
@@ -96,6 +97,8 @@ public class BubbleTextView extends TextView {
grid.iconDrawablePaddingPx);
mTextSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_textSizeOverride,
grid.allAppsIconTextSizePx);
+ mDeferShadowGenerationOnTouch =
+ a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
a.recycle();
if (mCustomShadowsEnabled) {
@@ -218,7 +221,7 @@ public class BubbleTextView extends TextView {
// So that the pressed outline is visible immediately on setStayPressed(),
// we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
// to create it)
- if (mPressedBackground == null) {
+ if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) {
mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
}
@@ -247,6 +250,10 @@ public class BubbleTextView extends TextView {
mStayPressed = stayPressed;
if (!stayPressed) {
mPressedBackground = null;
+ } else {
+ if (mPressedBackground == null) {
+ mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
+ }
}
// Only show the shadow effect when persistent pressed state is set.
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b5bb55ca7..ddd300257 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -122,8 +122,7 @@ public class DeviceProfile {
int hotseatAllAppsRank;
int allAppsNumRows;
int allAppsNumCols;
- // TODO(winsonc): to be used with the grid layout
- int allAppsRowsSize;
+ int appsViewNumCols;
int searchBarSpaceWidthPx;
int searchBarSpaceHeightPx;
int pageIndicatorHeightPx;
@@ -365,7 +364,7 @@ public class DeviceProfile {
}
}
- private void updateIconSize(float scale, int drawablePadding, Resources resources,
+ private void updateIconSize(float scale, int drawablePadding, Resources res,
DisplayMetrics dm) {
iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
@@ -374,9 +373,9 @@ public class DeviceProfile {
// Search Bar
searchBarSpaceWidthPx = Math.min(widthPx,
- resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
searchBarSpaceHeightPx = getSearchBarTopOffset()
- + resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
// Calculate the actual text height
Paint textPaint = new Paint();
@@ -384,7 +383,7 @@ public class DeviceProfile {
FontMetrics fm = textPaint.getFontMetrics();
cellWidthPx = iconSizePx;
cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
- final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
+ final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
// Hotseat
@@ -402,11 +401,11 @@ public class DeviceProfile {
allAppsCellWidthPx = allAppsIconSizePx;
allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
int maxLongEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
+ res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
int maxShortEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
+ res.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
int minEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
+ res.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
@@ -417,10 +416,17 @@ public class DeviceProfile {
allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
(allAppsCellHeightPx + allAppsCellPaddingPx);
allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
- allAppsNumCols = (availableWidthPx) /
- (allAppsCellWidthPx + allAppsCellPaddingPx);
+ allAppsNumCols = (availableWidthPx) / (allAppsCellWidthPx + allAppsCellPaddingPx);
allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
}
+
+ int appsContainerViewPx = res.getDimensionPixelSize(R.dimen.apps_container_width);
+ int appsViewLeftMarginPx =
+ res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+ int availableAppsWidthPx = (appsContainerViewPx > 0) ? appsContainerViewPx :
+ availableWidthPx;
+ appsViewNumCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
+ (allAppsCellWidthPx + allAppsCellPaddingPx);
}
void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
new file mode 100644
index 000000000..602a84566
--- /dev/null
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -0,0 +1,131 @@
+package com.android.launcher3.compat;
+
+import android.content.Context;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+/**
+ * Fallback class to support Alphabetic indexing if not supported by the framework.
+ * TODO(winsonc): disable for non-english locales
+ */
+class BaseAlphabeticIndex {
+
+ private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
+ private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
+
+ public BaseAlphabeticIndex() {}
+
+ /**
+ * Sets the max number of the label buckets in this index.
+ */
+ public void setMaxLabelCount(int count) {
+ // Not currently supported
+ }
+
+ /**
+ * Returns the index of the bucket in which the given string should appear.
+ */
+ public int getBucketIndex(String s) {
+ if (s.isEmpty()) {
+ return UNKNOWN_BUCKET_INDEX;
+ }
+ int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
+ if (index != -1) {
+ return index;
+ }
+ return UNKNOWN_BUCKET_INDEX;
+ }
+
+ /**
+ * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+ */
+ public String getBucketLabel(int index) {
+ return BUCKETS.substring(index, index + 1);
+ }
+}
+
+/**
+ * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base alphabetic index.
+ */
+public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
+
+ private Object mAlphabeticIndex;
+ private Method mAddLabelsMethod;
+ private Method mSetMaxLabelCountMethod;
+ private Method mGetBucketIndexMethod;
+ private Method mGetBucketLabelMethod;
+ private boolean mHasValidAlphabeticIndex;
+
+ public AlphabeticIndexCompat(Context context) {
+ super();
+ try {
+ Locale curLocale = context.getResources().getConfiguration().locale;
+ Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
+ Constructor ctor = clazz.getConstructor(Locale.class);
+ mAddLabelsMethod = clazz.getDeclaredMethod("addLabels", Locale.class);
+ mSetMaxLabelCountMethod = clazz.getDeclaredMethod("setMaxLabelCount", int.class);
+ mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
+ mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
+ mAlphabeticIndex = ctor.newInstance(curLocale);
+ try {
+ // Ensure we always have some base English locale buckets
+ if (!curLocale.getLanguage().equals(new Locale("en").getLanguage())) {
+ mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ mHasValidAlphabeticIndex = true;
+ } catch (Exception e) {
+ mHasValidAlphabeticIndex = false;
+ }
+ }
+
+ /**
+ * Sets the max number of the label buckets in this index.
+ * (ICU 51 default is 99)
+ */
+ public void setMaxLabelCount(int count) {
+ if (mHasValidAlphabeticIndex) {
+ try {
+ mSetMaxLabelCountMethod.invoke(mAlphabeticIndex, count);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ super.setMaxLabelCount(count);
+ }
+ }
+
+ /**
+ * Returns the index of the bucket in which {@param s} should appear.
+ * Function is synchronized because underlying routine walks an iterator
+ * whose state is maintained inside the index object.
+ */
+ public int getBucketIndex(String s) {
+ if (mHasValidAlphabeticIndex) {
+ try {
+ return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return super.getBucketIndex(s);
+ }
+
+ /**
+ * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+ */
+ public String getBucketLabel(int index) {
+ if (mHasValidAlphabeticIndex) {
+ try {
+ return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return super.getBucketLabel(index);
+ }
+}