summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/drawable-hdpi/ic_default_screen.pngbin0 -> 2046 bytes
-rw-r--r--res/drawable-hdpi/ic_default_screen_pressed.pngbin0 -> 1993 bytes
-rw-r--r--res/drawable-mdpi/ic_default_screen.pngbin0 -> 1597 bytes
-rw-r--r--res/drawable-mdpi/ic_default_screen_pressed.pngbin0 -> 1626 bytes
-rw-r--r--res/drawable-xhdpi/ic_default_screen.pngbin0 -> 2323 bytes
-rw-r--r--res/drawable-xhdpi/ic_default_screen_pressed.pngbin0 -> 2385 bytes
-rw-r--r--res/drawable-xxhdpi/ic_default_screen.pngbin0 -> 3187 bytes
-rw-r--r--res/drawable-xxhdpi/ic_default_screen_pressed.pngbin0 -> 3237 bytes
-rw-r--r--res/drawable/above_shadow.xml8
-rw-r--r--res/drawable/below_shadow.xml8
-rw-r--r--res/drawable/default_screen_button.xml19
-rw-r--r--res/drawable/launcheranimatedarrow_00000.pngbin0 -> 661 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00001.pngbin0 -> 749 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00002.pngbin0 -> 705 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00003.pngbin0 -> 746 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00004.pngbin0 -> 747 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00005.pngbin0 -> 664 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00006.pngbin0 -> 1007 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00007.pngbin0 -> 999 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00008.pngbin0 -> 710 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00009.pngbin0 -> 968 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00010.pngbin0 -> 990 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00011.pngbin0 -> 671 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00012.pngbin0 -> 744 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00013.pngbin0 -> 750 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00014.pngbin0 -> 747 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00015.pngbin0 -> 736 bytes
-rw-r--r--res/drawable/launcheranimatedarrow_00016.pngbin0 -> 668 bytes
-rw-r--r--res/drawable/listitem_bg.xml6
-rw-r--r--res/drawable/listitem_text.xml6
-rw-r--r--res/drawable/transition_arrow.xml21
-rw-r--r--res/drawable/transition_arrow_reverse.xml21
-rw-r--r--res/layout/overview_panel.xml175
-rw-r--r--res/layout/settings_pane_list_header.xml19
-rw-r--r--res/layout/settings_pane_list_item.xml38
-rw-r--r--res/values/attrs.xml10
-rw-r--r--res/values/cm_strings.xml36
-rw-r--r--res/values/colors.xml4
-rw-r--r--res/values/dimens.xml11
-rw-r--r--res/values/preferences_defaults.xml9
-rw-r--r--src/com/android/launcher3/BubbleTextView.java14
-rw-r--r--src/com/android/launcher3/CellLayout.java5
-rw-r--r--src/com/android/launcher3/DeviceProfile.java4
-rw-r--r--src/com/android/launcher3/Folder.java14
-rw-r--r--src/com/android/launcher3/FolderPagedView.java6
-rw-r--r--src/com/android/launcher3/InvariantDeviceProfile.java42
-rw-r--r--src/com/android/launcher3/Launcher.java123
-rw-r--r--src/com/android/launcher3/LauncherAppState.java4
-rw-r--r--src/com/android/launcher3/LauncherModel.java1
-rw-r--r--src/com/android/launcher3/OverviewPanel.java32
-rw-r--r--src/com/android/launcher3/OverviewSettingsPanel.java74
-rw-r--r--src/com/android/launcher3/VerticalSlidingPanel.java1317
-rw-r--r--src/com/android/launcher3/Workspace.java47
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java5
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java31
-rw-r--r--src/com/android/launcher3/list/AutoScrollListView.java117
-rw-r--r--src/com/android/launcher3/list/CompositeCursorAdapter.java532
-rw-r--r--src/com/android/launcher3/list/PinnedHeaderListAdapter.java137
-rw-r--r--src/com/android/launcher3/list/PinnedHeaderListView.java565
-rw-r--r--src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java307
-rw-r--r--src/com/android/launcher3/settings/SettingsProvider.java84
-rw-r--r--tests/src/com/android/launcher3/InvariantDeviceProfileTest.java2
62 files changed, 3756 insertions, 98 deletions
diff --git a/res/drawable-hdpi/ic_default_screen.png b/res/drawable-hdpi/ic_default_screen.png
new file mode 100644
index 000000000..41dcf8f6e
--- /dev/null
+++ b/res/drawable-hdpi/ic_default_screen.png
Binary files 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
index 000000000..7779058c5
--- /dev/null
+++ b/res/drawable-hdpi/ic_default_screen_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_default_screen.png b/res/drawable-mdpi/ic_default_screen.png
new file mode 100644
index 000000000..8a2c1e1c0
--- /dev/null
+++ b/res/drawable-mdpi/ic_default_screen.png
Binary files 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
index 000000000..20606ce62
--- /dev/null
+++ b/res/drawable-mdpi/ic_default_screen_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_default_screen.png b/res/drawable-xhdpi/ic_default_screen.png
new file mode 100644
index 000000000..735332ae5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_default_screen.png
Binary files 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
index 000000000..11dede0a0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_default_screen_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_default_screen.png b/res/drawable-xxhdpi/ic_default_screen.png
new file mode 100644
index 000000000..253bd2d4d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_default_screen.png
Binary files 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
index 000000000..5367b7abd
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_default_screen_pressed.png
Binary files differ
diff --git a/res/drawable/above_shadow.xml b/res/drawable/above_shadow.xml
new file mode 100644
index 000000000..99db324ab
--- /dev/null
+++ b/res/drawable/above_shadow.xml
@@ -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
index 000000000..fc70073a9
--- /dev/null
+++ b/res/drawable/below_shadow.xml
@@ -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
index 000000000..ad66137b0
--- /dev/null
+++ b/res/drawable/default_screen_button.xml
@@ -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
index 000000000..2ed7fe9a7
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00000.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00001.png b/res/drawable/launcheranimatedarrow_00001.png
new file mode 100644
index 000000000..f3707e07c
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00001.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00002.png b/res/drawable/launcheranimatedarrow_00002.png
new file mode 100644
index 000000000..3549389d0
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00002.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00003.png b/res/drawable/launcheranimatedarrow_00003.png
new file mode 100644
index 000000000..891e86c42
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00003.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00004.png b/res/drawable/launcheranimatedarrow_00004.png
new file mode 100644
index 000000000..7cfb1ef8c
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00004.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00005.png b/res/drawable/launcheranimatedarrow_00005.png
new file mode 100644
index 000000000..121f4d516
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00005.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00006.png b/res/drawable/launcheranimatedarrow_00006.png
new file mode 100644
index 000000000..3a38e71f7
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00006.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00007.png b/res/drawable/launcheranimatedarrow_00007.png
new file mode 100644
index 000000000..e81a719fd
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00007.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00008.png b/res/drawable/launcheranimatedarrow_00008.png
new file mode 100644
index 000000000..bd6f40981
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00008.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00009.png b/res/drawable/launcheranimatedarrow_00009.png
new file mode 100644
index 000000000..c7cb60daf
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00009.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00010.png b/res/drawable/launcheranimatedarrow_00010.png
new file mode 100644
index 000000000..1bf30dcc3
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00010.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00011.png b/res/drawable/launcheranimatedarrow_00011.png
new file mode 100644
index 000000000..3cb598806
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00011.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00012.png b/res/drawable/launcheranimatedarrow_00012.png
new file mode 100644
index 000000000..58070de06
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00012.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00013.png b/res/drawable/launcheranimatedarrow_00013.png
new file mode 100644
index 000000000..810d0a229
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00013.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00014.png b/res/drawable/launcheranimatedarrow_00014.png
new file mode 100644
index 000000000..3f9e51861
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00014.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00015.png b/res/drawable/launcheranimatedarrow_00015.png
new file mode 100644
index 000000000..348bfbbc6
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00015.png
Binary files differ
diff --git a/res/drawable/launcheranimatedarrow_00016.png b/res/drawable/launcheranimatedarrow_00016.png
new file mode 100644
index 000000000..5b0b28649
--- /dev/null
+++ b/res/drawable/launcheranimatedarrow_00016.png
Binary files differ
diff --git a/res/drawable/listitem_bg.xml b/res/drawable/listitem_bg.xml
new file mode 100644
index 000000000..be1fedbe5
--- /dev/null
+++ b/res/drawable/listitem_bg.xml
@@ -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
index 000000000..9637fd308
--- /dev/null
+++ b/res/drawable/listitem_text.xml
@@ -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
index 000000000..540db93d8
--- /dev/null
+++ b/res/drawable/transition_arrow.xml
@@ -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
index 000000000..855f08fb4
--- /dev/null
+++ b/res/drawable/transition_arrow_reverse.xml
@@ -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>
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 1f02dce3c..5698c86a9 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,53 +14,130 @@
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
index 000000000..2429b9b81
--- /dev/null
+++ b/res/layout/settings_pane_list_header.xml
@@ -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
index 000000000..58b2de862
--- /dev/null
+++ b/res/layout/settings_pane_list_item.xml
@@ -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
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index d7f9ef4fa..787e5ca23 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -134,4 +134,14 @@
<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>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
index 4ec60147f..e31fed119 100644
--- a/res/values/cm_strings.xml
+++ b/res/values/cm_strings.xml
@@ -18,9 +18,45 @@
<!-- 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>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index b70e1f895..0cad1cefc 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -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>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 799ea9803..8c64ef0c5 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -145,7 +145,7 @@
<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>
@@ -158,4 +158,13 @@
<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
index 000000000..9eb5ca58f
--- /dev/null
+++ b/res/values/preferences_defaults.xml
@@ -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>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 205c113a7..c15f07de9 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -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;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index a99d791bd..daf26411d 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -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());
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f77b34862..84b6835f8 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -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() {
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 994d7d30d..1e0827e54 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -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) {
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index cc9c5738a..a7940d552 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -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(
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index ae204c40c..6d4d95292 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -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;
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1976ca982..759ca041b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -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 {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d87ad67e5..d515f05e5 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -174,6 +174,10 @@ public class LauncherAppState {
return mInvariantDeviceProfile;
}
+ public void initInvariantDeviceProfile() {
+ mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
+ }
+
public static boolean isDogfoodBuild() {
return getInstance().mBuildInfo.isDogfoodBuild();
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b5922c6a3..e3170e93b 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -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
index 000000000..2fdca6330
--- /dev/null
+++ b/src/com/android/launcher3/OverviewPanel.java
@@ -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
index 000000000..b4ba06a4d
--- /dev/null
+++ b/src/com/android/launcher3/OverviewSettingsPanel.java
@@ -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
index 000000000..0ebbebc72
--- /dev/null
+++ b/src/com/android/launcher3/VerticalSlidingPanel.java
@@ -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];
+ }
+ };
+ }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 856e3b88a..59c870a8d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -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.
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index bff7752af..76f47c572 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -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
*/
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index a48390732..17d106731 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -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
index 000000000..733a753c1
--- /dev/null
+++ b/src/com/android/launcher3/list/AutoScrollListView.java
@@ -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
index 000000000..b163c501c
--- /dev/null
+++ b/src/com/android/launcher3/list/CompositeCursorAdapter.java
@@ -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
index 000000000..cc053e18f
--- /dev/null
+++ b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java
@@ -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
index 000000000..58e8791ab
--- /dev/null
+++ b/src/com/android/launcher3/list/PinnedHeaderListView.java
@@ -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
index 000000000..aa2f45171
--- /dev/null
+++ b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java
@@ -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
index 000000000..a90478cdf
--- /dev/null
+++ b/src/com/android/launcher3/settings/SettingsProvider.java
@@ -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();
+ }
+}
diff --git a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
index 1bc7c1190..dc3e05e9d 100644
--- a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
+++ b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
@@ -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