aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Edelmann <florian-edelmann@online.de>2014-02-23 17:58:18 +0100
committerJorge Ruesga <jorge@ruesga.com>2014-03-10 11:04:36 +0100
commit00f1ea75b0a01eee3d5ece23e59610d6b4586c60 (patch)
treea8b50dbcdbc2d6db5f13f70feb19c103ff67c1e3
parentb3f536b0a36f4b76283a038cce29acb27e17fd6d (diff)
downloadandroid_packages_apps_CMFileManager-00f1ea75b0a01eee3d5ece23e59610d6b4586c60.tar.gz
android_packages_apps_CMFileManager-00f1ea75b0a01eee3d5ece23e59610d6b4586c60.tar.bz2
android_packages_apps_CMFileManager-00f1ea75b0a01eee3d5ece23e59610d6b4586c60.zip
[CMFM] bookmarks and history in a navigation drawer
* used a modified version of DrawerLayout from the Support Library * moved bookmarks and history there -> always accessible * added a drawer icon * made all themeable * removed the BookmarksActivity and HistoryActivity * removed the BookmarksAdapter and HistoryAdapter as they are useless for LinearLayouts. I couldn't use ListViews because they scroll vertically - as well as the drawer itself. * removed the layouts for bookmarks and history Patch Set 2: tab cleanup and reset code I didn't touch Patch Set 3: fixed a FC after changing the theme when settings were opened by the drawer's overflow menu Patch Set 4: updated commit message Patch Set 5: updated commit message Patch Set 6: disabled "clear history" ActionBar action when history is empty Patch Set 7: fixed code style, fixed theme change issue with history items in the drawer, updated theme previews Patch Set 8: remove trailing whitespaces Change-Id: I215211e771ec7c96a2a669cdcc440556c310d6db
-rw-r--r--AndroidManifest.xml4
-rw-r--r--res/drawable-hdpi/ic_holo_light_navigation_drawer.pngbin0 -> 97 bytes
-rw-r--r--res/drawable-mdpi/ic_holo_light_navigation_drawer.pngbin0 -> 88 bytes
-rw-r--r--res/drawable-nodpi/theme_preview.pngbin66556 -> 62470 bytes
-rw-r--r--res/drawable-xhdpi/ic_holo_light_navigation_drawer.pngbin0 -> 107 bytes
-rw-r--r--res/drawable-xxhdpi/ic_holo_light_navigation_drawer.pngbin0 -> 120 bytes
-rw-r--r--res/layout/bookmarks.xml36
-rw-r--r--res/layout/bookmarks_item.xml94
-rw-r--r--res/layout/history.xml56
-rw-r--r--res/layout/history_item.xml99
-rw-r--r--res/layout/navigation.xml68
-rw-r--r--res/layout/navigation_drawer.xml62
-rw-r--r--res/layout/navigation_view_statusbar.xml20
-rw-r--r--res/menu/drawer.xml41
-rw-r--r--res/menu/navigation.xml8
-rw-r--r--res/raw/changelog4
-rw-r--r--res/values/strings.xml4
-rw-r--r--res/values/styles.xml22
-rw-r--r--res/values/theme.xml6
-rw-r--r--src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java631
-rw-r--r--src/com/cyanogenmod/filemanager/activities/HistoryActivity.java412
-rw-r--r--src/com/cyanogenmod/filemanager/activities/NavigationActivity.java817
-rw-r--r--src/com/cyanogenmod/filemanager/activities/SearchActivity.java2
-rw-r--r--src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java1
-rw-r--r--src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java230
-rw-r--r--src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java218
-rw-r--r--src/com/cyanogenmod/filemanager/ui/ThemeManager.java19
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java14
-rw-r--r--src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java17
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java554
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/DrawerLayout.java1621
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/ViewDragHelper.java1450
-rw-r--r--themes/res/drawable-hdpi/ic_holo_dark_navigation_drawer.pngbin0 -> 100 bytes
-rw-r--r--themes/res/drawable-mdpi/ic_holo_dark_navigation_drawer.pngbin0 -> 91 bytes
-rw-r--r--themes/res/drawable-nodpi/dark_theme_preview.pngbin64772 -> 59876 bytes
-rw-r--r--themes/res/drawable-xhdpi/ic_holo_dark_navigation_drawer.pngbin0 -> 109 bytes
-rw-r--r--themes/res/drawable-xxhdpi/ic_holo_dark_navigation_drawer.pngbin0 -> 123 bytes
-rw-r--r--themes/res/values/dark_theme.xml6
38 files changed, 4633 insertions, 1883 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4c1ec934..bdb70ab9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cyanogenmod.filemanager"
- android:versionCode="101"
- android:versionName="1.0.1">
+ android:versionCode="102"
+ android:versionName="1.0.2">
<original-package android:name="com.cyanogenmod.filemanager" />
diff --git a/res/drawable-hdpi/ic_holo_light_navigation_drawer.png b/res/drawable-hdpi/ic_holo_light_navigation_drawer.png
new file mode 100644
index 00000000..3f1933b0
--- /dev/null
+++ b/res/drawable-hdpi/ic_holo_light_navigation_drawer.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_holo_light_navigation_drawer.png b/res/drawable-mdpi/ic_holo_light_navigation_drawer.png
new file mode 100644
index 00000000..93e1d5e3
--- /dev/null
+++ b/res/drawable-mdpi/ic_holo_light_navigation_drawer.png
Binary files differ
diff --git a/res/drawable-nodpi/theme_preview.png b/res/drawable-nodpi/theme_preview.png
index f385813a..ba98ce0b 100644
--- a/res/drawable-nodpi/theme_preview.png
+++ b/res/drawable-nodpi/theme_preview.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_holo_light_navigation_drawer.png b/res/drawable-xhdpi/ic_holo_light_navigation_drawer.png
new file mode 100644
index 00000000..b9529d04
--- /dev/null
+++ b/res/drawable-xhdpi/ic_holo_light_navigation_drawer.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_holo_light_navigation_drawer.png b/res/drawable-xxhdpi/ic_holo_light_navigation_drawer.png
new file mode 100644
index 00000000..a4bf10dd
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_holo_light_navigation_drawer.png
Binary files differ
diff --git a/res/layout/bookmarks.xml b/res/layout/bookmarks.xml
deleted file mode 100644
index 5ae41a9c..00000000
--- a/res/layout/bookmarks.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
- -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <com.cyanogenmod.filemanager.ui.widgets.FlingerListView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/bookmarks_listview"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- <ProgressBar
- android:id="@+id/bookmarks_waiting"
- android:layout_width="@dimen/default_row_height"
- android:layout_height="@dimen/default_row_height"
- android:layout_centerInParent="true"
- android:indeterminate="true"
- android:indeterminateOnly="true"
- android:visibility="gone" />
-
-</RelativeLayout> \ No newline at end of file
diff --git a/res/layout/bookmarks_item.xml b/res/layout/bookmarks_item.xml
index fc8e4be5..2070494a 100644
--- a/res/layout/bookmarks_item.xml
+++ b/res/layout/bookmarks_item.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The CyanogenMod Project
+<!--
+ Copyright (C) 2012 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.
@@ -12,58 +13,53 @@
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.
- -->
+-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/default_row_height"
- android:background="@drawable/holo_list_selector_deselected" >
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:background="@drawable/holo_list_selector_deselected"
+ android:orientation="horizontal"
+ android:focusable="true"
+ android:clickable="true" >
- <ImageView
- android:id="@+id/bookmarks_item_icon"
- android:layout_width="@dimen/default_row_height"
- android:layout_height="match_parent"
- android:contentDescription="@null"
- android:gravity="center_horizontal|center_vertical"
- android:paddingLeft="@dimen/extra_margin"
- android:src="@null" />
+ <ImageView
+ android:id="@+id/bookmarks_item_icon"
+ android:layout_width="48dp"
+ android:layout_height="match_parent"
+ android:layout_marginRight="2dp"
+ android:contentDescription="@null"
+ android:padding="8dp"
+ android:src="@null" />
- <com.cyanogenmod.filemanager.ui.widgets.NonFocusableButtonItem
- android:id="@+id/bookmarks_item_action"
- android:layout_width="@dimen/default_buttom_width"
- android:layout_height="match_parent"
- android:layout_alignParentRight="true"
- android:contentDescription="@null"
- android:gravity="center_horizontal|center_vertical"
- android:src="@null" />
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:orientation="vertical" >
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_toLeftOf="@id/bookmarks_item_action"
- android:layout_toRightOf="@id/bookmarks_item_icon" >
+ <TextView
+ android:id="@+id/bookmarks_item_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/primary_text_appearance" />
- <TextView
- android:id="@+id/bookmarks_item_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_marginLeft="@dimen/default_margin"
- android:layout_marginTop="@dimen/default_margin"
- android:singleLine="true"
- android:textAppearance="@style/primary_text_appearance" />
+ <TextView
+ android:id="@+id/bookmarks_item_path"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/secondary_text_appearance" />
+ </LinearLayout>
- <TextView
- android:id="@+id/bookmarks_item_path"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true"
- android:layout_marginBottom="@dimen/default_margin"
- android:layout_marginLeft="@dimen/default_margin"
- android:singleLine="true"
- android:textAppearance="@style/secondary_text_appearance" />
- </RelativeLayout>
+ <com.cyanogenmod.filemanager.ui.widgets.NonFocusableButtonItem
+ android:id="@+id/bookmarks_item_action"
+ android:layout_width="@dimen/default_buttom_width"
+ android:layout_height="match_parent"
+ android:contentDescription="@null"
+ android:gravity="center_horizontal|center_vertical"
+ android:src="@null" />
-</RelativeLayout> \ No newline at end of file
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/history.xml b/res/layout/history.xml
deleted file mode 100644
index 45a2993b..00000000
--- a/res/layout/history.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
- -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:id="@+id/history_empty_msg"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentTop="true"
- android:layout_alignParentBottom="true"
- android:layout_margin="@dimen/extra_margin"
- android:gravity="center_horizontal|center_vertical"
- android:text="@string/msgs_history_empty"
- android:textAppearance="@style/primary_text_appearance_nohighlight"
- android:textSize="@dimen/title_text_size"
- android:visibility="gone" />
-
- <ListView
- android:id="@+id/history_listview"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentTop="true" />
-
- <View
- android:id="@+id/history_status"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_alignParentBottom="true"
- android:visibility="invisible"/>
-
- <ProgressBar
- android:id="@+id/history_waiting"
- android:layout_width="@dimen/default_row_height"
- android:layout_height="@dimen/default_row_height"
- android:layout_centerInParent="true"
- android:indeterminate="true"
- android:indeterminateOnly="true"
- android:visibility="gone" />
-
-</RelativeLayout> \ No newline at end of file
diff --git a/res/layout/history_item.xml b/res/layout/history_item.xml
index 2ef5fe97..ad987140 100644
--- a/res/layout/history_item.xml
+++ b/res/layout/history_item.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The CyanogenMod Project
+<!--
+ Copyright (C) 2012 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.
@@ -12,62 +13,56 @@
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.
- -->
+-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/default_row_height"
- android:background="@drawable/holo_list_selector_deselected" >
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:background="@drawable/holo_list_selector_deselected"
+ android:clickable="true"
+ android:focusable="true"
+ android:orientation="horizontal" >
- <ImageView
- android:id="@+id/history_item_icon"
- android:layout_width="@dimen/default_row_height"
- android:layout_height="match_parent"
- android:contentDescription="@null"
- android:gravity="center_horizontal|center_vertical"
- android:paddingLeft="@dimen/extra_margin"
- android:src="@null" />
+ <ImageView
+ android:id="@+id/history_item_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginRight="2dp"
+ android:contentDescription="@null"
+ android:padding="8dp"
+ android:src="@null" />
- <TextView
- android:id="@+id/history_item_position"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:layout_marginLeft="@dimen/default_margin"
- android:layout_marginTop="@dimen/default_margin"
- android:paddingRight="@dimen/extra_margin"
- android:singleLine="true"
- android:textAppearance="@style/primary_text_appearance"
- android:textStyle="normal" />
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:orientation="vertical" >
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_toLeftOf="@id/history_item_position"
- android:layout_toRightOf="@id/history_item_icon" >
+ <TextView
+ android:id="@+id/history_item_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/primary_text_appearance" />
- <TextView
- android:id="@+id/history_item_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_marginLeft="@dimen/default_margin"
- android:layout_marginTop="@dimen/default_margin"
- android:singleLine="true"
- android:textAppearance="@style/primary_text_appearance" />
+ <TextView
+ android:id="@+id/history_item_directory"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/secondary_text_appearance" />
+ </LinearLayout>
<TextView
- android:id="@+id/history_item_directory"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true"
- android:layout_marginBottom="@dimen/default_margin"
- android:layout_marginLeft="@dimen/default_margin"
- android:singleLine="true"
- android:textAppearance="@style/secondary_text_appearance" />
- </RelativeLayout>
+ android:id="@+id/history_item_position"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="@dimen/default_margin"
+ android:paddingRight="@dimen/extra_margin"
+ android:singleLine="true"
+ android:textAppearance="@style/primary_text_appearance"
+ android:textStyle="normal" />
-</RelativeLayout> \ No newline at end of file
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/navigation.xml b/res/layout/navigation.xml
index 68df9c4f..051f35d6 100644
--- a/res/layout/navigation.xml
+++ b/res/layout/navigation.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The CyanogenMod Project
+<!--
+ Copyright (C) 2012 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.
@@ -12,33 +13,42 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:filemanager="http://schemas.android.com/apk/res/com.cyanogenmod.filemanager"
- android:id="@+id/navigation_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <!-- Navigation View -->
- <com.cyanogenmod.filemanager.ui.widgets.NavigationView
- android:id="@+id/navigation_view"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- filemanager:navigation="browsable" />
+-->
- <!-- SelectionBar -->
- <com.cyanogenmod.filemanager.ui.widgets.SelectionView
- android:id="@+id/navigation_selectionbar"
+<com.cyanogenmod.filemanager.ui.widgets.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:filemanager="http://schemas.android.com/apk/res/com.cyanogenmod.filemanager"
+ android:id="@+id/drawer_layout"
android:layout_width="match_parent"
- android:layout_height="@dimen/small_row_height"
- android:background="@drawable/bg_holo_selectionbar"
- android:visibility="invisible" />
-
- <!-- StatusBar -->
- <include layout="@layout/navigation_view_statusbar" />
-
-</LinearLayout>
+ android:layout_height="match_parent" >
+
+ <!-- The main content view -->
+ <LinearLayout
+ android:id="@+id/navigation_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <!-- Navigation View -->
+ <com.cyanogenmod.filemanager.ui.widgets.NavigationView
+ android:id="@+id/navigation_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ filemanager:navigation="browsable" />
+
+ <!-- SelectionBar -->
+ <com.cyanogenmod.filemanager.ui.widgets.SelectionView
+ android:id="@+id/navigation_selectionbar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/small_row_height"
+ android:background="@drawable/bg_holo_selectionbar"
+ android:visibility="invisible" />
+
+ <!-- StatusBar -->
+ <include layout="@layout/navigation_view_statusbar" />
+ </LinearLayout>
+
+ <!-- The navigation drawer -->
+ <include layout="@layout/navigation_drawer" />
+
+</com.cyanogenmod.filemanager.ui.widgets.DrawerLayout> \ No newline at end of file
diff --git a/res/layout/navigation_drawer.xml b/res/layout/navigation_drawer.xml
new file mode 100644
index 00000000..9398ede5
--- /dev/null
+++ b/res/layout/navigation_drawer.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer"
+ android:layout_width="240dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="@android:color/background_light" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/bookmarks_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/bookmarks"
+ style="@style/drawer_header" />
+
+ <ProgressBar
+ android:id="@+id/bookmarks_loading"
+ android:layout_width="@dimen/default_row_height"
+ android:layout_height="@dimen/default_row_height"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:visibility="gone" />
+
+ <LinearLayout
+ android:id="@+id/bookmarks_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/extra_margin"
+ android:orientation="vertical" >
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/history_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/history"
+ style="@style/drawer_header" />
+
+ <TextView
+ android:id="@+id/history_empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/msgs_history_empty"
+ android:paddingLeft="@dimen/extra_margin"
+ style="@style/secondary_text_appearance" />
+
+ <LinearLayout
+ android:id="@+id/history_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/extra_margin"
+ android:orientation="vertical" >
+ </LinearLayout>
+ </LinearLayout>
+
+</ScrollView> \ No newline at end of file
diff --git a/res/layout/navigation_view_statusbar.xml b/res/layout/navigation_view_statusbar.xml
index 3474265c..47ba2ac9 100644
--- a/res/layout/navigation_view_statusbar.xml
+++ b/res/layout/navigation_view_statusbar.xml
@@ -60,26 +60,6 @@
android:src="@drawable/ic_holo_light_search"
android:visibility="invisible" />
- <com.cyanogenmod.filemanager.ui.widgets.ButtonItem
- android:id="@+id/ab_bookmarks"
- android:layout_width="@dimen/default_buttom_width"
- android:layout_height="match_parent"
- android:layout_toLeftOf="@id/ab_search"
- android:contentDescription="@string/actionbar_button_bookmarks_cd"
- android:onClick="onActionBarItemClick"
- android:src="@drawable/ic_holo_light_bookmarks"
- android:visibility="invisible" />
-
- <com.cyanogenmod.filemanager.ui.widgets.ButtonItem
- android:id="@+id/ab_history"
- android:layout_width="@dimen/default_buttom_width"
- android:layout_height="match_parent"
- android:layout_toLeftOf="@id/ab_bookmarks"
- android:contentDescription="@string/actionbar_button_history_cd"
- android:onClick="onActionBarItemClick"
- android:src="@drawable/ic_holo_light_history"
- android:visibility="invisible" />
-
</RelativeLayout>
</RelativeLayout>
diff --git a/res/menu/drawer.xml b/res/menu/drawer.xml
new file mode 100644
index 00000000..7a5ef1d5
--- /dev/null
+++ b/res/menu/drawer.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ** Copyright (C) 2012 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <!--
+ set android:visible="false" for every item as it should
+ not be visible until the drawer gets opened
+ -->
+
+ <!-- TODO: add functionality to set bookmark in NavigationActivity.java
+ <item
+ android:id="@+id/mnu_actions_add_to_bookmarks_current_folder"
+ android:showAsAction="never"
+ android:title="@string/actions_menu_add_to_bookmarks"
+ android:visible="false"/>-->
+ <item
+ android:id="@+id/mnu_clear_history"
+ android:showAsAction="never"
+ android:title="@string/menu_clear_history"
+ android:visible="false"/>
+ <item
+ android:id="@+id/mnu_settings"
+ android:showAsAction="never"
+ android:title="@string/menu_settings"
+ android:visible="false"/>
+
+</menu> \ No newline at end of file
diff --git a/res/menu/navigation.xml b/res/menu/navigation.xml
index 9aef8541..5111c94f 100644
--- a/res/menu/navigation.xml
+++ b/res/menu/navigation.xml
@@ -18,14 +18,6 @@
<!-- Actions -->
<item
- android:id="@+id/mnu_history"
- android:showAsAction="ifRoom"
- android:title="@string/menu_history"/>
- <item
- android:id="@+id/mnu_bookmarks"
- android:showAsAction="ifRoom"
- android:title="@string/menu_bookmarks"/>
- <item
android:id="@+id/mnu_search"
android:showAsAction="ifRoom"
android:title="@string/menu_search"/>
diff --git a/res/raw/changelog b/res/raw/changelog
index c64271e6..447938cc 100644
--- a/res/raw/changelog
+++ b/res/raw/changelog
@@ -1,6 +1,10 @@
CyanogenMod File Manager
========================
+Version 1.0.2
+-------------
+* move bookmarks and history into a navigation drawer (by Florian Edelmann)
+
Version 1.0.1
-------------
* NFC support
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 54bcb787..77fea1ef 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -722,6 +722,10 @@
<!-- Themes - Default theme author -->
<string name="themes_author">CyanogenMod</string>
+ <!-- Navigation drawer -->
+ <string name="drawer_open">Open navigation drawer</string>
+ <string name="drawer_close">Close navigation drawer</string>
+
<!-- ColorPickerDialog -->
<!-- The text of the alpha slider control -->
<string name="color_picker_alpha_slider_text">Alpha</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b6cd4a32..81b80a6a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -15,16 +15,14 @@
limitations under the License.
-->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:android="http://schemas.android.com/apk/res/android">
+
<!-- Base themes -->
<style name="FileManager.Theme.Holo.Light" parent="@android:style/Theme.Holo.Light">
<item name="android:windowBackground">@color/default_background</item>
<item name="android:actionBarStyle">@style/FileManager.Widget.ActionBar.White</item>
- <item name="android:homeAsUpIndicator">@drawable/ic_ab_back_holo_light</item>
- </style>
- <style name="FileManager.Theme.Holo" parent="@android:style/Theme.Holo">
- <item name="android:homeAsUpIndicator">@drawable/ic_ab_back_holo_light</item>
</style>
+ <style name="FileManager.Theme.Holo" parent="@android:style/Theme.Holo"></style>
<!-- A theme with overlay -->
<style name="FileManager.Theme.Holo.Light.Overlay" parent="@style/FileManager.Theme.Holo.Light">
@@ -113,7 +111,7 @@
<item name="android:minWidth">@dimen/breadcrumb_item_min_size</item>
</style>
- <!-- Breadcrumb ActionBar buttom -->
+ <!-- Breadcrumb ActionBar buttom -->
<style name="breadcrumb_actionbar_buttom">
<item name="android:paddingLeft">@dimen/extra_margin</item>
<item name="android:paddingRight">@dimen/default_margin</item>
@@ -140,4 +138,14 @@
<item name="android:layout_marginRight">@dimen/default_margin</item>
<item name="android:background">@color/divider_color</item>
</style>
-</resources>
+
+ <!-- Navigation drawer -->
+ <style name="drawer_header">
+ <item name="android:padding">@dimen/default_margin</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textSize">@dimen/primary_text_size</item>
+ <item name="android:textColor">@color/black_transparent</item>
+ <item name="android:textStyle">bold</item>
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/res/values/theme.xml b/res/values/theme.xml
index f6f4006d..d5128c91 100644
--- a/res/values/theme.xml
+++ b/res/values/theme.xml
@@ -83,6 +83,12 @@
<!-- The highlight color for terms found in the search result -->
<color name="search_highlight_color">@color/search_highlight</color>
+ <!-- The background color of the navigation drawer -->
+ <color name="drawer_color">#ffffffff</color>
+
+ <!-- The drawer icon -->
+ <drawable name="drawer_icon">@drawable/ic_holo_light_navigation_drawer</drawable>
+
<!-- The breadcrumb divider drawable -->
<drawable name="breadcrumb_divider_drawable">@drawable/ic_holo_light_breadcrumb_divider</drawable>
diff --git a/src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java b/src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java
deleted file mode 100644
index 68a8fedf..00000000
--- a/src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java
+++ /dev/null
@@ -1,631 +0,0 @@
-/*
- * Copyright (C) 2012 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.cyanogenmod.filemanager.activities;
-
-import android.app.ActionBar;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.content.res.XmlResourceParser;
-import android.database.Cursor;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.storage.StorageVolume;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.util.XmlUtils;
-import com.cyanogenmod.filemanager.FileManagerApplication;
-import com.cyanogenmod.filemanager.R;
-import com.cyanogenmod.filemanager.adapters.BookmarksAdapter;
-import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
-import com.cyanogenmod.filemanager.model.Bookmark;
-import com.cyanogenmod.filemanager.model.Bookmark.BOOKMARK_TYPE;
-import com.cyanogenmod.filemanager.model.FileSystemObject;
-import com.cyanogenmod.filemanager.preferences.AccessMode;
-import com.cyanogenmod.filemanager.preferences.Bookmarks;
-import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
-import com.cyanogenmod.filemanager.preferences.Preferences;
-import com.cyanogenmod.filemanager.ui.ThemeManager;
-import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
-import com.cyanogenmod.filemanager.ui.dialogs.InitialDirectoryDialog;
-import com.cyanogenmod.filemanager.ui.widgets.FlingerListView;
-import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerListener;
-import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder;
-import com.cyanogenmod.filemanager.util.CommandHelper;
-import com.cyanogenmod.filemanager.util.DialogHelper;
-import com.cyanogenmod.filemanager.util.ExceptionUtil;
-import com.cyanogenmod.filemanager.util.StorageHelper;
-
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * An activity for show bookmarks and links.
- */
-public class BookmarksActivity extends Activity implements OnItemClickListener, OnClickListener {
-
- private static final String TAG = "BookmarksActivity"; //$NON-NLS-1$
-
- private static boolean DEBUG = false;
-
- /**
- * A listener for flinging events from {@link FlingerListView}
- */
- private final OnItemFlingerListener mOnItemFlingerListener = new OnItemFlingerListener() {
-
- @Override
- public boolean onItemFlingerStart(
- AdapterView<?> parent, View view, int position, long id) {
- try {
- // Response if the item can be removed
- BookmarksAdapter adapter = (BookmarksAdapter)parent.getAdapter();
- Bookmark bookmark = adapter.getItem(position);
- if (bookmark != null &&
- bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) {
- return true;
- }
- } catch (Exception e) {
- ExceptionUtil.translateException(BookmarksActivity.this, e, true, false);
- }
- return false;
- }
-
- @Override
- public void onItemFlingerEnd(OnItemFlingerResponder responder,
- AdapterView<?> parent, View view, int position, long id) {
-
- try {
- // Response if the item can be removed
- BookmarksAdapter adapter = (BookmarksAdapter)parent.getAdapter();
- Bookmark bookmark = adapter.getItem(position);
- if (bookmark != null &&
- bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) {
- boolean result = Bookmarks.removeBookmark(BookmarksActivity.this, bookmark);
- if (!result) {
- //Show warning
- DialogHelper.showToast(BookmarksActivity.this,
- R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
- responder.cancel();
- return;
- }
- responder.accept();
- adapter.remove(bookmark);
- return;
- }
-
- // Cancels the flinger operation
- responder.cancel();
-
- } catch (Exception e) {
- ExceptionUtil.translateException(BookmarksActivity.this, e, true, false);
- responder.cancel();
- }
- }
- };
-
- private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null) {
- if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) {
- applyTheme();
- }
- }
- }
- };
-
- // Bookmark list XML tags
- private static final String TAG_BOOKMARKS = "Bookmarks"; //$NON-NLS-1$
- private static final String TAG_BOOKMARK = "bookmark"; //$NON-NLS-1$
-
- /**
- * @hide
- */
- ListView mBookmarksListView;
-
- private boolean mChRooted;
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onCreate(Bundle state) {
- if (DEBUG) {
- Log.d(TAG, "BookmarksActivity.onCreate"); //$NON-NLS-1$
- }
-
- // Register the broadcast receiver
- IntentFilter filter = new IntentFilter();
- filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
- registerReceiver(this.mNotificationReceiver, filter);
-
- // Is ChRooted?
- this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0;
-
- //Set in transition
- overridePendingTransition(R.anim.translate_to_right_in, R.anim.hold_out);
-
- //Set the main layout of the activity
- setContentView(R.layout.bookmarks);
-
- //Initialize action bars and data
- initTitleActionBar();
- initBookmarks();
-
- // Apply the theme
- applyTheme();
-
- //Save state
- super.onCreate(state);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onDestroy() {
- if (DEBUG) {
- Log.d(TAG, "BookmarksActivity.onDestroy"); //$NON-NLS-1$
- }
-
- // Unregister the receiver
- try {
- unregisterReceiver(this.mNotificationReceiver);
- } catch (Throwable ex) {
- /**NON BLOCK**/
- }
-
- //All destroy. Continue
- super.onDestroy();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onPause() {
- //Set out transition
- overridePendingTransition(R.anim.hold_in, R.anim.translate_to_left_out);
- super.onPause();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- }
-
- /**
- * Method that initializes the titlebar of the activity.
- */
- private void initTitleActionBar() {
- //Configure the action bar options
- getActionBar().setBackgroundDrawable(
- getResources().getDrawable(R.drawable.bg_holo_titlebar));
- getActionBar().setDisplayOptions(
- ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
- getActionBar().setDisplayHomeAsUpEnabled(true);
- View customTitle = getLayoutInflater().inflate(R.layout.simple_customtitle, null, false);
- TextView title = (TextView)customTitle.findViewById(R.id.customtitle_title);
- title.setText(R.string.bookmarks);
- title.setContentDescription(getString(R.string.bookmarks));
- getActionBar().setCustomView(customTitle);
- }
-
- /**
- * Method that initializes the titlebar of the activity.
- */
- private void initBookmarks() {
- this.mBookmarksListView = (ListView)findViewById(R.id.bookmarks_listview);
- List<Bookmark> bookmarks = new ArrayList<Bookmark>();
- BookmarksAdapter adapter = new BookmarksAdapter(this, bookmarks, this);
- this.mBookmarksListView.setAdapter(adapter);
- this.mBookmarksListView.setOnItemClickListener(this);
-
- // If we should set the listview to response to flinger gesture detection
- boolean useFlinger =
- Preferences.getSharedPreferences().getBoolean(
- FileManagerSettings.SETTINGS_USE_FLINGER.getId(),
- ((Boolean)FileManagerSettings.
- SETTINGS_USE_FLINGER.
- getDefaultValue()).booleanValue());
- if (useFlinger) {
- ((FlingerListView)this.mBookmarksListView).
- setOnItemFlingerListener(this.mOnItemFlingerListener);
- }
-
- // Reload the data
- refresh();
- }
-
- /**
- * Method that makes the refresh of the data.
- */
- void refresh() {
- // Retrieve the loading view
- final View waiting = findViewById(R.id.bookmarks_waiting);
- final BookmarksAdapter adapter = (BookmarksAdapter)this.mBookmarksListView.getAdapter();
-
- // Load the history in background
- AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
- Exception mCause;
- List<Bookmark> mBookmarks;
-
- @Override
- protected Boolean doInBackground(Void... params) {
- try {
- this.mBookmarks = loadBookmarks();
- return Boolean.TRUE;
-
- } catch (Exception e) {
- this.mCause = e;
- return Boolean.FALSE;
- }
- }
-
- @Override
- protected void onPreExecute() {
- waiting.setVisibility(View.VISIBLE);
- adapter.clear();
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- waiting.setVisibility(View.GONE);
- if (result.booleanValue()) {
- adapter.addAll(this.mBookmarks);
- BookmarksActivity.this.mBookmarksListView.setSelection(0);
-
- } else {
- if (this.mCause != null) {
- ExceptionUtil.translateException(BookmarksActivity.this, this.mCause);
- }
- }
- }
-
- @Override
- protected void onCancelled() {
- waiting.setVisibility(View.GONE);
- }
- };
- task.execute();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_BACK:
- back(true, null);
- return true;
- default:
- return super.onKeyUp(keyCode, event);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- back(true, null);
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- Bookmark bookmark = ((BookmarksAdapter)parent.getAdapter()).getItem(position);
- back(false, bookmark.mPath);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onClick(View v) {
- //Retrieve the position
- final int position = ((Integer)v.getTag()).intValue();
- final BookmarksAdapter adapter = (BookmarksAdapter)this.mBookmarksListView.getAdapter();
- final Bookmark bookmark = adapter.getItem(position);
-
- //Configure home
- if (bookmark.mType.compareTo(BOOKMARK_TYPE.HOME) == 0) {
- //Show a dialog for configure initial directory
- InitialDirectoryDialog dialog = new InitialDirectoryDialog(this);
- dialog.setOnValueChangedListener(new InitialDirectoryDialog.OnValueChangedListener() {
- @Override
- public void onValueChanged(String newInitialDir) {
- adapter.getItem(position).mPath = newInitialDir;
- adapter.notifyDataSetChanged();
- }
- });
- dialog.show();
- return;
- }
-
- //Remove bookmark
- if (bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) {
- boolean result = Bookmarks.removeBookmark(this, bookmark);
- if (!result) {
- //Show warning
- DialogHelper.showToast(this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
- return;
- }
- adapter.remove(bookmark);
- return;
- }
- }
-
- /**
- * Method that returns to previous activity and.
- *
- * @param cancelled Indicates if the activity was cancelled
- * @param path The path of the selected bookmark
- */
- private void back(final boolean cancelled, final String path) {
- Intent intent = new Intent();
- if (cancelled) {
- setResult(RESULT_CANCELED, intent);
- } else {
- // Check that the bookmark exists
- try {
- FileSystemObject fso = CommandHelper.getFileInfo(this, path, null);
- if (fso != null) {
- intent.putExtra(NavigationActivity.EXTRA_BOOKMARK_SELECTION, fso);
- setResult(RESULT_OK, intent);
- } else {
- // The bookmark not exists, delete the user-defined bookmark
- try {
- Bookmark b = Bookmarks.getBookmark(getContentResolver(), path);
- Bookmarks.removeBookmark(this, b);
- refresh();
- } catch (Exception ex) {/**NON BLOCK**/}
- }
- } catch (Exception e) {
- // Capture the exception
- ExceptionUtil.translateException(this, e);
- if (e instanceof NoSuchFileOrDirectory || e instanceof FileNotFoundException) {
- // The bookmark not exists, delete the user-defined bookmark
- try {
- Bookmark b = Bookmarks.getBookmark(getContentResolver(), path);
- Bookmarks.removeBookmark(this, b);
- refresh();
- } catch (Exception ex) {/**NON BLOCK**/}
- }
- return;
- }
- }
- finish();
- }
-
- /**
- * Method that loads all kind of bookmarks and join in
- * an array to be used in the listview adapter.
- *
- * @return List<Bookmark>
- * @hide
- */
- List<Bookmark> loadBookmarks() {
- // Bookmarks = HOME + FILESYSTEM + SD STORAGES + USER DEFINED
- // In ChRooted mode = SD STORAGES + USER DEFINED (from SD STORAGES)
- List<Bookmark> bookmarks = new ArrayList<Bookmark>();
- if (!this.mChRooted) {
- bookmarks.add(loadHomeBookmarks());
- bookmarks.addAll(loadFilesystemBookmarks());
- }
- bookmarks.addAll(loadSdStorageBookmarks());
- bookmarks.addAll(loadUserBookmarks());
- return bookmarks;
- }
-
- /**
- * Method that loads the home bookmark from the user preference.
- *
- * @return Bookmark The bookmark loaded
- */
- private Bookmark loadHomeBookmarks() {
- String initialDir = Preferences.getSharedPreferences().getString(
- FileManagerSettings.SETTINGS_INITIAL_DIR.getId(),
- (String)FileManagerSettings.SETTINGS_INITIAL_DIR.getDefaultValue());
- return new Bookmark(BOOKMARK_TYPE.HOME, getString(R.string.bookmarks_home), initialDir);
- }
-
- /**
- * Method that loads the filesystem bookmarks from the internal xml file.
- * (defined by this application)
- *
- * @return List<Bookmark> The bookmarks loaded
- */
- private List<Bookmark> loadFilesystemBookmarks() {
- try {
- //Initialize the bookmarks
- List<Bookmark> bookmarks = new ArrayList<Bookmark>();
-
- //Read the command list xml file
- XmlResourceParser parser = getResources().getXml(R.xml.filesystem_bookmarks);
-
- try {
- //Find the root element
- XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
- while (true) {
- XmlUtils.nextElement(parser);
- String element = parser.getName();
- if (element == null) {
- break;
- }
-
- if (TAG_BOOKMARK.equals(element)) {
- CharSequence name = null;
- CharSequence directory = null;
-
- try {
- name =
- getString(parser.getAttributeResourceValue(
- R.styleable.Bookmark_name, 0));
- } catch (Exception e) {/**NON BLOCK**/}
- try {
- directory =
- getString(parser.getAttributeResourceValue(
- R.styleable.Bookmark_directory, 0));
- } catch (Exception e) {/**NON BLOCK**/}
- if (directory == null) {
- directory =
- parser.getAttributeValue(R.styleable.Bookmark_directory);
- }
- if (name != null && directory != null) {
- bookmarks.add(
- new Bookmark(
- BOOKMARK_TYPE.FILESYSTEM,
- name.toString(),
- directory.toString()));
- }
- }
- }
-
- //Return the bookmarks
- return bookmarks;
-
- } finally {
- parser.close();
- }
- } catch (Throwable ex) {
- Log.e(TAG, "Load filesystem bookmarks failed", ex); //$NON-NLS-1$
- }
-
- //No data
- return new ArrayList<Bookmark>();
- }
-
- /**
- * Method that loads the secure digital card storage bookmarks from the system.
- *
- * @return List<Bookmark> The bookmarks loaded
- */
- private List<Bookmark> loadSdStorageBookmarks() {
- //Initialize the bookmarks
- List<Bookmark> bookmarks = new ArrayList<Bookmark>();
-
- try {
- //Recovery sdcards from storage manager
- StorageVolume[] volumes = StorageHelper.getStorageVolumes(getApplication());
- int cc = volumes.length;
- for (int i = 0; i < cc ; i++) {
- if (volumes[i].getPath().toLowerCase(Locale.ROOT).indexOf("usb") != -1) { //$NON-NLS-1$
- bookmarks.add(
- new Bookmark(
- BOOKMARK_TYPE.USB,
- StorageHelper.getStorageVolumeDescription(
- getApplication(), volumes[i]),
- volumes[i].getPath()));
- } else {
- bookmarks.add(
- new Bookmark(
- BOOKMARK_TYPE.SDCARD,
- StorageHelper.getStorageVolumeDescription(
- getApplication(), volumes[i]),
- volumes[i].getPath()));
- }
- }
-
- //Return the bookmarks
- return bookmarks;
- } catch (Throwable ex) {
- Log.e(TAG, "Load filesystem bookmarks failed", ex); //$NON-NLS-1$
- }
-
- //No data
- return new ArrayList<Bookmark>();
- }
-
- /**
- * Method that loads the user bookmarks (added by the user).
- *
- * @return List<Bookmark> The bookmarks loaded
- */
- private List<Bookmark> loadUserBookmarks() {
- List<Bookmark> bookmarks = new ArrayList<Bookmark>();
- Cursor cursor = Bookmarks.getAllBookmarks(this.getContentResolver());
- try {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- Bookmark bm = new Bookmark(cursor);
- if (this.mChRooted && !StorageHelper.isPathInStorageVolume(bm.mPath)) {
- continue;
- }
- bookmarks.add(bm);
- } while (cursor.moveToNext());
- }
- } finally {
- try {
- if (cursor != null) {
- cursor.close();
- }
- } catch (Exception e) {/**NON BLOCK**/}
- }
- return bookmarks;
- }
-
- /**
- * Method that applies the current theme to the activity
- * @hide
- */
- void applyTheme() {
- Theme theme = ThemeManager.getCurrentTheme(this);
- theme.setBaseTheme(this, false);
-
- //- ActionBar
- theme.setTitlebarDrawable(this, getActionBar(), "titlebar_drawable"); //$NON-NLS-1$
- View v = getActionBar().getCustomView().findViewById(R.id.customtitle_title);
- theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
- // -View
- theme.setBackgroundDrawable(
- this, this.mBookmarksListView, "background_drawable"); //$NON-NLS-1$
- if (((BookmarksAdapter)this.mBookmarksListView.getAdapter()) != null) {
- ((BookmarksAdapter)this.mBookmarksListView.getAdapter()).notifyThemeChanged();
- ((BookmarksAdapter)this.mBookmarksListView.getAdapter()).notifyDataSetChanged();
- }
- this.mBookmarksListView.setDivider(
- theme.getDrawable(this, "horizontal_divider_drawable")); //$NON-NLS-1$
- this.mBookmarksListView.invalidate();
- }
-}
diff --git a/src/com/cyanogenmod/filemanager/activities/HistoryActivity.java b/src/com/cyanogenmod/filemanager/activities/HistoryActivity.java
deleted file mode 100644
index 84de995c..00000000
--- a/src/com/cyanogenmod/filemanager/activities/HistoryActivity.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (C) 2012 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.cyanogenmod.filemanager.activities;
-
-import android.app.ActionBar;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ImageView;
-import android.widget.ListPopupWindow;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.cyanogenmod.filemanager.R;
-import com.cyanogenmod.filemanager.adapters.HighlightedSimpleMenuListAdapter;
-import com.cyanogenmod.filemanager.adapters.HistoryAdapter;
-import com.cyanogenmod.filemanager.adapters.SimpleMenuListAdapter;
-import com.cyanogenmod.filemanager.model.History;
-import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
-import com.cyanogenmod.filemanager.ui.ThemeManager;
-import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
-import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
-import com.cyanogenmod.filemanager.util.AndroidHelper;
-import com.cyanogenmod.filemanager.util.DialogHelper;
-import com.cyanogenmod.filemanager.util.ExceptionUtil;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * An activity for show navigation history.
- */
-public class HistoryActivity extends Activity implements OnItemClickListener {
-
- private static final String TAG = "HistoryActivity"; //$NON-NLS-1$
-
- private static boolean DEBUG = false;
-
- private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null) {
- if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) {
- applyTheme();
- }
- }
- }
- };
-
- /**
- * @hide
- */
- ListView mListView;
- /**
- * @hide
- */
- HistoryAdapter mAdapter;
- /**
- * @hide
- */
- boolean mIsEmpty;
- private boolean mIsClearHistory;
-
- private View mOptionsAnchorView;
-
- /**
- * Intent extra parameter for the history data.
- */
- public static final String EXTRA_HISTORY_LIST = "extra_history_list"; //$NON-NLS-1$
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onCreate(Bundle state) {
- if (DEBUG) {
- Log.d(TAG, "HistoryActivity.onCreate"); //$NON-NLS-1$
- }
-
- // Register the broadcast receiver
- IntentFilter filter = new IntentFilter();
- filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
- registerReceiver(this.mNotificationReceiver, filter);
-
- this.mIsEmpty = false;
- this.mIsClearHistory = false;
-
- //Set in transition
- overridePendingTransition(R.anim.translate_to_right_in, R.anim.hold_out);
-
- //Set the main layout of the activity
- setContentView(R.layout.history);
-
- //Initialize action bars and data
- initTitleActionBar();
- initHistory();
-
- // Apply the theme
- applyTheme();
-
- //Save state
- super.onCreate(state);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onDestroy() {
- if (DEBUG) {
- Log.d(TAG, "HistoryActivity.onDestroy"); //$NON-NLS-1$
- }
-
- // Unregister the receiver
- try {
- unregisterReceiver(this.mNotificationReceiver);
- } catch (Throwable ex) {
- /**NON BLOCK**/
- }
-
- //All destroy. Continue
- super.onDestroy();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onPause() {
- //Set out transition
- overridePendingTransition(R.anim.hold_in, R.anim.translate_to_left_out);
- super.onPause();
- }
-
- /**
- * Method that initializes the titlebar of the activity.
- */
- private void initTitleActionBar() {
- //Configure the action bar options
- getActionBar().setBackgroundDrawable(
- getResources().getDrawable(R.drawable.bg_holo_titlebar));
- getActionBar().setDisplayOptions(
- ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
- getActionBar().setDisplayHomeAsUpEnabled(true);
- View customTitle = getLayoutInflater().inflate(R.layout.simple_customtitle, null, false);
- TextView title = (TextView)customTitle.findViewById(R.id.customtitle_title);
- title.setText(R.string.history);
- title.setContentDescription(getString(R.string.history));
- ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
- configuration.setImageResource(R.drawable.ic_holo_light_overflow);
- configuration.setContentDescription(getString(R.string.actionbar_button_overflow_cd));
-
- View status = findViewById(R.id.history_status);
- boolean showOptionsMenu = AndroidHelper.showOptionsMenu(getApplicationContext());
- configuration.setVisibility(showOptionsMenu ? View.VISIBLE : View.GONE);
- this.mOptionsAnchorView = showOptionsMenu ? configuration : status;
-
- getActionBar().setCustomView(customTitle);
- }
-
- /**
- * Method invoked when an action item is clicked.
- *
- * @param view The button pushed
- */
- public void onActionBarItemClick(View view) {
- switch (view.getId()) {
- case R.id.ab_button1:
- //Overflow
- showOverflowPopUp(view);
- break;
-
- default:
- break;
- }
- }
-
- /**
- * Method that initializes the titlebar of the activity.
- */
- @SuppressWarnings("unchecked")
- private void initHistory() {
- // Retrieve the loading view
- final View waiting = findViewById(R.id.history_waiting);
-
- this.mListView = (ListView)findViewById(R.id.history_listview);
-
- // Load the history in background
- AsyncTask<Void, Void, List<History>> task = new AsyncTask<Void, Void, List<History>>() {
- Exception mCause;
- List<History> mHistory;
-
- @Override
- protected List<History> doInBackground(Void... params) {
- try {
- this.mHistory =
- (List<History>)getIntent().getSerializableExtra(EXTRA_HISTORY_LIST);
- if (this.mHistory.isEmpty()) {
- View msg = findViewById(R.id.history_empty_msg);
- msg.setVisibility(View.VISIBLE);
- return new ArrayList<History>();
- }
- HistoryActivity.this.mIsEmpty = this.mHistory.isEmpty();
-
- //Show inverted history
- final List<History> adapterList = new ArrayList<History>(this.mHistory);
- Collections.reverse(adapterList);
- return adapterList;
-
- } catch (Exception e) {
- this.mCause = e;
- return null;
- }
- }
-
- @Override
- protected void onPreExecute() {
- waiting.setVisibility(View.VISIBLE);
- }
-
- @Override
- protected void onPostExecute(List<History> result) {
- waiting.setVisibility(View.GONE);
- if (result != null) {
- HistoryActivity.this.mAdapter =
- new HistoryAdapter(HistoryActivity.this, result);
-
- if (HistoryActivity.this.mListView != null &&
- HistoryActivity.this.mAdapter != null) {
-
- HistoryActivity.this.mListView.
- setAdapter(HistoryActivity.this.mAdapter);
- HistoryActivity.this.mListView.
- setOnItemClickListener(HistoryActivity.this);
- }
-
- } else {
- if (this.mCause != null) {
- ExceptionUtil.translateException(HistoryActivity.this, this.mCause);
- }
- }
- }
-
- @Override
- protected void onCancelled() {
- waiting.setVisibility(View.GONE);
- }
- };
- task.execute();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_MENU:
- if (!this.mIsEmpty) {
- showOverflowPopUp(this.mOptionsAnchorView);
- }
- return true;
- case KeyEvent.KEYCODE_BACK:
- back(true, null);
- return true;
- default:
- return super.onKeyUp(keyCode, event);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- back(true, null);
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- History history = ((HistoryAdapter)parent.getAdapter()).getItem(position);
- back(false, history);
- }
-
- /**
- * Method that returns to previous activity and.
- *
- * @param cancelled Indicates if the activity was cancelled
- * @param history The selected history
- */
- private void back(final boolean cancelled, final History history) {
- Intent intent = new Intent();
- if (cancelled) {
- if (this.mIsClearHistory) {
- intent.putExtra(NavigationActivity.EXTRA_HISTORY_CLEAR, true);
- }
- setResult(RESULT_CANCELED, intent);
- } else {
- intent.putExtra(NavigationActivity.EXTRA_HISTORY_ENTRY_SELECTION, history);
- setResult(RESULT_OK, intent);
- }
- finish();
- }
-
- /**
- * Method that clean the history and return back to navigation view
- * @hide
- */
- void clearHistory() {
- if (this.mAdapter != null) {
- this.mAdapter.clear();
- View msg = findViewById(R.id.history_empty_msg);
- msg.setVisibility(View.VISIBLE);
- this.mIsClearHistory = true;
- }
- }
-
- /**
- * Method that shows a popup with the activity main menu.
- *
- * @param anchor The anchor of the popup
- */
- private void showOverflowPopUp(View anchor) {
- SimpleMenuListAdapter adapter =
- new HighlightedSimpleMenuListAdapter(this, R.menu.history);
- final ListPopupWindow popup =
- DialogHelper.createListPopupWindow(this, adapter, anchor);
- popup.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(
- final AdapterView<?> parent, final View v,
- final int position, final long id) {
- final int itemId = (int)id;
- switch (itemId) {
- case R.id.mnu_clear_history:
- popup.dismiss();
- clearHistory();
- break;
- }
- }
- });
- popup.show();
- }
-
- /**
- * Method that applies the current theme to the activity
- * @hide
- */
- void applyTheme() {
- Theme theme = ThemeManager.getCurrentTheme(this);
- theme.setBaseTheme(this, false);
-
- //- ActionBar
- theme.setTitlebarDrawable(this, getActionBar(), "titlebar_drawable"); //$NON-NLS-1$
- View v = getActionBar().getCustomView().findViewById(R.id.customtitle_title);
- theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
- v = findViewById(R.id.ab_button1);
- theme.setImageDrawable(this, (ImageView)v, "ab_overflow_drawable"); //$NON-NLS-1$
- // -View
- theme.setBackgroundDrawable(this, this.mListView, "background_drawable"); //$NON-NLS-1$
- if (this.mAdapter != null) {
- this.mAdapter.notifyThemeChanged();
- this.mAdapter.notifyDataSetChanged();
- }
- this.mListView.setDivider(
- theme.getDrawable(this, "horizontal_divider_drawable")); //$NON-NLS-1$
- this.mListView.invalidate();
- }
-}
diff --git a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java
index 3d97a04a..68852945 100644
--- a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java
+++ b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java
@@ -26,28 +26,47 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.nfc.NfcEvent;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.storage.StorageVolume;
+import android.transition.Visibility;
import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.ListAdapter;
import android.widget.ListPopupWindow;
+import android.widget.ListView;
import android.widget.PopupWindow;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.internal.util.XmlUtils;
import com.cyanogenmod.filemanager.FileManagerApplication;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences;
@@ -61,31 +80,40 @@ import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
import com.cyanogenmod.filemanager.listeners.OnHistoryListener;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
+import com.cyanogenmod.filemanager.model.Bookmark;
import com.cyanogenmod.filemanager.model.DiskUsage;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.History;
import com.cyanogenmod.filemanager.model.MountPoint;
+import com.cyanogenmod.filemanager.model.Bookmark.BOOKMARK_TYPE;
import com.cyanogenmod.filemanager.parcelables.HistoryNavigable;
import com.cyanogenmod.filemanager.parcelables.NavigationViewInfoParcelable;
import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable;
import com.cyanogenmod.filemanager.preferences.AccessMode;
+import com.cyanogenmod.filemanager.preferences.Bookmarks;
import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
import com.cyanogenmod.filemanager.preferences.NavigationLayoutMode;
import com.cyanogenmod.filemanager.preferences.ObjectIdentifier;
import com.cyanogenmod.filemanager.preferences.Preferences;
+import com.cyanogenmod.filemanager.ui.IconHolder;
import com.cyanogenmod.filemanager.ui.ThemeManager;
import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
import com.cyanogenmod.filemanager.ui.dialogs.ActionsDialog;
import com.cyanogenmod.filemanager.ui.dialogs.FilesystemInfoDialog;
+import com.cyanogenmod.filemanager.ui.dialogs.InitialDirectoryDialog;
import com.cyanogenmod.filemanager.ui.dialogs.FilesystemInfoDialog.OnMountListener;
+import com.cyanogenmod.filemanager.ui.widgets.ActionBarDrawerToggle;
import com.cyanogenmod.filemanager.ui.widgets.Breadcrumb;
import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
+import com.cyanogenmod.filemanager.ui.widgets.DrawerLayout;
+import com.cyanogenmod.filemanager.ui.widgets.FlingerListView;
import com.cyanogenmod.filemanager.ui.widgets.NavigationCustomTitleView;
import com.cyanogenmod.filemanager.ui.widgets.NavigationView;
import com.cyanogenmod.filemanager.ui.widgets.NavigationView.OnNavigationRequestMenuListener;
import com.cyanogenmod.filemanager.ui.widgets.NavigationView.OnNavigationSelectionChangedListener;
import com.cyanogenmod.filemanager.ui.widgets.SelectionView;
import com.cyanogenmod.filemanager.util.AndroidHelper;
+import com.cyanogenmod.filemanager.util.BookmarksHelper;
import com.cyanogenmod.filemanager.util.CommandHelper;
import com.cyanogenmod.filemanager.util.DialogHelper;
import com.cyanogenmod.filemanager.util.ExceptionUtil;
@@ -99,6 +127,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
/**
* The main navigation activity. This activity is the center of the application.
@@ -120,39 +149,19 @@ public class NavigationActivity extends Activity
private static boolean DEBUG = false;
- /**
- * Intent code for request a bookmark selection.
- */
- public static final int INTENT_REQUEST_BOOKMARK = 10001;
-
- /**
- * Intent code for request a history selection.
- */
- public static final int INTENT_REQUEST_HISTORY = 20001;
+ // Bookmark list XML tags
+ private static final String TAG_BOOKMARKS = "Bookmarks"; //$NON-NLS-1$
+ private static final String TAG_BOOKMARK = "bookmark"; //$NON-NLS-1$
/**
* Intent code for request a search.
*/
- public static final int INTENT_REQUEST_SEARCH = 30001;
-
-
- /**
- * Constant for extra information about selected bookmark.
- */
- public static final String EXTRA_BOOKMARK_SELECTION =
- "extra_bookmark_selection"; //$NON-NLS-1$
+ public static final int INTENT_REQUEST_SEARCH = 10001;
/**
- * Constant for extra information about selected history entry.
- */
- public static final String EXTRA_HISTORY_ENTRY_SELECTION =
- "extra_history_entry_selection"; //$NON-NLS-1$
-
- /**
- * Constant for extra information about clear selection action.
+ * Intent code for request a search.
*/
- public static final String EXTRA_HISTORY_CLEAR =
- "extra_history_clear_history"; //$NON-NLS-1$
+ public static final int INTENT_REQUEST_SETTINGS = 20001;
/**
* Constant for extra information about selected search entry.
@@ -297,6 +306,15 @@ public class NavigationActivity extends Activity
private ViewGroup mActionBar;
private SelectionView mSelectionBar;
+ private DrawerLayout mDrawerLayout;
+ private ScrollView mDrawer;
+ private ActionBarDrawerToggle mDrawerToggle;
+ private LinearLayout mDrawerHistory;
+ private TextView mDrawerHistoryEmpty;
+
+ private List<Bookmark> mBookmarks;
+ private LinearLayout mDrawerBookmarks;
+
private boolean mExitFlag = false;
private long mExitBackTimeout = -1;
@@ -373,6 +391,10 @@ public class NavigationActivity extends Activity
initStatusActionBar();
initSelectionBar();
+ // Initialize navigation drawer
+ initDrawer();
+ initBookmarks();
+
// Adjust layout (only when start on landscape mode)
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -408,6 +430,13 @@ public class NavigationActivity extends Activity
super.onCreate(state);
}
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ mDrawerToggle.syncState();
+ }
+
/**
* {@inheritDoc}
*/
@@ -427,6 +456,7 @@ public class NavigationActivity extends Activity
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
onLayoutChanged();
+ mDrawerToggle.onConfigurationChanged(newConfig);
}
/**
@@ -488,9 +518,12 @@ public class NavigationActivity extends Activity
//Display the welcome message?
if (firstUse) {
- AlertDialog dialog = DialogHelper.createAlertDialog(
- this, R.drawable.ic_launcher,
- R.string.welcome_title, getString(R.string.welcome_msg), false);
+ // open navigation drawer to show user that it exists
+ mDrawerLayout.openDrawer(mDrawer);
+
+ AlertDialog dialog = DialogHelper.createAlertDialog(this,
+ R.drawable.ic_launcher, R.string.welcome_title,
+ getString(R.string.welcome_msg), false);
DialogHelper.delegateDialogShow(this, dialog);
// Don't display again this dialog
@@ -505,6 +538,8 @@ public class NavigationActivity extends Activity
* Method that initializes the titlebar of the activity.
*/
private void initTitleActionBar() {
+ getActionBar().setTitle(R.string.app_name);
+
//Inflate the view and associate breadcrumb
View titleLayout = getLayoutInflater().inflate(
R.layout.navigation_view_customtitle, null, false);
@@ -585,6 +620,504 @@ public class NavigationActivity extends Activity
}
/**
+ * Method that initializes the navigation drawer of the activity.
+ */
+ private void initDrawer() {
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ mDrawer = (ScrollView) findViewById(R.id.drawer);
+ mDrawerBookmarks = (LinearLayout) findViewById(R.id.bookmarks_list);
+ mDrawerHistory = (LinearLayout) findViewById(R.id.history_list);
+ mDrawerHistoryEmpty = (TextView) findViewById(R.id.history_empty);
+
+ // Set the navigation drawer "hamburger" icon
+ mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
+ R.drawable.ic_holo_light_navigation_drawer,
+ R.string.drawer_open, R.string.drawer_close) {
+
+ /** Called when a drawer has settled in a completely closed state. */
+ public void onDrawerClosed(View view) {
+ super.onDrawerClosed(view);
+ getActionBar().setDisplayOptions(
+ ActionBar.DISPLAY_SHOW_CUSTOM
+ | ActionBar.DISPLAY_SHOW_HOME);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setHomeButtonEnabled(true);
+ invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+ }
+
+ /** Called when a drawer has settled in a completely open state. */
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ getActionBar().setDisplayOptions(
+ ActionBar.DISPLAY_SHOW_TITLE
+ | ActionBar.DISPLAY_SHOW_HOME);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setHomeButtonEnabled(true);
+
+ // change ActionBar title text color
+ Theme theme = ThemeManager
+ .getCurrentTheme(NavigationActivity.this);
+ // get ActionBar title TextView id
+ int titleId = Resources.getSystem().getIdentifier(
+ "action_bar_title", "id", "android");
+ TextView v = (TextView) findViewById(titleId);
+ theme.setTextColor(NavigationActivity.this, v, "text_color"); //$NON-NLS-1$
+
+ invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+ }
+ };
+
+ // Set the drawer toggle as the DrawerListener
+ mDrawerLayout.setDrawerListener(mDrawerToggle);
+
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setHomeButtonEnabled(true);
+ }
+
+ /**
+ * Method adds a history entry to the history list in the drawer
+ */
+ private void addHistoryToDrawer(int index, HistoryNavigable navigable) {
+ // hide empty message
+ mDrawerHistoryEmpty.setVisibility(View.GONE);
+
+ Theme theme = ThemeManager.getCurrentTheme(this);
+ IconHolder iconholder = new IconHolder(this, false);
+
+ // inflate single bookmark layout item and fill it
+ LinearLayout view = (LinearLayout) getLayoutInflater().inflate(
+ R.layout.history_item, null);
+
+ ImageView iconView = (ImageView) view
+ .findViewById(R.id.history_item_icon);
+ TextView name = (TextView) view.findViewById(R.id.history_item_name);
+ TextView directory = (TextView) view
+ .findViewById(R.id.history_item_directory);
+ TextView position = (TextView) view
+ .findViewById(R.id.history_item_position);
+
+ // if (history.getItem() instanceof NavigationViewInfoParcelable)
+ Drawable icon = iconholder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$
+ if (navigable instanceof SearchInfoParcelable) {
+ icon = iconholder.getDrawable("ic_history_search_drawable"); //$NON-NLS-1$
+ }
+ iconView.setImageDrawable(icon);
+
+ String title = navigable.getTitle();
+ if (title == null || title.trim().length() == 0) {
+ title = getString(R.string.root_directory_name);
+ }
+
+ name.setText(title);
+ directory.setText(navigable.getDescription());
+ position.setText(String.format("#%d", index + 1));
+
+ theme.setTextColor(this, name, "text_color");
+ theme.setTextColor(this, directory, "text_color");
+ theme.setTextColor(this, position, "text_color");
+
+ // handle item click
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final int index = mDrawerHistory.indexOfChild(v);
+ final int count = mDrawerHistory.getChildCount();
+ final History history = mHistory.get(count - index - 1);
+
+ navigateToHistory(history);
+ mDrawerLayout.closeDrawer(mDrawer);
+ }
+ });
+
+ // add as first child
+ mDrawerHistory.addView(view, 0);
+ }
+
+ /**
+ * Method takes a bookmark as argument and adds it to mBookmarks and the
+ * list in the drawer
+ */
+ public void addBookmark(Bookmark bookmark) {
+ mBookmarks.add(bookmark);
+ addBookmarkToDrawer(bookmark);
+ }
+
+ /**
+ * Method takes a bookmark as argument and adds it to the bookmark list in
+ * the drawer
+ */
+ private void addBookmarkToDrawer(Bookmark bookmark) {
+ Theme theme = ThemeManager.getCurrentTheme(this);
+ IconHolder iconholder = new IconHolder(this, false);
+
+ // inflate single bookmark layout item and fill it
+ LinearLayout view = (LinearLayout) getLayoutInflater().inflate(
+ R.layout.bookmarks_item, null);
+
+ ImageView icon = (ImageView) view
+ .findViewById(R.id.bookmarks_item_icon);
+ TextView name = (TextView) view.findViewById(R.id.bookmarks_item_name);
+ TextView path = (TextView) view.findViewById(R.id.bookmarks_item_path);
+ ImageButton actionButton = (ImageButton) view
+ .findViewById(R.id.bookmarks_item_action);
+
+ name.setText(bookmark.mName);
+ path.setText(bookmark.mPath);
+
+ theme.setTextColor(this, name, "text_color");
+ theme.setTextColor(this, path, "text_color");
+
+ icon.setImageDrawable(iconholder.getDrawable(BookmarksHelper
+ .getIcon(bookmark)));
+
+ Drawable action = null;
+ String actionCd = null;
+ if (bookmark.mType.compareTo(BOOKMARK_TYPE.HOME) == 0) {
+ action = iconholder.getDrawable("ic_config_drawable"); //$NON-NLS-1$
+ actionCd = getApplicationContext().getString(
+ R.string.bookmarks_button_config_cd);
+ }
+ else if (bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) {
+ action = iconholder.getDrawable("ic_close_drawable"); //$NON-NLS-1$
+ actionCd = getApplicationContext().getString(
+ R.string.bookmarks_button_remove_bookmark_cd);
+ }
+
+ actionButton.setImageDrawable(action);
+ actionButton.setVisibility(action != null ? View.VISIBLE : View.GONE);
+ actionButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final View v = (View) view.getParent();
+ final int index = mDrawerBookmarks.indexOfChild(v);
+ final Bookmark bookmark = mBookmarks.get(index);
+
+ // Configure home
+ if (bookmark.mType.compareTo(BOOKMARK_TYPE.HOME) == 0) {
+ // Show a dialog for configure initial directory
+ InitialDirectoryDialog dialog = new InitialDirectoryDialog(
+ NavigationActivity.this);
+ dialog.setOnValueChangedListener(new InitialDirectoryDialog.OnValueChangedListener() {
+ @Override
+ public void onValueChanged(String newInitialDir) {
+ bookmark.mPath = newInitialDir;
+
+ // reset drawer bookmarks list
+ initBookmarks();
+ }
+ });
+ dialog.show();
+ return;
+ }
+
+ // Remove bookmark
+ if (bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) {
+ boolean result = Bookmarks.removeBookmark(
+ getApplicationContext(), bookmark);
+ if (!result) { // Show warning
+ DialogHelper.showToast(getApplicationContext(),
+ R.string.msgs_operation_failure,
+ Toast.LENGTH_SHORT);
+ return;
+ }
+ mBookmarks.remove(bookmark);
+ mDrawerBookmarks.removeView(v);
+ return;
+ }
+ }
+ });
+ actionButton.setContentDescription(actionCd);
+
+ // handle item click
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final int index = mDrawerBookmarks.indexOfChild(v);
+ final Bookmark bookmark = mBookmarks.get(index);
+
+ // try to navigate to the bookmark path
+ try {
+ FileSystemObject fso = CommandHelper.getFileInfo(
+ getApplicationContext(), bookmark.mPath, null);
+ if (fso != null) {
+ getCurrentNavigationView().open(fso);
+ mDrawerLayout.closeDrawer(mDrawer);
+ }
+ else {
+ // The bookmark does not exist, delete the user-defined
+ // bookmark
+ try {
+ Bookmarks.removeBookmark(getApplicationContext(),
+ bookmark);
+
+ // reset bookmarks list to default
+ initBookmarks();
+ }
+ catch (Exception ex) {
+ }
+ }
+ }
+ catch (Exception e) { // Capture the exception
+ ExceptionUtil
+ .translateException(NavigationActivity.this, e);
+ if (e instanceof NoSuchFileOrDirectory
+ || e instanceof FileNotFoundException) {
+ // The bookmark does not exist, delete the user-defined
+ // bookmark
+ try {
+ Bookmarks.removeBookmark(getApplicationContext(),
+ bookmark);
+
+ // reset bookmarks list to default
+ initBookmarks();
+ }
+ catch (Exception ex) {
+ }
+ }
+ return;
+ }
+ }
+ });
+
+ mDrawerBookmarks.addView(view);
+ }
+
+ /**
+ * Method that initializes the bookmarks.
+ */
+ private void initBookmarks() {
+ // Retrieve the loading view
+ final View waiting = findViewById(R.id.bookmarks_loading);
+
+ // Load bookmarks in background
+ AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
+ Exception mCause;
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ try {
+ mBookmarks = loadBookmarks();
+ return Boolean.TRUE;
+
+ }
+ catch (Exception e) {
+ this.mCause = e;
+ return Boolean.FALSE;
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ waiting.setVisibility(View.VISIBLE);
+ mDrawerBookmarks.removeAllViews();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ waiting.setVisibility(View.GONE);
+ if (result.booleanValue()) {
+ for (Bookmark bookmark : mBookmarks) {
+ addBookmarkToDrawer(bookmark);
+ }
+ }
+ else {
+ if (this.mCause != null) {
+ ExceptionUtil.translateException(
+ NavigationActivity.this, this.mCause);
+ }
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ waiting.setVisibility(View.GONE);
+ }
+ };
+ task.execute();
+ }
+
+ /**
+ * Method that loads all kind of bookmarks and join in an array to be used
+ * in the listview adapter.
+ *
+ * @return List<Bookmark>
+ * @hide
+ */
+ List<Bookmark> loadBookmarks() {
+ // Bookmarks = HOME + FILESYSTEM + SD STORAGES + USER DEFINED
+ // In ChRooted mode = SD STORAGES + USER DEFINED (from SD STORAGES)
+ List<Bookmark> bookmarks = new ArrayList<Bookmark>();
+ if (!this.mChRooted) {
+ bookmarks.add(loadHomeBookmarks());
+ bookmarks.addAll(loadFilesystemBookmarks());
+ }
+ bookmarks.addAll(loadSdStorageBookmarks());
+ bookmarks.addAll(loadUserBookmarks());
+ return bookmarks;
+ }
+
+ /**
+ * Method that loads the home bookmark from the user preference.
+ *
+ * @return Bookmark The bookmark loaded
+ */
+ private Bookmark loadHomeBookmarks() {
+ String initialDir = Preferences.getSharedPreferences().getString(
+ FileManagerSettings.SETTINGS_INITIAL_DIR.getId(),
+ (String) FileManagerSettings.SETTINGS_INITIAL_DIR
+ .getDefaultValue());
+ return new Bookmark(BOOKMARK_TYPE.HOME,
+ getString(R.string.bookmarks_home), initialDir);
+ }
+
+ /**
+ * Method that loads the filesystem bookmarks from the internal xml file.
+ * (defined by this application)
+ *
+ * @return List<Bookmark> The bookmarks loaded
+ */
+ private List<Bookmark> loadFilesystemBookmarks() {
+ try {
+ // Initialize the bookmarks
+ List<Bookmark> bookmarks = new ArrayList<Bookmark>();
+
+ // Read the command list xml file
+ XmlResourceParser parser = getResources().getXml(
+ R.xml.filesystem_bookmarks);
+
+ try {
+ // Find the root element
+ XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+
+ if (TAG_BOOKMARK.equals(element)) {
+ CharSequence name = null;
+ CharSequence directory = null;
+
+ try {
+ name = getString(parser.getAttributeResourceValue(
+ R.styleable.Bookmark_name, 0));
+ }
+ catch (Exception e) {
+ /** NON BLOCK **/
+ }
+ try {
+ directory = getString(parser
+ .getAttributeResourceValue(
+ R.styleable.Bookmark_directory, 0));
+ }
+ catch (Exception e) {
+ /** NON BLOCK **/
+ }
+ if (directory == null) {
+ directory = parser
+ .getAttributeValue(R.styleable.Bookmark_directory);
+ }
+ if (name != null && directory != null) {
+ bookmarks.add(new Bookmark(
+ BOOKMARK_TYPE.FILESYSTEM, name.toString(),
+ directory.toString()));
+ }
+ }
+ }
+
+ // Return the bookmarks
+ return bookmarks;
+
+ }
+ finally {
+ parser.close();
+ }
+ }
+ catch (Throwable ex) {
+ Log.e(TAG, "Load filesystem bookmarks failed", ex); //$NON-NLS-1$
+ }
+
+ // No data
+ return new ArrayList<Bookmark>();
+ }
+
+ /**
+ * Method that loads the secure digital card storage bookmarks from the
+ * system.
+ *
+ * @return List<Bookmark> The bookmarks loaded
+ */
+ private List<Bookmark> loadSdStorageBookmarks() {
+ // Initialize the bookmarks
+ List<Bookmark> bookmarks = new ArrayList<Bookmark>();
+
+ try {
+ // Recovery sdcards from storage manager
+ StorageVolume[] volumes = StorageHelper
+ .getStorageVolumes(getApplication());
+ int cc = volumes.length;
+ for (int i = 0; i < cc; i++) {
+ if (volumes[i].getPath().toLowerCase(Locale.ROOT)
+ .indexOf("usb") != -1) { //$NON-NLS-1$
+ bookmarks.add(new Bookmark(BOOKMARK_TYPE.USB, StorageHelper
+ .getStorageVolumeDescription(getApplication(),
+ volumes[i]), volumes[i].getPath()));
+ }
+ else {
+ bookmarks.add(new Bookmark(BOOKMARK_TYPE.SDCARD,
+ StorageHelper.getStorageVolumeDescription(
+ getApplication(), volumes[i]), volumes[i]
+ .getPath()));
+ }
+ }
+
+ // Return the bookmarks
+ return bookmarks;
+ }
+ catch (Throwable ex) {
+ Log.e(TAG, "Load filesystem bookmarks failed", ex); //$NON-NLS-1$
+ }
+
+ // No data
+ return new ArrayList<Bookmark>();
+ }
+
+ /**
+ * Method that loads the user bookmarks (added by the user).
+ *
+ * @return List<Bookmark> The bookmarks loaded
+ */
+ private List<Bookmark> loadUserBookmarks() {
+ List<Bookmark> bookmarks = new ArrayList<Bookmark>();
+ Cursor cursor = Bookmarks.getAllBookmarks(this.getContentResolver());
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ Bookmark bm = new Bookmark(cursor);
+ if (this.mChRooted
+ && !StorageHelper.isPathInStorageVolume(bm.mPath)) {
+ continue;
+ }
+ bookmarks.add(bm);
+ }
+ while (cursor.moveToNext());
+ }
+ }
+ finally {
+ try {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ catch (Exception e) {
+ /** NON BLOCK **/
+ }
+ }
+ return bookmarks;
+ }
+
+ /**
* Method that initializes the navigation views of the activity
*/
private void initNavigationViews() {
@@ -820,16 +1353,58 @@ public class NavigationActivity extends Activity
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- if ((getActionBar().getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP)
- == ActionBar.DISPLAY_HOME_AS_UP) {
- checkBackAction();
- }
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
+ // Pass the event to ActionBarDrawerToggle, if it returns
+ // true, then it has handled the app icon touch event
+ if (mDrawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ // just handle the drawer list here
+ switch (item.getItemId()) {
+ case R.id.mnu_actions_add_to_bookmarks_current_folder:
+ // TODO add bookmark
+ Log.d(TAG, "add bookmark");
+ return true;
+ case R.id.mnu_clear_history:
+ clearHistory();
+ return true;
+ case R.id.mnu_settings:
+ openSettings();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Called when the menu is created. Just includes the drawer's overflow
+ * menu. All entries are hidden until onPrepareOptionsMenu unhides them.
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.drawer, menu);
+ return true;
+ }
+
+ /**
+ * Called whenever we call invalidateOptionsMenu()
+ */
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer);
+
+ for (int i = 0; i < menu.size(); i++) {
+ // show all items if drawer is open,
+ // hide them if not
+ menu.getItem(i).setVisible(drawerOpen);
+
+ if (menu.getItem(i).getItemId() == R.id.mnu_clear_history) {
+ menu.getItem(i).setEnabled(mHistory.size() > 0);
+ }
+ }
+
+ return super.onPrepareOptionsMenu(menu);
}
/**
@@ -907,15 +1482,8 @@ public class NavigationActivity extends Activity
//Action Bar buttons
//######################
case R.id.ab_actions:
- openActionsDialog(getCurrentNavigationView().getCurrentDir(), true);
- break;
-
- case R.id.ab_bookmarks:
- openBookmarks();
- break;
-
- case R.id.ab_history:
- openHistory();
+ openActionsDialog(getCurrentNavigationView().getCurrentDir(),
+ true);
break;
case R.id.ab_search:
@@ -936,34 +1504,15 @@ public class NavigationActivity extends Activity
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == INTENT_REQUEST_SETTINGS) {
+ // reset bookmarks list to default as the user could changed the
+ // root mode which changes the system bookmarks
+ initBookmarks();
+ return;
+ }
+
if (data != null) {
switch (requestCode) {
- case INTENT_REQUEST_BOOKMARK:
- if (resultCode == RESULT_OK) {
- FileSystemObject fso =
- (FileSystemObject)data.
- getSerializableExtra(EXTRA_BOOKMARK_SELECTION);
- if (fso != null) {
- //Open the fso
- getCurrentNavigationView().open(fso);
- }
- }
- break;
-
- case INTENT_REQUEST_HISTORY:
- if (resultCode == RESULT_OK) {
- //Change current directory
- History history =
- (History)data.getSerializableExtra(EXTRA_HISTORY_ENTRY_SELECTION);
- navigateToHistory(history);
- } else if (resultCode == RESULT_CANCELED) {
- boolean clear = data.getBooleanExtra(EXTRA_HISTORY_CLEAR, false);
- if (clear) {
- clearHistory();
- }
- }
- break;
-
case INTENT_REQUEST_SEARCH:
if (resultCode == RESULT_OK) {
//Change directory?
@@ -988,6 +1537,9 @@ public class NavigationActivity extends Activity
getCurrentNavigationView().refresh(true);
}
}
+ // reset bookmarks list to default as the user could have set a
+ // new bookmark in the search activity
+ initBookmarks();
break;
default:
@@ -1001,11 +1553,10 @@ public class NavigationActivity extends Activity
*/
@Override
public void onNewHistory(HistoryNavigable navigable) {
+ addHistoryToDrawer(this.mHistory.size(), navigable);
//Recollect information about current status
History history = new History(this.mHistory.size(), navigable);
this.mHistory.add(history);
- getActionBar().setDisplayHomeAsUpEnabled(true);
- getActionBar().setHomeButtonEnabled(true);
}
/**
@@ -1013,10 +1564,6 @@ public class NavigationActivity extends Activity
*/
@Override
public void onCheckHistory() {
- //Need to show HomeUp Button
- boolean enabled = this.mHistory != null && this.mHistory.size() > 0;
- getActionBar().setDisplayHomeAsUpEnabled(enabled);
- getActionBar().setHomeButtonEnabled(enabled);
}
/**
@@ -1184,21 +1731,7 @@ public class NavigationActivity extends Activity
switch (itemId) {
case R.id.mnu_settings:
//Settings
- Intent settings = new Intent(
- NavigationActivity.this, SettingsPreferences.class);
- startActivity(settings);
- break;
-
- case R.id.mnu_history:
- //History
- openHistory();
- popup.dismiss();
- break;
-
- case R.id.mnu_bookmarks:
- //Bookmarks
- openBookmarks();
- popup.dismiss();
+ openSettings();
break;
case R.id.mnu_search:
@@ -1307,7 +1840,8 @@ public class NavigationActivity extends Activity
*/
private void clearHistory() {
this.mHistory.clear();
- onCheckHistory();
+ mDrawerHistory.removeAllViews();
+ mDrawerHistoryEmpty.setVisibility(View.VISIBLE);
}
/**
@@ -1350,10 +1884,11 @@ public class NavigationActivity extends Activity
int cc = realHistory.getPosition();
for (int i = this.mHistory.size() - 1; i >= cc; i--) {
this.mHistory.remove(i);
+ mDrawerHistory.removeViewAt(0);
}
- if (this.mHistory.size() == 0) {
- getActionBar().setDisplayHomeAsUpEnabled(false);
- getActionBar().setHomeButtonEnabled(false);
+
+ if (mDrawerHistory.getChildCount() == 0) {
+ mDrawerHistoryEmpty.setVisibility(View.VISIBLE);
}
//Navigate
@@ -1460,45 +1995,37 @@ public class NavigationActivity extends Activity
}
// Show the dialog
- ActionsDialog dialog = new ActionsDialog(this, fso, global, false);
+ ActionsDialog dialog = new ActionsDialog(this, this, fso, global, false);
dialog.setOnRequestRefreshListener(this);
dialog.setOnSelectionListener(getCurrentNavigationView());
dialog.show();
}
/**
- * Method that opens the bookmarks activity.
- * @hide
- */
- void openBookmarks() {
- Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
- bookmarksIntent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- startActivityForResult(bookmarksIntent, INTENT_REQUEST_BOOKMARK);
- }
-
- /**
- * Method that opens the history activity.
+ * Method that opens the search activity.
+ *
* @hide
*/
- void openHistory() {
- Intent historyIntent = new Intent(this, HistoryActivity.class);
- historyIntent.putExtra(HistoryActivity.EXTRA_HISTORY_LIST, (Serializable)this.mHistory);
- historyIntent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- startActivityForResult(historyIntent, INTENT_REQUEST_HISTORY);
+ void openSearch() {
+ onSearchRequested();
}
/**
- * Method that opens the search activity.
+ * Method that opens the settings activity.
+ *
* @hide
*/
- void openSearch() {
- onSearchRequested();
+ void openSettings() {
+ Intent settingsIntent = new Intent(NavigationActivity.this,
+ SettingsPreferences.class);
+ startActivityForResult(settingsIntent, INTENT_REQUEST_SETTINGS);
}
/**
* Method that remove the {@link FileSystemObject} from the history
*/
private void removeFromHistory(FileSystemObject fso) {
+ // TODO remove drawer entry here, too
if (this.mHistory != null) {
int cc = this.mHistory.size();
for (int i = cc-1; i >= 0 ; i--) {
@@ -1631,12 +2158,18 @@ public class NavigationActivity extends Activity
*/
private void onLayoutChanged() {
Theme theme = ThemeManager.getCurrentTheme(this);
+ boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer);
// Apply only when the orientation was changed
int orientation = getResources().getConfiguration().orientation;
if (this.mOrientation == orientation) return;
this.mOrientation = orientation;
+ // imitate a closed drawer while layout is rebuilt to avoid NullPointerException
+ if (drawerOpen) {
+ mDrawerToggle.onDrawerClosed(mDrawer);
+ }
+
if (this.mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
// Landscape mode
ViewGroup statusBar = (ViewGroup)findViewById(R.id.navigation_statusbar);
@@ -1702,6 +2235,11 @@ public class NavigationActivity extends Activity
// Show holder
newParent.setVisibility(View.VISIBLE);
}
+
+ // if drawer was open, imitate reopening
+ if (drawerOpen) {
+ mDrawerToggle.onDrawerOpened(mDrawer);
+ }
}
/**
@@ -1713,11 +2251,19 @@ public class NavigationActivity extends Activity
Theme theme = ThemeManager.getCurrentTheme(this);
theme.setBaseTheme(this, false);
+ // imitate a closed drawer while layout is rebuilt to avoid NullPointerException
+ boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer);
+ if (drawerOpen) {
+ mDrawerToggle.onDrawerClosed(mDrawer);
+ }
+
//- Layout
View v = findViewById(R.id.navigation_layout);
theme.setBackgroundDrawable(this, v, "background_drawable"); //$NON-NLS-1$
+
//- ActionBar
theme.setTitlebarDrawable(this, getActionBar(), "titlebar_drawable"); //$NON-NLS-1$
+
//- StatusBar
v = findViewById(R.id.navigation_statusbar);
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -1731,10 +2277,7 @@ public class NavigationActivity extends Activity
theme.setImageDrawable(this, (ImageView)v, "ab_actions_drawable"); //$NON-NLS-1$
v = findViewById(R.id.ab_search);
theme.setImageDrawable(this, (ImageView)v, "ab_search_drawable"); //$NON-NLS-1$
- v = findViewById(R.id.ab_bookmarks);
- theme.setImageDrawable(this, (ImageView)v, "ab_bookmarks_drawable"); //$NON-NLS-1$
- v = findViewById(R.id.ab_history);
- theme.setImageDrawable(this, (ImageView)v, "ab_history_drawable"); //$NON-NLS-1$
+
//- Expanders
v = findViewById(R.id.ab_configuration);
theme.setImageDrawable(this, (ImageView)v, "expander_open_drawable"); //$NON-NLS-1$
@@ -1746,6 +2289,7 @@ public class NavigationActivity extends Activity
theme.setImageDrawable(this, (ImageView)v, "ab_layout_mode_drawable"); //$NON-NLS-1$
v = findViewById(R.id.ab_view_options);
theme.setImageDrawable(this, (ImageView)v, "ab_view_options_drawable"); //$NON-NLS-1$
+
//- SelectionBar
v = findViewById(R.id.navigation_selectionbar);
theme.setBackgroundDrawable(this, v, "selectionbar_drawable"); //$NON-NLS-1$
@@ -1753,11 +2297,38 @@ public class NavigationActivity extends Activity
theme.setImageDrawable(this, (ImageView)v, "ab_selection_done_drawable"); //$NON-NLS-1$
v = findViewById(R.id.navigation_status_selection_label);
theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+
+ // - Navigation drawer
+ theme.setBackgroundColor(this, mDrawer, "drawer_color");
+ v = findViewById(R.id.bookmarks_header);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+ v = findViewById(R.id.history_header);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+ v = findViewById(R.id.history_empty);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+ mDrawerToggle.setDrawerImageResource(theme.getResourceId(this, "drawer_icon"));
+
+ for (int i=0; i<mDrawerHistory.getChildCount(); i++) {
+ View item = mDrawerHistory.getChildAt(i);
+
+ v = item.findViewById(R.id.history_item_name);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+ v = item.findViewById(R.id.history_item_directory);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+ v = item.findViewById(R.id.history_item_position);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+ }
+
//- NavigationView
int cc = this.mNavigationViews.length;
for (int i = 0; i < cc; i++) {
getNavigationView(i).applyTheme();
}
+
+ // if drawer was open, imitate reopening
+ if (drawerOpen) {
+ mDrawerToggle.onDrawerOpened(mDrawer);
+ }
}
}
diff --git a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java
index c7cc6f18..a3b6b2ae 100644
--- a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java
+++ b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java
@@ -933,7 +933,7 @@ public class SearchActivity extends Activity
return;
}
- ActionsDialog dialog = new ActionsDialog(this, fso, false, true);
+ ActionsDialog dialog = new ActionsDialog(this, null, fso, false, true);
dialog.setOnRequestRefreshListener(this);
dialog.show();
}
diff --git a/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java b/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java
index 67ecfda6..e38d0ad1 100644
--- a/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java
+++ b/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java
@@ -146,6 +146,7 @@ public class SettingsPreferences extends PreferenceActivity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
+ setResult(RESULT_OK);
finish();
return true;
default:
diff --git a/src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java b/src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java
deleted file mode 100644
index bbb5e3c8..00000000
--- a/src/com/cyanogenmod/filemanager/adapters/BookmarksAdapter.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2012 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.cyanogenmod.filemanager.adapters;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.cyanogenmod.filemanager.R;
-import com.cyanogenmod.filemanager.model.Bookmark;
-import com.cyanogenmod.filemanager.model.Bookmark.BOOKMARK_TYPE;
-import com.cyanogenmod.filemanager.ui.IconHolder;
-import com.cyanogenmod.filemanager.ui.ThemeManager;
-import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
-import com.cyanogenmod.filemanager.util.BookmarksHelper;
-
-import java.util.List;
-
-/**
- * An implementation of {@link ArrayAdapter} for display bookmarks.
- */
-public class BookmarksAdapter extends ArrayAdapter<Bookmark> {
-
- /**
- * A class that conforms with the ViewHolder pattern to performance
- * the list view rendering.
- */
- private static class ViewHolder {
- /**
- * @hide
- */
- public ViewHolder() {
- super();
- }
- ImageView mIvIcon;
- TextView mTvName;
- TextView mTvPath;
- ImageButton mBtAction;
- }
-
- /**
- * A class that holds the full data information.
- */
- private static class DataHolder {
- /**
- * @hide
- */
- public DataHolder() {
- super();
- }
- Drawable mDwIcon;
- String mName;
- String mPath;
- Drawable mDwAction;
- String mActionCd;
- }
-
-
-
- private DataHolder[] mData;
- private IconHolder mIconHolder;
- private final OnClickListener mOnActionClickListener;
-
- //The resource item layout
- private static final int RESOURCE_LAYOUT = R.layout.bookmarks_item;
-
- //The resource of the item icon
- private static final int RESOURCE_ITEM_ICON = R.id.bookmarks_item_icon;
- //The resource of the item name
- private static final int RESOURCE_ITEM_NAME = R.id.bookmarks_item_name;
- //The resource of the item directory
- private static final int RESOURCE_ITEM_PATH = R.id.bookmarks_item_path;
- //The resource of the item button action
- private static final int RESOURCE_ITEM_ACTION = R.id.bookmarks_item_action;
-
- /**
- * Constructor of <code>BookmarksAdapter</code>.
- *
- * @param context The current context
- * @param bookmarks The bookmarks
- * @param onActionClickListener The listener for listen action clicks
- */
- public BookmarksAdapter(
- Context context, List<Bookmark> bookmarks, OnClickListener onActionClickListener) {
- super(context, RESOURCE_ITEM_NAME, bookmarks);
- this.mIconHolder = new IconHolder(context, false);
- this.mOnActionClickListener = onActionClickListener;
-
- //Do cache of the data for better performance
- processData(bookmarks);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void notifyDataSetChanged() {
- processData(null);
- super.notifyDataSetChanged();
- }
-
- /**
- * Method that dispose the elements of the adapter.
- */
- public void dispose() {
- clear();
- this.mData = null;
- if (mIconHolder != null) {
- mIconHolder.cleanup();
- mIconHolder = null;
- }
- }
-
- /**
- * Method that process the data before use {@link #getView} method.
- *
- * @param bookmarks The list of bookmarks (to better performance) or null.
- */
- private void processData(List<Bookmark> bookmarks) {
- this.mData = new DataHolder[getCount()];
- int cc = (bookmarks == null) ? getCount() : bookmarks.size();
- for (int i = 0; i < cc; i++) {
- //Bookmark info
- Bookmark bookmark = (bookmarks == null) ? getItem(i) : bookmarks.get(i);
-
- //Build the data holder
- this.mData[i] = new BookmarksAdapter.DataHolder();
- this.mData[i].mDwIcon =
- this.mIconHolder.getDrawable(BookmarksHelper.getIcon(bookmark));
- this.mData[i].mName = bookmark.mName;
- this.mData[i].mPath = bookmark.mPath;
- this.mData[i].mDwAction = null;
- this.mData[i].mActionCd = null;
- if (bookmark.mType.compareTo(BOOKMARK_TYPE.HOME) == 0) {
- this.mData[i].mDwAction =
- this.mIconHolder.getDrawable("ic_config_drawable"); //$NON-NLS-1$
- this.mData[i].mActionCd =
- getContext().getString(R.string.bookmarks_button_config_cd);
- } else if (bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) {
- this.mData[i].mDwAction =
- this.mIconHolder.getDrawable("ic_close_drawable"); //$NON-NLS-1$
- this.mData[i].mActionCd =
- getContext().getString(R.string.bookmarks_button_remove_bookmark_cd);
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
-
- //Check to reuse view
- View v = convertView;
- if (v == null) {
- //Create the view holder
- LayoutInflater li =
- (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- v = li.inflate(RESOURCE_LAYOUT, parent, false);
- ViewHolder viewHolder = new BookmarksAdapter.ViewHolder();
- viewHolder.mIvIcon = (ImageView)v.findViewById(RESOURCE_ITEM_ICON);
- viewHolder.mTvName = (TextView)v.findViewById(RESOURCE_ITEM_NAME);
- viewHolder.mTvPath = (TextView)v.findViewById(RESOURCE_ITEM_PATH);
- viewHolder.mBtAction = (ImageButton)v.findViewById(RESOURCE_ITEM_ACTION);
- viewHolder.mBtAction.setTag(Integer.valueOf(position));
- v.setTag(viewHolder);
-
- // Apply the current theme
- Theme theme = ThemeManager.getCurrentTheme(getContext());
- theme.setBackgroundDrawable(
- getContext(), v, "selectors_deselected_drawable"); //$NON-NLS-1$
- theme.setTextColor(
- getContext(), viewHolder.mTvName, "text_color"); //$NON-NLS-1$
- theme.setTextColor(
- getContext(), viewHolder.mTvPath, "text_color"); //$NON-NLS-1$
- }
-
- //Retrieve data holder
- final DataHolder dataHolder = this.mData[position];
-
- //Retrieve the view holder
- ViewHolder viewHolder = (ViewHolder)v.getTag();
-
- //Set the data
- viewHolder.mIvIcon.setImageDrawable(dataHolder.mDwIcon);
- viewHolder.mTvName.setText(dataHolder.mName);
- viewHolder.mTvPath.setText(dataHolder.mPath);
- boolean hasAction = dataHolder.mDwAction != null;
- viewHolder.mBtAction.setImageDrawable(hasAction ? dataHolder.mDwAction : null);
- viewHolder.mBtAction.setVisibility(hasAction ? View.VISIBLE : View.GONE);
- viewHolder.mBtAction.setOnClickListener(this.mOnActionClickListener);
- viewHolder.mBtAction.setContentDescription(dataHolder.mActionCd);
-
- //Return the view
- return v;
- }
-
- /**
- * Method that should be invoked when the theme of the app was changed
- */
- public void notifyThemeChanged() {
- if (mIconHolder != null) {
- mIconHolder.cleanup();
- }
- // Empty icon holder
- this.mIconHolder = new IconHolder(getContext(), false);
- }
-}
diff --git a/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java b/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java
deleted file mode 100644
index f8ebbf02..00000000
--- a/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2012 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.cyanogenmod.filemanager.adapters;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.cyanogenmod.filemanager.R;
-import com.cyanogenmod.filemanager.model.History;
-import com.cyanogenmod.filemanager.parcelables.NavigationViewInfoParcelable;
-import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable;
-import com.cyanogenmod.filemanager.ui.IconHolder;
-import com.cyanogenmod.filemanager.ui.ThemeManager;
-import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
-
-import java.util.List;
-
-/**
- * An implementation of {@link ArrayAdapter} for display history.
- */
-public class HistoryAdapter extends ArrayAdapter<History> {
-
- /**
- * A class that conforms with the ViewHolder pattern to performance
- * the list view rendering.
- */
- private static class ViewHolder {
- /**
- * @hide
- */
- public ViewHolder() {
- super();
- }
- ImageView mIvIcon;
- TextView mTvName;
- TextView mTvDirectory;
- TextView mTvPosition;
- }
-
- /**
- * A class that holds the full data information.
- */
- private static class DataHolder {
- /**
- * @hide
- */
- public DataHolder() {
- super();
- }
- Drawable mDwIcon;
- String mName;
- String mDirectory;
- String mPosition;
- }
-
-
-
- private DataHolder[] mData;
- private IconHolder mIconHolder;
-
- //The resource item layout
- private static final int RESOURCE_LAYOUT = R.layout.history_item;
-
- //The resource of the item icon
- private static final int RESOURCE_ITEM_ICON = R.id.history_item_icon;
- //The resource of the item name
- private static final int RESOURCE_ITEM_NAME = R.id.history_item_name;
- //The resource of the item directory
- private static final int RESOURCE_ITEM_DIRECTORY = R.id.history_item_directory;
- //The resource of the item position
- private static final int RESOURCE_ITEM_POSITION = R.id.history_item_position;
-
- /**
- * Constructor of <code>HistoryAdapter</code>.
- *
- * @param context The current context
- * @param history The history reference
- */
- public HistoryAdapter(Context context, List<History> history) {
- super(context, RESOURCE_ITEM_NAME, history);
- notifyThemeChanged(); // Reload icons
-
- //Do cache of the data for better performance
- processData(history);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void notifyDataSetChanged() {
- processData(null);
- super.notifyDataSetChanged();
- }
-
- /**
- * Method that dispose the elements of the adapter.
- */
- public void dispose() {
- clear();
- this.mData = null;
- if (mIconHolder != null) {
- mIconHolder.cleanup();
- mIconHolder = null;
- }
- }
-
- /**
- * Method that process the data before use {@link #getView} method.
- *
- * @param historyData The list of histories (to better performance) or null.
- */
- private void processData(List<History> historyData) {
- this.mData = new DataHolder[getCount()];
- int cc = (historyData == null) ? getCount() : historyData.size();
- for (int i = 0; i < cc; i++) {
- //History info
- History history = (historyData == null) ? getItem(i) : historyData.get(i);
-
- //Build the data holder
- this.mData[i] = new HistoryAdapter.DataHolder();
- if (history.getItem() instanceof NavigationViewInfoParcelable) {
- this.mData[i].mDwIcon =
- this.mIconHolder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$
- } else if (history.getItem() instanceof SearchInfoParcelable) {
- this.mData[i].mDwIcon =
- this.mIconHolder.getDrawable("ic_history_search_drawable"); //$NON-NLS-1$
- }
- this.mData[i].mName = history.getItem().getTitle();
- if (this.mData[i].mName == null || this.mData[i].mName.trim().length() == 0) {
- // Root directory
- this.mData[i].mName = getContext().getString(R.string.root_directory_name);
- }
- this.mData[i].mDirectory = history.getItem().getDescription();
- this.mData[i].mPosition = String.format("#%d", Integer.valueOf(i + 1)); //$NON-NLS-1$
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
-
- //Check to reuse view
- View v = convertView;
- if (v == null) {
- //Create the view holder
- LayoutInflater li =
- (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- v = li.inflate(RESOURCE_LAYOUT, parent, false);
- ViewHolder viewHolder = new HistoryAdapter.ViewHolder();
- viewHolder.mIvIcon = (ImageView)v.findViewById(RESOURCE_ITEM_ICON);
- viewHolder.mTvName = (TextView)v.findViewById(RESOURCE_ITEM_NAME);
- viewHolder.mTvDirectory = (TextView)v.findViewById(RESOURCE_ITEM_DIRECTORY);
- viewHolder.mTvPosition = (TextView)v.findViewById(RESOURCE_ITEM_POSITION);
- v.setTag(viewHolder);
-
- // Apply the current theme
- Theme theme = ThemeManager.getCurrentTheme(getContext());
- theme.setBackgroundDrawable(
- getContext(), v, "selectors_deselected_drawable"); //$NON-NLS-1$
- theme.setTextColor(
- getContext(), viewHolder.mTvName, "text_color"); //$NON-NLS-1$
- theme.setTextColor(
- getContext(), viewHolder.mTvDirectory, "text_color"); //$NON-NLS-1$
- theme.setTextColor(
- getContext(), viewHolder.mTvPosition, "text_color"); //$NON-NLS-1$
- }
-
- //Retrieve data holder
- final DataHolder dataHolder = this.mData[position];
-
- //Retrieve the view holder
- ViewHolder viewHolder = (ViewHolder)v.getTag();
-
- //Set the data
- viewHolder.mIvIcon.setImageDrawable(dataHolder.mDwIcon);
- viewHolder.mTvName.setText(dataHolder.mName);
- viewHolder.mTvDirectory.setText(dataHolder.mDirectory);
- viewHolder.mTvPosition.setText(dataHolder.mPosition);
-
- //Return the view
- return v;
- }
-
- /**
- * Method that should be invoked when the theme of the app was changed
- */
- public void notifyThemeChanged() {
- if (mIconHolder != null) {
- mIconHolder.cleanup();
- }
- // Empty icon holder (only have folders and search icons)
- this.mIconHolder = new IconHolder(getContext(), false);
- }
-
-}
diff --git a/src/com/cyanogenmod/filemanager/ui/ThemeManager.java b/src/com/cyanogenmod/filemanager/ui/ThemeManager.java
index 802ad1e5..65f264f2 100644
--- a/src/com/cyanogenmod/filemanager/ui/ThemeManager.java
+++ b/src/com/cyanogenmod/filemanager/ui/ThemeManager.java
@@ -486,6 +486,25 @@ public final class ThemeManager {
}
/**
+ * Method that returns the resource id of a drawable in the current theme
+ *
+ * @param ctx The current context
+ * @param resource The string resource
+ * @return int The resource id
+ */
+ public int getResourceId(Context ctx, String resource) {
+ String resId = mId + "_" + resource; //$NON-NLS-1$
+ int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$
+ if (id != 0) {
+ return id;
+ }
+
+ // Default theme
+ return mDefaultTheme.mResources.getIdentifier(
+ resource, "drawable", mDefaultTheme.mPackage); //$NON-NLS-1$
+ }
+
+ /**
* Method that returns an image drawable of the current theme
*
* @param ctx The current context
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
index 8c205434..bb34dceb 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
@@ -34,9 +34,11 @@ import android.widget.Toast;
import com.cyanogenmod.filemanager.FileManagerApplication;
import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.activities.NavigationActivity;
import com.cyanogenmod.filemanager.adapters.TwoColumnsMenuListAdapter;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
+import com.cyanogenmod.filemanager.model.Bookmark;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.Symlink;
import com.cyanogenmod.filemanager.model.SystemFile;
@@ -74,6 +76,7 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
* @hide
*/
final Context mContext;
+ final NavigationActivity mBackRef;
private final boolean mGlobal;
private final boolean mSearch;
private final boolean mChRooted;
@@ -105,12 +108,14 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
* @param global If the menu to display will be the global one (Global actions)
* @param search If the call is from search activity
*/
- public ActionsDialog(Context context, FileSystemObject fso, boolean global, boolean search) {
+ public ActionsDialog(Context context, NavigationActivity backRef, FileSystemObject fso,
+ boolean global, boolean search) {
super();
//Save the data
this.mFso = fso;
this.mContext = context;
+ this.mBackRef = backRef;
this.mGlobal = global;
this.mSearch = search;
this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0;
@@ -386,7 +391,12 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
//- Add to bookmarks
case R.id.mnu_actions_add_to_bookmarks:
case R.id.mnu_actions_add_to_bookmarks_current_folder:
- BookmarksActionPolicy.addToBookmarks(this.mContext, this.mFso);
+ Bookmark bookmark = BookmarksActionPolicy.addToBookmarks(
+ this.mContext, this.mFso);
+ if (mBackRef != null) {
+ // tell NavigationActivity's drawer to add the bookmark
+ mBackRef.addBookmark(bookmark);
+ }
break;
//- Add shortcut
diff --git a/src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java
index bc1d39f6..ce3fe2e6 100644
--- a/src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java
+++ b/src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java
@@ -38,7 +38,7 @@ public final class BookmarksActionPolicy extends ActionsPolicy {
* @param ctx The current context
* @param fso The file system object
*/
- public static void addToBookmarks(final Context ctx, final FileSystemObject fso) {
+ public static Bookmark addToBookmarks(final Context ctx, final FileSystemObject fso) {
try {
// Create the bookmark
Bookmark bookmark =
@@ -50,17 +50,20 @@ public final class BookmarksActionPolicy extends ActionsPolicy {
ctx,
R.string.msgs_operation_failure,
Toast.LENGTH_SHORT);
- } else {
- // Success
- DialogHelper.showToast(
- ctx,
- R.string.bookmarks_msgs_add_success,
- Toast.LENGTH_SHORT);
+ return null;
}
+ // Success
+ DialogHelper.showToast(
+ ctx,
+ R.string.bookmarks_msgs_add_success,
+ Toast.LENGTH_SHORT);
+ return bookmark;
+
} catch (Exception e) {
ExceptionUtil.translateException(ctx, e);
}
+ return null;
}
} \ No newline at end of file
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java b/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java
new file mode 100644
index 00000000..4ef9e483
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2013 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.
+ *
+ *
+ * (modified from android.support.v4.app)
+ */
+
+package com.cyanogenmod.filemanager.ui.widgets;
+
+import java.lang.reflect.Method;
+
+import android.R;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LevelListDrawable;
+import android.os.Build;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+/**
+ * This class provides a handy way to tie together the functionality of
+ * {@link DrawerLayout} and the framework <code>ActionBar</code> to implement
+ * the recommended design for navigation drawers.
+ *
+ * <p>
+ * To use <code>ActionBarDrawerToggle</code>, create one in your Activity and
+ * call through to the following methods corresponding to your Activity
+ * callbacks:
+ * </p>
+ *
+ * <ul>
+ * <li>
+ * {@link Activity#onConfigurationChanged(android.content.res.Configuration)
+ * onConfigurationChanged}</li>
+ * <li>{@link Activity#onOptionsItemSelected(android.view.MenuItem)
+ * onOptionsItemSelected}</li>
+ * </ul>
+ *
+ * <p>
+ * Call {@link #syncState()} from your <code>Activity</code>'s
+ * {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} to synchronize
+ * the indicator with the state of the linked DrawerLayout after
+ * <code>onRestoreInstanceState</code> has occurred.
+ * </p>
+ *
+ * <p>
+ * <code>ActionBarDrawerToggle</code> can be used directly as a
+ * {@link DrawerLayout.DrawerListener}, or if you are already providing your own
+ * listener, call through to each of the listener methods from your own.
+ * </p>
+ */
+public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
+ private static final String TAG = "ActionBarDrawerToggle";
+
+ /**
+ * Allows an implementing Activity to return an
+ * {@link ActionBarDrawerToggle.Delegate} to use with ActionBarDrawerToggle.
+ */
+ public interface DelegateProvider {
+
+ /**
+ * @return Delegate to use for ActionBarDrawableToggles, or null if the
+ * Activity does not wish to override the default behavior.
+ */
+ Delegate getDrawerToggleDelegate();
+ }
+
+ public interface Delegate {
+ /**
+ * @return Up indicator drawable as defined in the Activity's theme, or
+ * null if one is not defined.
+ */
+ Drawable getThemeUpIndicator();
+
+ /**
+ * Set the Action Bar's up indicator drawable and content description.
+ *
+ * @param upDrawable
+ * - Drawable to set as up indicator
+ * @param contentDescRes
+ * - Content description to set
+ */
+ void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes);
+
+ /**
+ * Set the Action Bar's up indicator content description.
+ *
+ * @param contentDescRes
+ * - Content description to set
+ */
+ void setActionBarDescription(int contentDescRes);
+ }
+
+ private static final int[] THEME_ATTRS = new int[] { R.attr.homeAsUpIndicator };
+
+ private static class ActionBarDrawerToggleImpl {
+ public static Drawable getThemeUpIndicator(Activity activity) {
+ final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS);
+ final Drawable result = a.getDrawable(0);
+ a.recycle();
+ return result;
+ }
+
+ public static Object setActionBarUpIndicator(Object info,
+ Activity activity, Drawable drawable, int contentDescRes) {
+ if (info == null) {
+ info = new SetIndicatorInfo(activity);
+ }
+
+ final ActionBar actionBar = activity.getActionBar();
+ actionBar.setHomeAsUpIndicator(drawable);
+ actionBar.setHomeActionContentDescription(contentDescRes);
+
+ return info;
+ }
+
+ public static Object setActionBarDescription(Object info,
+ Activity activity, int contentDescRes) {
+ if (info == null) {
+ info = new SetIndicatorInfo(activity);
+ }
+
+ final ActionBar actionBar = activity.getActionBar();
+ actionBar.setHomeActionContentDescription(contentDescRes);
+
+ return info;
+ }
+ }
+
+ private static class SetIndicatorInfo {
+ public Method setHomeAsUpIndicator;
+ public Method setHomeActionContentDescription;
+ public ImageView upIndicatorView;
+
+ SetIndicatorInfo(Activity activity) {
+ try {
+ setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod(
+ "setHomeAsUpIndicator", Drawable.class);
+ setHomeActionContentDescription = ActionBar.class
+ .getDeclaredMethod("setHomeActionContentDescription",
+ Integer.TYPE);
+
+ // If we got the method we won't need the stuff below.
+ return;
+ } catch (NoSuchMethodException e) {
+ // Oh well. We'll use the other mechanism below instead.
+ }
+
+ final View home = activity.findViewById(android.R.id.home);
+ if (home == null) {
+ // Action bar doesn't have a known configuration, an OEM messed
+ // with things.
+ return;
+ }
+
+ final ViewGroup parent = (ViewGroup) home.getParent();
+ final int childCount = parent.getChildCount();
+ if (childCount != 2) {
+ // No idea which one will be the right one, an OEM messed with
+ // things.
+ return;
+ }
+
+ final View first = parent.getChildAt(0);
+ final View second = parent.getChildAt(1);
+ final View up = first.getId() == android.R.id.home ? second : first;
+
+ if (up instanceof ImageView) {
+ // Jackpot! (Probably...)
+ upIndicatorView = (ImageView) up;
+ }
+ }
+ }
+
+ private static final ActionBarDrawerToggleImpl IMPL = new ActionBarDrawerToggleImpl();
+
+ /** Fraction of its total width by which to offset the toggle drawable. */
+ private static final float TOGGLE_DRAWABLE_OFFSET = 1 / 3f;
+
+ // android.R.id.home as defined by public API in v11
+ private static final int ID_HOME = 0x0102002c;
+
+ private final Activity mActivity;
+ private final Delegate mActivityImpl;
+ private final DrawerLayout mDrawerLayout;
+ private boolean mDrawerIndicatorEnabled = true;
+
+ private Drawable mThemeImage;
+ private Drawable mDrawerImage;
+ private SlideDrawable mSlider;
+ private int mDrawerImageResource;
+ private final int mOpenDrawerContentDescRes;
+ private final int mCloseDrawerContentDescRes;
+
+ private Object mSetIndicatorInfo;
+
+ /**
+ * Construct a new ActionBarDrawerToggle.
+ *
+ * <p>
+ * The given {@link Activity} will be linked to the specified
+ * {@link DrawerLayout}. The provided drawer indicator drawable will animate
+ * slightly off-screen as the drawer is opened, indicating that in the open
+ * state the drawer will move off-screen when pressed and in the closed
+ * state the drawer will move on-screen when pressed.
+ * </p>
+ *
+ * <p>
+ * String resources must be provided to describe the open/close drawer
+ * actions for accessibility services.
+ * </p>
+ *
+ * @param activity
+ * The Activity hosting the drawer
+ * @param drawerLayout
+ * The DrawerLayout to link to the given Activity's ActionBar
+ * @param drawerImageRes
+ * A Drawable resource to use as the drawer indicator
+ * @param openDrawerContentDescRes
+ * A String resource to describe the "open drawer" action for
+ * accessibility
+ * @param closeDrawerContentDescRes
+ * A String resource to describe the "close drawer" action for
+ * accessibility
+ */
+ public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
+ int drawerImageRes, int openDrawerContentDescRes,
+ int closeDrawerContentDescRes) {
+ mActivity = activity;
+
+ // Allow the Activity to provide an impl
+ if (activity instanceof DelegateProvider) {
+ mActivityImpl = ((DelegateProvider) activity)
+ .getDrawerToggleDelegate();
+ } else {
+ mActivityImpl = null;
+ }
+
+ mDrawerLayout = drawerLayout;
+ mDrawerImageResource = drawerImageRes;
+ mOpenDrawerContentDescRes = openDrawerContentDescRes;
+ mCloseDrawerContentDescRes = closeDrawerContentDescRes;
+
+ mThemeImage = getThemeUpIndicator();
+ mDrawerImage = activity.getResources().getDrawable(drawerImageRes);
+ mSlider = new SlideDrawable(mDrawerImage);
+ mSlider.setOffset(TOGGLE_DRAWABLE_OFFSET);
+ }
+
+ /**
+ * Synchronize the state of the drawer indicator/affordance with the linked
+ * DrawerLayout.
+ *
+ * <p>
+ * This should be called from your <code>Activity</code>'s
+ * {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} method to
+ * synchronize after the DrawerLayout's instance state has been restored,
+ * and any other time when the state may have diverged in such a way that
+ * the ActionBarDrawerToggle was not notified. (For example, if you stop
+ * forwarding appropriate drawer events for a period of time.)
+ * </p>
+ */
+ public void syncState() {
+ if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
+ mSlider.setPosition(1);
+ } else {
+ mSlider.setPosition(0);
+ }
+
+ if (mDrawerIndicatorEnabled) {
+ setActionBarUpIndicator(
+ mSlider,
+ mDrawerLayout.isDrawerOpen(Gravity.START) ? mCloseDrawerContentDescRes
+ : mOpenDrawerContentDescRes);
+ }
+ }
+
+ /**
+ * Enable or disable the drawer indicator. The indicator defaults to
+ * enabled.
+ *
+ * <p>
+ * When the indicator is disabled, the <code>ActionBar</code> will revert to
+ * displaying the home-as-up indicator provided by the <code>Activity</code>
+ * 's theme in the <code>android.R.attr.homeAsUpIndicator</code> attribute
+ * instead of the animated drawer glyph.
+ * </p>
+ *
+ * @param enable
+ * true to enable, false to disable
+ */
+ public void setDrawerIndicatorEnabled(boolean enable) {
+ if (enable != mDrawerIndicatorEnabled) {
+ if (enable) {
+ setActionBarUpIndicator(
+ mSlider,
+ mDrawerLayout.isDrawerOpen(Gravity.START) ? mCloseDrawerContentDescRes
+ : mOpenDrawerContentDescRes);
+ } else {
+ setActionBarUpIndicator(mThemeImage, 0);
+ }
+ mDrawerIndicatorEnabled = enable;
+ }
+ }
+
+ /**
+ * @return true if the enhanced drawer indicator is enabled, false otherwise
+ * @see #setDrawerIndicatorEnabled(boolean)
+ */
+ public boolean isDrawerIndicatorEnabled() {
+ return mDrawerIndicatorEnabled;
+ }
+
+ /**
+ * This method replaces the drawer image resource with a new one.
+ *
+ * @param newDrawerImageRes
+ * The new resource id
+ */
+ public void setDrawerImageResource(int newDrawerImageRes) {
+ mDrawerImageResource = newDrawerImageRes;
+ mDrawerImage = mActivity.getResources().getDrawable(
+ mDrawerImageResource);
+ mSlider = new SlideDrawable(mDrawerImage);
+ mSlider.setOffset(TOGGLE_DRAWABLE_OFFSET);
+ syncState();
+ }
+
+ /**
+ * This method should always be called by your <code>Activity</code>'s
+ * {@link Activity#onConfigurationChanged(android.content.res.Configuration)
+ * onConfigurationChanged} method.
+ *
+ * @param newConfig
+ * The new configuration
+ */
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Reload drawables that can change with configuration
+ mThemeImage = getThemeUpIndicator();
+ mDrawerImage = mActivity.getResources().getDrawable(
+ mDrawerImageResource);
+ syncState();
+ }
+
+ /**
+ * This method should be called by your <code>Activity</code>'s
+ * {@link Activity#onOptionsItemSelected(android.view.MenuItem)
+ * onOptionsItemSelected} method. If it returns true, your
+ * <code>onOptionsItemSelected</code> method should return true and skip
+ * further processing.
+ *
+ * @param item
+ * the MenuItem instance representing the selected menu item
+ * @return true if the event was handled and further processing should not
+ * occur
+ */
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item != null && item.getItemId() == ID_HOME
+ && mDrawerIndicatorEnabled) {
+ if (mDrawerLayout.isDrawerVisible(Gravity.START)) {
+ mDrawerLayout.closeDrawer(Gravity.START);
+ } else {
+ mDrawerLayout.openDrawer(Gravity.START);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@link DrawerLayout.DrawerListener} callback method. If you do not use
+ * your ActionBarDrawerToggle instance directly as your DrawerLayout's
+ * listener, you should call through to this method from your own listener
+ * object.
+ *
+ * @param drawerView
+ * The child view that was moved
+ * @param slideOffset
+ * The new offset of this drawer within its range, from 0-1
+ */
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ float glyphOffset = mSlider.getPosition();
+ if (slideOffset > 0.5f) {
+ glyphOffset = Math.max(glyphOffset,
+ Math.max(0.f, slideOffset - 0.5f) * 2);
+ } else {
+ glyphOffset = Math.min(glyphOffset, slideOffset * 2);
+ }
+ mSlider.setPosition(glyphOffset);
+ }
+
+ /**
+ * {@link DrawerLayout.DrawerListener} callback method. If you do not use
+ * your ActionBarDrawerToggle instance directly as your DrawerLayout's
+ * listener, you should call through to this method from your own listener
+ * object.
+ *
+ * @param drawerView
+ * Drawer view that is now open
+ */
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ mSlider.setPosition(1);
+ if (mDrawerIndicatorEnabled) {
+ setActionBarDescription(mCloseDrawerContentDescRes);
+ }
+ }
+
+ /**
+ * {@link DrawerLayout.DrawerListener} callback method. If you do not use
+ * your ActionBarDrawerToggle instance directly as your DrawerLayout's
+ * listener, you should call through to this method from your own listener
+ * object.
+ *
+ * @param drawerView
+ * Drawer view that is now closed
+ */
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ mSlider.setPosition(0);
+ if (mDrawerIndicatorEnabled) {
+ setActionBarDescription(mOpenDrawerContentDescRes);
+ }
+ }
+
+ /**
+ * {@link DrawerLayout.DrawerListener} callback method. If you do not use
+ * your ActionBarDrawerToggle instance directly as your DrawerLayout's
+ * listener, you should call through to this method from your own listener
+ * object.
+ *
+ * @param newState
+ * The new drawer motion state
+ */
+ @Override
+ public void onDrawerStateChanged(int newState) {
+ }
+
+ Drawable getThemeUpIndicator() {
+ if (mActivityImpl != null) {
+ return mActivityImpl.getThemeUpIndicator();
+ }
+ return IMPL.getThemeUpIndicator(mActivity);
+ }
+
+ void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
+ if (mActivityImpl != null) {
+ mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes);
+ return;
+ }
+ mSetIndicatorInfo = IMPL.setActionBarUpIndicator(mSetIndicatorInfo,
+ mActivity, upDrawable, contentDescRes);
+ }
+
+ void setActionBarDescription(int contentDescRes) {
+ if (mActivityImpl != null) {
+ mActivityImpl.setActionBarDescription(contentDescRes);
+ return;
+ }
+ mSetIndicatorInfo = IMPL.setActionBarDescription(mSetIndicatorInfo,
+ mActivity, contentDescRes);
+ }
+
+ private class SlideDrawable extends LevelListDrawable implements
+ Drawable.Callback {
+ private final boolean mHasMirroring = Build.VERSION.SDK_INT > 18;
+ private final Rect mTmpRect = new Rect();
+
+ private float mPosition;
+ private float mOffset;
+
+ private SlideDrawable(Drawable wrapped) {
+ super();
+
+ if (wrapped.isAutoMirrored()) {
+ this.setAutoMirrored(true);
+ }
+
+ addLevel(0, 0, wrapped);
+ }
+
+ /**
+ * Sets the current position along the offset.
+ *
+ * @param position
+ * a value between 0 and 1
+ */
+ public void setPosition(float position) {
+ mPosition = position;
+ invalidateSelf();
+ }
+
+ public float getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Specifies the maximum offset when the position is at 1.
+ *
+ * @param offset
+ * maximum offset as a fraction of the drawable width,
+ * positive to shift left or negative to shift right.
+ * @see #setPosition(float)
+ */
+ public void setOffset(float offset) {
+ mOffset = offset;
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ copyBounds(mTmpRect);
+ canvas.save();
+
+ // Layout direction must be obtained from the activity.
+ final boolean isLayoutRTL = mActivity.getWindow().getDecorView()
+ .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ final int flipRtl = isLayoutRTL ? -1 : 1;
+ final int width = mTmpRect.width();
+ canvas.translate(-mOffset * width * mPosition * flipRtl, 0);
+
+ // Force auto-mirroring if it's not supported by the platform.
+ if (isLayoutRTL && !mHasMirroring) {
+ canvas.translate(width, 0);
+ canvas.scale(-1, 1);
+ }
+
+ super.draw(canvas);
+ canvas.restore();
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/DrawerLayout.java b/src/com/cyanogenmod/filemanager/ui/widgets/DrawerLayout.java
new file mode 100644
index 00000000..66c0aedc
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/DrawerLayout.java
@@ -0,0 +1,1621 @@
+/*
+ * Copyright (C) 2013 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.
+ *
+ *
+ * (modified from android.support.v4.widget)
+ */
+
+
+package com.cyanogenmod.filemanager.ui.widgets;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * DrawerLayout acts as a top-level container for window content that allows for
+ * interactive "drawer" views to be pulled out from the edge of the window.
+ *
+ * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code>
+ * attribute on child views corresponding to which side of the view you want the drawer
+ * to emerge from: left or right. (Or start/end on platform versions that support layout direction.)
+ * </p>
+ *
+ * <p>To use a DrawerLayout, position your primary content view as the first child with
+ * a width and height of <code>match_parent</code>. Add drawers as child views after the main
+ * content view and set the <code>layout_gravity</code> appropriately. Drawers commonly use
+ * <code>match_parent</code> for height with a fixed width.</p>
+ *
+ * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views.
+ * Avoid performing expensive operations such as layout during animation as it can cause
+ * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state.
+ * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p>
+ *
+ * <p>As per the Android Design guide, any drawers positioned to the left/start should
+ * always contain content for navigating around the application, whereas any drawers
+ * positioned to the right/end should always contain actions to take on the current content.
+ * This preserves the same navigation left, actions right structure present in the Action Bar
+ * and elsewhere.</p>
+ */
+public class DrawerLayout extends ViewGroup {
+ private static final String TAG = "DrawerLayout";
+
+ /**
+ * Indicates that any drawers are in an idle, settled state. No animation is in progress.
+ */
+ public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
+
+ /**
+ * Indicates that a drawer is currently being dragged by the user.
+ */
+ public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
+
+ /**
+ * Indicates that a drawer is in the process of settling to a final position.
+ */
+ public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
+
+ /**
+ * The drawer is unlocked.
+ */
+ public static final int LOCK_MODE_UNLOCKED = 0;
+
+ /**
+ * The drawer is locked closed. The user may not open it, though
+ * the app may open it programmatically.
+ */
+ public static final int LOCK_MODE_LOCKED_CLOSED = 1;
+
+ /**
+ * The drawer is locked open. The user may not close it, though the app
+ * may close it programmatically.
+ */
+ public static final int LOCK_MODE_LOCKED_OPEN = 2;
+
+ private static final int MIN_DRAWER_MARGIN = 64; // dp
+
+ private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
+
+ /**
+ * Length of time to delay before peeking the drawer.
+ */
+ private static final int PEEK_DELAY = 160; // ms
+
+ /**
+ * Minimum velocity that will be detected as a fling
+ */
+ private static final int MIN_FLING_VELOCITY = 400; // dips per second
+
+ /**
+ * Experimental feature.
+ */
+ private static final boolean ALLOW_EDGE_LOCK = false;
+
+ private static final boolean CHILDREN_DISALLOW_INTERCEPT = true;
+
+ private static final float TOUCH_SLOP_SENSITIVITY = 1.f;
+
+ private static final int[] LAYOUT_ATTRS = new int[] {
+ android.R.attr.layout_gravity
+ };
+
+ private int mMinDrawerMargin;
+
+ private int mScrimColor = DEFAULT_SCRIM_COLOR;
+ private float mScrimOpacity;
+ private Paint mScrimPaint = new Paint();
+
+ private final ViewDragHelper mLeftDragger;
+ private final ViewDragHelper mRightDragger;
+ private final ViewDragCallback mLeftCallback;
+ private final ViewDragCallback mRightCallback;
+ private int mDrawerState;
+ private boolean mInLayout;
+ private boolean mFirstLayout = true;
+ private int mLockModeLeft;
+ private int mLockModeRight;
+ @SuppressWarnings("unused")
+ private boolean mDisallowInterceptRequested;
+ private boolean mChildrenCanceledTouch;
+
+ private DrawerListener mListener;
+
+ private float mInitialMotionX;
+ private float mInitialMotionY;
+
+ private Drawable mShadowLeft;
+ private Drawable mShadowRight;
+
+ /**
+ * Listener for monitoring events about drawers.
+ */
+ public interface DrawerListener {
+ /**
+ * Called when a drawer's position changes.
+ * @param drawerView The child view that was moved
+ * @param slideOffset The new offset of this drawer within its range, from 0-1
+ */
+ public void onDrawerSlide(View drawerView, float slideOffset);
+
+ /**
+ * Called when a drawer has settled in a completely open state.
+ * The drawer is interactive at this point.
+ *
+ * @param drawerView Drawer view that is now open
+ */
+ public void onDrawerOpened(View drawerView);
+
+ /**
+ * Called when a drawer has settled in a completely closed state.
+ *
+ * @param drawerView Drawer view that is now closed
+ */
+ public void onDrawerClosed(View drawerView);
+
+ /**
+ * Called when the drawer motion state changes. The new state will
+ * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
+ *
+ * @param newState The new drawer motion state
+ */
+ public void onDrawerStateChanged(int newState);
+ }
+
+ /**
+ * Stub/no-op implementations of all methods of {@link DrawerListener}.
+ * Override this if you only care about a few of the available callback methods.
+ */
+ public static abstract class SimpleDrawerListener implements DrawerListener {
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ }
+
+ @Override
+ public void onDrawerStateChanged(int newState) {
+ }
+ }
+
+ public DrawerLayout(Context context) {
+ this(context, null);
+ }
+
+ public DrawerLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final float density = getResources().getDisplayMetrics().density;
+ mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
+ final float minVel = MIN_FLING_VELOCITY * density;
+
+ mLeftCallback = new ViewDragCallback(Gravity.LEFT);
+ mRightCallback = new ViewDragCallback(Gravity.RIGHT);
+
+ mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
+ mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
+ mLeftDragger.setMinVelocity(minVel);
+ mLeftCallback.setDragger(mLeftDragger);
+
+ mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
+ mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
+ mRightDragger.setMinVelocity(minVel);
+ mRightCallback.setDragger(mRightDragger);
+
+ // So that we can catch the back button
+ setFocusableInTouchMode(true);
+
+ this.setAccessibilityDelegate(new AccessibilityDelegate());
+ this.setMotionEventSplittingEnabled(false);
+ }
+
+ /**
+ * Set a simple drawable used for the left or right shadow.
+ * The drawable provided must have a nonzero intrinsic width.
+ *
+ * @param shadowDrawable Shadow drawable to use at the edge of a drawer
+ * @param gravity Which drawer the shadow should apply to
+ */
+ public void setDrawerShadow(Drawable shadowDrawable, int gravity) {
+ /*
+ * TODO Someone someday might want to set more complex drawables here.
+ * They're probably nuts, but we might want to consider registering callbacks,
+ * setting states, etc. properly.
+ */
+
+ final int absGravity = Gravity.getAbsoluteGravity(gravity,
+ this.getLayoutDirection());
+ if ((absGravity & Gravity.LEFT) == Gravity.LEFT) {
+ mShadowLeft = shadowDrawable;
+ invalidate();
+ }
+ if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) {
+ mShadowRight = shadowDrawable;
+ invalidate();
+ }
+ }
+
+ /**
+ * Set a simple drawable used for the left or right shadow.
+ * The drawable provided must have a nonzero intrinsic width.
+ *
+ * @param resId Resource id of a shadow drawable to use at the edge of a drawer
+ * @param gravity Which drawer the shadow should apply to
+ */
+ public void setDrawerShadow(int resId, int gravity) {
+ setDrawerShadow(getResources().getDrawable(resId), gravity);
+ }
+
+ /**
+ * Set a color to use for the scrim that obscures primary content while a drawer is open.
+ *
+ * @param color Color to use in 0xAARRGGBB format.
+ */
+ public void setScrimColor(int color) {
+ mScrimColor = color;
+ invalidate();
+ }
+
+ /**
+ * Set a listener to be notified of drawer events.
+ *
+ * @param listener Listener to notify when drawer events occur
+ * @see DrawerListener
+ */
+ public void setDrawerListener(DrawerListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Enable or disable interaction with all drawers.
+ *
+ * <p>This allows the application to restrict the user's ability to open or close
+ * any drawer within this layout. DrawerLayout will still respond to calls to
+ * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
+ *
+ * <p>Locking drawers open or closed will implicitly open or close
+ * any drawers as appropriate.</p>
+ *
+ * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
+ * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
+ */
+ public void setDrawerLockMode(int lockMode) {
+ setDrawerLockMode(lockMode, Gravity.LEFT);
+ setDrawerLockMode(lockMode, Gravity.RIGHT);
+ }
+
+ /**
+ * Enable or disable interaction with the given drawer.
+ *
+ * <p>This allows the application to restrict the user's ability to open or close
+ * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
+ * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
+ *
+ * <p>Locking a drawer open or closed will implicitly open or close
+ * that drawer as appropriate.</p>
+ *
+ * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
+ * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
+ * @param edgeGravity Gravity.LEFT, RIGHT, START or END.
+ * Expresses which drawer to change the mode for.
+ *
+ * @see #LOCK_MODE_UNLOCKED
+ * @see #LOCK_MODE_LOCKED_CLOSED
+ * @see #LOCK_MODE_LOCKED_OPEN
+ */
+ public void setDrawerLockMode(int lockMode, int edgeGravity) {
+ final int absGravity = Gravity.getAbsoluteGravity(edgeGravity,
+ this.getLayoutDirection());
+ if (absGravity == Gravity.LEFT) {
+ mLockModeLeft = lockMode;
+ } else if (absGravity == Gravity.RIGHT) {
+ mLockModeRight = lockMode;
+ }
+ if (lockMode != LOCK_MODE_UNLOCKED) {
+ // Cancel interaction in progress
+ final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger;
+ helper.cancel();
+ }
+ switch (lockMode) {
+ case LOCK_MODE_LOCKED_OPEN:
+ final View toOpen = findDrawerWithGravity(absGravity);
+ if (toOpen != null) {
+ openDrawer(toOpen);
+ }
+ break;
+ case LOCK_MODE_LOCKED_CLOSED:
+ final View toClose = findDrawerWithGravity(absGravity);
+ if (toClose != null) {
+ closeDrawer(toClose);
+ }
+ break;
+ // default: do nothing
+ }
+ }
+
+ /**
+ * Enable or disable interaction with the given drawer.
+ *
+ * <p>This allows the application to restrict the user's ability to open or close
+ * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
+ * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
+ *
+ * <p>Locking a drawer open or closed will implicitly open or close
+ * that drawer as appropriate.</p>
+ *
+ * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
+ * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
+ * @param drawerView The drawer view to change the lock mode for
+ *
+ * @see #LOCK_MODE_UNLOCKED
+ * @see #LOCK_MODE_LOCKED_CLOSED
+ * @see #LOCK_MODE_LOCKED_OPEN
+ */
+ public void setDrawerLockMode(int lockMode, View drawerView) {
+ if (!isDrawerView(drawerView)) {
+ throw new IllegalArgumentException("View " + drawerView + " is not a " +
+ "drawer with appropriate layout_gravity");
+ }
+ final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
+ setDrawerLockMode(lockMode, gravity);
+ }
+
+ /**
+ * Check the lock mode of the drawer with the given gravity.
+ *
+ * @param edgeGravity Gravity of the drawer to check
+ * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
+ * {@link #LOCK_MODE_LOCKED_OPEN}.
+ */
+ public int getDrawerLockMode(int edgeGravity) {
+ final int absGravity = Gravity.getAbsoluteGravity(
+ edgeGravity, this.getLayoutDirection());
+ if (absGravity == Gravity.LEFT) {
+ return mLockModeLeft;
+ } else if (absGravity == Gravity.RIGHT) {
+ return mLockModeRight;
+ }
+ return LOCK_MODE_UNLOCKED;
+ }
+
+ /**
+ * Check the lock mode of the given drawer view.
+ *
+ * @param drawerView Drawer view to check lock mode
+ * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
+ * {@link #LOCK_MODE_LOCKED_OPEN}.
+ */
+ public int getDrawerLockMode(View drawerView) {
+ final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
+ if (absGravity == Gravity.LEFT) {
+ return mLockModeLeft;
+ } else if (absGravity == Gravity.RIGHT) {
+ return mLockModeRight;
+ }
+ return LOCK_MODE_UNLOCKED;
+ }
+
+ /**
+ * Resolve the shared state of all drawers from the component ViewDragHelpers.
+ * Should be called whenever a ViewDragHelper's state changes.
+ */
+ void updateDrawerState(int forGravity, int activeState, View activeDrawer) {
+ final int leftState = mLeftDragger.getViewDragState();
+ final int rightState = mRightDragger.getViewDragState();
+
+ final int state;
+ if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) {
+ state = STATE_DRAGGING;
+ } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) {
+ state = STATE_SETTLING;
+ } else {
+ state = STATE_IDLE;
+ }
+
+ if (activeDrawer != null && activeState == STATE_IDLE) {
+ final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams();
+ if (lp.onScreen == 0) {
+ dispatchOnDrawerClosed(activeDrawer);
+ } else if (lp.onScreen == 1) {
+ dispatchOnDrawerOpened(activeDrawer);
+ }
+ }
+
+ if (state != mDrawerState) {
+ mDrawerState = state;
+
+ if (mListener != null) {
+ mListener.onDrawerStateChanged(state);
+ }
+ }
+ }
+
+ void dispatchOnDrawerClosed(View drawerView) {
+ final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
+ if (lp.knownOpen) {
+ lp.knownOpen = false;
+ if (mListener != null) {
+ mListener.onDrawerClosed(drawerView);
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+ }
+
+ void dispatchOnDrawerOpened(View drawerView) {
+ final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
+ if (!lp.knownOpen) {
+ lp.knownOpen = true;
+ if (mListener != null) {
+ mListener.onDrawerOpened(drawerView);
+ }
+ drawerView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+ }
+
+ void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
+ if (mListener != null) {
+ mListener.onDrawerSlide(drawerView, slideOffset);
+ }
+ }
+
+ void setDrawerViewOffset(View drawerView, float slideOffset) {
+ final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
+ if (slideOffset == lp.onScreen) {
+ return;
+ }
+
+ lp.onScreen = slideOffset;
+ dispatchOnDrawerSlide(drawerView, slideOffset);
+ }
+
+ float getDrawerViewOffset(View drawerView) {
+ return ((LayoutParams) drawerView.getLayoutParams()).onScreen;
+ }
+
+ /**
+ * @return the absolute gravity of the child drawerView, resolved according
+ * to the current layout direction
+ */
+ int getDrawerViewAbsoluteGravity(View drawerView) {
+ final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
+ return Gravity.getAbsoluteGravity(gravity, this.getLayoutDirection());
+ }
+
+ boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) {
+ final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
+ return (absGravity & checkFor) == checkFor;
+ }
+
+ View findOpenDrawer() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (((LayoutParams) child.getLayoutParams()).knownOpen) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ void moveDrawerToOffset(View drawerView, float slideOffset) {
+ final float oldOffset = getDrawerViewOffset(drawerView);
+ final int width = drawerView.getWidth();
+ final int oldPos = (int) (width * oldOffset);
+ final int newPos = (int) (width * slideOffset);
+ final int dx = newPos - oldPos;
+
+ drawerView.offsetLeftAndRight(
+ checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx);
+ setDrawerViewOffset(drawerView, slideOffset);
+ }
+
+ /**
+ * @param gravity the gravity of the child to return. If specified as a
+ * relative value, it will be resolved according to the current
+ * layout direction.
+ * @return the drawer with the specified gravity
+ */
+ View findDrawerWithGravity(int gravity) {
+ final int absHorizGravity = Gravity.getAbsoluteGravity(
+ gravity, this.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
+ if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Simple gravity to string - only supports LEFT and RIGHT for debugging output.
+ *
+ * @param gravity Absolute gravity value
+ * @return LEFT or RIGHT as appropriate, or a hex string
+ */
+ static String gravityToString(int gravity) {
+ if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
+ return "LEFT";
+ }
+ if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
+ return "RIGHT";
+ }
+ return Integer.toHexString(gravity);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+ if (isInEditMode()) {
+ // Don't crash the layout editor. Consume all of the space if specified
+ // or pick a magic number from thin air otherwise.
+ // TODO Better communication with tools of this bogus state.
+ // It will crash on a real device.
+ if (widthMode == MeasureSpec.AT_MOST) {
+ widthMode = MeasureSpec.EXACTLY;
+ } else if (widthMode == MeasureSpec.UNSPECIFIED) {
+ widthMode = MeasureSpec.EXACTLY;
+ widthSize = 300;
+ }
+ if (heightMode == MeasureSpec.AT_MOST) {
+ heightMode = MeasureSpec.EXACTLY;
+ }
+ else if (heightMode == MeasureSpec.UNSPECIFIED) {
+ heightMode = MeasureSpec.EXACTLY;
+ heightSize = 300;
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
+ }
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+
+ // Gravity value for each drawer we've seen. Only one of each permitted.
+ int foundDrawers = 0;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (isContentView(child)) {
+ // Content views get measured at exactly the layout's size.
+ final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
+ widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
+ final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
+ heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
+ child.measure(contentWidthSpec, contentHeightSpec);
+ } else if (isDrawerView(child)) {
+ final int childGravity =
+ getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if ((foundDrawers & childGravity) != 0) {
+ throw new IllegalStateException("Child drawer has absolute gravity " +
+ gravityToString(childGravity) + " but this " + TAG + " already has a " +
+ "drawer view along that edge");
+ }
+ final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
+ mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
+ lp.width);
+ final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
+ lp.topMargin + lp.bottomMargin,
+ lp.height);
+ child.measure(drawerWidthSpec, drawerHeightSpec);
+ } else {
+ throw new IllegalStateException("Child " + child + " at index " + i +
+ " does not have a valid layout_gravity - must be Gravity.LEFT, " +
+ "Gravity.RIGHT or Gravity.NO_GRAVITY");
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mInLayout = true;
+ final int width = r - l;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (isContentView(child)) {
+ child.layout(lp.leftMargin, lp.topMargin,
+ lp.leftMargin + child.getMeasuredWidth(),
+ lp.topMargin + child.getMeasuredHeight());
+ } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+ int childLeft;
+
+ final float newOffset;
+ if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
+ childLeft = -childWidth + (int) (childWidth * lp.onScreen);
+ newOffset = (float) (childWidth + childLeft) / childWidth;
+ } else { // Right; onMeasure checked for us.
+ childLeft = width - (int) (childWidth * lp.onScreen);
+ newOffset = (float) (width - childLeft) / childWidth;
+ }
+
+ final boolean changeOffset = newOffset != lp.onScreen;
+
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ switch (vgrav) {
+ default:
+ case Gravity.TOP: {
+ child.layout(childLeft, lp.topMargin, childLeft + childWidth,
+ lp.topMargin + childHeight);
+ break;
+ }
+
+ case Gravity.BOTTOM: {
+ final int height = b - t;
+ child.layout(childLeft,
+ height - lp.bottomMargin - child.getMeasuredHeight(),
+ childLeft + childWidth,
+ height - lp.bottomMargin);
+ break;
+ }
+
+ case Gravity.CENTER_VERTICAL: {
+ final int height = b - t;
+ int childTop = (height - childHeight) / 2;
+
+ // Offset for margins. If things don't fit right because of
+ // bad measurement before, oh well.
+ if (childTop < lp.topMargin) {
+ childTop = lp.topMargin;
+ } else if (childTop + childHeight > height - lp.bottomMargin) {
+ childTop = height - lp.bottomMargin - childHeight;
+ }
+ child.layout(childLeft, childTop, childLeft + childWidth,
+ childTop + childHeight);
+ break;
+ }
+ }
+
+ if (changeOffset) {
+ setDrawerViewOffset(child, newOffset);
+ }
+
+ final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
+ if (child.getVisibility() != newVisibility) {
+ child.setVisibility(newVisibility);
+ }
+ }
+ }
+ mInLayout = false;
+ mFirstLayout = false;
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mInLayout) {
+ super.requestLayout();
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ final int childCount = getChildCount();
+ float scrimOpacity = 0;
+ for (int i = 0; i < childCount; i++) {
+ final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen;
+ scrimOpacity = Math.max(scrimOpacity, onscreen);
+ }
+ mScrimOpacity = scrimOpacity;
+
+ // "|" used on purpose; both need to run.
+ if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) {
+ this.postInvalidateOnAnimation();
+ }
+ }
+
+ private static boolean hasOpaqueBackground(View v) {
+ final Drawable bg = v.getBackground();
+ if (bg != null) {
+ return bg.getOpacity() == PixelFormat.OPAQUE;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ final int height = getHeight();
+ final boolean drawingContent = isContentView(child);
+ int clipLeft = 0, clipRight = getWidth();
+
+ final int restoreCount = canvas.save();
+ if (drawingContent) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ if (v == child || v.getVisibility() != VISIBLE ||
+ !hasOpaqueBackground(v) || !isDrawerView(v) ||
+ v.getHeight() < height) {
+ continue;
+ }
+
+ if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
+ final int vright = v.getRight();
+ if (vright > clipLeft) clipLeft = vright;
+ } else {
+ final int vleft = v.getLeft();
+ if (vleft < clipRight) clipRight = vleft;
+ }
+ }
+ canvas.clipRect(clipLeft, 0, clipRight, getHeight());
+ }
+ final boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restoreToCount(restoreCount);
+
+ if (mScrimOpacity > 0 && drawingContent) {
+ final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
+ final int imag = (int) (baseAlpha * mScrimOpacity);
+ final int color = imag << 24 | (mScrimColor & 0xffffff);
+ mScrimPaint.setColor(color);
+
+ canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
+ } else if (mShadowLeft != null && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
+ final int shadowWidth = mShadowLeft.getIntrinsicWidth();
+ final int childRight = child.getRight();
+ final int drawerPeekDistance = mLeftDragger.getEdgeSize();
+ final float alpha =
+ Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
+ mShadowLeft.setBounds(childRight, child.getTop(),
+ childRight + shadowWidth, child.getBottom());
+ mShadowLeft.setAlpha((int) (0xff * alpha));
+ mShadowLeft.draw(canvas);
+ } else if (mShadowRight != null && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) {
+ final int shadowWidth = mShadowRight.getIntrinsicWidth();
+ final int childLeft = child.getLeft();
+ final int showing = getWidth() - childLeft;
+ final int drawerPeekDistance = mRightDragger.getEdgeSize();
+ final float alpha =
+ Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f));
+ mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(),
+ childLeft, child.getBottom());
+ mShadowRight.setAlpha((int) (0xff * alpha));
+ mShadowRight.draw(canvas);
+ }
+ return result;
+ }
+
+ boolean isContentView(View child) {
+ return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
+ }
+
+ boolean isDrawerView(View child) {
+ final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
+ final int absGravity = Gravity.getAbsoluteGravity(gravity,
+ child.getLayoutDirection());
+ return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getActionMasked();
+
+ // "|" used deliberately here; both methods should be invoked.
+ final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) |
+ mRightDragger.shouldInterceptTouchEvent(ev);
+
+ boolean interceptForTap = false;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ mInitialMotionX = x;
+ mInitialMotionY = y;
+ if (mScrimOpacity > 0 &&
+ isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
+ interceptForTap = true;
+ }
+ mDisallowInterceptRequested = false;
+ mChildrenCanceledTouch = false;
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ // If we cross the touch slop, don't perform the delayed peek for an edge touch.
+ if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
+ mLeftCallback.removeCallbacks();
+ mRightCallback.removeCallbacks();
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ closeDrawers(true);
+ mDisallowInterceptRequested = false;
+ mChildrenCanceledTouch = false;
+ }
+ }
+
+ return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mLeftDragger.processTouchEvent(ev);
+ mRightDragger.processTouchEvent(ev);
+
+ final int action = ev.getAction();
+ boolean wantTouchEvents = true;
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ mInitialMotionX = x;
+ mInitialMotionY = y;
+ mDisallowInterceptRequested = false;
+ mChildrenCanceledTouch = false;
+ break;
+ }
+
+ case MotionEvent.ACTION_UP: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ boolean peekingOnly = true;
+ final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
+ if (touchedView != null && isContentView(touchedView)) {
+ final float dx = x - mInitialMotionX;
+ final float dy = y - mInitialMotionY;
+ final int slop = mLeftDragger.getTouchSlop();
+ if (dx * dx + dy * dy < slop * slop) {
+ // Taps close a dimmed open drawer but only if it isn't locked open.
+ final View openDrawer = findOpenDrawer();
+ if (openDrawer != null) {
+ peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN;
+ }
+ }
+ }
+ closeDrawers(peekingOnly);
+ mDisallowInterceptRequested = false;
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL: {
+ closeDrawers(true);
+ mDisallowInterceptRequested = false;
+ mChildrenCanceledTouch = false;
+ break;
+ }
+ }
+
+ return wantTouchEvents;
+ }
+
+ @SuppressWarnings("unused")
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (CHILDREN_DISALLOW_INTERCEPT ||
+ (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) &&
+ !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) {
+ // If we have an edge touch we want to skip this and track it for later instead.
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ mDisallowInterceptRequested = disallowIntercept;
+ if (disallowIntercept) {
+ closeDrawers(true);
+ }
+ }
+
+ /**
+ * Close all currently open drawer views by animating them out of view.
+ */
+ public void closeDrawers() {
+ closeDrawers(false);
+ }
+
+ void closeDrawers(boolean peekingOnly) {
+ boolean needsInvalidate = false;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {
+ continue;
+ }
+
+ final int childWidth = child.getWidth();
+
+ if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
+ needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,
+ -childWidth, child.getTop());
+ } else {
+ needsInvalidate |= mRightDragger.smoothSlideViewTo(child,
+ getWidth(), child.getTop());
+ }
+
+ lp.isPeeking = false;
+ }
+
+ mLeftCallback.removeCallbacks();
+ mRightCallback.removeCallbacks();
+
+ if (needsInvalidate) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Open the specified drawer view by animating it into view.
+ *
+ * @param drawerView Drawer view to open
+ */
+ public void openDrawer(View drawerView) {
+ if (!isDrawerView(drawerView)) {
+ throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
+ }
+
+ if (mFirstLayout) {
+ final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
+ lp.onScreen = 1.f;
+ lp.knownOpen = true;
+ } else {
+ if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
+ mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
+ } else {
+ mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
+ drawerView.getTop());
+ }
+ }
+ invalidate();
+ }
+
+ /**
+ * Open the specified drawer by animating it out of view.
+ *
+ * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
+ * GravityCompat.START or GravityCompat.END may also be used.
+ */
+ public void openDrawer(int gravity) {
+ final View drawerView = findDrawerWithGravity(gravity);
+ if (drawerView == null) {
+ throw new IllegalArgumentException("No drawer view found with gravity " +
+ gravityToString(gravity));
+ }
+ openDrawer(drawerView);
+ }
+
+ /**
+ * Close the specified drawer view by animating it into view.
+ *
+ * @param drawerView Drawer view to close
+ */
+ public void closeDrawer(View drawerView) {
+ if (!isDrawerView(drawerView)) {
+ throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
+ }
+
+ if (mFirstLayout) {
+ final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
+ lp.onScreen = 0.f;
+ lp.knownOpen = false;
+ } else {
+ if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
+ mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(),
+ drawerView.getTop());
+ } else {
+ mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
+ }
+ }
+ invalidate();
+ }
+
+ /**
+ * Close the specified drawer by animating it out of view.
+ *
+ * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
+ * GravityCompat.START or GravityCompat.END may also be used.
+ */
+ public void closeDrawer(int gravity) {
+ final View drawerView = findDrawerWithGravity(gravity);
+ if (drawerView == null) {
+ throw new IllegalArgumentException("No drawer view found with gravity " +
+ gravityToString(gravity));
+ }
+ closeDrawer(drawerView);
+ }
+
+ /**
+ * Check if the given drawer view is currently in an open state.
+ * To be considered "open" the drawer must have settled into its fully
+ * visible state. To check for partial visibility use
+ * {@link #isDrawerVisible(android.view.View)}.
+ *
+ * @param drawer Drawer view to check
+ * @return true if the given drawer view is in an open state
+ * @see #isDrawerVisible(android.view.View)
+ */
+ public boolean isDrawerOpen(View drawer) {
+ if (!isDrawerView(drawer)) {
+ throw new IllegalArgumentException("View " + drawer + " is not a drawer");
+ }
+ return ((LayoutParams) drawer.getLayoutParams()).knownOpen;
+ }
+
+ /**
+ * Check if the given drawer view is currently in an open state.
+ * To be considered "open" the drawer must have settled into its fully
+ * visible state. If there is no drawer with the given gravity this method
+ * will return false.
+ *
+ * @param drawerGravity Gravity of the drawer to check
+ * @return true if the given drawer view is in an open state
+ */
+ public boolean isDrawerOpen(int drawerGravity) {
+ final View drawerView = findDrawerWithGravity(drawerGravity);
+ if (drawerView != null) {
+ return isDrawerOpen(drawerView);
+ }
+ return false;
+ }
+
+ /**
+ * Check if a given drawer view is currently visible on-screen. The drawer
+ * may be only peeking onto the screen, fully extended, or anywhere inbetween.
+ *
+ * @param drawer Drawer view to check
+ * @return true if the given drawer is visible on-screen
+ * @see #isDrawerOpen(android.view.View)
+ */
+ public boolean isDrawerVisible(View drawer) {
+ if (!isDrawerView(drawer)) {
+ throw new IllegalArgumentException("View " + drawer + " is not a drawer");
+ }
+ return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0;
+ }
+
+ /**
+ * Check if a given drawer view is currently visible on-screen. The drawer
+ * may be only peeking onto the screen, fully extended, or anywhere inbetween.
+ * If there is no drawer with the given gravity this method will return false.
+ *
+ * @param drawerGravity Gravity of the drawer to check
+ * @return true if the given drawer is visible on-screen
+ */
+ public boolean isDrawerVisible(int drawerGravity) {
+ final View drawerView = findDrawerWithGravity(drawerGravity);
+ if (drawerView != null) {
+ return isDrawerVisible(drawerView);
+ }
+ return false;
+ }
+
+ private boolean hasPeekingDrawer() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+ if (lp.isPeeking) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams
+ ? new LayoutParams((LayoutParams) p)
+ : p instanceof ViewGroup.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);
+ }
+
+ private boolean hasVisibleDrawer() {
+ return findVisibleDrawer() != null;
+ }
+
+ private View findVisibleDrawer() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (isDrawerView(child) && isDrawerVisible(child)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ void cancelChildViewTouch() {
+ // Cancel child touches
+ if (!mChildrenCanceledTouch) {
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ getChildAt(i).dispatchTouchEvent(cancelEvent);
+ }
+ cancelEvent.recycle();
+ mChildrenCanceledTouch = true;
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
+ event.startTracking();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ final View visibleDrawer = findVisibleDrawer();
+ if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) {
+ closeDrawers();
+ }
+ return visibleDrawer != null;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ final SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
+ final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
+ if (toOpen != null) {
+ openDrawer(toOpen);
+ }
+ }
+
+ setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
+ setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+
+ final SavedState ss = new SavedState(superState);
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (!isDrawerView(child)) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.knownOpen) {
+ ss.openDrawerGravity = lp.gravity;
+ // Only one drawer can be open at a time.
+ break;
+ }
+ }
+
+ ss.lockModeLeft = mLockModeLeft;
+ ss.lockModeRight = mLockModeRight;
+
+ return ss;
+ }
+
+ /**
+ * State persisted across instances
+ */
+ protected static class SavedState extends BaseSavedState {
+ int openDrawerGravity = Gravity.NO_GRAVITY;
+ int lockModeLeft = LOCK_MODE_UNLOCKED;
+ int lockModeRight = LOCK_MODE_UNLOCKED;
+
+ public SavedState(Parcel in) {
+ super(in);
+ openDrawerGravity = in.readInt();
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(openDrawerGravity);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel source) {
+ return new SavedState(source);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private class ViewDragCallback extends ViewDragHelper.Callback {
+ private final int mAbsGravity;
+ private ViewDragHelper mDragger;
+
+ private final Runnable mPeekRunnable = new Runnable() {
+ @Override public void run() {
+ peekDrawer();
+ }
+ };
+
+ public ViewDragCallback(int gravity) {
+ mAbsGravity = gravity;
+ }
+
+ public void setDragger(ViewDragHelper dragger) {
+ mDragger = dragger;
+ }
+
+ public void removeCallbacks() {
+ DrawerLayout.this.removeCallbacks(mPeekRunnable);
+ }
+
+ @Override
+ public boolean tryCaptureView(View child, int pointerId) {
+ // Only capture views where the gravity matches what we're looking for.
+ // This lets us use two ViewDragHelpers, one for each side drawer.
+ return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity)
+ && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
+ }
+
+ @Override
+ public void onViewDragStateChanged(int state) {
+ updateDrawerState(mAbsGravity, state, mDragger.getCapturedView());
+ }
+
+ @Override
+ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ float offset;
+ final int childWidth = changedView.getWidth();
+
+ // This reverses the positioning shown in onLayout.
+ if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) {
+ offset = (float) (childWidth + left) / childWidth;
+ } else {
+ final int width = getWidth();
+ offset = (float) (width - left) / childWidth;
+ }
+ setDrawerViewOffset(changedView, offset);
+ changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
+ invalidate();
+ }
+
+ @Override
+ public void onViewCaptured(View capturedChild, int activePointerId) {
+ final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
+ lp.isPeeking = false;
+
+ closeOtherDrawer();
+ }
+
+ private void closeOtherDrawer() {
+ final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
+ final View toClose = findDrawerWithGravity(otherGrav);
+ if (toClose != null) {
+ closeDrawer(toClose);
+ }
+ }
+
+ @Override
+ public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ // Offset is how open the drawer is, therefore left/right values
+ // are reversed from one another.
+ final float offset = getDrawerViewOffset(releasedChild);
+ final int childWidth = releasedChild.getWidth();
+
+ int left;
+ if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) {
+ left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth;
+ } else {
+ final int width = getWidth();
+ left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width;
+ }
+
+ mDragger.settleCapturedViewAt(left, releasedChild.getTop());
+ invalidate();
+ }
+
+ @Override
+ public void onEdgeTouched(int edgeFlags, int pointerId) {
+ postDelayed(mPeekRunnable, PEEK_DELAY);
+ }
+
+ private void peekDrawer() {
+ final View toCapture;
+ final int childLeft;
+ final int peekDistance = mDragger.getEdgeSize();
+ final boolean leftEdge = mAbsGravity == Gravity.LEFT;
+ if (leftEdge) {
+ toCapture = findDrawerWithGravity(Gravity.LEFT);
+ childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
+ } else {
+ toCapture = findDrawerWithGravity(Gravity.RIGHT);
+ childLeft = getWidth() - peekDistance;
+ }
+ // Only peek if it would mean making the drawer more visible and the drawer isn't locked
+ if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) ||
+ (!leftEdge && toCapture.getLeft() > childLeft)) &&
+ getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
+ final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
+ mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
+ lp.isPeeking = true;
+ invalidate();
+
+ closeOtherDrawer();
+
+ cancelChildViewTouch();
+ }
+ }
+
+ @Override
+ public boolean onEdgeLock(int edgeFlags) {
+ if (ALLOW_EDGE_LOCK) {
+ final View drawer = findDrawerWithGravity(mAbsGravity);
+ if (drawer != null && !isDrawerOpen(drawer)) {
+ closeDrawer(drawer);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onEdgeDragStarted(int edgeFlags, int pointerId) {
+ final View toCapture;
+ if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
+ toCapture = findDrawerWithGravity(Gravity.LEFT);
+ } else {
+ toCapture = findDrawerWithGravity(Gravity.RIGHT);
+ }
+
+ if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
+ mDragger.captureChildView(toCapture, pointerId);
+ }
+ }
+
+ @Override
+ public int getViewHorizontalDragRange(View child) {
+ return child.getWidth();
+ }
+
+ @Override
+ public int clampViewPositionHorizontal(View child, int left, int dx) {
+ if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
+ return Math.max(-child.getWidth(), Math.min(left, 0));
+ } else {
+ final int width = getWidth();
+ return Math.max(width - child.getWidth(), Math.min(left, width));
+ }
+ }
+
+ @Override
+ public int clampViewPositionVertical(View child, int top, int dy) {
+ return child.getTop();
+ }
+ }
+
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+
+ public int gravity = Gravity.NO_GRAVITY;
+ float onScreen;
+ boolean isPeeking;
+ boolean knownOpen;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
+ this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
+ a.recycle();
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(int width, int height, int gravity) {
+ this(width, height);
+ this.gravity = gravity;
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ this.gravity = source.gravity;
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(ViewGroup.MarginLayoutParams source) {
+ super(source);
+ }
+ }
+
+ /*class AccessibilityDelegate extends AccessibilityDelegateCompat {
+ private final Rect mTmpRect = new Rect();
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
+ super.onInitializeAccessibilityNodeInfo(host, superNode);
+
+ info.setSource(host);
+ final ViewParent parent = host.getParentForAccessibility();
+ if (parent instanceof View) {
+ info.setParent((View) parent);
+ }
+ copyNodeInfoNoChildren(info, superNode);
+
+ superNode.recycle();
+
+ addChildrenForAccessibility(info, (ViewGroup) host);
+ }
+
+ private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) {
+ final int childCount = v.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = v.getChildAt(i);
+ if (filter(child)) {
+ continue;
+ }
+
+ // Adding children that are marked as not important for
+ // accessibility will break the hierarchy, so we need to check
+ // that value and re-parent views if necessary.
+ final int importance = child.getImportantForAccessibility();
+ switch (importance) {
+ case View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS:
+ // Always skip NO_HIDE views and their descendants.
+ break;
+ case View.IMPORTANT_FOR_ACCESSIBILITY_NO:
+ // Re-parent children of NO view groups, skip NO views.
+ if (child instanceof ViewGroup) {
+ addChildrenForAccessibility(info, (ViewGroup) child);
+ }
+ break;
+ case View.IMPORTANT_FOR_ACCESSIBILITY_AUTO:
+ // Force AUTO views to YES and add them.
+ child.setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ case View.IMPORTANT_FOR_ACCESSIBILITY_YES:
+ info.addChild(child);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+ AccessibilityEvent event) {
+ if (!filter(child)) {
+ return super.onRequestSendAccessibilityEvent(host, child, event);
+ }
+ return false;
+ }
+
+ public boolean filter(View child) {
+ final View openDrawer = findOpenDrawer();
+ return openDrawer != null && openDrawer != child;
+ }
+
+ /**
+ * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
+ * seem to be a few elements that are not easily cloneable using the underlying API.
+ * Leave it private here as it's not general-purpose useful.
+ */
+ /*private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
+ AccessibilityNodeInfoCompat src) {
+ final Rect rect = mTmpRect;
+
+ src.getBoundsInParent(rect);
+ dest.setBoundsInParent(rect);
+
+ src.getBoundsInScreen(rect);
+ dest.setBoundsInScreen(rect);
+
+ dest.setVisibleToUser(src.isVisibleToUser());
+ dest.setPackageName(src.getPackageName());
+ dest.setClassName(src.getClassName());
+ dest.setContentDescription(src.getContentDescription());
+
+ dest.setEnabled(src.isEnabled());
+ dest.setClickable(src.isClickable());
+ dest.setFocusable(src.isFocusable());
+ dest.setFocused(src.isFocused());
+ dest.setAccessibilityFocused(src.isAccessibilityFocused());
+ dest.setSelected(src.isSelected());
+ dest.setLongClickable(src.isLongClickable());
+
+ dest.addAction(src.getActions());
+ }
+ }*/
+}
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/ViewDragHelper.java b/src/com/cyanogenmod/filemanager/ui/widgets/ViewDragHelper.java
new file mode 100644
index 00000000..d9cd711d
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/ViewDragHelper.java
@@ -0,0 +1,1450 @@
+/*
+ * Copyright (C) 2013 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.
+ *
+ *
+ * (modified from android.support.v4.widget)
+ */
+
+package com.cyanogenmod.filemanager.ui.widgets;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+import java.util.Arrays;
+
+/**
+ * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
+ * of useful operations and state tracking for allowing a user to drag and reposition
+ * views within their parent ViewGroup.
+ */
+public class ViewDragHelper {
+ @SuppressWarnings("unused")
+ private static final String TAG = "ViewDragHelper";
+
+ /**
+ * A null/invalid pointer ID.
+ */
+ public static final int INVALID_POINTER = -1;
+
+ /**
+ * A view is not currently being dragged or animating as a result of a fling/snap.
+ */
+ public static final int STATE_IDLE = 0;
+
+ /**
+ * A view is currently being dragged. The position is currently changing as a result
+ * of user input or simulated user input.
+ */
+ public static final int STATE_DRAGGING = 1;
+
+ /**
+ * A view is currently settling into place as a result of a fling or
+ * predefined non-interactive motion.
+ */
+ public static final int STATE_SETTLING = 2;
+
+ /**
+ * Edge flag indicating that the left edge should be affected.
+ */
+ public static final int EDGE_LEFT = 1 << 0;
+
+ /**
+ * Edge flag indicating that the right edge should be affected.
+ */
+ public static final int EDGE_RIGHT = 1 << 1;
+
+ /**
+ * Edge flag indicating that the top edge should be affected.
+ */
+ public static final int EDGE_TOP = 1 << 2;
+
+ /**
+ * Edge flag indicating that the bottom edge should be affected.
+ */
+ public static final int EDGE_BOTTOM = 1 << 3;
+
+ /**
+ * Edge flag set indicating all edges should be affected.
+ */
+ public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
+
+ /**
+ * Indicates that a check should occur along the horizontal axis
+ */
+ public static final int DIRECTION_HORIZONTAL = 1 << 0;
+
+ /**
+ * Indicates that a check should occur along the vertical axis
+ */
+ public static final int DIRECTION_VERTICAL = 1 << 1;
+
+ /**
+ * Indicates that a check should occur along all axes
+ */
+ public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
+
+ private static final int EDGE_SIZE = 20; // dp
+
+ private static final int BASE_SETTLE_DURATION = 256; // ms
+ private static final int MAX_SETTLE_DURATION = 600; // ms
+
+ // Current drag state; idle, dragging or settling
+ private int mDragState;
+
+ // Distance to travel before a drag may begin
+ private int mTouchSlop;
+
+ // Last known position/pointer tracking
+ private int mActivePointerId = INVALID_POINTER;
+ private float[] mInitialMotionX;
+ private float[] mInitialMotionY;
+ private float[] mLastMotionX;
+ private float[] mLastMotionY;
+ private int[] mInitialEdgesTouched;
+ private int[] mEdgeDragsInProgress;
+ private int[] mEdgeDragsLocked;
+ private int mPointersDown;
+
+ private VelocityTracker mVelocityTracker;
+ private float mMaxVelocity;
+ private float mMinVelocity;
+
+ private int mEdgeSize;
+ private int mTrackingEdges;
+
+ private Scroller mScroller;
+
+ private final Callback mCallback;
+
+ private View mCapturedView;
+ private boolean mReleaseInProgress;
+
+ private final ViewGroup mParentView;
+
+ /**
+ * A Callback is used as a communication channel with the ViewDragHelper back to the
+ * parent view using it. <code>on*</code>methods are invoked on siginficant events and several
+ * accessor methods are expected to provide the ViewDragHelper with more information
+ * about the state of the parent view upon request. The callback also makes decisions
+ * governing the range and draggability of child views.
+ */
+ public static abstract class Callback {
+ /**
+ * Called when the drag state changes. See the <code>STATE_*</code> constants
+ * for more information.
+ *
+ * @param state The new drag state
+ *
+ * @see #STATE_IDLE
+ * @see #STATE_DRAGGING
+ * @see #STATE_SETTLING
+ */
+ public void onViewDragStateChanged(int state) {}
+
+ /**
+ * Called when the captured view's position changes as the result of a drag or settle.
+ *
+ * @param changedView View whose position changed
+ * @param left New X coordinate of the left edge of the view
+ * @param top New Y coordinate of the top edge of the view
+ * @param dx Change in X position from the last call
+ * @param dy Change in Y position from the last call
+ */
+ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
+
+ /**
+ * Called when a child view is captured for dragging or settling. The ID of the pointer
+ * currently dragging the captured view is supplied. If activePointerId is
+ * identified as {@link #INVALID_POINTER} the capture is programmatic instead of
+ * pointer-initiated.
+ *
+ * @param capturedChild Child view that was captured
+ * @param activePointerId Pointer id tracking the child capture
+ */
+ public void onViewCaptured(View capturedChild, int activePointerId) {}
+
+ /**
+ * Called when the child view is no longer being actively dragged.
+ * The fling velocity is also supplied, if relevant. The velocity values may
+ * be clamped to system minimums or maximums.
+ *
+ * <p>Calling code may decide to fling or otherwise release the view to let it
+ * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)}
+ * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes
+ * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING}
+ * and the view capture will not fully end until it comes to a complete stop.
+ * If neither of these methods is invoked before <code>onViewReleased</code> returns,
+ * the view will stop in place and the ViewDragHelper will return to
+ * {@link #STATE_IDLE}.</p>
+ *
+ * @param releasedChild The captured child view now being released
+ * @param xvel X velocity of the pointer as it left the screen in pixels per second.
+ * @param yvel Y velocity of the pointer as it left the screen in pixels per second.
+ */
+ public void onViewReleased(View releasedChild, float xvel, float yvel) {}
+
+ /**
+ * Called when one of the subscribed edges in the parent view has been touched
+ * by the user while no child view is currently captured.
+ *
+ * @param edgeFlags A combination of edge flags describing the edge(s) currently touched
+ * @param pointerId ID of the pointer touching the described edge(s)
+ * @see #EDGE_LEFT
+ * @see #EDGE_TOP
+ * @see #EDGE_RIGHT
+ * @see #EDGE_BOTTOM
+ */
+ public void onEdgeTouched(int edgeFlags, int pointerId) {}
+
+ /**
+ * Called when the given edge may become locked. This can happen if an edge drag
+ * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}
+ * was called. This method should return true to lock this edge or false to leave it
+ * unlocked. The default behavior is to leave edges unlocked.
+ *
+ * @param edgeFlags A combination of edge flags describing the edge(s) locked
+ * @return true to lock the edge, false to leave it unlocked
+ */
+ public boolean onEdgeLock(int edgeFlags) {
+ return false;
+ }
+
+ /**
+ * Called when the user has started a deliberate drag away from one
+ * of the subscribed edges in the parent view while no child view is currently captured.
+ *
+ * @param edgeFlags A combination of edge flags describing the edge(s) dragged
+ * @param pointerId ID of the pointer touching the described edge(s)
+ * @see #EDGE_LEFT
+ * @see #EDGE_TOP
+ * @see #EDGE_RIGHT
+ * @see #EDGE_BOTTOM
+ */
+ public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
+
+ /**
+ * Called to determine the Z-order of child views.
+ *
+ * @param index the ordered position to query for
+ * @return index of the view that should be ordered at position <code>index</code>
+ */
+ public int getOrderedChildIndex(int index) {
+ return index;
+ }
+
+ /**
+ * Return the magnitude of a draggable child view's horizontal range of motion in pixels.
+ * This method should return 0 for views that cannot move horizontally.
+ *
+ * @param child Child view to check
+ * @return range of horizontal motion in pixels
+ */
+ public int getViewHorizontalDragRange(View child) {
+ return 0;
+ }
+
+ /**
+ * Return the magnitude of a draggable child view's vertical range of motion in pixels.
+ * This method should return 0 for views that cannot move vertically.
+ *
+ * @param child Child view to check
+ * @return range of vertical motion in pixels
+ */
+ public int getViewVerticalDragRange(View child) {
+ return 0;
+ }
+
+ /**
+ * Called when the user's input indicates that they want to capture the given child view
+ * with the pointer indicated by pointerId. The callback should return true if the user
+ * is permitted to drag the given view with the indicated pointer.
+ *
+ * <p>ViewDragHelper may call this method multiple times for the same view even if
+ * the view is already captured; this indicates that a new pointer is trying to take
+ * control of the view.</p>
+ *
+ * <p>If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)}
+ * will follow if the capture is successful.</p>
+ *
+ * @param child Child the user is attempting to capture
+ * @param pointerId ID of the pointer attempting the capture
+ * @return true if capture should be allowed, false otherwise
+ */
+ public abstract boolean tryCaptureView(View child, int pointerId);
+
+ /**
+ * Restrict the motion of the dragged child view along the horizontal axis.
+ * The default implementation does not allow horizontal motion; the extending
+ * class must override this method and provide the desired clamping.
+ *
+ *
+ * @param child Child view being dragged
+ * @param left Attempted motion along the X axis
+ * @param dx Proposed change in position for left
+ * @return The new clamped position for left
+ */
+ public int clampViewPositionHorizontal(View child, int left, int dx) {
+ return 0;
+ }
+
+ /**
+ * Restrict the motion of the dragged child view along the vertical axis.
+ * The default implementation does not allow vertical motion; the extending
+ * class must override this method and provide the desired clamping.
+ *
+ *
+ * @param child Child view being dragged
+ * @param top Attempted motion along the Y axis
+ * @param dy Proposed change in position for top
+ * @return The new clamped position for top
+ */
+ public int clampViewPositionVertical(View child, int top, int dy) {
+ return 0;
+ }
+ }
+
+ /**
+ * Interpolator defining the animation curve for mScroller
+ */
+ private static final Interpolator sInterpolator = new Interpolator() {
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ private final Runnable mSetIdleRunnable = new Runnable() {
+ public void run() {
+ setDragState(STATE_IDLE);
+ }
+ };
+
+ /**
+ * Factory method to create a new ViewDragHelper.
+ *
+ * @param forParent Parent view to monitor
+ * @param cb Callback to provide information and receive events
+ * @return a new ViewDragHelper instance
+ */
+ public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
+ return new ViewDragHelper(forParent.getContext(), forParent, cb);
+ }
+
+ /**
+ * Factory method to create a new ViewDragHelper.
+ *
+ * @param forParent Parent view to monitor
+ * @param sensitivity Multiplier for how sensitive the helper should be about detecting
+ * the start of a drag. Larger values are more sensitive. 1.0f is normal.
+ * @param cb Callback to provide information and receive events
+ * @return a new ViewDragHelper instance
+ */
+ public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
+ final ViewDragHelper helper = create(forParent, cb);
+ helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
+ return helper;
+ }
+
+ /**
+ * Apps should use ViewDragHelper.create() to get a new instance.
+ * This will allow VDH to use internal compatibility implementations for different
+ * platform versions.
+ *
+ * @param context Context to initialize config-dependent params from
+ * @param forParent Parent view to monitor
+ */
+ private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
+ if (forParent == null) {
+ throw new IllegalArgumentException("Parent view may not be null");
+ }
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback may not be null");
+ }
+
+ mParentView = forParent;
+ mCallback = cb;
+
+ final ViewConfiguration vc = ViewConfiguration.get(context);
+ final float density = context.getResources().getDisplayMetrics().density;
+ mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
+
+ mTouchSlop = vc.getScaledTouchSlop();
+ mMaxVelocity = vc.getScaledMaximumFlingVelocity();
+ mMinVelocity = vc.getScaledMinimumFlingVelocity();
+ mScroller = new Scroller(context, sInterpolator);
+ }
+
+ /**
+ * Set the minimum velocity that will be detected as having a magnitude greater than zero
+ * in pixels per second. Callback methods accepting a velocity will be clamped appropriately.
+ *
+ * @param minVel Minimum velocity to detect
+ */
+ public void setMinVelocity(float minVel) {
+ mMinVelocity = minVel;
+ }
+
+ /**
+ * Return the currently configured minimum velocity. Any flings with a magnitude less
+ * than this value in pixels per second. Callback methods accepting a velocity will receive
+ * zero as a velocity value if the real detected velocity was below this threshold.
+ *
+ * @return the minimum velocity that will be detected
+ */
+ public float getMinVelocity() {
+ return mMinVelocity;
+ }
+
+ /**
+ * Retrieve the current drag state of this helper. This will return one of
+ * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
+ * @return The current drag state
+ */
+ public int getViewDragState() {
+ return mDragState;
+ }
+
+ /**
+ * Enable edge tracking for the selected edges of the parent view.
+ * The callback's {@link Callback#onEdgeTouched(int, int)} and
+ * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
+ * for edges for which edge tracking has been enabled.
+ *
+ * @param edgeFlags Combination of edge flags describing the edges to watch
+ * @see #EDGE_LEFT
+ * @see #EDGE_TOP
+ * @see #EDGE_RIGHT
+ * @see #EDGE_BOTTOM
+ */
+ public void setEdgeTrackingEnabled(int edgeFlags) {
+ mTrackingEdges = edgeFlags;
+ }
+
+ /**
+ * Return the size of an edge. This is the range in pixels along the edges of this view
+ * that will actively detect edge touches or drags if edge tracking is enabled.
+ *
+ * @return The size of an edge in pixels
+ * @see #setEdgeTrackingEnabled(int)
+ */
+ public int getEdgeSize() {
+ return mEdgeSize;
+ }
+
+ /**
+ * Capture a specific child view for dragging within the parent. The callback will be notified
+ * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
+ * capture this view.
+ *
+ * @param childView Child view to capture
+ * @param activePointerId ID of the pointer that is dragging the captured child view
+ */
+ public void captureChildView(View childView, int activePointerId) {
+ if (childView.getParent() != mParentView) {
+ throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
+ "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
+ }
+
+ mCapturedView = childView;
+ mActivePointerId = activePointerId;
+ mCallback.onViewCaptured(childView, activePointerId);
+ setDragState(STATE_DRAGGING);
+ }
+
+ /**
+ * @return The currently captured view, or null if no view has been captured.
+ */
+ public View getCapturedView() {
+ return mCapturedView;
+ }
+
+ /**
+ * @return The ID of the pointer currently dragging the captured view,
+ * or {@link #INVALID_POINTER}.
+ */
+ public int getActivePointerId() {
+ return mActivePointerId;
+ }
+
+ /**
+ * @return The minimum distance in pixels that the user must travel to initiate a drag
+ */
+ public int getTouchSlop() {
+ return mTouchSlop;
+ }
+
+ /**
+ * The result of a call to this method is equivalent to
+ * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event.
+ */
+ public void cancel() {
+ mActivePointerId = INVALID_POINTER;
+ clearMotionHistory();
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * {@link #cancel()}, but also abort all motion in progress and snap to the end of any
+ * animation.
+ */
+ public void abort() {
+ cancel();
+ if (mDragState == STATE_SETTLING) {
+ final int oldX = mScroller.getCurrX();
+ final int oldY = mScroller.getCurrY();
+ mScroller.abortAnimation();
+ final int newX = mScroller.getCurrX();
+ final int newY = mScroller.getCurrY();
+ mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);
+ }
+ setDragState(STATE_IDLE);
+ }
+
+ /**
+ * Animate the view <code>child</code> to the given (left, top) position.
+ * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
+ * on each subsequent frame to continue the motion until it returns false. If this method
+ * returns false there is no further work to do to complete the movement.
+ *
+ * <p>This operation does not count as a capture event, though {@link #getCapturedView()}
+ * will still report the sliding view while the slide is in progress.</p>
+ *
+ * @param child Child view to capture and animate
+ * @param finalLeft Final left position of child
+ * @param finalTop Final top position of child
+ * @return true if animation should continue through {@link #continueSettling(boolean)} calls
+ */
+ public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
+ mCapturedView = child;
+ mActivePointerId = INVALID_POINTER;
+
+ return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
+ }
+
+ /**
+ * Settle the captured view at the given (left, top) position.
+ * The appropriate velocity from prior motion will be taken into account.
+ * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
+ * on each subsequent frame to continue the motion until it returns false. If this method
+ * returns false there is no further work to do to complete the movement.
+ *
+ * @param finalLeft Settled left edge position for the captured view
+ * @param finalTop Settled top edge position for the captured view
+ * @return true if animation should continue through {@link #continueSettling(boolean)} calls
+ */
+ public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
+ if (!mReleaseInProgress) {
+ throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
+ "Callback#onViewReleased");
+ }
+
+ return forceSettleCapturedViewAt(finalLeft, finalTop,
+ (int) mVelocityTracker.getXVelocity(mActivePointerId),
+ (int) mVelocityTracker.getYVelocity(mActivePointerId));
+ }
+
+ /**
+ * Settle the captured view at the given (left, top) position.
+ *
+ * @param finalLeft Target left position for the captured view
+ * @param finalTop Target top position for the captured view
+ * @param xvel Horizontal velocity
+ * @param yvel Vertical velocity
+ * @return true if animation should continue through {@link #continueSettling(boolean)} calls
+ */
+ private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
+ final int startLeft = mCapturedView.getLeft();
+ final int startTop = mCapturedView.getTop();
+ final int dx = finalLeft - startLeft;
+ final int dy = finalTop - startTop;
+
+ if (dx == 0 && dy == 0) {
+ // Nothing to do. Send callbacks, be done.
+ mScroller.abortAnimation();
+ setDragState(STATE_IDLE);
+ return false;
+ }
+
+ final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
+ mScroller.startScroll(startLeft, startTop, dx, dy, duration);
+
+ setDragState(STATE_SETTLING);
+ return true;
+ }
+
+ private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
+ xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
+ yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
+ final int absDx = Math.abs(dx);
+ final int absDy = Math.abs(dy);
+ final int absXVel = Math.abs(xvel);
+ final int absYVel = Math.abs(yvel);
+ final int addedVel = absXVel + absYVel;
+ final int addedDistance = absDx + absDy;
+
+ final float xweight = xvel != 0 ? (float) absXVel / addedVel :
+ (float) absDx / addedDistance;
+ final float yweight = yvel != 0 ? (float) absYVel / addedVel :
+ (float) absDy / addedDistance;
+
+ int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
+ int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
+
+ return (int) (xduration * xweight + yduration * yweight);
+ }
+
+ private int computeAxisDuration(int delta, int velocity, int motionRange) {
+ if (delta == 0) {
+ return 0;
+ }
+
+ final int width = mParentView.getWidth();
+ final int halfWidth = width / 2;
+ final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
+ final float distance = halfWidth + halfWidth *
+ distanceInfluenceForSnapDuration(distanceRatio);
+
+ int duration;
+ velocity = Math.abs(velocity);
+ if (velocity > 0) {
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+ } else {
+ final float range = (float) Math.abs(delta) / motionRange;
+ duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
+ }
+ return Math.min(duration, MAX_SETTLE_DURATION);
+ }
+
+ /**
+ * Clamp the magnitude of value for absMin and absMax.
+ * If the value is below the minimum, it will be clamped to zero.
+ * If the value is above the maximum, it will be clamped to the maximum.
+ *
+ * @param value Value to clamp
+ * @param absMin Absolute value of the minimum significant value to return
+ * @param absMax Absolute value of the maximum value to return
+ * @return The clamped value with the same sign as <code>value</code>
+ */
+ private int clampMag(int value, int absMin, int absMax) {
+ final int absValue = Math.abs(value);
+ if (absValue < absMin) return 0;
+ if (absValue > absMax) return value > 0 ? absMax : -absMax;
+ return value;
+ }
+
+ /**
+ * Clamp the magnitude of value for absMin and absMax.
+ * If the value is below the minimum, it will be clamped to zero.
+ * If the value is above the maximum, it will be clamped to the maximum.
+ *
+ * @param value Value to clamp
+ * @param absMin Absolute value of the minimum significant value to return
+ * @param absMax Absolute value of the maximum value to return
+ * @return The clamped value with the same sign as <code>value</code>
+ */
+ private float clampMag(float value, float absMin, float absMax) {
+ final float absValue = Math.abs(value);
+ if (absValue < absMin) return 0;
+ if (absValue > absMax) return value > 0 ? absMax : -absMax;
+ return value;
+ }
+
+ private float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ /**
+ * Settle the captured view based on standard free-moving fling behavior.
+ * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
+ * to continue the motion until it returns false.
+ *
+ * @param minLeft Minimum X position for the view's left edge
+ * @param minTop Minimum Y position for the view's top edge
+ * @param maxLeft Maximum X position for the view's left edge
+ * @param maxTop Maximum Y position for the view's top edge
+ */
+ public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
+ if (!mReleaseInProgress) {
+ throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
+ "Callback#onViewReleased");
+ }
+
+ mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
+ (int) mVelocityTracker.getXVelocity(mActivePointerId),
+ (int) mVelocityTracker.getYVelocity(mActivePointerId),
+ minLeft, maxLeft, minTop, maxTop);
+
+ setDragState(STATE_SETTLING);
+ }
+
+ /**
+ * Move the captured settling view by the appropriate amount for the current time.
+ * If <code>continueSettling</code> returns true, the caller should call it again
+ * on the next frame to continue.
+ *
+ * @param deferCallbacks true if state callbacks should be deferred via posted message.
+ * Set this to true if you are calling this method from
+ * {@link android.view.View#computeScroll()} or similar methods
+ * invoked as part of layout or drawing.
+ * @return true if settle is still in progress
+ */
+ public boolean continueSettling(boolean deferCallbacks) {
+ if (mDragState == STATE_SETTLING) {
+ boolean keepGoing = mScroller.computeScrollOffset();
+ final int x = mScroller.getCurrX();
+ final int y = mScroller.getCurrY();
+ final int dx = x - mCapturedView.getLeft();
+ final int dy = y - mCapturedView.getTop();
+
+ if (dx != 0) {
+ mCapturedView.offsetLeftAndRight(dx);
+ }
+ if (dy != 0) {
+ mCapturedView.offsetTopAndBottom(dy);
+ }
+
+ if (dx != 0 || dy != 0) {
+ mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
+ }
+
+ if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
+ // Close enough. The interpolator/scroller might think we're still moving
+ // but the user sure doesn't.
+ mScroller.abortAnimation();
+ keepGoing = mScroller.isFinished();
+ }
+
+ if (!keepGoing) {
+ if (deferCallbacks) {
+ mParentView.post(mSetIdleRunnable);
+ } else {
+ setDragState(STATE_IDLE);
+ }
+ }
+ }
+
+ return mDragState == STATE_SETTLING;
+ }
+
+ /**
+ * Like all callback events this must happen on the UI thread, but release
+ * involves some extra semantics. During a release (mReleaseInProgress)
+ * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
+ * or {@link #flingCapturedView(int, int, int, int)}.
+ */
+ private void dispatchViewReleased(float xvel, float yvel) {
+ mReleaseInProgress = true;
+ mCallback.onViewReleased(mCapturedView, xvel, yvel);
+ mReleaseInProgress = false;
+
+ if (mDragState == STATE_DRAGGING) {
+ // onViewReleased didn't call a method that would have changed this. Go idle.
+ setDragState(STATE_IDLE);
+ }
+ }
+
+ private void clearMotionHistory() {
+ if (mInitialMotionX == null) {
+ return;
+ }
+ Arrays.fill(mInitialMotionX, 0);
+ Arrays.fill(mInitialMotionY, 0);
+ Arrays.fill(mLastMotionX, 0);
+ Arrays.fill(mLastMotionY, 0);
+ Arrays.fill(mInitialEdgesTouched, 0);
+ Arrays.fill(mEdgeDragsInProgress, 0);
+ Arrays.fill(mEdgeDragsLocked, 0);
+ mPointersDown = 0;
+ }
+
+ private void clearMotionHistory(int pointerId) {
+ if (mInitialMotionX == null) {
+ return;
+ }
+ mInitialMotionX[pointerId] = 0;
+ mInitialMotionY[pointerId] = 0;
+ mLastMotionX[pointerId] = 0;
+ mLastMotionY[pointerId] = 0;
+ mInitialEdgesTouched[pointerId] = 0;
+ mEdgeDragsInProgress[pointerId] = 0;
+ mEdgeDragsLocked[pointerId] = 0;
+ mPointersDown &= ~(1 << pointerId);
+ }
+
+ private void ensureMotionHistorySizeForId(int pointerId) {
+ if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
+ float[] imx = new float[pointerId + 1];
+ float[] imy = new float[pointerId + 1];
+ float[] lmx = new float[pointerId + 1];
+ float[] lmy = new float[pointerId + 1];
+ int[] iit = new int[pointerId + 1];
+ int[] edip = new int[pointerId + 1];
+ int[] edl = new int[pointerId + 1];
+
+ if (mInitialMotionX != null) {
+ System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
+ System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
+ System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
+ System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
+ System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);
+ System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
+ System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
+ }
+
+ mInitialMotionX = imx;
+ mInitialMotionY = imy;
+ mLastMotionX = lmx;
+ mLastMotionY = lmy;
+ mInitialEdgesTouched = iit;
+ mEdgeDragsInProgress = edip;
+ mEdgeDragsLocked = edl;
+ }
+ }
+
+ private void saveInitialMotion(float x, float y, int pointerId) {
+ ensureMotionHistorySizeForId(pointerId);
+ mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
+ mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
+ mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
+ mPointersDown |= 1 << pointerId;
+ }
+
+ private void saveLastMotion(MotionEvent ev) {
+ final int pointerCount = ev.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ final int pointerId = ev.getPointerId(i);
+ final float x = ev.getX(i);
+ final float y = ev.getY(i);
+ mLastMotionX[pointerId] = x;
+ mLastMotionY[pointerId] = y;
+ }
+ }
+
+ /**
+ * Check if the given pointer ID represents a pointer that is currently down (to the best
+ * of the ViewDragHelper's knowledge).
+ *
+ * <p>The state used to report this information is populated by the methods
+ * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
+ * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not
+ * been called for all relevant MotionEvents to track, the information reported
+ * by this method may be stale or incorrect.</p>
+ *
+ * @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent
+ * @return true if the pointer with the given ID is still down
+ */
+ public boolean isPointerDown(int pointerId) {
+ return (mPointersDown & 1 << pointerId) != 0;
+ }
+
+ void setDragState(int state) {
+ if (mDragState != state) {
+ mDragState = state;
+ mCallback.onViewDragStateChanged(state);
+ if (state == STATE_IDLE) {
+ mCapturedView = null;
+ }
+ }
+ }
+
+ /**
+ * Attempt to capture the view with the given pointer ID. The callback will be involved.
+ * This will put us into the "dragging" state. If we've already captured this view with
+ * this pointer this method will immediately return true without consulting the callback.
+ *
+ * @param toCapture View to capture
+ * @param pointerId Pointer to capture with
+ * @return true if capture was successful
+ */
+ boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
+ if (toCapture == mCapturedView && mActivePointerId == pointerId) {
+ // Already done!
+ return true;
+ }
+ if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
+ mActivePointerId = pointerId;
+ captureChildView(toCapture, pointerId);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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 along the X axis
+ * @param dy Delta scrolled in pixels along the Y axis
+ * @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 dy, 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--) {
+ // TODO: Add versioned support here for transformed views.
+ // This will not work for transformed views in Honeycomb+
+ 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, dy, x + scrollX - child.getLeft(),
+ y + scrollY - child.getTop())) {
+ return true;
+ }
+ }
+ }
+
+ return checkV && (v.canScrollHorizontally(-dx) ||
+ v.canScrollVertically(-dy));
+ }
+
+ /**
+ * Check if this event as provided to the parent view's onInterceptTouchEvent should
+ * cause the parent to intercept the touch event stream.
+ *
+ * @param ev MotionEvent provided to onInterceptTouchEvent
+ * @return true if the parent view should return true from onInterceptTouchEvent
+ */
+ public boolean shouldInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getActionMasked();
+ final int actionIndex = ev.getActionIndex();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ // Reset things for a new event stream, just in case we didn't get
+ // the whole previous stream.
+ cancel();
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ final int pointerId = ev.getPointerId(0);
+ saveInitialMotion(x, y, pointerId);
+
+ final View toCapture = findTopChildUnder((int) x, (int) y);
+
+ // Catch a settling view if possible.
+ if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
+ tryCaptureViewForDrag(toCapture, pointerId);
+ }
+
+ final int edgesTouched = mInitialEdgesTouched[pointerId];
+ if ((edgesTouched & mTrackingEdges) != 0) {
+ mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int pointerId = ev.getPointerId(actionIndex);
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+
+ saveInitialMotion(x, y, pointerId);
+
+ // A ViewDragHelper can only manipulate one view at a time.
+ if (mDragState == STATE_IDLE) {
+ final int edgesTouched = mInitialEdgesTouched[pointerId];
+ if ((edgesTouched & mTrackingEdges) != 0) {
+ mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
+ }
+ } else if (mDragState == STATE_SETTLING) {
+ // Catch a settling view if possible.
+ final View toCapture = findTopChildUnder((int) x, (int) y);
+ if (toCapture == mCapturedView) {
+ tryCaptureViewForDrag(toCapture, pointerId);
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ // First to cross a touch slop over a draggable view wins. Also report edge drags.
+ final int pointerCount = ev.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ final int pointerId = ev.getPointerId(i);
+ final float x = ev.getX(i);
+ final float y = ev.getY(i);
+ final float dx = x - mInitialMotionX[pointerId];
+ final float dy = y - mInitialMotionY[pointerId];
+
+ reportNewEdgeDrags(dx, dy, pointerId);
+ if (mDragState == STATE_DRAGGING) {
+ // Callback might have started an edge drag
+ break;
+ }
+
+ final View toCapture = findTopChildUnder((int) x, (int) y);
+ if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
+ tryCaptureViewForDrag(toCapture, pointerId)) {
+ break;
+ }
+ }
+ saveLastMotion(ev);
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int pointerId = ev.getPointerId(actionIndex);
+ clearMotionHistory(pointerId);
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ cancel();
+ break;
+ }
+ }
+
+ return mDragState == STATE_DRAGGING;
+ }
+
+ /**
+ * Process a touch event received by the parent view. This method will dispatch callback events
+ * as needed before returning. The parent view's onTouchEvent implementation should call this.
+ *
+ * @param ev The touch event received by the parent view
+ */
+ public void processTouchEvent(MotionEvent ev) {
+ final int action = ev.getActionMasked();
+ final int actionIndex = ev.getActionIndex();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ // Reset things for a new event stream, just in case we didn't get
+ // the whole previous stream.
+ cancel();
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ final int pointerId = ev.getPointerId(0);
+ final View toCapture = findTopChildUnder((int) x, (int) y);
+
+ saveInitialMotion(x, y, pointerId);
+
+ // Since the parent is already directly processing this touch event,
+ // there is no reason to delay for a slop before dragging.
+ // Start immediately if possible.
+ tryCaptureViewForDrag(toCapture, pointerId);
+
+ final int edgesTouched = mInitialEdgesTouched[pointerId];
+ if ((edgesTouched & mTrackingEdges) != 0) {
+ mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int pointerId = ev.getPointerId(actionIndex);
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+
+ saveInitialMotion(x, y, pointerId);
+
+ // A ViewDragHelper can only manipulate one view at a time.
+ if (mDragState == STATE_IDLE) {
+ // If we're idle we can do anything! Treat it like a normal down event.
+
+ final View toCapture = findTopChildUnder((int) x, (int) y);
+ tryCaptureViewForDrag(toCapture, pointerId);
+
+ final int edgesTouched = mInitialEdgesTouched[pointerId];
+ if ((edgesTouched & mTrackingEdges) != 0) {
+ mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
+ }
+ } else if (isCapturedViewUnder((int) x, (int) y)) {
+ // We're still tracking a captured view. If the same view is under this
+ // point, we'll swap to controlling it with this pointer instead.
+ // (This will still work if we're "catching" a settling view.)
+
+ tryCaptureViewForDrag(mCapturedView, pointerId);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ if (mDragState == STATE_DRAGGING) {
+ final int index = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(index);
+ final float y = ev.getY(index);
+ final int idx = (int) (x - mLastMotionX[mActivePointerId]);
+ final int idy = (int) (y - mLastMotionY[mActivePointerId]);
+
+ dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
+
+ saveLastMotion(ev);
+ } else {
+ // Check to see if any pointer is now over a draggable view.
+ final int pointerCount = ev.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ final int pointerId = ev.getPointerId(i);
+ final float x = ev.getX(i);
+ final float y = ev.getY(i);
+ final float dx = x - mInitialMotionX[pointerId];
+ final float dy = y - mInitialMotionY[pointerId];
+
+ reportNewEdgeDrags(dx, dy, pointerId);
+ if (mDragState == STATE_DRAGGING) {
+ // Callback might have started an edge drag.
+ break;
+ }
+
+ final View toCapture = findTopChildUnder((int) x, (int) y);
+ if (checkTouchSlop(toCapture, dx, dy) &&
+ tryCaptureViewForDrag(toCapture, pointerId)) {
+ break;
+ }
+ }
+ saveLastMotion(ev);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int pointerId = ev.getPointerId(actionIndex);
+ if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
+ // Try to find another pointer that's still holding on to the captured view.
+ int newActivePointer = INVALID_POINTER;
+ final int pointerCount = ev.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ final int id = ev.getPointerId(i);
+ if (id == mActivePointerId) {
+ // This one's going away, skip.
+ continue;
+ }
+
+ final float x = ev.getX(i);
+ final float y = ev.getY(i);
+ if (findTopChildUnder((int) x, (int) y) == mCapturedView &&
+ tryCaptureViewForDrag(mCapturedView, id)) {
+ newActivePointer = mActivePointerId;
+ break;
+ }
+ }
+
+ if (newActivePointer == INVALID_POINTER) {
+ // We didn't find another pointer still touching the view, release it.
+ releaseViewForPointerUp();
+ }
+ }
+ clearMotionHistory(pointerId);
+ break;
+ }
+
+ case MotionEvent.ACTION_UP: {
+ if (mDragState == STATE_DRAGGING) {
+ releaseViewForPointerUp();
+ }
+ cancel();
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL: {
+ if (mDragState == STATE_DRAGGING) {
+ dispatchViewReleased(0, 0);
+ }
+ cancel();
+ break;
+ }
+ }
+ }
+
+ private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
+ int dragsStarted = 0;
+ if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
+ dragsStarted |= EDGE_LEFT;
+ }
+ if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
+ dragsStarted |= EDGE_TOP;
+ }
+ if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
+ dragsStarted |= EDGE_RIGHT;
+ }
+ if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
+ dragsStarted |= EDGE_BOTTOM;
+ }
+
+ if (dragsStarted != 0) {
+ mEdgeDragsInProgress[pointerId] |= dragsStarted;
+ mCallback.onEdgeDragStarted(dragsStarted, pointerId);
+ }
+ }
+
+ private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
+ final float absDelta = Math.abs(delta);
+ final float absODelta = Math.abs(odelta);
+
+ if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 ||
+ (mEdgeDragsLocked[pointerId] & edge) == edge ||
+ (mEdgeDragsInProgress[pointerId] & edge) == edge ||
+ (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
+ return false;
+ }
+ if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
+ mEdgeDragsLocked[pointerId] |= edge;
+ return false;
+ }
+ return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
+ }
+
+ /**
+ * Check if we've crossed a reasonable touch slop for the given child view.
+ * If the child cannot be dragged along the horizontal or vertical axis, motion
+ * along that axis will not count toward the slop check.
+ *
+ * @param child Child to check
+ * @param dx Motion since initial position along X axis
+ * @param dy Motion since initial position along Y axis
+ * @return true if the touch slop has been crossed
+ */
+ private boolean checkTouchSlop(View child, float dx, float dy) {
+ if (child == null) {
+ return false;
+ }
+ final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
+ final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;
+
+ if (checkHorizontal && checkVertical) {
+ return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
+ } else if (checkHorizontal) {
+ return Math.abs(dx) > mTouchSlop;
+ } else if (checkVertical) {
+ return Math.abs(dy) > mTouchSlop;
+ }
+ return false;
+ }
+
+ /**
+ * Check if any pointer tracked in the current gesture has crossed
+ * the required slop threshold.
+ *
+ * <p>This depends on internal state populated by
+ * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
+ * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
+ * the results of this method after all currently available touch data
+ * has been provided to one of these two methods.</p>
+ *
+ * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
+ * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
+ * @return true if the slop threshold has been crossed, false otherwise
+ */
+ public boolean checkTouchSlop(int directions) {
+ final int count = mInitialMotionX.length;
+ for (int i = 0; i < count; i++) {
+ if (checkTouchSlop(directions, i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if the specified pointer tracked in the current gesture has crossed
+ * the required slop threshold.
+ *
+ * <p>This depends on internal state populated by
+ * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
+ * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
+ * the results of this method after all currently available touch data
+ * has been provided to one of these two methods.</p>
+ *
+ * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
+ * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
+ * @param pointerId ID of the pointer to slop check as specified by MotionEvent
+ * @return true if the slop threshold has been crossed, false otherwise
+ */
+ public boolean checkTouchSlop(int directions, int pointerId) {
+ if (!isPointerDown(pointerId)) {
+ return false;
+ }
+
+ final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL;
+ final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL;
+
+ final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId];
+ final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId];
+
+ if (checkHorizontal && checkVertical) {
+ return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
+ } else if (checkHorizontal) {
+ return Math.abs(dx) > mTouchSlop;
+ } else if (checkVertical) {
+ return Math.abs(dy) > mTouchSlop;
+ }
+ return false;
+ }
+
+ /**
+ * Check if any of the edges specified were initially touched in the currently active gesture.
+ * If there is no currently active gesture this method will return false.
+ *
+ * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
+ * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
+ * {@link #EDGE_ALL}
+ * @return true if any of the edges specified were initially touched in the current gesture
+ */
+ public boolean isEdgeTouched(int edges) {
+ final int count = mInitialEdgesTouched.length;
+ for (int i = 0; i < count; i++) {
+ if (isEdgeTouched(edges, i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if any of the edges specified were initially touched by the pointer with
+ * the specified ID. If there is no currently active gesture or if there is no pointer with
+ * the given ID currently down this method will return false.
+ *
+ * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
+ * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
+ * {@link #EDGE_ALL}
+ * @return true if any of the edges specified were initially touched in the current gesture
+ */
+ public boolean isEdgeTouched(int edges, int pointerId) {
+ return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0;
+ }
+
+ private void releaseViewForPointerUp() {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ final float xvel = clampMag(
+ mVelocityTracker.getXVelocity(mActivePointerId),
+ mMinVelocity, mMaxVelocity);
+ final float yvel = clampMag(
+ mVelocityTracker.getYVelocity(mActivePointerId),
+ mMinVelocity, mMaxVelocity);
+ dispatchViewReleased(xvel, yvel);
+ }
+
+ private void dragTo(int left, int top, int dx, int dy) {
+ int clampedX = left;
+ int clampedY = top;
+ final int oldLeft = mCapturedView.getLeft();
+ final int oldTop = mCapturedView.getTop();
+ if (dx != 0) {
+ clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
+ mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
+ }
+ if (dy != 0) {
+ clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
+ mCapturedView.offsetTopAndBottom(clampedY - oldTop);
+ }
+
+ if (dx != 0 || dy != 0) {
+ final int clampedDx = clampedX - oldLeft;
+ final int clampedDy = clampedY - oldTop;
+ mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
+ clampedDx, clampedDy);
+ }
+ }
+
+ /**
+ * Determine if the currently captured view is under the given point in the
+ * parent view's coordinate system. If there is no captured view this method
+ * will return false.
+ *
+ * @param x X position to test in the parent's coordinate system
+ * @param y Y position to test in the parent's coordinate system
+ * @return true if the captured view is under the given point, false otherwise
+ */
+ public boolean isCapturedViewUnder(int x, int y) {
+ return isViewUnder(mCapturedView, x, y);
+ }
+
+ /**
+ * Determine if the supplied view is under the given point in the
+ * parent view's coordinate system.
+ *
+ * @param view Child view of the parent to hit test
+ * @param x X position to test in the parent's coordinate system
+ * @param y Y position to test in the parent's coordinate system
+ * @return true if the supplied view is under the given point, false otherwise
+ */
+ public boolean isViewUnder(View view, int x, int y) {
+ if (view == null) {
+ return false;
+ }
+ return x >= view.getLeft() &&
+ x < view.getRight() &&
+ y >= view.getTop() &&
+ y < view.getBottom();
+ }
+
+ /**
+ * Find the topmost child under the given point within the parent view's coordinate system.
+ * The child order is determined using {@link Callback#getOrderedChildIndex(int)}.
+ *
+ * @param x X position to test in the parent's coordinate system
+ * @param y Y position to test in the parent's coordinate system
+ * @return The topmost child view under (x, y) or null if none found.
+ */
+ public View findTopChildUnder(int x, int y) {
+ final int childCount = mParentView.getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
+ if (x >= child.getLeft() && x < child.getRight() &&
+ y >= child.getTop() && y < child.getBottom()) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ private int getEdgesTouched(int x, int y) {
+ int result = 0;
+
+ if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;
+ if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;
+ if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;
+ if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;
+
+ return result;
+ }
+}
diff --git a/themes/res/drawable-hdpi/ic_holo_dark_navigation_drawer.png b/themes/res/drawable-hdpi/ic_holo_dark_navigation_drawer.png
new file mode 100644
index 00000000..48faf108
--- /dev/null
+++ b/themes/res/drawable-hdpi/ic_holo_dark_navigation_drawer.png
Binary files differ
diff --git a/themes/res/drawable-mdpi/ic_holo_dark_navigation_drawer.png b/themes/res/drawable-mdpi/ic_holo_dark_navigation_drawer.png
new file mode 100644
index 00000000..9680d157
--- /dev/null
+++ b/themes/res/drawable-mdpi/ic_holo_dark_navigation_drawer.png
Binary files differ
diff --git a/themes/res/drawable-nodpi/dark_theme_preview.png b/themes/res/drawable-nodpi/dark_theme_preview.png
index c18581f9..0ba30cd7 100644
--- a/themes/res/drawable-nodpi/dark_theme_preview.png
+++ b/themes/res/drawable-nodpi/dark_theme_preview.png
Binary files differ
diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_navigation_drawer.png b/themes/res/drawable-xhdpi/ic_holo_dark_navigation_drawer.png
new file mode 100644
index 00000000..e2e12be6
--- /dev/null
+++ b/themes/res/drawable-xhdpi/ic_holo_dark_navigation_drawer.png
Binary files differ
diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_navigation_drawer.png b/themes/res/drawable-xxhdpi/ic_holo_dark_navigation_drawer.png
new file mode 100644
index 00000000..4c1220dc
--- /dev/null
+++ b/themes/res/drawable-xxhdpi/ic_holo_dark_navigation_drawer.png
Binary files differ
diff --git a/themes/res/values/dark_theme.xml b/themes/res/values/dark_theme.xml
index 6bf10f49..c55371bb 100644
--- a/themes/res/values/dark_theme.xml
+++ b/themes/res/values/dark_theme.xml
@@ -72,6 +72,12 @@
<!-- The highlight color for terms found in the search result -->
<color name="dark_search_highlight_color">#9933b5e5</color>
+ <!-- The background color of the navigation drawer -->
+ <color name="dark_drawer_color">#ff222222</color>
+
+ <!-- The drawer icon -->
+ <drawable name="dark_drawer_icon">@drawable/ic_holo_dark_navigation_drawer</drawable>
+
<!-- FileSystem locked drawable -->
<drawable name="dark_filesystem_locked_drawable">@drawable/ic_holo_dark_fs_locked</drawable>
<!-- FileSystem unlocked drawable -->