OSDN Git Service

Reimplement CM Settings Overview Panel in the new Launcher Part 1
authorYvonne Wong <ywong@cyngn.com>
Fri, 20 Nov 2015 18:49:59 +0000 (10:49 -0800)
committerYvonne Wong <ywong@cyngn.com>
Mon, 30 Nov 2015 19:50:09 +0000 (11:50 -0800)
- Adds vertical sliding panel and animations associated with opening and closing the panel
- Adds the views for settings and animation for the drawer arrow
- Enables hiding workspace icon labels, hiding drawer icon labels, scrolling wallpaper, and larger icons
- Changes how ragged grid custom icon sizes gets defined

Change-Id: I1a82215a09486b4770494e665e598efdbabd1d3e

62 files changed:
res/drawable-hdpi/ic_default_screen.png [new file with mode: 0644]
res/drawable-hdpi/ic_default_screen_pressed.png [new file with mode: 0644]
res/drawable-mdpi/ic_default_screen.png [new file with mode: 0644]
res/drawable-mdpi/ic_default_screen_pressed.png [new file with mode: 0644]
res/drawable-xhdpi/ic_default_screen.png [new file with mode: 0644]
res/drawable-xhdpi/ic_default_screen_pressed.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_default_screen.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_default_screen_pressed.png [new file with mode: 0644]
res/drawable/above_shadow.xml [new file with mode: 0644]
res/drawable/below_shadow.xml [new file with mode: 0644]
res/drawable/default_screen_button.xml [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00000.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00001.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00002.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00003.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00004.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00005.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00006.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00007.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00008.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00009.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00010.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00011.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00012.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00013.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00014.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00015.png [new file with mode: 0644]
res/drawable/launcheranimatedarrow_00016.png [new file with mode: 0644]
res/drawable/listitem_bg.xml [new file with mode: 0644]
res/drawable/listitem_text.xml [new file with mode: 0644]
res/drawable/transition_arrow.xml [new file with mode: 0644]
res/drawable/transition_arrow_reverse.xml [new file with mode: 0644]
res/layout/overview_panel.xml
res/layout/settings_pane_list_header.xml [new file with mode: 0644]
res/layout/settings_pane_list_item.xml [new file with mode: 0644]
res/values/attrs.xml
res/values/cm_strings.xml
res/values/colors.xml
res/values/dimens.xml
res/values/preferences_defaults.xml [new file with mode: 0644]
src/com/android/launcher3/BubbleTextView.java
src/com/android/launcher3/CellLayout.java
src/com/android/launcher3/DeviceProfile.java
src/com/android/launcher3/Folder.java
src/com/android/launcher3/FolderPagedView.java
src/com/android/launcher3/InvariantDeviceProfile.java
src/com/android/launcher3/Launcher.java
src/com/android/launcher3/LauncherAppState.java
src/com/android/launcher3/LauncherModel.java
src/com/android/launcher3/OverviewPanel.java [new file with mode: 0644]
src/com/android/launcher3/OverviewSettingsPanel.java [new file with mode: 0644]
src/com/android/launcher3/VerticalSlidingPanel.java [new file with mode: 0644]
src/com/android/launcher3/Workspace.java
src/com/android/launcher3/allapps/AllAppsContainerView.java
src/com/android/launcher3/allapps/AllAppsGridAdapter.java
src/com/android/launcher3/list/AutoScrollListView.java [new file with mode: 0644]
src/com/android/launcher3/list/CompositeCursorAdapter.java [new file with mode: 0644]
src/com/android/launcher3/list/PinnedHeaderListAdapter.java [new file with mode: 0644]
src/com/android/launcher3/list/PinnedHeaderListView.java [new file with mode: 0644]
src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java [new file with mode: 0644]
src/com/android/launcher3/settings/SettingsProvider.java [new file with mode: 0644]
tests/src/com/android/launcher3/InvariantDeviceProfileTest.java

diff --git a/res/drawable-hdpi/ic_default_screen.png b/res/drawable-hdpi/ic_default_screen.png
new file mode 100644 (file)
index 0000000..41dcf8f
Binary files /dev/null and b/res/drawable-hdpi/ic_default_screen.png differ
diff --git a/res/drawable-hdpi/ic_default_screen_pressed.png b/res/drawable-hdpi/ic_default_screen_pressed.png
new file mode 100644 (file)
index 0000000..7779058
Binary files /dev/null and b/res/drawable-hdpi/ic_default_screen_pressed.png differ
diff --git a/res/drawable-mdpi/ic_default_screen.png b/res/drawable-mdpi/ic_default_screen.png
new file mode 100644 (file)
index 0000000..8a2c1e1
Binary files /dev/null and b/res/drawable-mdpi/ic_default_screen.png differ
diff --git a/res/drawable-mdpi/ic_default_screen_pressed.png b/res/drawable-mdpi/ic_default_screen_pressed.png
new file mode 100644 (file)
index 0000000..20606ce
Binary files /dev/null and b/res/drawable-mdpi/ic_default_screen_pressed.png differ
diff --git a/res/drawable-xhdpi/ic_default_screen.png b/res/drawable-xhdpi/ic_default_screen.png
new file mode 100644 (file)
index 0000000..735332a
Binary files /dev/null and b/res/drawable-xhdpi/ic_default_screen.png differ
diff --git a/res/drawable-xhdpi/ic_default_screen_pressed.png b/res/drawable-xhdpi/ic_default_screen_pressed.png
new file mode 100644 (file)
index 0000000..11dede0
Binary files /dev/null and b/res/drawable-xhdpi/ic_default_screen_pressed.png differ
diff --git a/res/drawable-xxhdpi/ic_default_screen.png b/res/drawable-xxhdpi/ic_default_screen.png
new file mode 100644 (file)
index 0000000..253bd2d
Binary files /dev/null and b/res/drawable-xxhdpi/ic_default_screen.png differ
diff --git a/res/drawable-xxhdpi/ic_default_screen_pressed.png b/res/drawable-xxhdpi/ic_default_screen_pressed.png
new file mode 100644 (file)
index 0000000..5367b7a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_default_screen_pressed.png differ
diff --git a/res/drawable/above_shadow.xml b/res/drawable/above_shadow.xml
new file mode 100644 (file)
index 0000000..99db324
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+            android:startColor="#20000000"
+            android:endColor="@android:color/transparent"
+            android:angle="90" >
+    </gradient>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/below_shadow.xml b/res/drawable/below_shadow.xml
new file mode 100644 (file)
index 0000000..fc70073
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+            android:startColor="#20000000"
+            android:endColor="@android:color/transparent"
+            android:angle="270" >
+    </gradient>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/default_screen_button.xml b/res/drawable/default_screen_button.xml
new file mode 100644 (file)
index 0000000..ad66137
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/ic_default_screen_pressed" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_default_screen_pressed" />
+    <item android:state_activated="true" android:drawable="@drawable/ic_default_screen_pressed" />
+    <item android:drawable="@drawable/ic_default_screen" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/launcheranimatedarrow_00000.png b/res/drawable/launcheranimatedarrow_00000.png
new file mode 100644 (file)
index 0000000..2ed7fe9
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00000.png differ
diff --git a/res/drawable/launcheranimatedarrow_00001.png b/res/drawable/launcheranimatedarrow_00001.png
new file mode 100644 (file)
index 0000000..f3707e0
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00001.png differ
diff --git a/res/drawable/launcheranimatedarrow_00002.png b/res/drawable/launcheranimatedarrow_00002.png
new file mode 100644 (file)
index 0000000..3549389
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00002.png differ
diff --git a/res/drawable/launcheranimatedarrow_00003.png b/res/drawable/launcheranimatedarrow_00003.png
new file mode 100644 (file)
index 0000000..891e86c
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00003.png differ
diff --git a/res/drawable/launcheranimatedarrow_00004.png b/res/drawable/launcheranimatedarrow_00004.png
new file mode 100644 (file)
index 0000000..7cfb1ef
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00004.png differ
diff --git a/res/drawable/launcheranimatedarrow_00005.png b/res/drawable/launcheranimatedarrow_00005.png
new file mode 100644 (file)
index 0000000..121f4d5
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00005.png differ
diff --git a/res/drawable/launcheranimatedarrow_00006.png b/res/drawable/launcheranimatedarrow_00006.png
new file mode 100644 (file)
index 0000000..3a38e71
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00006.png differ
diff --git a/res/drawable/launcheranimatedarrow_00007.png b/res/drawable/launcheranimatedarrow_00007.png
new file mode 100644 (file)
index 0000000..e81a719
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00007.png differ
diff --git a/res/drawable/launcheranimatedarrow_00008.png b/res/drawable/launcheranimatedarrow_00008.png
new file mode 100644 (file)
index 0000000..bd6f409
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00008.png differ
diff --git a/res/drawable/launcheranimatedarrow_00009.png b/res/drawable/launcheranimatedarrow_00009.png
new file mode 100644 (file)
index 0000000..c7cb60d
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00009.png differ
diff --git a/res/drawable/launcheranimatedarrow_00010.png b/res/drawable/launcheranimatedarrow_00010.png
new file mode 100644 (file)
index 0000000..1bf30dc
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00010.png differ
diff --git a/res/drawable/launcheranimatedarrow_00011.png b/res/drawable/launcheranimatedarrow_00011.png
new file mode 100644 (file)
index 0000000..3cb5988
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00011.png differ
diff --git a/res/drawable/launcheranimatedarrow_00012.png b/res/drawable/launcheranimatedarrow_00012.png
new file mode 100644 (file)
index 0000000..58070de
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00012.png differ
diff --git a/res/drawable/launcheranimatedarrow_00013.png b/res/drawable/launcheranimatedarrow_00013.png
new file mode 100644 (file)
index 0000000..810d0a2
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00013.png differ
diff --git a/res/drawable/launcheranimatedarrow_00014.png b/res/drawable/launcheranimatedarrow_00014.png
new file mode 100644 (file)
index 0000000..3f9e518
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00014.png differ
diff --git a/res/drawable/launcheranimatedarrow_00015.png b/res/drawable/launcheranimatedarrow_00015.png
new file mode 100644 (file)
index 0000000..348bfbb
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00015.png differ
diff --git a/res/drawable/launcheranimatedarrow_00016.png b/res/drawable/launcheranimatedarrow_00016.png
new file mode 100644 (file)
index 0000000..5b0b286
Binary files /dev/null and b/res/drawable/launcheranimatedarrow_00016.png differ
diff --git a/res/drawable/listitem_bg.xml b/res/drawable/listitem_bg.xml
new file mode 100644 (file)
index 0000000..be1fedb
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:state_enabled="true"
+          android:state_pressed="true" android:drawable="@android:color/white" />
+    <item android:drawable="@color/slideup_panel_bg_color" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/listitem_text.xml b/res/drawable/listitem_text.xml
new file mode 100644 (file)
index 0000000..9637fd3
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:state_enabled="true"
+          android:state_pressed="true" android:color="@color/slideup_panel_bg_color" />
+    <item android:color="@android:color/white" />
+</selector>
diff --git a/res/drawable/transition_arrow.xml b/res/drawable/transition_arrow.xml
new file mode 100644 (file)
index 0000000..540db93
--- /dev/null
@@ -0,0 +1,21 @@
+<animation-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/transition_arrow" android:oneshot="true">
+    <item android:drawable="@drawable/launcheranimatedarrow_00000" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00001" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00002" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00003" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00004" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00005" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00006" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00007" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00008" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00009" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00010" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00011" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00012" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00013" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00014" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00015" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00016" android:duration="30" />
+</animation-list>
diff --git a/res/drawable/transition_arrow_reverse.xml b/res/drawable/transition_arrow_reverse.xml
new file mode 100644 (file)
index 0000000..855f08f
--- /dev/null
@@ -0,0 +1,21 @@
+<animation-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/transition_arrow_reverse" android:oneshot="true">
+    <item android:drawable="@drawable/launcheranimatedarrow_00016" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00015" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00014" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00013" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00012" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00011" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00010" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00009" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00008" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00007" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00006" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00005" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00004" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00003" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00002" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00001" android:duration="30" />
+    <item android:drawable="@drawable/launcheranimatedarrow_00000" android:duration="30" />
+</animation-list>
index 1f02dce..5698c86 100644 (file)
      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="wrap_content"
-    android:layout_gravity="center_horizontal|bottom"
-    android:gravity="top"
-    android:orientation="horizontal" >
-
-    <TextView
-        android:id="@+id/wallpaper_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/wallpaper_button"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:text="@string/wallpaper_button_text"
-        android:textAllCaps="true"
-        android:textColor="@android:color/white"
-        android:textSize="12sp" />
-
-    <TextView
-        android:id="@+id/widget_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/widget_button"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:text="@string/widget_button_text"
-        android:textAllCaps="true"
-        android:textColor="@android:color/white"
-        android:textSize="12sp" />
-
-    <TextView
-        android:id="@+id/settings_button"
-        android:layout_width="0dp"
+<com.android.launcher3.OverviewPanel xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:slidingpanel="http://schemas.android.com/apk/res/com.android.launcher3"
+     android:id="@+id/sliding_layout"
+     android:layout_width="match_parent"
+     android:layout_height="match_parent"
+     android:gravity="bottom"
+     slidingpanel:overlay="false"
+     slidingpanel:panelHeight="@dimen/sliding_panel_padding" >
+
+    <LinearLayout
+        android:id="@+id/default_home_screen_panel"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/setting_button"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:text="@string/settings_button_text"
-        android:textAllCaps="true"
-        android:textColor="@android:color/white"
-        android:textSize="12sp" />
-
-</LinearLayout>
\ No newline at end of file
+        android:layout_gravity="center_horizontal"
+        android:background="@color/slideup_panel_bg_color"
+        android:paddingTop="@dimen/overview_panel_top_padding" >
+
+        <ImageView
+            android:id="@+id/default_screen_button"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/app_icon_size"
+            android:layout_weight="1"
+            android:src="@drawable/default_screen_button"
+            android:scaleType="fitCenter"
+            android:gravity="top"
+            android:paddingLeft="@dimen/overview_panel_button_spacing"
+            android:paddingRight="@dimen/overview_panel_button_spacing"/>
+    </LinearLayout>
+
+    <FrameLayout android:orientation="vertical"
+         android:layout_width="match_parent"
+         android:background="@color/slideup_panel_bg_color"
+         android:layout_height="match_parent" >
+
+        <LinearLayout
+            android:id="@+id/settings_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_horizontal|bottom"
+            android:orientation="vertical" >
+
+            <LinearLayout
+                android:id="@+id/settings_pane_header"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:paddingTop="@dimen/overview_panel_top_padding" >
+
+                <ImageView
+                    android:id="@+id/settings_drag_arrow"
+                    android:layout_width="@dimen/overview_panel_button_spacing"
+                    android:layout_height="@dimen/overview_panel_button_spacing"
+                    android:layout_gravity="center_horizontal"
+                    android:adjustViewBounds="true"
+                    android:background="@drawable/launcheranimatedarrow_00000"/>
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                    android:paddingBottom="@dimen/overview_panel_bottom_padding"
+                    android:paddingTop="@dimen/overview_panel_list_padding" >
+
+                    <TextView
+                        android:id="@+id/wallpaper_button"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:drawablePadding="4dp"
+                        android:drawableTop="@drawable/wallpaper_button"
+                        android:fontFamily="sans-serif-condensed"
+                        android:gravity="center_horizontal"
+                        android:text="@string/wallpaper_button_text"
+                        android:textAllCaps="true"
+                        android:textColor="@android:color/white"
+                        android:textSize="12sp" />
+
+                    <TextView
+                        android:id="@+id/widget_button"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:drawablePadding="4dp"
+                        android:drawableTop="@drawable/widget_button"
+                        android:fontFamily="sans-serif-condensed"
+                        android:gravity="center_horizontal"
+                        android:text="@string/widget_button_text"
+                        android:textAllCaps="true"
+                        android:textColor="@android:color/white"
+                        android:textSize="12sp" />
+
+                    <TextView
+                        android:id="@+id/settings_button"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:drawablePadding="4dp"
+                        android:drawableTop="@drawable/setting_button"
+                        android:fontFamily="sans-serif-condensed"
+                        android:gravity="center_horizontal"
+                        android:text="@string/settings_button_text"
+                        android:textAllCaps="true"
+                        android:textColor="@android:color/white"
+                        android:textSize="12sp" />
+
+                </LinearLayout>
+
+            </LinearLayout>
+
+            <view
+                class="com.android.launcher3.list.PinnedHeaderListView"
+                android:id="@+id/settings_home_screen_listview"
+                android:layout_width="match_parent"
+                android:layout_height="0dip"
+                android:fastScrollEnabled="true"
+                android:layout_weight="1" />
+
+        </LinearLayout>
+
+        <LinearLayout
+                android:id="@+id/dark_panel"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@android:color/background_dark"
+                android:visibility="gone" />
+    </FrameLayout>
+</com.android.launcher3.OverviewPanel>
\ No newline at end of file
diff --git a/res/layout/settings_pane_list_header.xml b/res/layout/settings_pane_list_header.xml
new file mode 100644 (file)
index 0000000..2429b9b
--- /dev/null
@@ -0,0 +1,19 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal|bottom"
+    android:background="@color/slideup_panel_bg_color"
+    android:paddingLeft="@dimen/overview_panel_list_padding"
+    android:paddingRight="@dimen/overview_panel_list_padding"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/item_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:drawablePadding="4dp"
+        android:fontFamily="sans-serif-condensed"
+        android:gravity="left"
+        android:textSize="16sp"
+        android:textColor="@color/settings_header_text"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/settings_pane_list_item.xml b/res/layout/settings_pane_list_item.xml
new file mode 100644 (file)
index 0000000..58b2de8
--- /dev/null
@@ -0,0 +1,38 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal|bottom"
+    android:background="@drawable/listitem_bg"
+    android:paddingStart="@dimen/overview_panel_list_padding"
+    android:paddingEnd="@dimen/overview_panel_list_padding"
+    android:paddingBottom="@dimen/overview_panel_list_padding"
+    android:paddingTop="@dimen/overview_panel_list_padding"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/item_state"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:drawablePadding="4dp"
+        android:fontFamily="sans-serif-condensed"
+        android:textSize="20sp"
+        android:textAllCaps="true"
+        android:textColor="@drawable/listitem_text"
+        android:layout_alignParentEnd="true"
+        android:ellipsize="end"
+        android:maxLines="1"/>
+
+    <TextView
+        android:id="@+id/item_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:drawablePadding="4dp"
+        android:fontFamily="sans-serif-condensed"
+        android:textSize="20sp"
+        android:textColor="@drawable/listitem_text"
+        android:layout_toStartOf="@id/item_state"
+        android:layout_alignParentStart="true"
+        android:ellipsize="end"
+        android:maxLines="1"/>
+</RelativeLayout>
\ No newline at end of file
index d7f9ef4..787e5ca 100644 (file)
         <attr name="precision" format="float" />
         <attr name="sizeToFit" format="boolean" />
     </declare-styleable>
+
+    <declare-styleable name="VerticalSlidingPanel">
+        <attr name="panelHeight" format="dimension" />
+        <attr name="shadowHeight" format="dimension" />
+        <attr name="parallaxOffset" format="dimension" />
+        <attr name="fadeColor" format="color" />
+        <attr name="flingVelocity" format="integer" />
+        <attr name="dragView" format="reference" />
+        <attr name="overlay" format="boolean"/>
+    </declare-styleable>
 </resources>
index 4ec6014..e31fed1 100644 (file)
     <!-- Application name -->
     <string name="cm_application_name" translatable="false">Trebuchet</string>
 
+    <string name="home_screen_settings">HOME SCREEN SETTINGS</string>
+    <string name="drawer_settings">DRAWER SETTINGS</string>
+    <string name="app_settings">APP SETTINGS</string>
+
+    <!-- Settings states -->
+    <string name="setting_state_on">ON</string>
+    <string name="setting_state_off">OFF</string>
+    <string name="setting_state_disabled">DISABLED</string>
+
     <!-- Folder titles -->
     <string name="google_title" translatable="false">Google</string>
     <string name="play_folder_title">Play</string>
     <string name="partner_title" translatable="false"></string>
 
+    <!-- Wallpaper scroll effect -->
+    <string name="scrolling_wallpaper">Scroll wallpaper</string>
+
+    <!-- Dynamic Grid -->
+    <string name="grid_size_text">Grid size</string>
+    <string name="grid_size_comfortable">Comfortable</string>
+    <string name="grid_size_cozy">Cozy</string>
+    <string name="grid_size_condensed">Condensed</string>
+    <string name="grid_size_custom">Custom (<xliff:g id="rows">%1$d</xliff:g> \u00d7 <xliff:g id="columns">%2$d</xliff:g>)</string>
+    <string name="preferences_interface_homescreen_custom">Select custom size</string>
+
+    <!-- Home screen search bar -->
+    <string name="home_screen_search_text">Search bar</string>
+
+    <!-- Larger icons -->
+    <string name="larger_icons_text">Larger icons</string>
+
+    <!-- Icon labels -->
+    <string name="icon_labels">Icon labels</string>
+    <string name="icon_labels_show">Show</string>
+    <string name="icon_labels_hide">Hide</string>
+
+    <!-- Protected apps -->
+    <string name="protected_app_settings">Protected apps</string>
+
+    <!-- Search Manager doesn't exist -->
+    <string name="search_activity_not_found">A search activity could not be found!</string>
 </resources>
index b70e1f8..0cad1ce 100644 (file)
@@ -36,6 +36,7 @@
     <color name="quantum_panel_text_color_dark">#FFF</color>
     <color name="quantum_panel_bg_color">#FFF5F5F5</color>
     <color name="quantum_panel_bg_color_dark">#76000000</color>
+    <color name="slideup_panel_bg_color">#FF374248</color>
 
     <color name="outline_color">#FFFFFFFF</color>
 
@@ -53,6 +54,9 @@
     <color name="widgets_view_item_text_color">#C4C4C4</color>
     <color name="widgets_cell_color">#263238</color>
 
+    <!-- CM Settings -->
+    <color name="settings_header_text">#FF6cd2ea</color>
+
     <color name="app_scrubber_highlight_color">@android:color/white</color>
     <color name="app_scrubber_gray_color">@android:color/darker_gray</color>
     <color name="scrubber_background">#CC14191E</color>
index 799ea98..8c64ef0 100644 (file)
     <dimen name="blur_size_click_shadow">4dp</dimen>
     <dimen name="click_shadow_high_shift">2dp</dimen>
 
-    <dimen name="app_drawer_scrubber_padding">20dp</dimen>
+<!-- Pending widget -->
     <dimen name="pending_widget_min_padding">8dp</dimen>
     <dimen name="pending_widget_elevation">2dp</dimen>
 
     <integer name="folder_translate_y_dist">300</integer>
     <integer name="folder_icon_translate_y_dist">100</integer>
 
+<!-- Overview panel -->
+    <dimen name="sliding_panel_padding">175dp</dimen>
+    <dimen name="overview_panel_top_padding">20dp</dimen>
+    <dimen name="overview_panel_bottom_padding">50dp</dimen>
+    <dimen name="overview_panel_button_spacing">30dp</dimen>
+    <dimen name="overview_panel_list_padding">16dp</dimen>
+    <dimen name="overview_mode_page_offset">60dp</dimen>
+    <dimen name="overview_scaling_padding">50dp</dimen>
+
 </resources>
diff --git a/res/values/preferences_defaults.xml b/res/values/preferences_defaults.xml
new file mode 100644 (file)
index 0000000..9eb5ca5
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <bool name="preferences_interface_homescreen_search_default">true</bool>
+    <bool name="preferences_interface_homescreen_scrolling_wallpaper_scroll_default">true</bool>
+    <bool name="preferences_interface_homescreen_hide_icon_labels_default">false</bool>
+    <bool name="preferences_interface_drawer_hide_icon_labels_default">false</bool>
+    <bool name="preferences_interface_drawer_compact_default">false</bool>
+    <bool name="preferences_interface_general_icons_large_default">false</bool>
+</resources>
index 205c113..c15f07d 100644 (file)
@@ -43,6 +43,7 @@ import android.widget.TextView;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.settings.SettingsProvider;
 
 /**
  * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
@@ -86,7 +87,7 @@ public class BubbleTextView extends TextView
     private final boolean mDeferShadowGenerationOnTouch;
     private final boolean mCustomShadowsEnabled;
     private final boolean mLayoutHorizontal;
-    private int mIconSize;
+    private final int mIconSize;
     private int mTextColor;
 
     private boolean mStayPressed;
@@ -131,6 +132,13 @@ public class BubbleTextView extends TextView
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             defaultIconSize = grid.allAppsIconSizePx;
         }
+        boolean useCompactDrawer = SettingsProvider.getBoolean(context,
+                SettingsProvider.SETTINGS_UI_DRAWER_COMPACT,
+                R.bool.preferences_interface_drawer_compact_default);
+        if (!useCompactDrawer) {
+            defaultIconSize = getResources()
+                    .getDimensionPixelSize(R.dimen.all_apps_icon_size_ragged);
+        }
 
         mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
                 defaultIconSize);
@@ -435,10 +443,6 @@ public class BubbleTextView extends TextView
         if (mBackground != null) mBackground.setCallback(null);
     }
 
-    public void setIconSize(int iconSize) {
-        mIconSize = iconSize;
-    }
-
     @Override
     public void setTextColor(int color) {
         mTextColor = color;
index a99d791..daf2641 100644 (file)
@@ -615,9 +615,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
         final LayoutParams lp = params;
 
         // Hotseat icons - remove text
-        if (child instanceof BubbleTextView) {
-            BubbleTextView bubbleChild = (BubbleTextView) child;
-            bubbleChild.setTextVisibility(!mIsHotseat);
+        if (mIsHotseat && child instanceof BubbleTextView) {
+            ((BubbleTextView) child).setTextVisibility(false);
         }
 
         child.setScaleX(getChildrenScale());
index f77b348..84b6835 100644 (file)
@@ -468,7 +468,7 @@ public class DeviceProfile {
         }
 
         // Layout the Overview Mode
-        ViewGroup overviewMode = launcher.getOverviewPanel();
+        /*ViewGroup overviewMode = launcher.getOverviewPanel();
         if (overviewMode != null) {
             int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
@@ -505,7 +505,7 @@ public class DeviceProfile {
                     }
                 }
             }
-        }
+        }*/
     }
 
     private int getCurrentWidth() {
index 994d7d3..1e0827e 100644 (file)
@@ -63,6 +63,7 @@ import com.android.launcher3.FolderInfo.FolderListener;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
+import com.android.launcher3.settings.SettingsProvider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.UiThreadCircularReveal;
 
@@ -229,6 +230,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
         mFolderName.setInputType(mFolderName.getInputType() |
                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
 
+        boolean hideLabels = SettingsProvider.getBoolean(mLauncher,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+        if (hideLabels) {
+            mFolderName.setVisibility(View.GONE);
+        }
+
         mFooter = findViewById(R.id.folder_footer);
 
         // We find out how tall footer wants to be (it is set to wrap_content), so that
@@ -326,7 +334,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
         String newTitle = mFolderName.getText().toString();
-        mInfo.setTitle(newTitle);
+        if (!SettingsProvider.getBoolean(mLauncher,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_homescreen_hide_icon_labels_default)) {
+            mInfo.setTitle(newTitle);
+        }
         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
 
         if (commit) {
index cc9c573..a7940d5 100644 (file)
@@ -30,6 +30,7 @@ import android.view.animation.OvershootInterpolator;
 import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
 import com.android.launcher3.PageIndicator.PageMarkerResources;
 import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.settings.SettingsProvider;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -214,6 +215,11 @@ public class FolderPagedView extends PagedView {
         textView.setOnClickListener(mFolder);
         textView.setOnLongClickListener(mFolder);
         textView.setOnFocusChangeListener(mFocusIndicatorView);
+        if (SettingsProvider.getBoolean(mFolder.mLauncher,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_homescreen_hide_icon_labels_default)) {
+            textView.setTextVisibility(false);
+        }
         textView.setOnKeyListener(mKeyListener);
 
         textView.setLayoutParams(new CellLayout.LayoutParams(
index ae204c4..6d4d952 100644 (file)
@@ -24,6 +24,7 @@ import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.WindowManager;
 
+import com.android.launcher3.settings.SettingsProvider;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -132,7 +133,8 @@ public class InvariantDeviceProfile {
         minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
 
         ArrayList<InvariantDeviceProfile> closestProfiles =
-                findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles());
+                findClosestDeviceProfiles(minWidthDps, minHeightDps,
+                        getPredefinedDeviceProfiles(context));
         InvariantDeviceProfile interpolatedDeviceProfileOut =
                 invDistWeightedInterpolate(minWidthDps,  minHeightDps, closestProfiles);
 
@@ -169,35 +171,49 @@ public class InvariantDeviceProfile {
                 smallSide, largeSide, false /* isLandscape */);
     }
 
-    ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() {
+    ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
+        boolean useLargeIcons = SettingsProvider.getBoolean(context,
+                SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
+                R.bool.preferences_interface_general_icons_large_default);
         ArrayList<InvariantDeviceProfile> predefinedDeviceProfiles = new ArrayList<>();
         // width, height, #rows, #columns, #folder rows, #folder columns,
         // iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId.
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
-                255, 300,     2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
+                255, 300,     2, 3, 2, 3, 3, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 3,
+                (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
-                255, 400,     3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
+                255, 400,     3, 3, 3, 3, 3, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 3,
+                (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
-                275, 420,     3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
+                275, 420,     3, 4, 3, 4, 4, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 5,
+                (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
-                255, 450,     3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
+                255, 450,     3, 4, 3, 4, 4, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 5,
+                (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
-                296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
+                296, 491.33f, 4, 4, 4, 4, 4, (useLargeIcons? DEFAULT_ICON_SIZE_DP : 48), 13, 5,
+                (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
-                335, 567,     4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
+                335, 567,     4, 4, 4, 4, 4, (useLargeIcons ? 70 : DEFAULT_ICON_SIZE_DP), 13, 5,
+                (useLargeIcons? 68 : 56), R.xml.default_workspace_4x4));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
-                359, 567,     4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
+                359, 567,     4, 4, 4, 4, 4, (useLargeIcons ? 70 : DEFAULT_ICON_SIZE_DP), 13, 5,
+                (useLargeIcons? 68 : 56), R.xml.default_workspace_4x4));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone",
-                406, 694,     5, 5, 4, 4, 4, 64, 14.4f,  5, 56, R.xml.default_workspace_5x5));
+                406, 694,     5, 5, 4, 4, 4, (useLargeIcons ? 76 : 64), 14.4f,  5,
+                (useLargeIcons ? 68 : 56), R.xml.default_workspace_5x5));
         // The tablet profile is odd in that the landscape orientation
         // also includes the nav bar on the side
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7",
-                575, 904,     5, 6, 4, 5, 4, 72, 14.4f,  7, 60, R.xml.default_workspace_5x6));
+                575, 904,     5, 6, 4, 5, 4, (useLargeIcons ? 88 : 72), 14.4f,  7,
+                (useLargeIcons ? 72 : 60), R.xml.default_workspace_5x6));
         // Larger tablet profiles always have system bars on the top & bottom
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10",
-                727, 1207,    5, 6, 4, 5, 4, 76, 14.4f,  7, 64, R.xml.default_workspace_5x6));
+                727, 1207,    5, 6, 4, 5, 4, (useLargeIcons ? 92 : 76), 14.4f,  7,
+                (useLargeIcons ? 76 : 64), R.xml.default_workspace_5x6));
         predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet",
-                1527, 2527,   7, 7, 6, 6, 4, 100, 20,  7, 72, R.xml.default_workspace_4x4));
+                1527, 2527,   7, 7, 6, 6, 4, (useLargeIcons ? 124 : 100), 20,  7,
+                (useLargeIcons ? 84 : 72), R.xml.default_workspace_4x4));
         return predefinedDeviceProfiles;
     }
 
index 1976ca9..759ca04 100644 (file)
@@ -54,6 +54,7 @@ import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
+import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -105,6 +106,7 @@ import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.settings.SettingsProvider;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
@@ -261,6 +263,7 @@ public class Launcher extends Activity
 
     @Thunk Hotseat mHotseat;
     private ViewGroup mOverviewPanel;
+    OverviewSettingsPanel mOverviewSettingsPanel;
 
     private View mAllAppsButton;
     private View mWidgetsButton;
@@ -355,6 +358,9 @@ public class Launcher extends Activity
     // the press state and keep this reference to reset the press state when we return to launcher.
     private BubbleTextView mWaitingForResume;
 
+    // Preferences
+    private boolean mHideIconLabels;
+
     protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
             new HashMap<String, CustomAppWidget>();
 
@@ -430,17 +436,11 @@ public class Launcher extends Activity
         LauncherAppState.setApplicationContext(getApplicationContext());
         LauncherAppState app = LauncherAppState.getInstance();
 
-        // Load configuration-specific DeviceProfile
-        mDeviceProfile = getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE ?
-                        app.getInvariantDeviceProfile().landscapeProfile
-                            : app.getInvariantDeviceProfile().portraitProfile;
+        initializeDeviceProfile(app);
 
         mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
                 Context.MODE_PRIVATE);
         mIsSafeModeEnabled = getPackageManager().isSafeMode();
-        mModel = app.setLauncher(this);
-        mIconCache = app.getIconCache();
 
         mDragController = new DragController(this);
         mInflater = getLayoutInflater();
@@ -1391,7 +1391,11 @@ public class Launcher extends Activity
             mHotseat.setOnLongClickListener(this);
         }
 
+        // Setup the overview panel
         mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
+        mOverviewSettingsPanel = new OverviewSettingsPanel(this);
+        mOverviewSettingsPanel.initializeAdapter();
+
         mWidgetsButton = findViewById(R.id.widget_button);
         mWidgetsButton.setOnClickListener(new OnClickListener() {
             @Override
@@ -1429,6 +1433,36 @@ public class Launcher extends Activity
             settingsButton.setVisibility(View.GONE);
         }
 
+        View defaultScreenButton = findViewById(R.id.default_screen_button);
+        defaultScreenButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                if (!mWorkspace.isSwitchingState()) {
+                    onClickDefaultScreenButton(arg0);
+                }
+            }
+        });
+        defaultScreenButton.setOnTouchListener(getHapticFeedbackTouchListener());
+
+        final VerticalSlidingPanel verticalSlidingPanel = ((VerticalSlidingPanel) mOverviewPanel);
+        verticalSlidingPanel.setPanelSlideListener(new SettingsPanelSlideListener());
+        verticalSlidingPanel.setEnableDragViewTouchEvents(true);
+
+        View settingsPaneHeader = mOverviewPanel.findViewById(R.id.settings_pane_header);
+        if (settingsPaneHeader != null) {
+            verticalSlidingPanel.setDragView(settingsPaneHeader);
+            settingsPaneHeader.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (verticalSlidingPanel.isExpanded()) {
+                        verticalSlidingPanel.collapsePane();
+                    } else {
+                        verticalSlidingPanel.expandPane();
+                    }
+                }
+            });
+        }
+
         mOverviewPanel.setAlpha(0f);
 
         // Setup the workspace
@@ -1517,6 +1551,7 @@ public class Launcher extends Activity
         BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon,
                 parent, false);
         favorite.applyFromShortcutInfo(info, mIconCache);
+        favorite.setTextVisibility(!mHideIconLabels);
         favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
         favorite.setOnClickListener(this);
         favorite.setOnFocusChangeListener(mFocusHandler);
@@ -1660,6 +1695,38 @@ public class Launcher extends Activity
         }
     };
 
+    public void initializeDeviceProfile(LauncherAppState app) {
+        // Load configuration-specific DeviceProfile
+        mDeviceProfile = getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE ?
+                app.getInvariantDeviceProfile().landscapeProfile
+                : app.getInvariantDeviceProfile().portraitProfile;
+
+        mModel = app.setLauncher(this);
+        mIconCache = app.getIconCache();
+
+        mHideIconLabels = SettingsProvider.getBoolean(this,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+    }
+
+    public void reloadLauncher()
+    {
+        // Re-initialize device profile
+        LauncherAppState app = LauncherAppState.getInstance();
+        app.initInvariantDeviceProfile();
+        initializeDeviceProfile(app);
+
+        mDeviceProfile.layout(this);
+
+        // Reload
+        mModel.resetLoadedState(true, true);
+        mModel.startLoader(mWorkspace.getRestorePage(), LauncherModel.LOADER_FLAG_NONE);
+        mWorkspace.updateCustomContentVisibility();
+
+        mAppsView.reset();
+    }
+
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -2405,6 +2472,9 @@ public class Launcher extends Activity
         // Create the view
         FolderIcon newFolder =
             FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
+        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            newFolder.setTextVisible(!mHideIconLabels);
+        }
         mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
                 isWorkspaceLocked());
         // Force measure the new folder icon
@@ -2802,6 +2872,11 @@ public class Launcher extends Activity
         }
     }
 
+    protected void onClickDefaultScreenButton(View v) {
+        if (LOGD) Log.d(TAG, "onClickDefaultScreenButton");
+        // TODO
+    }
+
     public View.OnTouchListener getHapticFeedbackTouchListener() {
         if (mHapticFeedbackTouchListener == null) {
             mHapticFeedbackTouchListener = new View.OnTouchListener() {
@@ -3855,6 +3930,7 @@ public class Launcher extends Activity
                     view = FolderIcon.fromXml(R.layout.folder_icon, this,
                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                             (FolderInfo) item, mIconCache);
+                    ((FolderIcon) view).setTextVisible(!mHideIconLabels);
                     break;
                 default:
                     throw new RuntimeException("Invalid Item Type");
@@ -4781,6 +4857,39 @@ public class Launcher extends Activity
             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
         }
     }
+
+    class SettingsPanelSlideListener extends VerticalSlidingPanel.SimplePanelSlideListener {
+        ImageView mAnimatedArrow;
+
+        public SettingsPanelSlideListener() {
+            super();
+            mAnimatedArrow = (ImageView) mOverviewPanel.findViewById(R.id.settings_drag_arrow);
+        }
+
+        @Override
+        public void onPanelCollapsed(View panel) {
+            mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow_reverse);
+
+            AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground();
+            frameAnimation.start();
+
+            /*if (mLauncher.updateGridIfNeeded()) {
+                Workspace workspace = mLauncher.getWorkspace();
+                if (workspace.isInOverviewMode()) {
+                    workspace.setChildrenOutlineAlpha(1.0f);
+                    mLauncher.mSearchDropTargetBar.hideSearchBar(false);
+                }
+            }*/
+        }
+
+        @Override
+        public void onPanelExpanded(View panel) {
+            mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow);
+
+            AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground();
+            frameAnimation.start();
+        }
+    }
 }
 
 interface DebugIntents {
index d87ad67..d515f05 100644 (file)
@@ -174,6 +174,10 @@ public class LauncherAppState {
         return mInvariantDeviceProfile;
     }
 
+    public void initInvariantDeviceProfile() {
+        mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
+    }
+
     public static boolean isDogfoodBuild() {
         return getInstance().mBuildInfo.isDogfoodBuild();
     }
index b5922c6..e3170e9 100644 (file)
@@ -94,6 +94,7 @@ public class LauncherModel extends BroadcastReceiver
     public static final int LOADER_FLAG_NONE = 0;
     public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
     public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
+    public static final int LOADER_FLAG_RESIZE_GRID = 1 << 2;
 
     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
     private static final long INVALID_SCREEN_ID = -1L;
diff --git a/src/com/android/launcher3/OverviewPanel.java b/src/com/android/launcher3/OverviewPanel.java
new file mode 100644 (file)
index 0000000..2fdca63
--- /dev/null
@@ -0,0 +1,32 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+public class OverviewPanel extends VerticalSlidingPanel implements Insettable {
+    public OverviewPanel(Context context) {
+        super(context);
+    }
+
+    public OverviewPanel(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public OverviewPanel(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        LinearLayout layout = (LinearLayout)
+                findViewById(R.id.settings_container);
+        FrameLayout.LayoutParams lp =
+                (FrameLayout.LayoutParams) layout.getLayoutParams();
+        lp.bottomMargin = insets.bottom;
+        layout.setLayoutParams(lp);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/OverviewSettingsPanel.java b/src/com/android/launcher3/OverviewSettingsPanel.java
new file mode 100644 (file)
index 0000000..b4ba06a
--- /dev/null
@@ -0,0 +1,74 @@
+package com.android.launcher3;
+
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.widget.ListView;
+import com.android.launcher3.list.PinnedHeaderListView;
+import com.android.launcher3.list.SettingsPinnedHeaderAdapter;
+
+public class OverviewSettingsPanel {
+    public static final int HOME_SETTINGS_POSITION = 0;
+    public static final int DRAWER_SETTINGS_POSITION = 1;
+    public static final int APP_SETTINGS_POSITION = 2;
+
+    private Launcher mLauncher;
+    private SettingsPinnedHeaderAdapter mSettingsAdapter;
+    private PinnedHeaderListView mListView;
+
+    OverviewSettingsPanel(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    // One time initialization of the SettingsPinnedHeaderAdapter
+    public void initializeAdapter() {
+        // Settings pane Listview
+        mListView = (PinnedHeaderListView) mLauncher
+                .findViewById(R.id.settings_home_screen_listview);
+        mListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER);
+        Resources res = mLauncher.getResources();
+        String[] headers = new String[] {
+                res.getString(R.string.home_screen_settings),
+                res.getString(R.string.drawer_settings),
+                res.getString(R.string.app_settings)};
+
+        String[] values = new String[]{
+                res.getString(R.string.home_screen_search_text),
+                res.getString(R.string.icon_labels),
+                res.getString(R.string.scrolling_wallpaper),
+                res.getString(R.string.grid_size_text)};
+
+        String[] valuesDrawer = new String[] {
+                res.getString(R.string.icon_labels)};
+
+        String[] valuesApp = new String[] {
+                res.getString(R.string.larger_icons_text),
+                res.getString(R.string.protected_app_settings)};
+
+        mSettingsAdapter = new SettingsPinnedHeaderAdapter(mLauncher);
+        mSettingsAdapter.setHeaders(headers);
+        mSettingsAdapter.addPartition(false, true);
+        mSettingsAdapter.addPartition(false, true);
+        mSettingsAdapter.addPartition(false, true);
+        mSettingsAdapter.mPinnedHeaderCount = headers.length;
+
+        mSettingsAdapter.changeCursor(HOME_SETTINGS_POSITION, createCursor(headers[0], values));
+        mSettingsAdapter.changeCursor(DRAWER_SETTINGS_POSITION, createCursor(headers[1],
+                valuesDrawer));
+        mSettingsAdapter.changeCursor(APP_SETTINGS_POSITION, createCursor(headers[2], valuesApp));
+        mListView.setAdapter(mSettingsAdapter);
+    }
+
+    private Cursor createCursor(String header, String[] values) {
+        MatrixCursor cursor = new MatrixCursor(new String[]{"_id", header});
+        int count = values.length;
+        for (int i = 0; i < count; i++) {
+            cursor.addRow(new Object[]{i, values[i]});
+        }
+        return cursor;
+    }
+
+    public void notifyDataSetInvalidated() {
+        mSettingsAdapter.notifyDataSetInvalidated();
+    }
+}
diff --git a/src/com/android/launcher3/VerticalSlidingPanel.java b/src/com/android/launcher3/VerticalSlidingPanel.java
new file mode 100644 (file)
index 0000000..0ebbebc
--- /dev/null
@@ -0,0 +1,1317 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+
+public class VerticalSlidingPanel extends ViewGroup {
+    private static final String TAG = VerticalSlidingPanel.class.getSimpleName();
+
+    /**
+     * Default peeking out panel height
+     */
+    private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
+
+    /**
+     * Default height of the shadow above the peeking out panel
+     */
+    private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
+
+    /**
+     * If no fade color is given by default it will fade to 80% gray.
+     */
+    private static final int DEFAULT_FADE_COLOR = 0x99000000;
+
+    /**
+     * Default Minimum velocity that will be detected as a fling
+     */
+    private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
+    /**
+     * Default is set to false because that is how it was written
+     */
+    private static final boolean DEFAULT_OVERLAY_FLAG = false;
+    /**
+     * Default attributes for layout
+     */
+    private static final int[] DEFAULT_ATTRS = new int[] {
+            android.R.attr.gravity
+    };
+
+    /**
+     * Minimum velocity that will be detected as a fling
+     */
+    private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
+
+    /**
+     * The fade color used for the panel covered by the slider. 0 = no fading.
+     */
+    private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
+
+    /**
+     * Default parallax length of the main view
+     */
+    private static final int DEFAULT_PARALLAX_OFFSET = 0;
+
+    /**
+     * The paint used to dim the main layout when sliding
+     */
+    private final Paint mCoveredFadePaint = new Paint();
+
+    /**
+     * Drawable used to draw the shadow between panes.
+     */
+    private final Drawable mShadowDrawable;
+
+    /**
+     * The size of the overhang in pixels.
+     */
+    private int mPanelHeight = -1;
+
+    /**
+     * The size of the shadow in pixels.
+     */
+    private int mShadowHeight = -1;
+
+    /**
+     * Parallax offset
+     */
+    private int mParallaxOffset = -1;
+
+    /**
+     * True if the collapsed panel should be dragged up.
+     */
+    private boolean mIsSlidingUp;
+
+    /**
+     * True if a panel can slide with the current measurements
+     */
+    private boolean mCanSlide;
+
+    /**
+     * Panel overlays the windows instead of putting it underneath it.
+     */
+    private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
+
+    /**
+     * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
+     * used for dragging.
+     */
+    private View mDragView;
+
+    /**
+     * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
+     * used for dragging.
+     */
+    private int mDragViewResId = -1;
+
+    /**
+     * The child view that can slide, if any.
+     */
+    private View mSlideableView;
+
+    /**
+     * The main view
+     */
+    private View mMainView;
+
+    /**
+     * Current state of the slideable view.
+     */
+    private enum SlideState {
+        EXPANDED,
+        COLLAPSED,
+        ANCHORED
+    }
+    private SlideState mSlideState = SlideState.COLLAPSED;
+
+    /**
+     * How far the panel is offset from its expanded position.
+     * range [0, 1] where 0 = expanded, 1 = collapsed.
+     */
+    private float mSlideOffset;
+
+    /**
+     * How far in pixels the slideable panel may move.
+     */
+    private int mSlideRange;
+
+    /**
+     * A panel view is locked into internal scrolling or another condition that
+     * is preventing a drag.
+     */
+    private boolean mIsUnableToDrag;
+
+    /**
+     * Flag indicating that sliding feature is enabled\disabled
+     */
+    private boolean mIsSlidingEnabled;
+
+    /**
+     * Flag indicating if a drag view can have its own touch events.  If set
+     * to true, a drag view can scroll horizontally and have its own click listener.
+     *
+     * Default is set to false.
+     */
+    private boolean mIsUsingDragViewTouchEvents;
+
+    /**
+     * Threshold to tell if there was a scroll touch event.
+     */
+    private final int mScrollTouchSlop;
+
+    private float mInitialMotionX;
+    private float mInitialMotionY;
+    private float mAnchorPoint = 0.f;
+    private TranslateAnimation mAnimation;
+
+    private PanelSlideListener mPanelSlideListener;
+
+    private final ViewDragHelper mDragHelper;
+
+    /**
+     * Stores whether or not the pane was expanded the last time it was slideable.
+     * If expand/collapse operations are invoked this state is modified. Used by
+     * instance state save/restore.
+     */
+    private boolean mFirstLayout = true;
+
+    private final Rect mTmpRect = new Rect();
+
+    /**
+     * Listener for monitoring events about sliding panes.
+     */
+    public interface PanelSlideListener {
+        /**
+         * Called when a sliding pane's position changes.
+         * @param panel The child view that was moved
+         * @param slideOffset The new offset of this sliding pane within its range, from 0-1
+         */
+        public void onPanelSlide(View panel, float slideOffset);
+        /**
+         * Called when a sliding pane becomes slid completely collapsed. The pane may or may not
+         * be interactive at this point depending on if it's shown or hidden
+         * @param panel The child view that was slid to an collapsed position, revealing other panes
+         */
+        public void onPanelCollapsed(View panel);
+
+        /**
+         * Called when a sliding pane becomes slid completely expanded. The pane is now guaranteed
+         * to be interactive. It may now obscure other views in the layout.
+         * @param panel The child view that was slid to a expanded position
+         */
+        public void onPanelExpanded(View panel);
+
+        public void onPanelAnchored(View panel);
+    }
+
+    /**
+     * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
+     * of the listener methods you can extend this instead of implement the full interface.
+     */
+    public static class SimplePanelSlideListener implements PanelSlideListener {
+        @Override
+        public void onPanelSlide(View panel, float slideOffset) {
+        }
+        @Override
+        public void onPanelCollapsed(View panel) {
+        }
+        @Override
+        public void onPanelExpanded(View panel) {
+        }
+        @Override
+        public void onPanelAnchored(View panel) {
+        }
+    }
+
+    public VerticalSlidingPanel(Context context) {
+        this(context, null);
+    }
+
+    public VerticalSlidingPanel(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VerticalSlidingPanel(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        if (attrs != null) {
+            TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
+
+            if (defAttrs != null) {
+                int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);
+                if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {
+                    throw new IllegalArgumentException("gravity must be set to either top or bottom");
+                }
+                mIsSlidingUp = gravity == Gravity.BOTTOM;
+            }
+
+            defAttrs.recycle();
+
+            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.VerticalSlidingPanel);
+
+            if (ta != null) {
+                mPanelHeight = ta.getDimensionPixelSize(R.styleable.VerticalSlidingPanel_panelHeight, -1);
+                mShadowHeight = ta.getDimensionPixelSize(R.styleable.VerticalSlidingPanel_shadowHeight, -1);
+                mParallaxOffset = ta.getDimensionPixelSize(R.styleable.VerticalSlidingPanel_parallaxOffset, -1);
+
+                mMinFlingVelocity = ta.getInt(R.styleable.VerticalSlidingPanel_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);
+                mCoveredFadeColor = ta.getColor(R.styleable.VerticalSlidingPanel_fadeColor, DEFAULT_FADE_COLOR);
+
+                mDragViewResId = ta.getResourceId(R.styleable.VerticalSlidingPanel_dragView, -1);
+
+                mOverlayContent = ta.getBoolean(R.styleable.VerticalSlidingPanel_overlay,DEFAULT_OVERLAY_FLAG);
+            }
+
+            ta.recycle();
+        }
+
+        final float density = context.getResources().getDisplayMetrics().density;
+        if (mPanelHeight == -1) {
+            mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
+        }
+        if (mShadowHeight == -1) {
+            mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
+        }
+        if (mParallaxOffset == -1) {
+            mParallaxOffset = (int) (DEFAULT_PARALLAX_OFFSET * density);
+        }
+        // If the shadow height is zero, don't show the shadow
+        if (mShadowHeight > 0) {
+            if (mIsSlidingUp) {
+                mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow,
+                        context.getTheme());
+            } else {
+                mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow,
+                        context.getTheme());
+            }
+
+        } else {
+            mShadowDrawable = null;
+        }
+
+        setWillNotDraw(false);
+
+        mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
+        mDragHelper.setMinVelocity(mMinFlingVelocity * density);
+
+        mCanSlide = true;
+        mIsSlidingEnabled = true;
+
+        ViewConfiguration vc = ViewConfiguration.get(context);
+        mScrollTouchSlop = vc.getScaledTouchSlop();
+    }
+
+    /**
+     * Set the Drag View after the view is inflated
+     */
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        if (mDragViewResId != -1) {
+            mDragView = findViewById(mDragViewResId);
+        }
+    }
+
+    /**
+     * Set the color used to fade the pane covered by the sliding pane out when the pane
+     * will become fully covered in the expanded state.
+     *
+     * @param color An ARGB-packed color value
+     */
+    public void setCoveredFadeColor(int color) {
+        mCoveredFadeColor = color;
+        invalidate();
+    }
+
+    /**
+     * @return The ARGB-packed color value used to fade the fixed pane
+     */
+    public int getCoveredFadeColor() {
+        return mCoveredFadeColor;
+    }
+
+    /**
+     * Set sliding enabled flag
+     * @param enabled flag value
+     */
+    public void setSlidingEnabled(boolean enabled) {
+        mIsSlidingEnabled = enabled;
+    }
+
+    /**
+     * Set the collapsed panel height in pixels
+     *
+     * @param val A height in pixels
+     */
+    public void setPanelHeight(int val) {
+        mPanelHeight = val;
+        requestLayout();
+    }
+
+    /**
+     * @return The current collapsed panel height
+     */
+    public int getPanelHeight() {
+        return mPanelHeight;
+    }
+
+    /**
+     * @return The current parallax offset
+     */
+    public int getCurrentParallaxOffset() {
+        int offset = (int)(mParallaxOffset * (1 - mSlideOffset));
+        return mIsSlidingUp ? -offset : offset;
+    }
+
+    /**
+     * Sets the panel slide listener
+     * @param listener
+     */
+    public void setPanelSlideListener(PanelSlideListener listener) {
+        mPanelSlideListener = listener;
+    }
+
+    /**
+     * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
+     *
+     * @param dragView A view that will be used to drag the panel.
+     */
+    public void setDragView(View dragView) {
+        mDragView = dragView;
+    }
+
+    /**
+     * Set an anchor point where the panel can stop during sliding
+     *
+     * @param anchorPoint A value between 0 and 1, determining the position of the anchor point
+     *                    starting from the top of the layout.
+     */
+    public void setAnchorPoint(float anchorPoint) {
+        if (anchorPoint > 0 && anchorPoint < 1)
+            mAnchorPoint = anchorPoint;
+    }
+
+    /**
+     * Sets whether or not the panel overlays the content
+     * @param overlayed
+     */
+    public void setOverlayed(boolean overlayed) {
+        mOverlayContent = overlayed;
+    }
+
+    /**
+     * Check if the panel is set as an overlay.
+     */
+    public boolean isOverlayed() {
+        return mOverlayContent;
+    }
+
+    void dispatchOnPanelSlide(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
+        }
+    }
+
+    void dispatchOnPanelExpanded(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelExpanded(panel);
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    void dispatchOnPanelCollapsed(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelCollapsed(panel);
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    void dispatchOnPanelAnchored(View panel) {
+        if (mPanelSlideListener != null) {
+            mPanelSlideListener.onPanelAnchored(panel);
+        }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    void updateObscuredViewVisibility() {
+        if (getChildCount() == 0) {
+            return;
+        }
+        final int leftBound = getPaddingLeft();
+        final int rightBound = getWidth() - getPaddingRight();
+        final int topBound = getPaddingTop();
+        final int bottomBound = getHeight() - getPaddingBottom();
+        final int left;
+        final int right;
+        final int top;
+        final int bottom;
+        if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
+            left = mSlideableView.getLeft();
+            right = mSlideableView.getRight();
+            top = mSlideableView.getTop();
+            bottom = mSlideableView.getBottom();
+        } else {
+            left = right = top = bottom = 0;
+        }
+        View child = getChildAt(0);
+        final int clampedChildLeft = Math.max(leftBound, child.getLeft());
+        final int clampedChildTop = Math.max(topBound, child.getTop());
+        final int clampedChildRight = Math.min(rightBound, child.getRight());
+        final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
+        final int vis;
+        if (clampedChildLeft >= left && clampedChildTop >= top &&
+                clampedChildRight <= right && clampedChildBottom <= bottom) {
+            vis = INVISIBLE;
+        } else {
+            vis = VISIBLE;
+        }
+        child.setVisibility(vis);
+    }
+
+    void setAllChildrenVisible() {
+        for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == INVISIBLE) {
+                child.setVisibility(VISIBLE);
+            }
+        }
+    }
+
+    private static boolean hasOpaqueBackground(View v) {
+        final Drawable bg = v.getBackground();
+        if (bg != null) {
+            return bg.getOpacity() == PixelFormat.OPAQUE;
+        }
+        return false;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mFirstLayout = true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mFirstLayout = true;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthMode != MeasureSpec.EXACTLY) {
+            throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
+        } else if (heightMode != MeasureSpec.EXACTLY) {
+            throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
+        }
+
+        int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
+        int panelHeight = mPanelHeight;
+
+        final int childCount = getChildCount();
+
+        if (childCount > 2) {
+            Log.e(TAG, "onMeasure: More than two child views are not supported.");
+        } else if (getChildAt(1) != null && getChildAt(1).getVisibility() == GONE) {
+            panelHeight = 0;
+        }
+
+        // We'll find the current one below.
+        mSlideableView = null;
+        mCanSlide = false;
+
+        // First pass. Measure based on child LayoutParams width/height.
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            int height = layoutHeight;
+            if (child.getVisibility() == GONE) {
+                lp.dimWhenOffset = false;
+                continue;
+            }
+
+            if (i == 1) {
+                lp.slideable = true;
+                lp.dimWhenOffset = true;
+                mSlideableView = child;
+                mCanSlide = true;
+            } else {
+                if (!mOverlayContent) {
+                    height -= panelHeight;
+                }
+                mMainView = child;
+            }
+
+            int childWidthSpec;
+            if (lp.width == LayoutParams.WRAP_CONTENT) {
+                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+            } else if (lp.width == LayoutParams.MATCH_PARENT) {
+                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+            } else {
+                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+            }
+
+            int childHeightSpec;
+            if (lp.height == LayoutParams.WRAP_CONTENT) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+            } else if (lp.height == LayoutParams.MATCH_PARENT) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+            }
+
+            child.measure(childWidthSpec, childHeightSpec);
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int paddingLeft = getPaddingLeft();
+        final int paddingTop = getPaddingTop();
+        final int slidingTop = getSlidingTop();
+
+        final int childCount = getChildCount();
+
+        if (mFirstLayout) {
+            switch (mSlideState) {
+                case EXPANDED:
+                    mSlideOffset = mCanSlide ? 0.f : 1.f;
+                    break;
+                case ANCHORED:
+                    mSlideOffset = mCanSlide ? mAnchorPoint : 1.f;
+                    break;
+                default:
+                    mSlideOffset = 1.f;
+                    break;
+            }
+        }
+
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final int childHeight = child.getMeasuredHeight();
+
+            if (lp.slideable) {
+                mSlideRange = childHeight - mPanelHeight;
+            }
+
+            int childTop;
+            if (mIsSlidingUp) {
+                childTop = lp.slideable ? slidingTop + (int) (mSlideRange * mSlideOffset) : paddingTop;
+            } else {
+                childTop = lp.slideable ? slidingTop - (int) (mSlideRange * mSlideOffset) : paddingTop;
+                if (!lp.slideable && !mOverlayContent) {
+                    childTop += mPanelHeight;
+                }
+            }
+            final int childBottom = childTop + childHeight;
+            final int childLeft = paddingLeft;
+            final int childRight = childLeft + child.getMeasuredWidth();
+
+            child.layout(childLeft, childTop, childRight, childBottom);
+        }
+
+        if (mFirstLayout) {
+            updateObscuredViewVisibility();
+        }
+
+        mFirstLayout = false;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        // Recalculate sliding panes and their details
+        if (h != oldh) {
+            mFirstLayout = true;
+        }
+    }
+
+    /**
+     * Set if the drag view can have its own touch events.  If set
+     * to true, a drag view can scroll horizontally and have its own click listener.
+     *
+     * Default is set to false.
+     */
+    public void setEnableDragViewTouchEvents(boolean enabled) {
+        mIsUsingDragViewTouchEvents = enabled;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        final int action = MotionEventCompat.getActionMasked(ev);
+
+        if (mAnimation != null || !mCanSlide || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
+            mDragHelper.cancel();
+            return super.onInterceptTouchEvent(ev);
+        }
+
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+            mDragHelper.cancel();
+            return false;
+        }
+
+        final float x = ev.getX();
+        final float y = ev.getY();
+        boolean interceptTap = false;
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN: {
+                mIsUnableToDrag = false;
+                mInitialMotionX = x;
+                mInitialMotionY = y;
+                if (isDragViewUnder((int) x, (int) y) && !mIsUsingDragViewTouchEvents) {
+                    interceptTap = true;
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_MOVE: {
+                final float adx = Math.abs(x - mInitialMotionX);
+                final float ady = Math.abs(y - mInitialMotionY);
+                final int dragSlop = mDragHelper.getTouchSlop();
+
+                // Handle any horizontal scrolling on the drag view.
+                if (mIsUsingDragViewTouchEvents) {
+                    if (adx > mScrollTouchSlop && ady < mScrollTouchSlop) {
+                        return super.onInterceptTouchEvent(ev);
+                    }
+                    // Intercept the touch if the drag view has any vertical scroll.
+                    // onTouchEvent will determine if the view should drag vertically.
+                    else if (ady > mScrollTouchSlop) {
+                        interceptTap = isDragViewUnder((int) x, (int) y);
+                    }
+                }
+
+                if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) x, (int) y)) {
+                    mDragHelper.cancel();
+                    mIsUnableToDrag = true;
+                    return false;
+                }
+                break;
+            }
+        }
+
+        final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
+
+        return interceptForDrag;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (!mCanSlide || !mIsSlidingEnabled || mAnimation != null) {
+            return super.onTouchEvent(ev);
+        }
+
+        mDragHelper.processTouchEvent(ev);
+
+        final int action = ev.getAction();
+        boolean wantTouchEvents = true;
+
+        switch (action & MotionEventCompat.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                final float x = ev.getX();
+                final float y = ev.getY();
+
+                //Fix to allow both SettingPanel Drag and Workspace Drag
+                if (mSlideState == SlideState.COLLAPSED) {
+                    if (y < mSlideableView.getTop()) {
+                        return false;
+                    }
+                }
+
+                mInitialMotionX = x;
+                mInitialMotionY = y;
+                break;
+            }
+
+            case MotionEvent.ACTION_UP: {
+                final float x = ev.getX();
+                final float y = ev.getY();
+                final float dx = x - mInitialMotionX;
+                final float dy = y - mInitialMotionY;
+                final int slop = mDragHelper.getTouchSlop();
+                View dragView = mDragView != null ? mDragView : mSlideableView;
+                if (dx * dx + dy * dy < slop * slop &&
+                        isDragViewUnder((int) x, (int) y)) {
+                    dragView.playSoundEffect(SoundEffectConstants.CLICK);
+                    if (!isExpanded() && !isAnchored()) {
+                        expandPane(mAnchorPoint);
+                    } else {
+                        collapsePane();
+                    }
+                    break;
+                }
+                break;
+            }
+        }
+
+        return wantTouchEvents;
+    }
+
+    private boolean isDragViewUnder(int x, int y) {
+        View dragView = mDragView != null ? mDragView : mSlideableView;
+        if (dragView == null) return false;
+        int[] viewLocation = new int[2];
+        dragView.getLocationOnScreen(viewLocation);
+        int[] parentLocation = new int[2];
+        this.getLocationOnScreen(parentLocation);
+        int screenX = parentLocation[0] + x;
+        int screenY = parentLocation[1] + y;
+        return screenX >= viewLocation[0] && screenX < viewLocation[0] + dragView.getWidth() &&
+                screenY >= viewLocation[1] && screenY < viewLocation[1] + dragView.getHeight();
+    }
+
+    private boolean expandPane(View pane, int initialVelocity, float mSlideOffset) {
+        if (mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean collapsePane(View pane, int initialVelocity) {
+        if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
+            return true;
+        }
+        return false;
+    }
+
+    private int getSlidingTop() {
+        if (mSlideableView != null) {
+            return mIsSlidingUp
+                    ? getMeasuredHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight()
+                    : getPaddingTop();
+        }
+
+        return getMeasuredHeight() - getPaddingBottom();
+    }
+
+    /**
+     * Collapse the sliding pane if it is currently slideable. If first layout
+     * has already completed this will animate.
+     *
+     * @return true if the pane was slideable and is now collapsed/in the process of collapsing
+     */
+    public boolean collapsePane() {
+        return collapsePane(mSlideableView, 0);
+    }
+
+    /**
+     * Expand the sliding pane if it is currently slideable. If first layout
+     * has already completed this will animate.
+     *
+     * @return true if the pane was slideable and is now expanded/in the process of expading
+     */
+    public boolean expandPane() {
+        return expandPane(0);
+    }
+
+    /**
+     * Partially expand the sliding pane up to a specific offset
+     *
+     * @param mSlideOffset Value between 0 and 1, where 0 is completely expanded.
+     * @return true if the pane was slideable and is now expanded/in the process of expading
+     */
+    public boolean expandPane(float mSlideOffset) {
+        if (!isPaneVisible()) {
+            showPane();
+        }
+        return expandPane(mSlideableView, 0, mSlideOffset);
+    }
+
+    /**
+     * Check if the layout is completely expanded.
+     *
+     * @return true if sliding panels are completely expanded
+     */
+    public boolean isExpanded() {
+        return mSlideState == SlideState.EXPANDED;
+    }
+
+    /**
+     * Check if the layout is anchored in an intermediate point.
+     *
+     * @return true if sliding panels are anchored
+     */
+    public boolean isAnchored() {
+        return mSlideState == SlideState.ANCHORED;
+    }
+
+    /**
+     * Check if the content in this layout cannot fully fit side by side and therefore
+     * the content pane can be slid back and forth.
+     *
+     * @return true if content in this layout can be expanded
+     */
+    public boolean isSlideable() {
+        return mCanSlide;
+    }
+
+    public boolean isPaneVisible() {
+        if (getChildCount() < 2) {
+            return false;
+        }
+        View slidingPane = getChildAt(1);
+        return slidingPane.getVisibility() == View.VISIBLE;
+    }
+
+    public void showPane() {
+        if (getChildCount() < 2) {
+            return;
+        }
+        final View slidingPane = getChildAt(1);
+        mAnimation = new TranslateAnimation(0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight(), 0);
+        mAnimation.setDuration(400);
+        mAnimation.setAnimationListener(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+                slidingPane.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                requestLayout();
+                mAnimation = null;
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+        });
+        slidingPane.startAnimation(mAnimation);
+    }
+
+    public void hidePane() {
+        if (mSlideableView == null) {
+            return;
+        }
+        mAnimation = new TranslateAnimation(0, 0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight());
+        mAnimation.setDuration(500);
+        mAnimation.setAnimationListener(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                mSlideableView.setVisibility(View.GONE);
+                requestLayout();
+                mAnimation = null;
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+
+            }
+        });
+        mSlideableView.startAnimation(mAnimation);
+    }
+
+    private void onPanelDragged(int newTop) {
+        final int topBound = getSlidingTop();
+        mSlideOffset = mIsSlidingUp
+                ? (float) (newTop - topBound) / mSlideRange
+                : (float) (topBound - newTop) / mSlideRange;
+        dispatchOnPanelSlide(mSlideableView);
+
+        if (mParallaxOffset > 0) {
+            int mainViewOffset = getCurrentParallaxOffset();
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+                mMainView.setTranslationY(mainViewOffset);
+            } else {
+                mMainView.animate().translationY(mainViewOffset);
+            }
+        }
+    }
+
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        boolean result;
+        final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
+
+        boolean drawScrim = false;
+
+        if (mCanSlide && !lp.slideable && mSlideableView != null) {
+            // Clip against the slider; no sense drawing what will immediately be covered,
+            // Unless the panel is set to overlay content
+            if (!mOverlayContent) {
+                canvas.getClipBounds(mTmpRect);
+                if (mIsSlidingUp) {
+                    mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
+                } else {
+                    mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
+                }
+                canvas.clipRect(mTmpRect);
+            }
+            if (mSlideOffset < 1) {
+                drawScrim = true;
+            }
+        }
+
+        result = super.drawChild(canvas, child, drawingTime);
+        canvas.restoreToCount(save);
+
+        if (drawScrim) {
+            final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
+            final int imag = (int) (baseAlpha * (1 - mSlideOffset));
+            final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
+            mCoveredFadePaint.setColor(color);
+            canvas.drawRect(mTmpRect, mCoveredFadePaint);
+        }
+
+        return result;
+    }
+
+    /**
+     * Smoothly animate mDraggingPane to the target X position within its range.
+     *
+     * @param slideOffset position to animate to
+     * @param velocity initial velocity in case of fling, or 0.
+     */
+    boolean smoothSlideTo(float slideOffset, int velocity) {
+        if (!mCanSlide) {
+            // Nothing to do.
+            return false;
+        }
+
+        final int topBound = getSlidingTop();
+        int y = mIsSlidingUp
+                ? (int) (topBound + slideOffset * mSlideRange)
+                : (int) (topBound - slideOffset * mSlideRange);
+
+        if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
+            setAllChildrenVisible();
+            ViewCompat.postInvalidateOnAnimation(this);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mDragHelper.continueSettling(true)) {
+            if (!mCanSlide) {
+                mDragHelper.abort();
+                return;
+            }
+
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+
+        if (mSlideableView == null) {
+            // No need to draw a shadow if we don't have one.
+            return;
+        }
+
+        final int right = mSlideableView.getRight();
+        final int top;
+        final int bottom;
+        if (mIsSlidingUp) {
+            top = mSlideableView.getTop() - mShadowHeight;
+            bottom = mSlideableView.getTop();
+        } else {
+            top = mSlideableView.getBottom();
+            bottom = mSlideableView.getBottom() + mShadowHeight;
+        }
+        final int left = mSlideableView.getLeft();
+
+        if (mShadowDrawable != null) {
+            mShadowDrawable.setBounds(left, top, right, bottom);
+            mShadowDrawable.draw(c);
+        }
+    }
+
+    /**
+     * Tests scrollability within child views of v given a delta of dx.
+     *
+     * @param v View to test for horizontal scrollability
+     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
+     *               or just its children (false).
+     * @param dx Delta scrolled in pixels
+     * @param x X coordinate of the active touch point
+     * @param y Y coordinate of the active touch point
+     * @return true if child views of v can be scrolled by delta of dx.
+     */
+    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+        if (v instanceof ViewGroup) {
+            final ViewGroup group = (ViewGroup) v;
+            final int scrollX = v.getScrollX();
+            final int scrollY = v.getScrollY();
+            final int count = group.getChildCount();
+            // Count backwards - let topmost views consume scroll distance first.
+            for (int i = count - 1; i >= 0; i--) {
+                final View child = group.getChildAt(i);
+                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
+                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
+                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
+                                y + scrollY - child.getTop())) {
+                    return true;
+                }
+            }
+        }
+        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
+    }
+
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams();
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof MarginLayoutParams
+                ? new LayoutParams((MarginLayoutParams) p)
+                : new LayoutParams(p);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams && super.checkLayoutParams(p);
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+
+        SavedState ss = new SavedState(superState);
+        ss.mSlideState = mSlideState;
+
+        return ss;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        mSlideState = ss.mSlideState;
+    }
+
+    private class DragHelperCallback extends ViewDragHelper.Callback {
+
+        @Override
+        public boolean tryCaptureView(View child, int pointerId) {
+            if (mIsUnableToDrag) {
+                return false;
+            }
+
+            return ((LayoutParams) child.getLayoutParams()).slideable;
+        }
+
+        @Override
+        public void onViewDragStateChanged(int state) {
+            int anchoredTop = (int)(mAnchorPoint*mSlideRange);
+
+            if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
+                if (mSlideOffset == 0) {
+                    if (mSlideState != SlideState.EXPANDED) {
+                        updateObscuredViewVisibility();
+                        dispatchOnPanelExpanded(mSlideableView);
+                        mSlideState = SlideState.EXPANDED;
+                    }
+                } else if (mSlideOffset == (float)anchoredTop/(float)mSlideRange) {
+                    if (mSlideState != SlideState.ANCHORED) {
+                        updateObscuredViewVisibility();
+                        dispatchOnPanelAnchored(mSlideableView);
+                        mSlideState = SlideState.ANCHORED;
+                    }
+                } else if (mSlideState != SlideState.COLLAPSED) {
+                    dispatchOnPanelCollapsed(mSlideableView);
+                    mSlideState = SlideState.COLLAPSED;
+                }
+            }
+        }
+
+        @Override
+        public void onViewCaptured(View capturedChild, int activePointerId) {
+            // Make all child views visible in preparation for sliding things around
+            setAllChildrenVisible();
+        }
+
+        @Override
+        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+            onPanelDragged(top);
+            invalidate();
+        }
+
+        @Override
+        public void onViewReleased(View releasedChild, float xvel, float yvel) {
+            int top = mIsSlidingUp
+                    ? getSlidingTop()
+                    : getSlidingTop() - mSlideRange;
+
+            if (mAnchorPoint != 0) {
+                int anchoredTop;
+                float anchorOffset;
+                if (mIsSlidingUp) {
+                    anchoredTop = (int)(mAnchorPoint*mSlideRange);
+                    anchorOffset = (float)anchoredTop/(float)mSlideRange;
+                } else {
+                    anchoredTop = mPanelHeight - (int)(mAnchorPoint*mSlideRange);
+                    anchorOffset = (float)(mPanelHeight - anchoredTop)/(float)mSlideRange;
+                }
+
+                if (yvel > 0 || (yvel == 0 && mSlideOffset >= (1f+anchorOffset)/2)) {
+                    top += mSlideRange;
+                } else if (yvel == 0 && mSlideOffset < (1f+anchorOffset)/2
+                        && mSlideOffset >= anchorOffset/2) {
+                    top += mSlideRange * mAnchorPoint;
+                }
+
+            } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
+                top += mSlideRange;
+            }
+
+            mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
+            invalidate();
+        }
+
+        @Override
+        public int getViewVerticalDragRange(View child) {
+            return mSlideRange;
+        }
+
+        @Override
+        public int clampViewPositionVertical(View child, int top, int dy) {
+            final int topBound;
+            final int bottomBound;
+            if (mIsSlidingUp) {
+                topBound = getSlidingTop();
+                bottomBound = topBound + mSlideRange;
+            } else {
+                bottomBound = getPaddingTop();
+                topBound = bottomBound - mSlideRange;
+            }
+
+            return Math.min(Math.max(top, topBound), bottomBound);
+        }
+    }
+
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        private static final int[] ATTRS = new int[] {
+                android.R.attr.layout_weight
+        };
+
+        /**
+         * True if this pane is the slideable pane in the layout.
+         */
+        boolean slideable;
+
+        /**
+         * True if this view should be drawn dimmed
+         * when it's been offset from its default position.
+         */
+        boolean dimWhenOffset;
+
+        Paint dimPaint;
+
+        public LayoutParams() {
+            super(MATCH_PARENT, MATCH_PARENT);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
+            a.recycle();
+        }
+
+    }
+
+    static class SavedState extends BaseSavedState {
+        SlideState mSlideState;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            try {
+                mSlideState = Enum.valueOf(SlideState.class, in.readString());
+            } catch (IllegalArgumentException e) {
+                mSlideState = SlideState.COLLAPSED;
+            }
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeString(mSlideState.toString());
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+}
index 856e3b8..59c870a 100644 (file)
@@ -68,6 +68,7 @@ import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.settings.SettingsProvider;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperUtils;
@@ -217,6 +218,7 @@ public class Workspace extends PagedView
     private boolean mWorkspaceFadeInAdjacentScreens;
 
     WallpaperOffsetInterpolator mWallpaperOffset;
+    private boolean mScrollWallpaper;
     @Thunk boolean mWallpaperIsLiveWallpaper;
     @Thunk int mNumPagesForWallpaperParallax;
     @Thunk float mLastSetWallpaperOffsetSteps = 0;
@@ -290,6 +292,8 @@ public class Workspace extends PagedView
         }
     };
 
+    private boolean mHideIconLabels;
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -312,6 +316,10 @@ public class Workspace extends PagedView
 
         mOutlineHelper = HolographicOutlineHelper.obtain(context);
 
+        mHideIconLabels = SettingsProvider.getBoolean(context,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+
         mLauncher = (Launcher) context;
         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
         final Resources res = getResources();
@@ -964,6 +972,8 @@ public class Workspace extends PagedView
      */
     void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
             boolean insert, boolean computeXYFromRank) {
+        reloadSettings();
+
         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
             if (getScreenWithId(screenId) == null) {
                 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
@@ -994,9 +1004,10 @@ public class Workspace extends PagedView
                 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
             }
         } else {
-            // Show folder title if not in the hotseat
             if (child instanceof FolderIcon) {
-                ((FolderIcon) child).setTextVisible(true);
+                ((FolderIcon) child).setTextVisible(!mHideIconLabels);
+            } else if (child instanceof BubbleTextView) {
+                ((BubbleTextView) child).setTextVisibility(!mHideIconLabels);
             }
             layout = getScreenWithId(screenId);
             child.setOnKeyListener(new IconKeyEventListener());
@@ -1533,7 +1544,8 @@ public class Workspace extends PagedView
     @Override
     public void computeScroll() {
         super.computeScroll();
-        mWallpaperOffset.syncWithScroll();
+
+        if (mScrollWallpaper) mWallpaperOffset.syncWithScroll();
     }
 
     @Override
@@ -1702,6 +1714,8 @@ public class Workspace extends PagedView
             }
         }
 
+        setScrollingWallpaper();
+
         // Update wallpaper dimensions if they were changed since last onResume
         // (we also always set the wallpaper dimensions in the constructor)
         if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
@@ -1715,7 +1729,8 @@ public class Workspace extends PagedView
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()
+                && mScrollWallpaper) {
             mWallpaperOffset.syncWithScroll();
             mWallpaperOffset.jumpToFinal();
         }
@@ -3523,6 +3538,7 @@ public class Workspace extends PagedView
             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
                         (FolderInfo) info, mIconCache);
+                ((FolderIcon) view).setTextVisible(!mHideIconLabels);
                 break;
             default:
                 throw new IllegalStateException("Unknown item type: " + info.itemType);
@@ -4482,6 +4498,29 @@ public class Workspace extends PagedView
         sourceData.putInt(Stats.SOURCE_EXTRA_CONTAINER_PAGE, getCurrentPage());
     }
 
+    private void reloadSettings() {
+        mHideIconLabels = SettingsProvider.getBoolean(mLauncher,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+
+        setScrollingWallpaper();
+    }
+
+    /**
+     * Gets the preference for whether to apply scrolling wallpaper effect or not and applies the
+     * preference.
+     */
+    private void setScrollingWallpaper() {
+        mScrollWallpaper = SettingsProvider.getBoolean(mLauncher,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
+                R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default);
+        if (!mScrollWallpaper) {
+            if (mWindowToken != null) mWallpaperManager.setWallpaperOffsets(mWindowToken, 0f, 0.5f);
+        } else {
+            mWallpaperOffset.syncWithScroll();
+        }
+    }
+
     /**
      * Used as a workaround to ensure that the AppWidgetService receives the
      * PACKAGE_ADDED broadcast before updating widgets.
index bff7752..76f47c5 100644 (file)
@@ -229,6 +229,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
         updateScrubber();
     }
 
+    public void reset() {
+        List<AppInfo> apps = mApps.getApps();
+        updateApps(apps);
+    }
+
     /**
      * Updates existing apps in the list
      */
index a483907..17d1067 100644 (file)
@@ -41,6 +41,7 @@ import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.settings.SettingsProvider;
 import com.android.launcher3.util.Thunk;
 
 import java.util.HashMap;
@@ -356,7 +357,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
     @Thunk Paint mSectionTextPaint;
     @Thunk Paint mPredictedAppsDividerPaint;
 
-    private int mIconSize;
     private int mAllAppsTextColor;
 
     public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
@@ -381,11 +381,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
                 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin) :
                 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin_with_sections);
 
-        mIconSize = mSectionStrategy ==
-                AllAppsContainerView.SECTION_STRATEGY_GRID ?
-                res.getDimensionPixelSize(R.dimen.all_apps_icon_size_grid) :
-                res.getDimensionPixelSize(R.dimen.all_apps_icon_size_ragged);
-
         mAllAppsTextColor = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ?
                 res.getColor(R.color.quantum_panel_text_color_dark) :
                 res.getColor(R.color.quantum_panel_text_color);
@@ -440,10 +435,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
                 AllAppsContainerView.SECTION_STRATEGY_GRID ?
                 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin) :
                 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin_with_sections);
-        mIconSize = mSectionStrategy ==
-                AllAppsContainerView.SECTION_STRATEGY_GRID ?
-                res.getDimensionPixelSize(R.dimen.all_apps_icon_size_grid) :
-                res.getDimensionPixelSize(R.dimen.all_apps_icon_size_ragged);
     }
 
     /**
@@ -487,12 +478,18 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
 
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        boolean hideIconLabels = SettingsProvider.getBoolean(mLauncher,
+                SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_drawer_hide_icon_labels_default);
         switch (viewType) {
             case SECTION_BREAK_VIEW_TYPE:
                 return new ViewHolder(new View(parent.getContext()));
             case ICON_VIEW_TYPE: {
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.all_apps_icon, parent, false);
+                if (hideIconLabels) {
+                    icon.setTextVisibility(!hideIconLabels);
+                }
                 icon.setOnTouchListener(mTouchListener);
                 icon.setOnClickListener(mIconClickListener);
                 icon.setOnLongClickListener(mIconLongClickListener);
@@ -504,6 +501,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
             case PREDICTION_ICON_VIEW_TYPE: {
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.all_apps_prediction_bar_icon, parent, false);
+                if (hideIconLabels) {
+                    icon.setTextVisibility(!hideIconLabels);
+                }
                 icon.setOnTouchListener(mTouchListener);
                 icon.setOnClickListener(mIconClickListener);
                 icon.setOnLongClickListener(mIconLongClickListener);
@@ -535,12 +535,17 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
 
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
+        boolean hideIconLabels = SettingsProvider.getBoolean(mLauncher,
+                SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+                R.bool.preferences_interface_drawer_hide_icon_labels_default);
         switch (holder.getItemViewType()) {
             case ICON_VIEW_TYPE: {
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                 BubbleTextView icon = (BubbleTextView) holder.mContent;
-                icon.setIconSize(mIconSize);
                 icon.setTextColor(mAllAppsTextColor);
+                if (hideIconLabels) {
+                    icon.setTextVisibility(!hideIconLabels);
+                }
                 icon.applyFromApplicationInfo(info);
                 icon.setFastScrollDimmed(mIconsDimmed, !mIconsDimmed);
                 break;
@@ -548,8 +553,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
             case PREDICTION_ICON_VIEW_TYPE: {
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                 BubbleTextView icon = (BubbleTextView) holder.mContent;
-                icon.setIconSize(mIconSize);
                 icon.setTextColor(mAllAppsTextColor);
+                if (hideIconLabels) {
+                    icon.setTextVisibility(!hideIconLabels);
+                }
                 icon.applyFromApplicationInfo(info);
                 break;
             }
diff --git a/src/com/android/launcher3/list/AutoScrollListView.java b/src/com/android/launcher3/list/AutoScrollListView.java
new file mode 100644 (file)
index 0000000..733a753
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ListView;
+
+/**
+ * A ListView that can be asked to scroll (smoothly or otherwise) to a specific
+ * position.  This class takes advantage of similar functionality that exists
+ * in {@link ListView} and enhances it.
+ */
+public class AutoScrollListView extends ListView {
+
+    /**
+     * Position the element at about 1/3 of the list height
+     */
+    private static final float PREFERRED_SELECTION_OFFSET_FROM_TOP = 0.33f;
+
+    private int mRequestedScrollPosition = -1;
+    private boolean mSmoothScrollRequested;
+
+    public AutoScrollListView(Context context) {
+        super(context);
+    }
+
+    public AutoScrollListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AutoScrollListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * Brings the specified position to view by optionally performing a jump-scroll maneuver:
+     * first it jumps to some position near the one requested and then does a smooth
+     * scroll to the requested position.  This creates an impression of full smooth
+     * scrolling without actually traversing the entire list.  If smooth scrolling is
+     * not requested, instantly positions the requested item at a preferred offset.
+     */
+    public void requestPositionToScreen(int position, boolean smoothScroll) {
+        mRequestedScrollPosition = position;
+        mSmoothScrollRequested = smoothScroll;
+        requestLayout();
+    }
+
+    @Override
+    protected void layoutChildren() {
+        super.layoutChildren();
+        if (mRequestedScrollPosition == -1) {
+            return;
+        }
+
+        final int position = mRequestedScrollPosition;
+        mRequestedScrollPosition = -1;
+
+        int firstPosition = getFirstVisiblePosition() + 1;
+        int lastPosition = getLastVisiblePosition();
+        if (position >= firstPosition && position <= lastPosition) {
+            return; // Already on screen
+        }
+
+        final int offset = (int) (getHeight() * PREFERRED_SELECTION_OFFSET_FROM_TOP);
+        if (!mSmoothScrollRequested) {
+            setSelectionFromTop(position, offset);
+
+            // Since we have changed the scrolling position, we need to redo child layout
+            // Calling "requestLayout" in the middle of a layout pass has no effect,
+            // so we call layoutChildren explicitly
+            super.layoutChildren();
+
+        } else {
+            // We will first position the list a couple of screens before or after
+            // the new selection and then scroll smoothly to it.
+            int twoScreens = (lastPosition - firstPosition) * 2;
+            int preliminaryPosition;
+            if (position < firstPosition) {
+                preliminaryPosition = position + twoScreens;
+                if (preliminaryPosition >= getCount()) {
+                    preliminaryPosition = getCount() - 1;
+                }
+                if (preliminaryPosition < firstPosition) {
+                    setSelection(preliminaryPosition);
+                    super.layoutChildren();
+                }
+            } else {
+                preliminaryPosition = position - twoScreens;
+                if (preliminaryPosition < 0) {
+                    preliminaryPosition = 0;
+                }
+                if (preliminaryPosition > lastPosition) {
+                    setSelection(preliminaryPosition);
+                    super.layoutChildren();
+                }
+            }
+
+
+            smoothScrollToPositionFromTop(position, offset);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/list/CompositeCursorAdapter.java b/src/com/android/launcher3/list/CompositeCursorAdapter.java
new file mode 100644 (file)
index 0000000..b163c50
--- /dev/null
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * A general purpose adapter that is composed of multiple cursors. It just
+ * appends them in the order they are added.
+ */
+public abstract class CompositeCursorAdapter extends BaseAdapter {
+
+    private static final int INITIAL_CAPACITY = 2;
+
+    public static class Partition {
+        boolean showIfEmpty;
+        boolean hasHeader;
+
+        Cursor cursor;
+        int idColumnIndex;
+        int count;
+
+        public Partition(boolean showIfEmpty, boolean hasHeader) {
+            this.showIfEmpty = showIfEmpty;
+            this.hasHeader = hasHeader;
+        }
+
+        /**
+         * True if the directory should be shown even if no contacts are found.
+         */
+        public boolean getShowIfEmpty() {
+            return showIfEmpty;
+        }
+
+        public boolean getHasHeader() {
+            return hasHeader;
+        }
+    }
+
+    private final Context mContext;
+    private ArrayList<Partition> mPartitions;
+    private int mCount = 0;
+    private boolean mCacheValid = true;
+    private boolean mNotificationsEnabled = true;
+    private boolean mNotificationNeeded;
+
+    public CompositeCursorAdapter(Context context) {
+        this(context, INITIAL_CAPACITY);
+    }
+
+    public CompositeCursorAdapter(Context context, int initialCapacity) {
+        mContext = context;
+        mPartitions = new ArrayList<Partition>();
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Registers a partition. The cursor for that partition can be set later.
+     * Partitions should be added in the order they are supposed to appear in the
+     * list.
+     */
+    public void addPartition(boolean showIfEmpty, boolean hasHeader) {
+        addPartition(new Partition(showIfEmpty, hasHeader));
+    }
+
+    public void addPartition(Partition partition) {
+        mPartitions.add(partition);
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    public void addPartition(int location, Partition partition) {
+        mPartitions.add(location, partition);
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    public void removePartition(int partitionIndex) {
+        Cursor cursor = mPartitions.get(partitionIndex).cursor;
+        if (cursor != null && !cursor.isClosed()) {
+            cursor.close();
+        }
+        mPartitions.remove(partitionIndex);
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Removes cursors for all partitions.
+     */
+    // TODO: Is this really what this is supposed to do? Just remove the cursors? Not close them?
+    // Not remove the partitions themselves? Isn't this leaking?
+
+    public void clearPartitions() {
+        for (Partition partition : mPartitions) {
+            partition.cursor = null;
+        }
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Closes all cursors and removes all partitions.
+     */
+    public void close() {
+        for (Partition partition : mPartitions) {
+            Cursor cursor = partition.cursor;
+            if (cursor != null && !cursor.isClosed()) {
+                cursor.close();
+            }
+        }
+        mPartitions.clear();
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    public void setHasHeader(int partitionIndex, boolean flag) {
+        mPartitions.get(partitionIndex).hasHeader = flag;
+        invalidate();
+    }
+
+    public void setShowIfEmpty(int partitionIndex, boolean flag) {
+        mPartitions.get(partitionIndex).showIfEmpty = flag;
+        invalidate();
+    }
+
+    public Partition getPartition(int partitionIndex) {
+        return mPartitions.get(partitionIndex);
+    }
+
+    protected void invalidate() {
+        mCacheValid = false;
+    }
+
+    public int getPartitionCount() {
+        return mPartitions.size();
+    }
+
+    protected void ensureCacheValid() {
+        if (mCacheValid) {
+            return;
+        }
+
+        mCount = 0;
+        for (Partition partition : mPartitions) {
+            Cursor cursor = partition.cursor;
+            int count = cursor != null ? cursor.getCount() : 0;
+            if (partition.hasHeader) {
+                if (count != 0 || partition.showIfEmpty) {
+                    count++;
+                }
+            }
+            partition.count = count;
+            mCount += count;
+        }
+
+        mCacheValid = true;
+    }
+
+    /**
+     * Returns true if the specified partition was configured to have a header.
+     */
+    public boolean hasHeader(int partition) {
+        return mPartitions.get(partition).hasHeader;
+    }
+
+    /**
+     * Returns the total number of list items in all partitions.
+     */
+    public int getCount() {
+        ensureCacheValid();
+        return mCount;
+    }
+
+    /**
+     * Returns the cursor for the given partition
+     */
+    public Cursor getCursor(int partition) {
+        return mPartitions.get(partition).cursor;
+    }
+
+    /**
+     * Changes the cursor for an individual partition.
+     */
+    public void changeCursor(int partition, Cursor cursor) {
+        Cursor prevCursor = mPartitions.get(partition).cursor;
+        if (prevCursor != cursor) {
+            if (prevCursor != null && !prevCursor.isClosed()) {
+                prevCursor.close();
+            }
+            mPartitions.get(partition).cursor = cursor;
+            if (cursor != null) {
+                mPartitions.get(partition).idColumnIndex = cursor.getColumnIndex("_id");
+            }
+            invalidate();
+            notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * Returns true if the specified partition has no cursor or an empty cursor.
+     */
+    public boolean isPartitionEmpty(int partition) {
+        Cursor cursor = mPartitions.get(partition).cursor;
+        return cursor == null || cursor.getCount() == 0;
+    }
+
+    /**
+     * Given a list position, returns the index of the corresponding partition.
+     */
+    public int getPartitionForPosition(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start + mPartitions.get(i).count;
+            if (position >= start && position < end) {
+                return i;
+            }
+            start = end;
+        }
+        return -1;
+    }
+
+    /**
+     * Given a list position, return the offset of the corresponding item in its
+     * partition.  The header, if any, will have offset -1.
+     */
+    public int getOffsetInPartition(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (Partition partition : mPartitions) {
+            int end = start + partition.count;
+            if (position >= start && position < end) {
+                int offset = position - start;
+                if (partition.hasHeader) {
+                    offset--;
+                }
+                return offset;
+            }
+            start = end;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the first list position for the specified partition.
+     */
+    public int getPositionForPartition(int partition) {
+        ensureCacheValid();
+        int position = 0;
+        for (int i = 0; i < partition; i++) {
+            position += mPartitions.get(i).count;
+        }
+        return position;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return getItemViewTypeCount() + 1;
+    }
+
+    /**
+     * Returns the overall number of item view types across all partitions. An
+     * implementation of this method needs to ensure that the returned count is
+     * consistent with the values returned by {@link #getItemViewType(int,int)}.
+     */
+    public int getItemViewTypeCount() {
+        return 1;
+    }
+
+    /**
+     * Returns the view type for the list item at the specified position in the
+     * specified partition.
+     */
+    protected int getItemViewType(int partition, int position) {
+        return 1;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start  + mPartitions.get(i).count;
+            if (position >= start && position < end) {
+                int offset = position - start;
+                if (mPartitions.get(i).hasHeader) {
+                    offset--;
+                }
+                if (offset == -1) {
+                    return IGNORE_ITEM_VIEW_TYPE;
+                } else {
+                    return getItemViewType(i, offset);
+                }
+            }
+            start = end;
+        }
+
+        throw new ArrayIndexOutOfBoundsException(position);
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start + mPartitions.get(i).count;
+            if (position >= start && position < end) {
+                int offset = position - start;
+                if (mPartitions.get(i).hasHeader) {
+                    offset--;
+                }
+                View view;
+                if (offset == -1) {
+                    view = getHeaderView(i, mPartitions.get(i).cursor, convertView, parent);
+                } else {
+                    if (!mPartitions.get(i).cursor.moveToPosition(offset)) {
+                        throw new IllegalStateException("Couldn't move cursor to position "
+                                + offset);
+                    }
+                    view = getView(i, mPartitions.get(i).cursor, offset, convertView, parent);
+                }
+                if (view == null) {
+                    throw new NullPointerException("View should not be null, partition: " + i
+                            + " position: " + offset);
+                }
+                return view;
+            }
+            start = end;
+        }
+
+        throw new ArrayIndexOutOfBoundsException(position);
+    }
+
+    /**
+     * Returns the header view for the specified partition, creating one if needed.
+     */
+    protected View getHeaderView(int partition, Cursor cursor, View convertView,
+                                 ViewGroup parent) {
+        View view = convertView != null
+                ? convertView
+                : newHeaderView(mContext, partition, cursor, parent);
+        bindHeaderView(view, partition, cursor);
+        return view;
+    }
+
+    /**
+     * Creates the header view for the specified partition.
+     */
+    protected View newHeaderView(Context context, int partition, Cursor cursor,
+                                 ViewGroup parent) {
+        return null;
+    }
+
+    /**
+     * Binds the header view for the specified partition.
+     */
+    protected void bindHeaderView(View view, int partition, Cursor cursor) {
+    }
+
+    /**
+     * Returns an item view for the specified partition, creating one if needed.
+     */
+    protected View getView(int partition, Cursor cursor, int position, View convertView,
+                           ViewGroup parent) {
+        View view;
+        if (convertView != null) {
+            view = convertView;
+        } else {
+            view = newView(mContext, partition, cursor, position, parent);
+        }
+        bindView(view, partition, cursor, position);
+        return view;
+    }
+
+    /**
+     * Creates an item view for the specified partition and position. Position
+     * corresponds directly to the current cursor position.
+     */
+    protected abstract View newView(Context context, int partition, Cursor cursor, int position,
+                                    ViewGroup parent);
+
+    /**
+     * Binds an item view for the specified partition and position. Position
+     * corresponds directly to the current cursor position.
+     */
+    protected abstract void bindView(View v, int partition, Cursor cursor, int position);
+
+    /**
+     * Returns a pre-positioned cursor for the specified list position.
+     */
+    public Object getItem(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (Partition mPartition : mPartitions) {
+            int end = start + mPartition.count;
+            if (position >= start && position < end) {
+                int offset = position - start;
+                if (mPartition.hasHeader) {
+                    offset--;
+                }
+                if (offset == -1) {
+                    return null;
+                }
+                Cursor cursor = mPartition.cursor;
+                cursor.moveToPosition(offset);
+                return cursor;
+            }
+            start = end;
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the item ID for the specified list position.
+     */
+    public long getItemId(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (Partition mPartition : mPartitions) {
+            int end = start + mPartition.count;
+            if (position >= start && position < end) {
+                int offset = position - start;
+                if (mPartition.hasHeader) {
+                    offset--;
+                }
+                if (offset == -1) {
+                    return 0;
+                }
+                if (mPartition.idColumnIndex == -1) {
+                    return 0;
+                }
+
+                Cursor cursor = mPartition.cursor;
+                if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) {
+                    return 0;
+                }
+                return cursor.getLong(mPartition.idColumnIndex);
+            }
+            start = end;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Returns false if any partition has a header.
+     */
+    @Override
+    public boolean areAllItemsEnabled() {
+        for (Partition mPartition : mPartitions) {
+            if (mPartition.hasHeader) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true for all items except headers.
+     */
+    @Override
+    public boolean isEnabled(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start + mPartitions.get(i).count;
+            if (position >= start && position < end) {
+                int offset = position - start;
+                if (mPartitions.get(i).hasHeader && offset == 0) {
+                    return false;
+                } else {
+                    return isEnabled(i, offset);
+                }
+            }
+            start = end;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if the item at the specified offset of the specified
+     * partition is selectable and clickable.
+     */
+    protected boolean isEnabled(int partition, int position) {
+        return true;
+    }
+
+    /**
+     * Enable or disable data change notifications.  It may be a good idea to
+     * disable notifications before making changes to several partitions at once.
+     */
+    public void setNotificationsEnabled(boolean flag) {
+        mNotificationsEnabled = flag;
+        if (flag && mNotificationNeeded) {
+            notifyDataSetChanged();
+        }
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        if (mNotificationsEnabled) {
+            mNotificationNeeded = false;
+            super.notifyDataSetChanged();
+        } else {
+            mNotificationNeeded = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/list/PinnedHeaderListAdapter.java b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java
new file mode 100644 (file)
index 0000000..cc053e1
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers.
+ */
+public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
+        implements PinnedHeaderListView.PinnedHeaderAdapter {
+
+    public static final int PARTITION_HEADER_TYPE = 0;
+
+    private boolean mPinnedPartitionHeadersEnabled;
+    private boolean mHeaderVisibility[];
+
+    public PinnedHeaderListAdapter(Context context) {
+        super(context);
+    }
+
+    public PinnedHeaderListAdapter(Context context, int initialCapacity) {
+        super(context, initialCapacity);
+    }
+
+    public boolean getPinnedPartitionHeadersEnabled() {
+        return mPinnedPartitionHeadersEnabled;
+    }
+
+    public void setPinnedPartitionHeadersEnabled(boolean flag) {
+        this.mPinnedPartitionHeadersEnabled = flag;
+    }
+
+    @Override
+    public int getPinnedHeaderCount() {
+        if (mPinnedPartitionHeadersEnabled) {
+            return getPartitionCount();
+        } else {
+            return 0;
+        }
+    }
+
+    protected boolean isPinnedPartitionHeaderVisible(int partition) {
+        return getPinnedPartitionHeadersEnabled() && hasHeader(partition)
+                && !isPartitionEmpty(partition);
+    }
+
+    /**
+     * The default implementation creates the same type of view as a normal
+     * partition header.
+     */
+    @Override
+    public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) {
+        if (hasHeader(partition)) {
+            View view = null;
+            if (convertView != null) {
+                Integer headerType = (Integer)convertView.getTag();
+                if (headerType != null && headerType == PARTITION_HEADER_TYPE) {
+                    view = convertView;
+                }
+            }
+            if (view == null) {
+                view = newHeaderView(getContext(), partition, null, parent);
+                view.setTag(PARTITION_HEADER_TYPE);
+                view.setFocusable(false);
+                view.setEnabled(false);
+            }
+            bindHeaderView(view, partition, getCursor(partition));
+            view.setLayoutDirection(parent.getLayoutDirection());
+            return view;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void configurePinnedHeaders(PinnedHeaderListView listView) {
+        if (!getPinnedPartitionHeadersEnabled()) {
+            return;
+        }
+
+        int size = getPartitionCount();
+        boolean unCached = false;
+        // Cache visibility bits, because we will need them several times later on
+        if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
+            mHeaderVisibility = new boolean[size];
+            unCached = true;
+        }
+        for (int i = 0; i < size; i++) {
+            boolean visible = isPinnedPartitionHeaderVisible(i);
+            mHeaderVisibility[i] = visible;
+            if (!visible) {
+                listView.setHeaderInvisible(i, true);
+            }
+        }
+
+        int headerViewsCount = listView.getHeaderViewsCount();
+
+        // Starting at the top, find and pin headers for partitions preceding the visible one(s)
+        int topHeaderHeight = 0;
+        for (int i = 0; i < size; i++) {
+            if (mHeaderVisibility[i]) {
+                int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount;
+                int partition = getPartitionForPosition(position);
+                if (i > partition) {
+                    break;
+                }
+
+                if (!unCached){
+                    listView.setHeaderPinnedAtTop(i, topHeaderHeight, false);
+                    topHeaderHeight += listView.getPinnedHeaderHeight(i);
+                }
+
+            }
+        }
+    }
+
+    @Override
+    public int getScrollPositionForHeader(int viewIndex) {
+        return getPositionForPartition(viewIndex);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/list/PinnedHeaderListView.java b/src/com/android/launcher3/list/PinnedHeaderListView.java
new file mode 100644 (file)
index 0000000..58e8791
--- /dev/null
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ListAdapter;
+
+/**
+ * A ListView that maintains a header pinned at the top of the list. The
+ * pinned header can be pushed up and dissolved as needed.
+ */
+public class PinnedHeaderListView extends AutoScrollListView
+        implements OnScrollListener, OnItemSelectedListener {
+
+    /**
+     * Adapter interface.  The list adapter must implement this interface.
+     */
+    public interface PinnedHeaderAdapter {
+
+        /**
+         * Returns the overall number of pinned headers, visible or not.
+         */
+        int getPinnedHeaderCount();
+
+        /**
+         * Creates or updates the pinned header view.
+         */
+        View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent);
+
+        /**
+         * Configures the pinned headers to match the visible list items. The
+         * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop},
+         * {@link PinnedHeaderListView#setHeaderPinnedAtBottom},
+         * {@link PinnedHeaderListView#setFadingHeader} or
+         * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that
+         * needs to change its position or visibility.
+         */
+        void configurePinnedHeaders(PinnedHeaderListView listView);
+
+        /**
+         * Returns the list position to scroll to if the pinned header is touched.
+         * Return -1 if the list does not need to be scrolled.
+         */
+        int getScrollPositionForHeader(int viewIndex);
+    }
+
+    private static final int MAX_ALPHA = 255;
+    private static final int TOP = 0;
+    private static final int BOTTOM = 1;
+    private static final int FADING = 2;
+
+    private static final int DEFAULT_ANIMATION_DURATION = 20;
+
+    private static final int DEFAULT_SMOOTH_SCROLL_DURATION = 100;
+
+    private static final class PinnedHeader {
+        View view;
+        boolean visible;
+        int y;
+        int height;
+        int alpha;
+        int state;
+
+        boolean animating;
+        boolean targetVisible;
+        int sourceY;
+        int targetY;
+        long targetTime;
+    }
+
+    private PinnedHeaderAdapter mAdapter;
+    private int mSize;
+    private PinnedHeader[] mHeaders;
+    private RectF mBounds = new RectF();
+    private Rect mClipRect = new Rect();
+    private OnScrollListener mOnScrollListener;
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private int mScrollState;
+
+    private boolean mScrollToSectionOnHeaderTouch = false;
+    private boolean mHeaderTouched = false;
+
+    private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
+    private boolean mAnimating;
+    private long mAnimationTargetTime;
+    private int mHeaderPaddingStart;
+    private int mHeaderWidth;
+
+    public PinnedHeaderListView(Context context) {
+        this(context, null, android.R.attr.listViewStyle);
+    }
+
+    public PinnedHeaderListView(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.listViewStyle);
+    }
+
+    public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        super.setOnScrollListener(this);
+        super.setOnItemSelectedListener(this);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mHeaderPaddingStart = getPaddingStart();
+        mHeaderWidth = r - l - mHeaderPaddingStart - getPaddingEnd();
+    }
+
+    public void setPinnedHeaderAnimationDuration(int duration) {
+        mAnimationDuration = duration;
+    }
+
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        mAdapter = (PinnedHeaderAdapter)adapter;
+        super.setAdapter(adapter);
+    }
+
+    @Override
+    public void setOnScrollListener(OnScrollListener onScrollListener) {
+        mOnScrollListener = onScrollListener;
+        super.setOnScrollListener(this);
+    }
+
+    @Override
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+        super.setOnItemSelectedListener(this);
+    }
+
+    public void setScrollToSectionOnHeaderTouch(boolean value) {
+        mScrollToSectionOnHeaderTouch = value;
+    }
+
+    @Override
+    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                         int totalItemCount) {
+        if (mAdapter != null) {
+            int count = mAdapter.getPinnedHeaderCount();
+            if (count != mSize) {
+                mSize = count;
+                if (mHeaders == null) {
+                    mHeaders = new PinnedHeader[mSize];
+                } else if (mHeaders.length < mSize) {
+                    PinnedHeader[] headers = mHeaders;
+                    mHeaders = new PinnedHeader[mSize];
+                    System.arraycopy(headers, 0, mHeaders, 0, headers.length);
+                }
+            }
+
+            for (int i = 0; i < mSize; i++) {
+                if (mHeaders[i] == null) {
+                    mHeaders[i] = new PinnedHeader();
+                }
+                mHeaders[i].view = mAdapter.getPinnedHeaderView(i, mHeaders[i].view, this);
+            }
+
+            mAnimationTargetTime = System.currentTimeMillis() + mAnimationDuration;
+            mAdapter.configurePinnedHeaders(this);
+            invalidateIfAnimating();
+
+        }
+        if (mOnScrollListener != null) {
+            mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
+        }
+    }
+
+    @Override
+    protected float getTopFadingEdgeStrength() {
+        // Disable vertical fading at the top when the pinned header is present
+        return mSize > 0 ? 0 : super.getTopFadingEdgeStrength();
+    }
+
+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        mScrollState = scrollState;
+        if (mOnScrollListener != null) {
+            mOnScrollListener.onScrollStateChanged(this, scrollState);
+        }
+    }
+
+    /**
+     * Ensures that the selected item is positioned below the top-pinned headers
+     * and above the bottom-pinned ones.
+     */
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        int height = getHeight();
+
+        int windowTop = 0;
+        int windowBottom = height;
+
+        for (int i = 0; i < mSize; i++) {
+            PinnedHeader header = mHeaders[i];
+            if (header.visible) {
+                if (header.state == TOP) {
+                    windowTop = header.y + header.height;
+                } else if (header.state == BOTTOM) {
+                    windowBottom = header.y;
+                    break;
+                }
+            }
+        }
+
+        View selectedView = getSelectedView();
+        if (selectedView != null) {
+            if (selectedView.getTop() < windowTop) {
+                setSelectionFromTop(position, windowTop);
+            } else if (selectedView.getBottom() > windowBottom) {
+                setSelectionFromTop(position, windowBottom - selectedView.getHeight());
+            }
+        }
+
+        if (mOnItemSelectedListener != null) {
+            mOnItemSelectedListener.onItemSelected(parent, view, position, id);
+        }
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+        if (mOnItemSelectedListener != null) {
+            mOnItemSelectedListener.onNothingSelected(parent);
+        }
+    }
+
+    public int getPinnedHeaderHeight(int viewIndex) {
+        ensurePinnedHeaderLayout(viewIndex);
+        return mHeaders[viewIndex].view.getHeight();
+    }
+
+    /**
+     * Set header to be pinned at the top.
+     *
+     * @param viewIndex index of the header view
+     * @param y is position of the header in pixels.
+     * @param animate true if the transition to the new coordinate should be animated
+     */
+    public void setHeaderPinnedAtTop(int viewIndex, int y, boolean animate) {
+        ensurePinnedHeaderLayout(viewIndex);
+        PinnedHeader header = mHeaders[viewIndex];
+        header.visible = true;
+        header.y = y;
+        header.state = TOP;
+
+        // TODO perhaps we should animate at the top as well
+        header.animating = false;
+    }
+
+    /**
+     * Set header to be pinned at the bottom.
+     *
+     * @param viewIndex index of the header view
+     * @param y is position of the header in pixels.
+     * @param animate true if the transition to the new coordinate should be animated
+     */
+    public void setHeaderPinnedAtBottom(int viewIndex, int y, boolean animate) {
+        ensurePinnedHeaderLayout(viewIndex);
+        PinnedHeader header = mHeaders[viewIndex];
+        header.state = BOTTOM;
+        if (header.animating) {
+            header.targetTime = mAnimationTargetTime;
+            header.sourceY = header.y;
+            header.targetY = y;
+        } else if (animate && (header.y != y || !header.visible)) {
+            if (header.visible) {
+                header.sourceY = header.y;
+            } else {
+                header.visible = true;
+                header.sourceY = y + header.height;
+            }
+            header.animating = true;
+            header.targetVisible = true;
+            header.targetTime = mAnimationTargetTime;
+            header.targetY = y;
+        } else {
+            header.visible = true;
+            header.y = y;
+        }
+    }
+
+    /**
+     * Set header to be pinned at the top of the first visible item.
+     *
+     * @param viewIndex index of the header view
+     * @param position is position of the header in pixels.
+     */
+    public void setFadingHeader(int viewIndex, int position, boolean fade) {
+        ensurePinnedHeaderLayout(viewIndex);
+
+        View child = getChildAt(position - getFirstVisiblePosition());
+        if (child == null) return;
+
+        PinnedHeader header = mHeaders[viewIndex];
+        header.visible = true;
+        header.state = FADING;
+        header.alpha = MAX_ALPHA;
+        header.animating = false;
+
+        int top = getTotalTopPinnedHeaderHeight();
+        header.y = top;
+        if (fade) {
+            int bottom = child.getBottom() - top;
+            int headerHeight = header.height;
+            if (bottom < headerHeight) {
+                int portion = bottom - headerHeight;
+                header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight;
+                header.y = top + portion;
+            }
+        }
+    }
+
+    /**
+     * Makes header invisible.
+     *
+     * @param viewIndex index of the header view
+     * @param animate true if the transition to the new coordinate should be animated
+     */
+    public void setHeaderInvisible(int viewIndex, boolean animate) {
+        PinnedHeader header = mHeaders[viewIndex];
+        if (header.visible && (animate || header.animating) && header.state == BOTTOM) {
+            header.sourceY = header.y;
+            if (!header.animating) {
+                header.visible = true;
+                header.targetY = getBottom() + header.height;
+            }
+            header.animating = true;
+            header.targetTime = mAnimationTargetTime;
+            header.targetVisible = false;
+        } else {
+            header.visible = false;
+        }
+    }
+
+    private void ensurePinnedHeaderLayout(int viewIndex) {
+        View view = mHeaders[viewIndex].view;
+        if (view.isLayoutRequested()) {
+            int widthSpec = View.MeasureSpec.makeMeasureSpec(mHeaderWidth, View.MeasureSpec.EXACTLY);
+            int heightSpec;
+            ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+            if (layoutParams != null && layoutParams.height > 0) {
+                heightSpec = View.MeasureSpec
+                        .makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY);
+            } else {
+                heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+            }
+            view.measure(widthSpec, heightSpec);
+            int height = view.getMeasuredHeight();
+            mHeaders[viewIndex].height = height;
+            view.layout(0, 0, mHeaderWidth, height);
+        }
+    }
+
+    /**
+     * Returns the sum of heights of headers pinned to the top.
+     */
+    public int getTotalTopPinnedHeaderHeight() {
+        for (int i = mSize; --i >= 0;) {
+            PinnedHeader header = mHeaders[i];
+            if (header.visible && header.state == TOP) {
+                return header.y + header.height;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the list item position at the specified y coordinate.
+     */
+    public int getPositionAt(int y) {
+        do {
+            int position = pointToPosition(getPaddingLeft() + 1, y);
+            if (position != -1) {
+                return position;
+            }
+            // If position == -1, we must have hit a separator. Let's examine
+            // a nearby pixel
+            y--;
+        } while (y > 0);
+        return 0;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        mHeaderTouched = false;
+        if (super.onInterceptTouchEvent(ev)) {
+            return true;
+        }
+
+        if (mScrollState == SCROLL_STATE_IDLE) {
+            final int y = (int)ev.getY();
+            final int x = (int)ev.getX();
+            for (int i = mSize; --i >= 0;) {
+                PinnedHeader header = mHeaders[i];
+                // For RTL layouts, this also takes into account that the scrollbar is on the left
+                // side.
+                final int padding = getPaddingLeft();
+                if (header.visible && header.y <= y && header.y + header.height > y &&
+                        x >= padding && padding + mHeaderWidth >= x) {
+                    mHeaderTouched = true;
+                    if (mScrollToSectionOnHeaderTouch &&
+                            ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        return smoothScrollToPartition(i);
+                    } else {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mHeaderTouched) {
+            if (ev.getAction() == MotionEvent.ACTION_UP) {
+                mHeaderTouched = false;
+            }
+            return true;
+        }
+        return super.onTouchEvent(ev);
+    };
+
+    private boolean smoothScrollToPartition(int partition) {
+        final int position = mAdapter.getScrollPositionForHeader(partition);
+        if (position == -1) {
+            return false;
+        }
+
+        int offset = 0;
+        for (int i = 0; i < partition; i++) {
+            PinnedHeader header = mHeaders[i];
+            if (header.visible) {
+                offset += header.height;
+            }
+        }
+        smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset,
+                DEFAULT_SMOOTH_SCROLL_DURATION);
+        return true;
+    }
+
+    private void invalidateIfAnimating() {
+        mAnimating = false;
+        for (int i = 0; i < mSize; i++) {
+            if (mHeaders[i].animating) {
+                mAnimating = true;
+                invalidate();
+                return;
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        long currentTime = mAnimating ? System.currentTimeMillis() : 0;
+
+        int top = 0;
+        int bottom = getBottom();
+        boolean hasVisibleHeaders = false;
+        for (int i = 0; i < mSize; i++) {
+            PinnedHeader header = mHeaders[i];
+            if (header.visible) {
+                hasVisibleHeaders = true;
+                if (header.state == BOTTOM && header.y < bottom) {
+                    bottom = header.y;
+                } else if (header.state == TOP || header.state == FADING) {
+                    int newTop = header.y + header.height;
+                    if (newTop > top) {
+                        top = newTop;
+                    }
+                }
+            }
+        }
+
+        if (hasVisibleHeaders) {
+            canvas.save();
+            mClipRect.set(0, top, getWidth(), bottom);
+            canvas.clipRect(mClipRect);
+        }
+
+        super.dispatchDraw(canvas);
+
+        if (hasVisibleHeaders) {
+            canvas.restore();
+
+            // First draw top headers, then the bottom ones to handle the Z axis correctly
+            for (int i = mSize; --i >= 0;) {
+                PinnedHeader header = mHeaders[i];
+                if (header.visible && (header.state == TOP || header.state == FADING)) {
+                    drawHeader(canvas, header, currentTime);
+                }
+            }
+
+            for (int i = 0; i < mSize; i++) {
+                PinnedHeader header = mHeaders[i];
+                if (header.visible && header.state == BOTTOM) {
+                    drawHeader(canvas, header, currentTime);
+                }
+            }
+        }
+
+        invalidateIfAnimating();
+    }
+
+    private void drawHeader(Canvas canvas, PinnedHeader header, long currentTime) {
+        if (header.animating) {
+            int timeLeft = (int)(header.targetTime - currentTime);
+            if (timeLeft <= 0) {
+                header.y = header.targetY;
+                header.visible = header.targetVisible;
+                header.animating = false;
+            } else {
+                header.y = header.targetY + (header.sourceY - header.targetY) * timeLeft
+                        / mAnimationDuration;
+            }
+        }
+        if (header.visible) {
+            View view = header.view;
+            int saveCount = canvas.save();
+            canvas.translate(isLayoutRtl() ?
+                            getWidth() - mHeaderPaddingStart - mHeaderWidth : mHeaderPaddingStart,
+                    header.y);
+            if (header.state == FADING) {
+                mBounds.set(0, 0, mHeaderWidth, view.getHeight());
+                canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG);
+            }
+            view.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+    }
+
+    /**
+     * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
+     */
+    public boolean isLayoutRtl() {
+        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java
new file mode 100644 (file)
index 0000000..aa2f451
--- /dev/null
@@ -0,0 +1,307 @@
+package com.android.launcher3.list;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.OverviewSettingsPanel;
+import com.android.launcher3.R;
+import com.android.launcher3.settings.SettingsProvider;
+
+public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter {
+    private Launcher mLauncher;
+    private Context mContext;
+
+    class SettingsPosition {
+        int partition = 0;
+        int position = 0;
+
+        SettingsPosition (int partition, int position) {
+            this.partition = partition;
+            this.position = position;
+        }
+    }
+
+    public SettingsPinnedHeaderAdapter(Context context) {
+        super(context);
+        mLauncher = (Launcher) context;
+        mContext = context;
+    }
+
+    private String[] mHeaders;
+    public int mPinnedHeaderCount;
+
+    public void setHeaders(String[] headers) {
+        this.mHeaders = headers;
+    }
+
+    @Override
+    protected View newHeaderView(Context context, int partition, Cursor cursor,
+                                 ViewGroup parent) {
+        LayoutInflater inflater = LayoutInflater.from(context);
+        return inflater.inflate(R.layout.settings_pane_list_header, null);
+    }
+
+    @Override
+    protected void bindHeaderView(View view, int partition, Cursor cursor) {
+        TextView textView = (TextView) view.findViewById(R.id.item_name);
+        textView.setText(mHeaders[partition]);
+        textView.setTypeface(textView.getTypeface(), Typeface.BOLD);
+
+        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
+    }
+
+    @Override
+    protected View newView(Context context, int partition, Cursor cursor, int position,
+                           ViewGroup parent) {
+        LayoutInflater inflater = LayoutInflater.from(context);
+        return inflater.inflate(R.layout.settings_pane_list_item, null);
+    }
+
+    @Override
+    protected void bindView(View v, int partition, Cursor cursor, int position) {
+        TextView text = (TextView)v.findViewById(R.id.item_name);
+        // RTL
+        Configuration config = mLauncher.getResources().getConfiguration();
+        if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            text.setGravity(Gravity.RIGHT);
+        }
+
+        String title = cursor.getString(1);
+        text.setText(title);
+
+        v.setTag(new SettingsPosition(partition, position));
+
+        Resources res = mLauncher.getResources();
+
+
+        boolean current = false;
+        String state = "";
+
+        switch (partition) {
+            case OverviewSettingsPanel.HOME_SETTINGS_POSITION:
+                switch (position) {
+                    /*case 0:
+                        current = mLauncher.isSearchBarEnabled();
+                        state = current ? res.getString(R.string.setting_state_on)
+                                : res.getString(R.string.setting_state_off);
+                        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+                        break;*/
+                    case 1:
+                        current = SettingsProvider.getBoolean(mContext,
+                                SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                                R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+                        state = current ? res.getString(R.string.icon_labels_hide)
+                                : res.getString(R.string.icon_labels_show);
+                        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+                        break;
+                    case 2:
+                        current = SettingsProvider.getBoolean(mContext,
+                                SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
+                                R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default);
+                        state = current ? res.getString(R.string.setting_state_on)
+                                : res.getString(R.string.setting_state_off);
+                        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+                        break;
+                    /*case 3:
+                        updateDynamicGridSizeSettingsItem(v);
+                        break;*/
+                    default:
+                        ((TextView) v.findViewById(R.id.item_state)).setText("");
+                }
+                break;
+            case OverviewSettingsPanel.DRAWER_SETTINGS_POSITION:
+                switch (position) {
+                    case 0:
+                        current = SettingsProvider.getBoolean(mContext,
+                                SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+                                R.bool.preferences_interface_drawer_hide_icon_labels_default);
+                        state = current ? res.getString(R.string.icon_labels_hide)
+                                : res.getString(R.string.icon_labels_show);
+                        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+                        break;
+                    default:
+                        ((TextView) v.findViewById(R.id.item_state)).setText("");
+                }
+                break;
+            default:
+                switch (position) {
+                    case 0:
+                        current = SettingsProvider.getBoolean(mContext,
+                                SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
+                                R.bool.preferences_interface_general_icons_large_default);
+                        state = current ? res.getString(R.string.setting_state_on)
+                                : res.getString(R.string.setting_state_off);
+                        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+                        break;
+                    default:
+                        ((TextView) v.findViewById(R.id.item_state)).setText("");
+                }
+        }
+
+        v.setOnClickListener(mSettingsItemListener);
+    }
+
+    @Override
+    public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        View view = inflater.inflate(R.layout.settings_pane_list_header, parent, false);
+        view.setFocusable(false);
+        view.setEnabled(false);
+        bindHeaderView(view, viewIndex, null);
+        return view;
+    }
+
+    @Override
+    public int getPinnedHeaderCount() {
+        return mPinnedHeaderCount;
+    }
+
+    /*public void updateDynamicGridSizeSettingsItem(View v) {
+        DeviceProfile.GridSize gridSize = DeviceProfile.GridSize.getModeForValue(
+                SettingsProvider.getIntCustomDefault(mLauncher,
+                        SettingsProvider.SETTINGS_UI_DYNAMIC_GRID_SIZE, 0));
+        String state = "";
+
+        switch (gridSize) {
+            case Comfortable:
+                state = mLauncher.getResources().getString(R.string.grid_size_comfortable);
+                break;
+            case Cozy:
+                state = mLauncher.getResources().getString(R.string.grid_size_cozy);
+                break;
+            case Condensed:
+                state = mLauncher.getResources().getString(R.string.grid_size_condensed);
+                break;
+            case Custom:
+                int rows = SettingsProvider.getIntCustomDefault(mLauncher,
+                        SettingsProvider.SETTINGS_UI_HOMESCREEN_ROWS, 0);
+                int columns = SettingsProvider.getIntCustomDefault(mLauncher,
+                        SettingsProvider.SETTINGS_UI_HOMESCREEN_COLUMNS, 0);
+                state = rows + " " + "\u00d7" + " " + columns;
+                break;
+        }
+        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+    }*/
+
+    OnClickListener mSettingsItemListener = new OnClickListener() {
+
+        @Override
+        public void onClick(View v) {
+            int partition = ((SettingsPosition) v.getTag()).partition;
+            int position = ((SettingsPosition) v.getTag()).position;
+
+            switch (partition) {
+                case OverviewSettingsPanel.HOME_SETTINGS_POSITION:
+                    switch (position) {
+                        /*case 0:
+                            updateSearchBarVisibility(v);
+                            mLauncher.setUpdateDynamicGrid(false);
+                            break;*/
+                        case 1:
+                            onIconLabelsBooleanChanged(v,
+                                    SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+                                    R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+                            mLauncher.reloadLauncher();
+                            break;
+                        case 2:
+                            onSettingsBooleanChanged(v,
+                                    SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
+                                    R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default);
+                            mLauncher.reloadLauncher();
+                            break;
+                        /*case 3:
+                            mLauncher.onClickDynamicGridSizeButton();
+                            break;*/
+                    }
+                    break;
+                case OverviewSettingsPanel.DRAWER_SETTINGS_POSITION:
+                    switch (position) {
+                        case 0:
+                            onIconLabelsBooleanChanged(v,
+                                    SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+                                    R.bool.preferences_interface_drawer_hide_icon_labels_default);
+                            mLauncher.reloadLauncher();
+                            break;
+                    }
+                    break;
+                default:
+                    switch (position) {
+                        case 0:
+                            onSettingsBooleanChanged(v,
+                                    SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
+                                    R.bool.preferences_interface_general_icons_large_default);
+                            mLauncher.reloadLauncher();
+                            break;
+                        /*case 1:
+                            Intent intent = new Intent();
+                            intent.setClassName(OverviewSettingsPanel.ANDROID_SETTINGS,
+                                    OverviewSettingsPanel.ANDROID_PROTECTED_APPS);
+                            mLauncher.startActivity(intent);
+                            break;*/
+                    }
+            }
+
+            View defaultHome = mLauncher.findViewById(R.id.default_home_screen_panel);
+            defaultHome.setVisibility(getCursor(0).getCount() > 1 ? View.VISIBLE : View.GONE);
+        }
+    };
+
+    /*private void updateSearchBarVisibility(View v) {
+        boolean isSearchEnabled = SettingsProvider.getBoolean(mContext,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+                R.bool.preferences_interface_homescreen_search_default);
+
+        if (!isSearchEnabled) {
+            if (!Utilities.searchActivityExists(mContext)) {
+                Toast.makeText(mContext, mContext.getString(R.string.search_activity_not_found),
+                        Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        onSettingsBooleanChanged(v,
+                SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+                R.bool.preferences_interface_homescreen_search_default);
+    }*/
+
+    private void onSettingsBooleanChanged(View v, String key, int res) {
+        boolean current = SettingsProvider.getBoolean(
+                mContext, key, res);
+
+        // Set new state
+        SettingsProvider.putBoolean(mContext, key, !current);
+        SettingsProvider.putBoolean(mContext, SettingsProvider.SETTINGS_CHANGED, true);
+
+        String state = current ? mLauncher.getResources().getString(
+                R.string.setting_state_off) : mLauncher.getResources().getString(
+                R.string.setting_state_on);
+        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+    }
+
+    private void onIconLabelsBooleanChanged(View v, String key, int res) {
+        boolean current = SettingsProvider.getBoolean(
+                mContext, key, res);
+
+        // Set new state
+        SettingsProvider.putBoolean(mContext, key, !current);
+        SettingsProvider.putBoolean(mContext, SettingsProvider.SETTINGS_CHANGED, true);
+
+        String state = current ? mLauncher.getResources().getString(
+                R.string.icon_labels_show) : mLauncher.getResources().getString(
+                R.string.icon_labels_hide);
+        ((TextView) v.findViewById(R.id.item_state)).setText(state);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/settings/SettingsProvider.java b/src/com/android/launcher3/settings/SettingsProvider.java
new file mode 100644 (file)
index 0000000..a90478c
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod 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.settings;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public final class SettingsProvider {
+    public static final String SETTINGS_KEY = "trebuchet_preferences";
+    public static final String SETTINGS_CHANGED = "settings_changed";
+
+    public static final String SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID = "ui_homescreen_default_screen_id";
+    public static final String SETTINGS_UI_HOMESCREEN_SEARCH = "ui_homescreen_search";
+    public static final String SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS = "ui_homescreen_general_hide_icon_labels";
+    public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL = "ui_homescreen_scrolling_wallpaper_scroll";
+    public static final String SETTINGS_UI_DYNAMIC_GRID_SIZE = "ui_dynamic_grid_size";
+    public static final String SETTINGS_UI_HOMESCREEN_ROWS = "ui_homescreen_rows";
+    public static final String SETTINGS_UI_HOMESCREEN_COLUMNS = "ui_homescreen_columns";
+    public static final String SETTINGS_UI_DRAWER_HIDE_ICON_LABELS = "ui_drawer_hide_icon_labels";
+    public static final String SETTINGS_UI_DRAWER_COMPACT = "ui_drawer_compact";
+    public static final String SETTINGS_UI_GENERAL_ICONS_LARGE = "ui_general_icons_large";
+
+    public static SharedPreferences get(Context context) {
+        return context.getSharedPreferences(SETTINGS_KEY, Context.MODE_PRIVATE);
+    }
+
+    public static int getIntCustomDefault(Context context, String key, int def) {
+        return get(context).getInt(key, def);
+    }
+
+    public static int getInt(Context context, String key, int resource) {
+        return getIntCustomDefault(context, key, context.getResources().getInteger(resource));
+    }
+
+    public static long getLongCustomDefault(Context context, String key, long def) {
+        return get(context).getLong(key, def);
+    }
+
+    public static long getLong(Context context, String key, int resource) {
+        return getLongCustomDefault(context, key, context.getResources().getInteger(resource));
+    }
+
+    public static boolean getBooleanCustomDefault(Context context, String key, boolean def) {
+        return get(context).getBoolean(key, def);
+    }
+
+    public static boolean getBoolean(Context context, String key, int resource) {
+        return getBooleanCustomDefault(context, key, context.getResources().getBoolean(resource));
+    }
+
+    public static String getStringCustomDefault(Context context, String key, String def) {
+        return get(context).getString(key, def);
+    }
+
+    public static String getString(Context context, String key, int resource) {
+        return getStringCustomDefault(context, key, context.getResources().getString(resource));
+    }
+
+    public static void putString(Context context, String key, String value) {
+        get(context).edit().putString(key, value).commit();
+    }
+
+    public static void putInt(Context context, String key, int value) {
+        get(context).edit().putInt(key, value).commit();
+    }
+
+    public static void putBoolean(Context context, String key, boolean value) {
+        get(context).edit().putBoolean(key, value).commit();
+    }
+}
index 1bc7c11..dc3e05e 100644 (file)
@@ -42,7 +42,7 @@ public class InvariantDeviceProfileTest extends AndroidTestCase {
     protected void setUp() throws Exception {
         super.setUp();
         mInvariantProfile = new InvariantDeviceProfile(getContext());
-        mPredefinedDeviceProfiles = mInvariantProfile.getPredefinedDeviceProfiles();
+        mPredefinedDeviceProfiles = mInvariantProfile.getPredefinedDeviceProfiles(getContext());
     }
 
     @Override