aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk3
-rw-r--r--AndroidManifest.xml46
-rw-r--r--CHANGELOG.md9
-rw-r--r--README.md2
-rw-r--r--proguard.flags7
-rw-r--r--res/drawable-hdpi/ic_holo_light_delete.pngbin0 -> 537 bytes
-rw-r--r--res/drawable-hdpi/ic_holo_light_print.pngbin0 -> 496 bytes
-rw-r--r--res/drawable-hdpi/ic_holo_light_remote.pngbin0 -> 853 bytes
-rw-r--r--res/drawable-hdpi/ic_holo_light_secure.pngbin0 -> 1046 bytes
-rw-r--r--res/drawable-hdpi/ic_holo_light_settings.pngbin0 -> 1546 bytes
-rw-r--r--res/drawable-hdpi/ic_overlay_remote.pngbin0 -> 408 bytes
-rw-r--r--res/drawable-hdpi/ic_overlay_secure.pngbin0 -> 452 bytes
-rw-r--r--res/drawable-mdpi/ic_holo_light_delete.pngbin0 -> 417 bytes
-rw-r--r--res/drawable-mdpi/ic_holo_light_print.pngbin0 -> 482 bytes
-rw-r--r--res/drawable-mdpi/ic_holo_light_remote.pngbin0 -> 625 bytes
-rw-r--r--res/drawable-mdpi/ic_holo_light_secure.pngbin0 -> 792 bytes
-rw-r--r--res/drawable-mdpi/ic_holo_light_settings.pngbin0 -> 1082 bytes
-rw-r--r--res/drawable-mdpi/ic_overlay_remote.pngbin0 -> 319 bytes
-rw-r--r--res/drawable-mdpi/ic_overlay_secure.pngbin0 -> 344 bytes
-rw-r--r--res/drawable-nodpi/theme_preview.pngbin62470 -> 143219 bytes
-rw-r--r--res/drawable-xhdpi/ic_holo_light_delete.pngbin0 -> 638 bytes
-rw-r--r--res/drawable-xhdpi/ic_holo_light_print.pngbin0 -> 754 bytes
-rw-r--r--res/drawable-xhdpi/ic_holo_light_remote.pngbin0 -> 1122 bytes
-rw-r--r--res/drawable-xhdpi/ic_holo_light_secure.pngbin0 -> 1378 bytes
-rw-r--r--res/drawable-xhdpi/ic_holo_light_settings.pngbin0 -> 2381 bytes
-rw-r--r--res/drawable-xhdpi/ic_overlay_remote.pngbin0 -> 701 bytes
-rw-r--r--res/drawable-xhdpi/ic_overlay_secure.pngbin0 -> 757 bytes
-rw-r--r--res/drawable-xxhdpi/ic_holo_light_delete.pngbin0 -> 844 bytes
-rw-r--r--res/drawable-xxhdpi/ic_holo_light_print.pngbin0 -> 857 bytes
-rw-r--r--res/drawable-xxhdpi/ic_holo_light_remote.pngbin0 -> 1610 bytes
-rw-r--r--res/drawable-xxhdpi/ic_holo_light_secure.pngbin0 -> 1960 bytes
-rw-r--r--res/drawable-xxhdpi/ic_holo_light_settings.pngbin0 -> 3002 bytes
-rw-r--r--res/drawable-xxhdpi/ic_overlay_remote.pngbin0 -> 1412 bytes
-rw-r--r--res/drawable-xxhdpi/ic_overlay_secure.pngbin0 -> 1490 bytes
-rw-r--r--res/drawable/fso_folder_remote.xml22
-rw-r--r--res/drawable/fso_folder_secure.xml22
-rw-r--r--res/drawable/holo_button_selector.xml3
-rw-r--r--res/layout/associations_dialog.xml8
-rw-r--r--res/layout/color_picker_pref_item.xml2
-rw-r--r--res/layout/history_item.xml11
-rw-r--r--res/layout/navigation_drawer.xml179
-rw-r--r--res/layout/simple_customtitle.xml10
-rw-r--r--res/layout/unlock_dialog_message.xml124
-rw-r--r--res/menu/actions.xml8
-rw-r--r--res/menu/drawer.xml41
-rw-r--r--res/menu/navigation.xml6
-rw-r--r--res/raw/changelog7
-rw-r--r--res/values/colors.xml2
-rw-r--r--res/values/overlay.xml7
-rw-r--r--res/values/strings.xml86
-rw-r--r--res/values/theme.xml17
-rw-r--r--res/xml/preferences_headers.xml3
-rw-r--r--res/xml/preferences_storage.xml45
-rw-r--r--src/com/cyanogenmod/filemanager/FileManagerApplication.java5
-rw-r--r--src/com/cyanogenmod/filemanager/activities/EditorActivity.java81
-rw-r--r--src/com/cyanogenmod/filemanager/activities/NavigationActivity.java413
-rw-r--r--src/com/cyanogenmod/filemanager/activities/SearchActivity.java213
-rw-r--r--src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java180
-rw-r--r--src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java2
-rw-r--r--src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java167
-rw-r--r--src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java2
-rw-r--r--src/com/cyanogenmod/filemanager/commands/java/FindCommand.java28
-rw-r--r--src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java3
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java278
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java117
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java116
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java125
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java122
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java125
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java271
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java258
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java167
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java135
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java89
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/Program.java125
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java235
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java401
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java47
-rw-r--r--src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java228
-rw-r--r--src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java4
-rw-r--r--src/com/cyanogenmod/filemanager/commands/shell/Program.java1
-rw-r--r--src/com/cyanogenmod/filemanager/commands/shell/Shell.java2
-rw-r--r--src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java3
-rw-r--r--src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java36
-rw-r--r--src/com/cyanogenmod/filemanager/console/CancelledOperationException.java34
-rw-r--r--src/com/cyanogenmod/filemanager/console/Console.java13
-rw-r--r--src/com/cyanogenmod/filemanager/console/VirtualConsole.java144
-rw-r--r--src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java292
-rw-r--r--src/com/cyanogenmod/filemanager/console/java/JavaConsole.java122
-rw-r--r--src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java145
-rw-r--r--src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java640
-rw-r--r--src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java37
-rw-r--r--src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java64
-rw-r--r--src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java101
-rw-r--r--src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java399
-rw-r--r--src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java15
-rw-r--r--src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java5
-rw-r--r--src/com/cyanogenmod/filemanager/model/Bookmark.java12
-rw-r--r--src/com/cyanogenmod/filemanager/model/FileSystemObject.java47
-rw-r--r--src/com/cyanogenmod/filemanager/model/History.java11
-rw-r--r--src/com/cyanogenmod/filemanager/model/MountPoint.java116
-rw-r--r--src/com/cyanogenmod/filemanager/model/Permissions.java26
-rw-r--r--src/com/cyanogenmod/filemanager/model/Query.java20
-rw-r--r--src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java38
-rw-r--r--src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java34
-rw-r--r--src/com/cyanogenmod/filemanager/preferences/Preferences.java2
-rw-r--r--src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java421
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java107
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java2
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java2
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java10
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java12
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java1
-rw-r--r--src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java73
-rw-r--r--src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java1017
-rw-r--r--src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java1
-rw-r--r--src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java1
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java25
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java14
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java1
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java2
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java44
-rw-r--r--src/com/cyanogenmod/filemanager/util/AIDHelper.java19
-rw-r--r--src/com/cyanogenmod/filemanager/util/AndroidHelper.java65
-rw-r--r--src/com/cyanogenmod/filemanager/util/BookmarksHelper.java6
-rw-r--r--src/com/cyanogenmod/filemanager/util/CommandHelper.java514
-rw-r--r--src/com/cyanogenmod/filemanager/util/DialogHelper.java78
-rw-r--r--src/com/cyanogenmod/filemanager/util/ExceptionUtil.java26
-rw-r--r--src/com/cyanogenmod/filemanager/util/FileHelper.java59
-rw-r--r--src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java6
-rw-r--r--src/com/cyanogenmod/filemanager/util/ParseHelper.java2
-rw-r--r--src/com/cyanogenmod/filemanager/util/StringHelper.java27
-rw-r--r--tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java16
-rw-r--r--themes/res/drawable-hdpi/ic_holo_dark_delete.pngbin0 -> 529 bytes
-rw-r--r--themes/res/drawable-hdpi/ic_holo_dark_print.pngbin0 -> 422 bytes
-rw-r--r--themes/res/drawable-hdpi/ic_holo_dark_remote.pngbin0 -> 656 bytes
-rwxr-xr-xthemes/res/drawable-hdpi/ic_holo_dark_secure.pngbin0 -> 756 bytes
-rw-r--r--themes/res/drawable-hdpi/ic_holo_dark_settings.pngbin0 -> 1424 bytes
-rw-r--r--themes/res/drawable-mdpi/ic_holo_dark_delete.pngbin0 -> 424 bytes
-rw-r--r--themes/res/drawable-mdpi/ic_holo_dark_print.pngbin0 -> 402 bytes
-rw-r--r--themes/res/drawable-mdpi/ic_holo_dark_remote.pngbin0 -> 504 bytes
-rw-r--r--themes/res/drawable-mdpi/ic_holo_dark_secure.pngbin0 -> 604 bytes
-rw-r--r--themes/res/drawable-mdpi/ic_holo_dark_settings.pngbin0 -> 1033 bytes
-rw-r--r--themes/res/drawable-nodpi/dark_background_disabled.9.pngbin0 -> 158 bytes
-rw-r--r--themes/res/drawable-nodpi/dark_theme_preview.pngbin59876 -> 137674 bytes
-rw-r--r--themes/res/drawable-xhdpi/ic_holo_dark_delete.pngbin0 -> 593 bytes
-rw-r--r--themes/res/drawable-xhdpi/ic_holo_dark_print.pngbin0 -> 581 bytes
-rw-r--r--themes/res/drawable-xhdpi/ic_holo_dark_remote.pngbin0 -> 836 bytes
-rw-r--r--themes/res/drawable-xhdpi/ic_holo_dark_secure.pngbin0 -> 957 bytes
-rw-r--r--themes/res/drawable-xhdpi/ic_holo_dark_settings.pngbin0 -> 2112 bytes
-rw-r--r--themes/res/drawable-xxhdpi/ic_holo_dark_delete.pngbin0 -> 783 bytes
-rw-r--r--themes/res/drawable-xxhdpi/ic_holo_dark_print.pngbin0 -> 679 bytes
-rw-r--r--themes/res/drawable-xxhdpi/ic_holo_dark_remote.pngbin0 -> 1176 bytes
-rw-r--r--themes/res/drawable-xxhdpi/ic_holo_dark_secure.pngbin0 -> 1342 bytes
-rw-r--r--themes/res/drawable-xxhdpi/ic_holo_dark_settings.pngbin0 -> 2775 bytes
-rw-r--r--themes/res/drawable/dark_holo_button_selector.xml3
-rw-r--r--themes/res/values/dark_theme.xml13
157 files changed, 8448 insertions, 1085 deletions
diff --git a/Android.mk b/Android.mk
index bbd4b66f..7aaf9073 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,6 +21,9 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_SRC_FILES += $(call all-java-files-under, themes/src)
LOCAL_SRC_FILES += $(call all-java-files-under, libs/android-syntax-highlight/src)
LOCAL_SRC_FILES += $(call all-java-files-under, libs/color-picker-view/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := libtruezip
+
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, themes/res res)
LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
LOCAL_AAPT_FLAGS := --auto-add-overlay
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ac00a7b8..528e2a3b 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="102"
- android:versionName="1.0.2">
+ android:versionCode="103"
+ android:versionName="2.0.0">
<original-package android:name="com.cyanogenmod.filemanager" />
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
<uses-permission android:name="android.permission.NFC"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.cyanogenmod.filemanager.permissions.READ_THEME"/>
@@ -38,7 +39,8 @@
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
- android:theme="@style/FileManager.Theme.Holo.Light" >
+ android:theme="@style/FileManager.Theme.Holo.Light"
+ android:supportsRtl="true">
<meta-data
android:name="android.app.default_searchable"
@@ -54,6 +56,12 @@
android:authorities="com.cyanogenmod.filemanager.providers.bookmarks"
android:exported="false" />
+ <provider
+ android:name=".providers.SecureResourceProvider"
+ android:authorities="com.cyanogenmod.filemanager.providers.resources"
+ android:grantUriPermissions="true"
+ android:exported="true" />
+
<activity
android:name=".activities.NavigationActivity"
android:label="@string/app_name"
@@ -77,24 +85,6 @@
</activity>
<activity
- android:name=".activities.BookmarksActivity"
- android:label="@string/bookmarks"
- android:uiOptions="none"
- android:windowSoftInputMode="adjustNothing"
- android:configChanges="orientation|keyboardHidden|screenSize"
- android:exported="false">
- </activity>
-
- <activity
- android:name=".activities.HistoryActivity"
- android:label="@string/history"
- android:uiOptions="none"
- android:configChanges="orientation|keyboardHidden|screenSize"
- android:windowSoftInputMode="adjustNothing"
- android:exported="false">
- </activity>
-
- <activity
android:name=".activities.SearchActivity"
android:label="@string/search"
android:launchMode="singleTop"
@@ -168,6 +158,8 @@
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<category android:name="android.intent.category.DEFAULT" />
+ <category android:name="com.cyanogenmod.filemanager.category.INTERNAL_VIEWER" />
+ <category android:name="com.cyanogenmod.filemanager.category.EDITOR" />
<data android:scheme="file" />
<data android:mimeType="text/*" />
@@ -200,6 +192,18 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".console.secure.SecureStorageKeyPromptDialog$SecureStorageKeyPromptActivity"
+ android:label="@string/app_name"
+ android:uiOptions="none"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:theme="@android:style/Theme.Holo.Light.Dialog"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c64271e6..bba634b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
CyanogenMod File Manager
========================
+Version 2.0.0
+-------------
+* Secure storage support
+* Print support
+
+Version 1.0.2
+-------------
+* Drawer navigation support (by Florian Edelmann)
+
Version 1.0.1
-------------
* NFC support
diff --git a/README.md b/README.md
index 7b33f947..63ded0ac 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ This source was released under the terms of
Visit [CyanogenMod Github](https://github.com/CyanogenMod) and [CyanogenMod
Code Review](http://review.cyanogenmod.com/) to get the source and patches.
-This application uses also third party libraries. Checkout the license individual
+This application uses also third party libraries. Checkout the individual
license of every library in libs folder.
Copyright © 2012 The CyanogenMod Project
diff --git a/proguard.flags b/proguard.flags
index 6f046926..c6a4e5bf 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -57,3 +57,10 @@
public <init>(...);
}
+#keep library packages
+-keep public class de.schlichtherle.truezip.** {
+ public protected *;
+}
+-keep public class libtruezip.** {
+ public protected *;
+}
diff --git a/res/drawable-hdpi/ic_holo_light_delete.png b/res/drawable-hdpi/ic_holo_light_delete.png
new file mode 100644
index 00000000..57f9b4d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_holo_light_delete.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_holo_light_print.png b/res/drawable-hdpi/ic_holo_light_print.png
new file mode 100644
index 00000000..5b5fc364
--- /dev/null
+++ b/res/drawable-hdpi/ic_holo_light_print.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_holo_light_remote.png b/res/drawable-hdpi/ic_holo_light_remote.png
new file mode 100644
index 00000000..82234433
--- /dev/null
+++ b/res/drawable-hdpi/ic_holo_light_remote.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_holo_light_secure.png b/res/drawable-hdpi/ic_holo_light_secure.png
new file mode 100644
index 00000000..75fe8a9e
--- /dev/null
+++ b/res/drawable-hdpi/ic_holo_light_secure.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_holo_light_settings.png b/res/drawable-hdpi/ic_holo_light_settings.png
new file mode 100644
index 00000000..6da677ad
--- /dev/null
+++ b/res/drawable-hdpi/ic_holo_light_settings.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_overlay_remote.png b/res/drawable-hdpi/ic_overlay_remote.png
new file mode 100644
index 00000000..2ee56395
--- /dev/null
+++ b/res/drawable-hdpi/ic_overlay_remote.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_overlay_secure.png b/res/drawable-hdpi/ic_overlay_secure.png
new file mode 100644
index 00000000..d8e692d9
--- /dev/null
+++ b/res/drawable-hdpi/ic_overlay_secure.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_holo_light_delete.png b/res/drawable-mdpi/ic_holo_light_delete.png
new file mode 100644
index 00000000..e8ea0b10
--- /dev/null
+++ b/res/drawable-mdpi/ic_holo_light_delete.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_holo_light_print.png b/res/drawable-mdpi/ic_holo_light_print.png
new file mode 100644
index 00000000..e4a53d08
--- /dev/null
+++ b/res/drawable-mdpi/ic_holo_light_print.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_holo_light_remote.png b/res/drawable-mdpi/ic_holo_light_remote.png
new file mode 100644
index 00000000..87eeff50
--- /dev/null
+++ b/res/drawable-mdpi/ic_holo_light_remote.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_holo_light_secure.png b/res/drawable-mdpi/ic_holo_light_secure.png
new file mode 100644
index 00000000..dcf1b117
--- /dev/null
+++ b/res/drawable-mdpi/ic_holo_light_secure.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_holo_light_settings.png b/res/drawable-mdpi/ic_holo_light_settings.png
new file mode 100644
index 00000000..be398696
--- /dev/null
+++ b/res/drawable-mdpi/ic_holo_light_settings.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_overlay_remote.png b/res/drawable-mdpi/ic_overlay_remote.png
new file mode 100644
index 00000000..318dc87e
--- /dev/null
+++ b/res/drawable-mdpi/ic_overlay_remote.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_overlay_secure.png b/res/drawable-mdpi/ic_overlay_secure.png
new file mode 100644
index 00000000..80998961
--- /dev/null
+++ b/res/drawable-mdpi/ic_overlay_secure.png
Binary files differ
diff --git a/res/drawable-nodpi/theme_preview.png b/res/drawable-nodpi/theme_preview.png
index ba98ce0b..866ed72e 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_delete.png b/res/drawable-xhdpi/ic_holo_light_delete.png
new file mode 100644
index 00000000..a5cbd4f7
--- /dev/null
+++ b/res/drawable-xhdpi/ic_holo_light_delete.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_holo_light_print.png b/res/drawable-xhdpi/ic_holo_light_print.png
new file mode 100644
index 00000000..1927fcc6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_holo_light_print.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_holo_light_remote.png b/res/drawable-xhdpi/ic_holo_light_remote.png
new file mode 100644
index 00000000..a45da433
--- /dev/null
+++ b/res/drawable-xhdpi/ic_holo_light_remote.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_holo_light_secure.png b/res/drawable-xhdpi/ic_holo_light_secure.png
new file mode 100644
index 00000000..5a6d50db
--- /dev/null
+++ b/res/drawable-xhdpi/ic_holo_light_secure.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_holo_light_settings.png b/res/drawable-xhdpi/ic_holo_light_settings.png
new file mode 100644
index 00000000..09f504de
--- /dev/null
+++ b/res/drawable-xhdpi/ic_holo_light_settings.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_overlay_remote.png b/res/drawable-xhdpi/ic_overlay_remote.png
new file mode 100644
index 00000000..a0003e85
--- /dev/null
+++ b/res/drawable-xhdpi/ic_overlay_remote.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_overlay_secure.png b/res/drawable-xhdpi/ic_overlay_secure.png
new file mode 100644
index 00000000..174a1d84
--- /dev/null
+++ b/res/drawable-xhdpi/ic_overlay_secure.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_holo_light_delete.png b/res/drawable-xxhdpi/ic_holo_light_delete.png
new file mode 100644
index 00000000..743fbfd5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_holo_light_delete.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_holo_light_print.png b/res/drawable-xxhdpi/ic_holo_light_print.png
new file mode 100644
index 00000000..6d1fdf67
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_holo_light_print.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_holo_light_remote.png b/res/drawable-xxhdpi/ic_holo_light_remote.png
new file mode 100644
index 00000000..0cb6fb7a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_holo_light_remote.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_holo_light_secure.png b/res/drawable-xxhdpi/ic_holo_light_secure.png
new file mode 100644
index 00000000..a959f743
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_holo_light_secure.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_holo_light_settings.png b/res/drawable-xxhdpi/ic_holo_light_settings.png
new file mode 100644
index 00000000..f3246735
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_holo_light_settings.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_overlay_remote.png b/res/drawable-xxhdpi/ic_overlay_remote.png
new file mode 100644
index 00000000..52fb8701
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_overlay_remote.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_overlay_secure.png b/res/drawable-xxhdpi/ic_overlay_secure.png
new file mode 100644
index 00000000..dc9a0b80
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_overlay_secure.png
Binary files differ
diff --git a/res/drawable/fso_folder_remote.xml b/res/drawable/fso_folder_remote.xml
new file mode 100644
index 00000000..79f40efd
--- /dev/null
+++ b/res/drawable/fso_folder_remote.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item android:drawable="@drawable/ic_fso_folder_drawable"/>
+ <item android:drawable="@drawable/ic_overlay_remote_drawable"/>
+
+</layer-list>
diff --git a/res/drawable/fso_folder_secure.xml b/res/drawable/fso_folder_secure.xml
new file mode 100644
index 00000000..1f27b843
--- /dev/null
+++ b/res/drawable/fso_folder_secure.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item android:drawable="@drawable/ic_fso_folder_drawable"/>
+ <item android:drawable="@drawable/ic_overlay_secure_drawable"/>
+
+</layer-list>
diff --git a/res/drawable/holo_button_selector.xml b/res/drawable/holo_button_selector.xml
index 97ebf269..d6a5a8d7 100644
--- a/res/drawable/holo_button_selector.xml
+++ b/res/drawable/holo_button_selector.xml
@@ -24,6 +24,9 @@
android:state_enabled="true"
android:state_focused="true"/>
<item
+ android:drawable="@color/default_background_disabled"
+ android:state_enabled="false"/>
+ <item
android:drawable="@color/default_background"/>
</selector>
diff --git a/res/layout/associations_dialog.xml b/res/layout/associations_dialog.xml
index 210301a9..18009c9d 100644
--- a/res/layout/associations_dialog.xml
+++ b/res/layout/associations_dialog.xml
@@ -26,13 +26,17 @@
android:stretchMode="columnWidth"
android:scrollbars="vertical"
android:horizontalSpacing="@dimen/default_margin"
+ android:layout_marginBottom="@dimen/extra_margin"
android:numColumns="@integer/associations_items_per_row" />
<CheckBox android:id="@+id/associations_remember"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="@dimen/extra_margin"
+ android:layout_marginLeft="@dimen/extra_margin"
+ android:layout_marginRight="@dimen/extra_margin"
+ android:layout_marginBottom="@dimen/extra_margin"
android:textAppearance="@style/secondary_text_appearance"
- android:text="@string/associations_dialog_remember" />
+ android:text="@string/associations_dialog_remember"
+ android:visibility="gone" />
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/color_picker_pref_item.xml b/res/layout/color_picker_pref_item.xml
index 02c44c1a..e250561c 100644
--- a/res/layout/color_picker_pref_item.xml
+++ b/res/layout/color_picker_pref_item.xml
@@ -19,7 +19,7 @@
android:layout_height="32dp"
android:background="@android:color/darker_gray">
<afzkl.development.mColorPicker.views.ColorPanelView
- android:id="@+android:id/color_picker"
+ android:id="@+id/color_picker"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="1dp"
diff --git a/res/layout/history_item.xml b/res/layout/history_item.xml
index ef02927b..9aa4d8a3 100644
--- a/res/layout/history_item.xml
+++ b/res/layout/history_item.xml
@@ -56,15 +56,4 @@
android:textAppearance="@style/secondary_text_appearance" />
</LinearLayout>
- <TextView
- 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" />
-
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/navigation_drawer.xml b/res/layout/navigation_drawer.xml
index 9398ede5..c5e29092 100644
--- a/res/layout/navigation_drawer.xml
+++ b/res/layout/navigation_drawer.xml
@@ -1,62 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer"
- android:layout_width="240dp"
+ android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"
- android:background="@android:color/background_light" >
+ android:background="@android:color/background_light">
+
+ <RelativeLayout
+ android:id="@+id/drawer_actionbar"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:layout_alignParentBottom="true">
+
+ <com.cyanogenmod.filemanager.ui.widgets.ButtonItem
+ android:id="@+id/ab_settings"
+ android:layout_width="@dimen/default_buttom_width"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:contentDescription="@string/menu_settings"
+ android:onClick="onActionBarItemClick"
+ android:src="@drawable/ic_holo_light_settings" />
+
+ <com.cyanogenmod.filemanager.ui.widgets.ButtonItem
+ android:id="@+id/ab_clear_history"
+ android:layout_width="@dimen/default_buttom_width"
+ android:layout_height="match_parent"
+ android:layout_toRightOf="@id/ab_settings"
+ android:contentDescription="@string/menu_clear_history"
+ android:onClick="onActionBarItemClick"
+ android:src="@drawable/ic_holo_light_delete" />
+ </RelativeLayout>
<LinearLayout
+ android:id="@+id/drawer_drawer_divider"
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" />
+ android:layout_above="@id/drawer_actionbar">
+ <include layout="@layout/vertical_divider" />
+ </LinearLayout>
- <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" />
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_above="@id/drawer_drawer_divider"
+ android:layout_alignParentTop="true">
<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" >
+ android:orientation="vertical">
- </LinearLayout>
+ <LinearLayout
+ android:id="@+id/filesystem_info_dialog_tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/default_row_height"
+ android:orientation="horizontal" >
- <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/drawer_bookmarks_tab"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/holo_selector"
+ android:clickable="true"
+ android:gravity="center_horizontal|center_vertical"
+ android:text="@string/bookmarks"
+ android:textAllCaps="true"
+ android:textAppearance="@style/primary_text_appearance" />
- <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" />
+ <include
+ android:id="@+id/drawer_tab_divider1"
+ layout="@layout/horizontal_divider" />
+
+ <TextView
+ android:id="@+id/drawer_history_tab"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/holo_selector"
+ android:clickable="true"
+ android:gravity="center_horizontal|center_vertical"
+ android:text="@string/history"
+ android:textAllCaps="true"
+ android:textAppearance="@style/primary_text_appearance" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <include
+ android:id="@+id/drawer_tab_divider2"
+ layout="@layout/vertical_divider" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/drawer_bookmarks"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/default_margin"
+ android:layout_marginRight="@dimen/default_margin"
+ android:orientation="vertical">
+
+ <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>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/drawer_history"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/default_margin"
+ android:layout_marginRight="@dimen/default_margin"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <TextView
+ android:id="@+id/history_empty"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/msgs_history_empty"
+ android:padding="@dimen/extra_margin"
+ android:gravity="center_horizontal"
+ android:textAppearance="@style/primary_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>
- <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
+ </ScrollView>
+</RelativeLayout> \ No newline at end of file
diff --git a/res/layout/simple_customtitle.xml b/res/layout/simple_customtitle.xml
index 97dafcaa..68286934 100644
--- a/res/layout/simple_customtitle.xml
+++ b/res/layout/simple_customtitle.xml
@@ -25,6 +25,14 @@
android:layout_alignParentRight="true">
<com.cyanogenmod.filemanager.ui.widgets.ButtonItem
+ android:id="@+id/ab_button0"
+ style="@style/breadcrumb_actionbar_buttom"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:onClick="onActionBarItemClick"
+ android:visibility="gone" />
+
+ <com.cyanogenmod.filemanager.ui.widgets.ButtonItem
android:id="@+id/ab_button1"
style="@style/breadcrumb_actionbar_buttom"
android:layout_width="wrap_content"
@@ -51,7 +59,7 @@
android:layout_marginRight="@dimen/extra_large_margin"
android:textAppearance="@style/title_text_appearance"
android:layout_alignParentLeft="true"
- android:layout_toLeftOf="@id/ab_button1"
+ android:layout_toLeftOf="@id/ab_button0"
android:layout_alignWithParentIfMissing="true"
android:contentDescription="@null"
android:gravity="left|center_vertical"
diff --git a/res/layout/unlock_dialog_message.xml b/res/layout/unlock_dialog_message.xml
new file mode 100644
index 00000000..8c7a465c
--- /dev/null
+++ b/res/layout/unlock_dialog_message.xml
@@ -0,0 +1,124 @@
+<?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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- Dialog -->
+ <TextView
+ android:id="@+id/unlock_dialog_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/double_margin"
+ android:layout_marginLeft="@dimen/extra_large_margin"
+ android:layout_marginRight="@dimen/extra_large_margin"
+ android:textAppearance="@style/primary_text_appearance" />
+
+ <!-- Password -->
+ <LinearLayout
+ android:id="@+id/unlock_old_password_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/double_margin"
+ android:layout_marginLeft="@dimen/extra_large_margin"
+ android:layout_marginRight="@dimen/extra_large_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/unlock_old_password_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/primary_text_appearance"
+ android:text="@string/secure_storage_unlock_old_key_title"
+ android:textStyle="normal"/>
+ <EditText
+ android:id="@+id/unlock_old_password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:textStyle="normal"
+ android:maxLines="1" />
+ </LinearLayout>
+
+ <!-- Password -->
+ <LinearLayout
+ android:id="@+id/unlock_password_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/double_margin"
+ android:layout_marginLeft="@dimen/extra_large_margin"
+ android:layout_marginRight="@dimen/extra_large_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/unlock_password_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/primary_text_appearance"
+ android:text="@string/secure_storage_unlock_key_title"
+ android:textStyle="normal"/>
+ <EditText
+ android:id="@+id/unlock_password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:textStyle="normal"
+ android:maxLines="1" />
+ </LinearLayout>
+
+ <!-- Repeat Password -->
+ <LinearLayout
+ android:id="@+id/unlock_repeat_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/double_margin"
+ android:layout_marginLeft="@dimen/extra_large_margin"
+ android:layout_marginRight="@dimen/extra_large_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/unlock_repeat_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/primary_text_appearance"
+ android:text="@string/secure_storage_unlock_repeat_title"
+ android:textStyle="normal"/>
+ <EditText
+ android:id="@+id/unlock_repeat"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:textStyle="normal"
+ android:maxLines="1" />
+ </LinearLayout>
+
+ <!-- Validation message -->
+ <TextView
+ android:id="@+id/unlock_validation_msg"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="@dimen/extra_large_margin"
+ android:layout_marginRight="@dimen/extra_large_margin"
+ android:background="@drawable/holo_selector"
+ android:contentDescription="@null"
+ android:drawableLeft="@drawable/ic_holo_light_fs_warning"
+ android:drawablePadding="@dimen/default_margin"
+ android:gravity="left|center_vertical"
+ android:singleLine="false"
+ android:textSize="@dimen/note_text_size"
+ android:visibility="invisible" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/menu/actions.xml b/res/menu/actions.xml
index ba5f50cf..bb2d8e50 100644
--- a/res/menu/actions.xml
+++ b/res/menu/actions.xml
@@ -74,6 +74,10 @@
android:id="@+id/mnu_actions_add_shortcut_current_folder"
android:showAsAction="ifRoom"
android:title="@string/actions_menu_add_shortcut"/>
+ <item
+ android:id="@+id/mnu_actions_global_set_as_home"
+ android:showAsAction="ifRoom"
+ android:title="@string/actions_menu_set_as_home"/>
</group>
<!-- FileSystemObject Actions -->
@@ -150,6 +154,10 @@
android:id="@+id/mnu_actions_open_parent_folder"
android:showAsAction="ifRoom"
android:title="@string/actions_menu_open_parent_folder"/>
+ <item
+ android:id="@+id/mnu_actions_set_as_home"
+ android:showAsAction="ifRoom"
+ android:title="@string/actions_menu_set_as_home"/>
</group>
</menu> \ No newline at end of file
diff --git a/res/menu/drawer.xml b/res/menu/drawer.xml
deleted file mode 100644
index 7a5ef1d5..00000000
--- a/res/menu/drawer.xml
+++ /dev/null
@@ -1,41 +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.
--->
-<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 5111c94f..e63074ba 100644
--- a/res/menu/navigation.xml
+++ b/res/menu/navigation.xml
@@ -22,10 +22,4 @@
android:showAsAction="ifRoom"
android:title="@string/menu_search"/>
- <!-- Overflow actions -->
- <item
- android:id="@+id/mnu_settings"
- android:showAsAction="never"
- android:title="@string/menu_settings"/>
-
</menu> \ No newline at end of file
diff --git a/res/raw/changelog b/res/raw/changelog
index 447938cc..bba634b3 100644
--- a/res/raw/changelog
+++ b/res/raw/changelog
@@ -1,9 +1,14 @@
CyanogenMod File Manager
========================
+Version 2.0.0
+-------------
+* Secure storage support
+* Print support
+
Version 1.0.2
-------------
-* move bookmarks and history into a navigation drawer (by Florian Edelmann)
+* Drawer navigation support (by Florian Edelmann)
Version 1.0.1
-------------
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5dad8206..22841846 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -18,6 +18,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- The default background color -->
<color name="default_background">@android:color/white</color>
+ <!-- The default dissabled background color -->
+ <color name="default_background_disabled">#f2f2f2</color>
<!-- A black color with some transparency -->
<color name="black_transparent">#99000000</color>
diff --git a/res/values/overlay.xml b/res/values/overlay.xml
index 1258d25b..88c5d683 100644
--- a/res/values/overlay.xml
+++ b/res/values/overlay.xml
@@ -26,6 +26,9 @@
<!-- The system directory -->
<string name="system_dir" translatable="false">/system</string>
+ <!-- The virtual storage directory (virtual filesystem are listed here) -->
+ <string name="virtual_storage_dir" translatable="false">/storage</string>
+
<!-- The shell commands used by this application. All of this commands should
exist to allow the use of a shell console (and access to root). If any
of this commands are not present in the system, then app will start in
@@ -85,8 +88,8 @@
<!-- The system properties -->
<string name="system_props_file" translatable="false">/system/build.prop</string>
- <!-- The size of the buffers use by the console (in bytes). Default: 4k -->
- <integer name="buffer_size">4096</integer>
+ <!-- The size of the buffers use by the console (in bytes) -->
+ <integer name="buffer_size">8192</integer>
<!-- The number of lines to show in the console dialog -->
<integer name="console_max_lines">80</integer>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2c51d02f..06859586 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -167,6 +167,8 @@
<string name="actionbar_button_storage_cd">Storage volumes</string>
<!-- ActionBar Buttons - Save -->
<string name="actionbar_button_save_cd">Save</string>
+ <!-- ActionBar Buttons - Print -->
+ <string name="actionbar_button_print_cd">Print</string>
<!-- Navigation View - Sort - Sort by name (ascending) -->
<string name="sort_by_name_asc">By name \u25B2</string>
@@ -221,6 +223,8 @@
<string name="filesystem_info_dialog_options">Options:</string>
<!-- Filesystem Info Dialog - Dump/Pass Label -->
<string name="filesystem_info_dialog_dump_pass">Dump / Pass:</string>
+ <!-- Filesystem Info Dialog - Virtual Label -->
+ <string name="filesystem_info_dialog_virtual">Virtual:</string>
<!-- Filesystem Info Dialog - Total Disk Usage -->
<string name="filesystem_info_dialog_total_disk_usage">Total:</string>
<!-- Filesystem Info Dialog - Used Disk Usage -->
@@ -353,6 +357,10 @@
<string name="bookmarks_root_folder">Root folder</string>
<!-- Bookmarks - Bookmark name - System folder -->
<string name="bookmarks_system_folder">System folder</string>
+ <!-- Bookmarks - Bookmark name - Secure storage -->
+ <string name="bookmarks_secure">Secure storage</string>
+ <!-- Bookmarks - Bookmark name - Remote storage -->
+ <string name="bookmarks_remote">Remote storage</string>
<!-- Bookmarks - Bookmark name - Button - Initial directory content description -->
<string name="bookmarks_button_config_cd">Set the initial folder.</string>
<!-- Bookmarks - Bookmark name - Button - Remove bookmark content description -->
@@ -476,6 +484,8 @@
<string name="actions_menu_compute_checksum">Compute checksum</string>
<!-- Actions Dialog - Menu - Print -->
<string name="actions_menu_print">Print</string>
+ <!-- Actions Dialog - Menu - Set as home -->
+ <string name="actions_menu_set_as_home">Set as home</string>
<!-- Actions - Ask user prior to do an undone operation. Dialog message -->
<string name="actions_ask_undone_operation_msg">This action cannot be undone. Do you want to continue?</string>
@@ -547,6 +557,23 @@
<!-- For example "2 folders and 1 file selected." -->
<string name="selection_folders_and_files"><xliff:g id="folders">%1$s</xliff:g> and <xliff:g id="files">%2$s</xliff:g> selected.</string>
+ <!-- Category descriptions -->
+ <string name="category_system">SYSTEM</string>
+ <string name="category_app">APP</string>
+ <string name="category_binary">BINARY</string>
+ <string name="category_text">TEXT</string>
+ <string name="category_document">DOCUMENT</string>
+ <string name="category_ebook">EBOOK</string>
+ <string name="category_mail">MAIL</string>
+ <string name="category_compress">COMPRESS</string>
+ <string name="category_exec">EXECUTABLE</string>
+ <string name="category_database">DATABASE</string>
+ <string name="category_font">FONT</string>
+ <string name="category_image">IMAGE</string>
+ <string name="category_audio">AUDIO</string>
+ <string name="category_video">VIDEO</string>
+ <string name="category_security">SECURITY</string>
+
<!-- Compression - Compression modes dialog title -->
<string name="compression_mode_title">Compression mode</string>
<!-- Compression - Supported archive and compression modes -->
@@ -571,6 +598,8 @@
<string name="pref_general">General settings</string>
<!-- Preferences - Search title -->
<string name="pref_search">Search options</string>
+ <!-- Preferences - Storage title -->
+ <string name="pref_storage">Storage options</string>
<!-- Preferences - Editor title -->
<string name="pref_editor">Editor options</string>
<!-- Preferences - Themes title -->
@@ -578,7 +607,7 @@
<!-- Preferences - About title -->
<string name="pref_about">About</string>
<!-- Preferences - About summary -->
- <string name="pref_about_summary">File Manager v<xliff:g id="version">%1$s</xliff:g>\nCopyright \u00A9 2012-2014 The CyanogenMod Project</string>
+ <string name="pref_about_summary">File Manager v<xliff:g id="version">%1$s</xliff:g>\nCopyright \u00A9 2012&#8211;2014 The CyanogenMod Project</string>
<!-- Preferences - General - Behaviour category -->
<string name="pref_general_behaviour_category">General</string>
@@ -653,7 +682,20 @@
<!-- Preferences - Search - Remove saved search terms summary -->
<string name="pref_remove_saved_search_terms_summary">Tap to remove all the saved search terms</string>
<!-- Preferences - Search - Suggestions were truncated -->
- <string name="pref_remove_saved_search_terms_msg">All saved search terms were removed.</string>
+ <string name="pref_remove_saved_search_terms_msg">All saved search terms were removed</string>
+ <!-- Preferences - Storage - Secure Storage category -->
+ <string name="pref_secure_storage_category">Secure storage</string>
+ <!-- Preferences - Storage - Secure Storage - Delayed sync title -->
+ <string name="pref_secure_storage_delayed_sync_title">Delayed synchronization</string>
+ <!-- Preferences - Storage - Secure Storage - Delayed sync summary -->
+ <string name="pref_secure_storage_delayed_sync_summary">Synchronization of secure file systems
+ is a costly operation. Enable this option to allow better time responses after every operation,
+ performing the synchronization when the filesystem is in unused state, but at the expense of
+ lost the pending information not synced if the app crash.</string>
+ <!-- Preferences - Storage - Secure Storage - Reset password title -->
+ <string name="pref_secure_storage_reset_password_title">Reset password</string>
+ <!-- Preferences - Storage - Secure Storage - Delete storage title -->
+ <string name="pref_secure_storage_delete_storage_title">Delete storage</string>
<!-- Preferences - Editor - Behaviour category -->
<string name="pref_editor_behaviour_category">Behaviour</string>
<!-- Preferences - Editor - No suggestions -->
@@ -725,6 +767,46 @@
<string name="ash_quoted_string">Quoted string</string>
<string name="ash_variable">Variable</string>
+ <!-- Secure Storage -->
+ <!-- Secure Storage dialog title - Unlock -->
+ <string name="secure_storage_unlock_title">Unlock storage</string>
+ <!-- Secure Storage dialog title - Create -->
+ <string name="secure_storage_create_title">Create storage</string>
+ <!-- Secure Storage dialog title - Reset -->
+ <string name="secure_storage_reset_title">Reset password</string>
+ <!-- Secure Storage dialog title - Delete -->
+ <string name="secure_storage_delete_title">Delete storage</string>
+ <!-- Secure Storage unlock dialog message (secure storage exists) -->
+ <string name="secure_storage_unlock_key_prompt_msg">Type the password to unlock the secure storage filesystem.</string>
+ <!-- Secure Storage unlock dialog message (new secure storage) -->
+ <string name="secure_storage_unlock_key_new_msg">Type a password to protect the secure storage filesystem.</string>
+ <!-- Secure Storage unlock dialog message (reset the current password) -->
+ <string name="secure_storage_unlock_key_reset_msg">Type the current and new passwords to reset the secure storage filesystem.</string>
+ <!-- Secure Storage unlock dialog message (delete the secure storage) -->
+ <string name="secure_storage_unlock_key_delete_msg">Type the current password to delete the secure storage filesystem.</string>
+ <!-- Secure Storage unlock dialog old key title -->
+ <string name="secure_storage_unlock_old_key_title">Old password:</string>
+ <!-- Secure Storage unlock dialog key title -->
+ <string name="secure_storage_unlock_new_key_title">New Password:</string>
+ <!-- Secure Storage unlock dialog key title -->
+ <string name="secure_storage_unlock_key_title">Password:</string>
+ <!-- Secure Storage unlock dialog repeat key title-->
+ <string name="secure_storage_unlock_repeat_title">Repeat password:</string>
+ <!-- Secure Storage create button -->
+ <string name="secure_storage_create_button">Create</string>
+ <!-- Secure Storage unlock button -->
+ <string name="secure_storage_unlock_button">Unlock</string>
+ <!-- Secure Storage reset button -->
+ <string name="secure_storage_reset_button">Reset</string>
+ <!-- Secure Storage delete button -->
+ <string name="secure_storage_delete_button">Delete</string>
+ <!-- Secure Storage unlock failed toast -->
+ <string name="secure_storage_unlock_failed">Cannot unlock the storage</string>
+ <!-- Secure Storage unlock validation, length -->
+ <string name="secure_storage_unlock_validation_length">Password must have at least <xliff:g id="characters">%1$d</xliff:g> characters.</string>
+ <!-- Secure Storage unlock validation, equal -->
+ <string name="secure_storage_unlock_validation_equals">Passwords are not the same.</string>
+
<!-- Print messages -->
<!-- Unsupported document format -->
<string name="print_unsupported_document">Unsupported document format</string>
diff --git a/res/values/theme.xml b/res/values/theme.xml
index d5128c91..29208011 100644
--- a/res/values/theme.xml
+++ b/res/values/theme.xml
@@ -69,6 +69,12 @@
<drawable name="ab_save_drawable">@drawable/ic_holo_light_save</drawable>
<!-- The drawable for the tab action bar button -->
<drawable name="ab_tab_drawable">@drawable/ic_holo_light_tab</drawable>
+ <!-- The drawable for the print action bar button -->
+ <drawable name="ab_print_drawable">@drawable/ic_holo_light_print</drawable>
+ <!-- The drawable for the settings action bar button -->
+ <drawable name="ab_settings_drawable">@drawable/ic_holo_light_settings</drawable>
+ <!-- The drawable for the delete action bar button -->
+ <drawable name="ab_delete_drawable">@drawable/ic_holo_light_delete</drawable>
<!-- The close action drawable from the expander bar -->
<drawable name="expander_close_drawable">@drawable/ic_holo_light_expander_close</drawable>
@@ -99,6 +105,11 @@
<!-- FileSystem warning drawable -->
<drawable name="filesystem_warning_drawable">@drawable/ic_holo_light_fs_warning</drawable>
+ <!-- Secure FileSystem icon -->
+ <drawable name="secure_filesystem_drawable">@drawable/ic_holo_light_secure</drawable>
+ <!-- Remote FileSystem icon -->
+ <drawable name="remote_filesystem_drawable">@drawable/ic_holo_light_remote</drawable>
+
<!-- The popup menu checkable selector drawable -->
<drawable name="popup_checkable_selector_drawable">@drawable/checkable_selector</drawable>
<!-- The menu checkable selector drawable -->
@@ -131,6 +142,8 @@
<drawable name="ic_user_defined_bookmark_drawable">@drawable/ic_holo_light_user_defined_bookmark</drawable>
<drawable name="ic_history_search_drawable">@drawable/ic_holo_light_history_search</drawable>
<drawable name="ic_copy_drawable">@drawable/ic_holo_light_copy</drawable>
+ <drawable name="ic_secure_drawable">@drawable/ic_holo_light_secure</drawable>
+ <drawable name="ic_remote_drawable">@drawable/ic_holo_light_remote</drawable>
<!-- Disk usage graph -->
<color name="disk_usage_total_color">@color/disk_usage_total</color>
@@ -181,6 +194,10 @@
<drawable name="fso_type_text_drawable">@drawable/fso_type_text</drawable>
<drawable name="fso_type_video_drawable">@drawable/fso_type_video</drawable>
+ <!-- Overlay -->
+ <drawable name="ic_overlay_secure_drawable">@drawable/ic_overlay_secure</drawable>
+ <drawable name="ic_overlay_remote_drawable">@drawable/ic_overlay_remote</drawable>
+
<!-- Syntax Highlight -->
<color name="ash_text_color">@color/black_transparent</color>
<color name="ash_assignment_color">@color/black_transparent</color>
diff --git a/res/xml/preferences_headers.xml b/res/xml/preferences_headers.xml
index 80c4509d..2c97dced 100644
--- a/res/xml/preferences_headers.xml
+++ b/res/xml/preferences_headers.xml
@@ -22,6 +22,9 @@
android:fragment="com.cyanogenmod.filemanager.activities.preferences.SearchPreferenceFragment"
android:title="@string/pref_search" />
<header
+ android:fragment="com.cyanogenmod.filemanager.activities.preferences.StoragePreferenceFragment"
+ android:title="@string/pref_storage" />
+ <header
android:fragment="com.cyanogenmod.filemanager.activities.preferences.EditorPreferenceFragment"
android:title="@string/pref_editor" />
<header
diff --git a/res/xml/preferences_storage.xml b/res/xml/preferences_storage.xml
new file mode 100644
index 00000000..47f27eea
--- /dev/null
+++ b/res/xml/preferences_storage.xml
@@ -0,0 +1,45 @@
+<?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.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Secure Storage -->
+ <PreferenceCategory
+ android:key="secure_storage"
+ android:title="@string/pref_secure_storage_category">
+
+ <!-- Reset password -->
+ <Preference
+ android:key="secure_storage_reset_password"
+ android:title="@string/pref_secure_storage_reset_password_title"/>
+
+ <!-- Delete storage -->
+ <Preference
+ android:key="secure_storage_delete_storage"
+ android:title="@string/pref_secure_storage_delete_storage_title"/>
+
+ <!-- Delayed synchronization -->
+ <CheckBoxPreference
+ android:key="cm_filemanager_secure_storage_delayed_sync"
+ android:title="@string/pref_secure_storage_delayed_sync_title"
+ android:summary="@string/pref_secure_storage_delayed_sync_summary"
+ android:persistent="true"
+ android:defaultValue="true" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/src/com/cyanogenmod/filemanager/FileManagerApplication.java b/src/com/cyanogenmod/filemanager/FileManagerApplication.java
index be475dff..8230c1fe 100644
--- a/src/com/cyanogenmod/filemanager/FileManagerApplication.java
+++ b/src/com/cyanogenmod/filemanager/FileManagerApplication.java
@@ -28,6 +28,7 @@ import com.cyanogenmod.filemanager.console.Console;
import com.cyanogenmod.filemanager.console.ConsoleAllocException;
import com.cyanogenmod.filemanager.console.ConsoleBuilder;
import com.cyanogenmod.filemanager.console.ConsoleHolder;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
import com.cyanogenmod.filemanager.console.shell.PrivilegedConsole;
import com.cyanogenmod.filemanager.preferences.AccessMode;
import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
@@ -269,7 +270,9 @@ public final class FileManagerApplication extends Application {
Theme theme = ThemeManager.getCurrentTheme(getApplicationContext());
theme.setBaseTheme(getApplicationContext(), false);
- //Create a console for background tasks
+ //Create a console for background tasks. Register the virtual console prior to
+ // the real console so mount point can be listed properly
+ VirtualMountPointConsole.registerVirtualConsoles(getApplicationContext());
allocBackgroundConsole(getApplicationContext());
//Force the load of mime types
diff --git a/src/com/cyanogenmod/filemanager/activities/EditorActivity.java b/src/com/cyanogenmod/filemanager/activities/EditorActivity.java
index 16dd0355..8e6c413a 100644
--- a/src/com/cyanogenmod/filemanager/activities/EditorActivity.java
+++ b/src/com/cyanogenmod/filemanager/activities/EditorActivity.java
@@ -74,6 +74,7 @@ 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.policy.PrintActionPolicy;
import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
import com.cyanogenmod.filemanager.util.AndroidHelper;
import com.cyanogenmod.filemanager.util.CommandHelper;
@@ -236,6 +237,21 @@ public class EditorActivity extends Activity implements TextWatcher {
return v;
}
+
+ /**
+ * Return the view as a document
+ *
+ * @return StringBuilder a buffer to the document
+ */
+ public StringBuilder toStringDocument() {
+ StringBuilder sb = new StringBuilder();
+ int c = getCount();
+ for (int i = 0; i < c; i++) {
+ sb.append(getItem(i));
+ sb.append("\n");
+ }
+ return sb;
+ }
}
/**
@@ -279,7 +295,12 @@ public class EditorActivity extends Activity implements TextWatcher {
* {@inheritDoc}
*/
@Override
- public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/}
+ public void onAsyncEnd(boolean cancelled) {
+ if (!cancelled && StringHelper.isBinaryData(mByteBuffer.toByteArray())) {
+ EditorActivity.this.mBinary = true;
+ EditorActivity.this.mReadOnly = true;
+ }
+ }
/**
* {@inheritDoc}
@@ -298,20 +319,7 @@ public class EditorActivity extends Activity implements TextWatcher {
public void onPartialResult(Object result) {
try {
if (result == null) return;
- byte[] partial = (byte[])result;
-
- // Check if the file is a binary file. In this case the editor
- // is read-only
- if (!EditorActivity.this.mReadOnly) {
- for (int i = 0; i < partial.length-1; i++) {
- if (!StringHelper.isPrintableCharacter((char)partial[i])) {
- EditorActivity.this.mBinary = true;
- EditorActivity.this.mReadOnly = true;
- break;
- }
- }
- }
-
+ byte[] partial = (byte[]) result;
this.mByteBuffer.write(partial, 0, partial.length);
this.mSize += partial.length;
if (this.mListener != null && this.mReadFso != null) {
@@ -514,6 +522,10 @@ public class EditorActivity extends Activity implements TextWatcher {
* @hide
*/
ButtonItem mSave;
+ /**
+ * @hide
+ */
+ ButtonItem mPrint;
// No suggestions status
/**
@@ -557,6 +569,8 @@ public class EditorActivity extends Activity implements TextWatcher {
*/
String mHexLineSeparator;
+ private boolean mHexDump;
+
/**
* Intent extra parameter for the path of the file to open.
*/
@@ -576,6 +590,12 @@ public class EditorActivity extends Activity implements TextWatcher {
// Load typeface for hex editor
mHexTypeface = Typeface.createFromAsset(getAssets(), "fonts/Courier-Prime.ttf");
+ // Save hexdump user preference
+ mHexDump = Preferences.getSharedPreferences().getBoolean(
+ FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(),
+ ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.
+ getDefaultValue()).booleanValue());
+
// Register the broadcast receiver
IntentFilter filter = new IntentFilter();
filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
@@ -657,11 +677,17 @@ public class EditorActivity extends Activity implements TextWatcher {
this.mTitle = (TextView)customTitle.findViewById(R.id.customtitle_title);
this.mTitle.setText(R.string.editor);
this.mTitle.setContentDescription(getString(R.string.editor));
- this.mSave = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
+
+ this.mSave = (ButtonItem)customTitle.findViewById(R.id.ab_button0);
this.mSave.setImageResource(R.drawable.ic_holo_light_save);
this.mSave.setContentDescription(getString(R.string.actionbar_button_save_cd));
this.mSave.setVisibility(View.GONE);
+ this.mPrint = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
+ this.mPrint.setImageResource(R.drawable.ic_holo_light_print);
+ this.mPrint.setContentDescription(getString(R.string.actionbar_button_print_cd));
+ this.mPrint.setVisibility(View.VISIBLE);
+
ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button2);
configuration.setImageResource(R.drawable.ic_holo_light_overflow);
configuration.setContentDescription(getString(R.string.actionbar_button_overflow_cd));
@@ -913,11 +939,19 @@ public class EditorActivity extends Activity implements TextWatcher {
*/
public void onActionBarItemClick(View view) {
switch (view.getId()) {
- case R.id.ab_button1:
+ case R.id.ab_button0:
// Save the file
checkAndWrite();
break;
+ case R.id.ab_button1:
+ // Print the file
+ StringBuilder sb = mBinary
+ ? ((HexDumpAdapter)mBinaryEditor.getAdapter()).toStringDocument()
+ : new StringBuilder(mEditor.getText().toString());
+ PrintActionPolicy.printStringDocument(this, mFso, sb);
+ break;
+
case R.id.ab_button2:
// Show overflow menu
showOverflowPopUp(this.mOptionsAnchorView);
@@ -1105,12 +1139,7 @@ public class EditorActivity extends Activity implements TextWatcher {
// Now we have the byte array with all the data. is a binary file?
// Then dump them byte array to hex dump string (only if users settings
// to dump file)
- boolean hexDump =
- Preferences.getSharedPreferences().getBoolean(
- FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(),
- ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.
- getDefaultValue()).booleanValue());
- if (activity.mBinary && hexDump) {
+ if (activity.mBinary && mHexDump) {
// we do not use the Hexdump helper class, because we need to show the
// progress of the dump process
final String data = toHexPrintableString(toHexDump(
@@ -1158,7 +1187,7 @@ public class EditorActivity extends Activity implements TextWatcher {
}
} else {
// Now we have the buffer, set the text of the editor
- if (activity.mBinary) {
+ if (activity.mBinary && mHexDump) {
HexDumpAdapter adapter = new HexDumpAdapter(EditorActivity.this,
this.mReader.mBinaryBuffer);
mBinaryEditor.setAdapter(adapter);
@@ -1522,8 +1551,10 @@ public class EditorActivity extends Activity implements TextWatcher {
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);
+ v = findViewById(R.id.ab_button0);
theme.setImageDrawable(this, (ImageView)v, "ab_save_drawable"); //$NON-NLS-1$
+ v = findViewById(R.id.ab_button1);
+ theme.setImageDrawable(this, (ImageView)v, "ab_print_drawable"); //$NON-NLS-1$
v = findViewById(R.id.ab_button2);
theme.setImageDrawable(this, (ImageView)v, "ab_overflow_drawable"); //$NON-NLS-1$
//- View
diff --git a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java
index 97473c06..6770acfa 100644
--- a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java
+++ b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java
@@ -69,6 +69,9 @@ import com.cyanogenmod.filemanager.console.ConsoleAllocException;
import com.cyanogenmod.filemanager.console.ConsoleBuilder;
import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.VirtualConsole;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
import com.cyanogenmod.filemanager.listeners.OnHistoryListener;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
import com.cyanogenmod.filemanager.model.Bookmark;
@@ -170,6 +173,12 @@ public class NavigationActivity extends Activity
public static final String EXTRA_NAVIGATE_TO =
"extra_navigate_to"; //$NON-NLS-1$
+ /**
+ * Constant for extra information for request to add navigation to the history
+ */
+ public static final String EXTRA_ADD_TO_HISTORY =
+ "extra_add_to_history"; //$NON-NLS-1$
+
// The timeout needed to reset the exit status for back button
// After this time user need to tap 2 times the back button to
// exit, and the toast is shown again after the first tap.
@@ -295,11 +304,88 @@ public class NavigationActivity extends Activity
FileHelper.sReloadDateTimeFormats = true;
NavigationActivity.this.getCurrentNavigationView().refresh();
}
+ } else if (intent.getAction().compareTo(
+ FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED) == 0) {
+ onRequestBookmarksRefresh();
+ removeUnmountedHistory();
+ removeUnmountedSelection();
}
}
}
};
+ private OnClickListener mOnClickDrawerTabListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.drawer_bookmarks_tab:
+ if (!mBookmarksTab.isSelected()) {
+ mBookmarksTab.setSelected(true);
+ mHistoryTab.setSelected(false);
+ mBookmarksTab.setTextAppearance(
+ NavigationActivity.this, R.style.primary_text_appearance);
+ mHistoryTab.setTextAppearance(
+ NavigationActivity.this, R.style.secondary_text_appearance);
+ mHistoryLayout.setVisibility(View.GONE);
+ mBookmarksLayout.setVisibility(View.VISIBLE);
+ applyTabTheme();
+
+ try {
+ Preferences.savePreference(FileManagerSettings.USER_PREF_LAST_DRAWER_TAB,
+ Integer.valueOf(0), true);
+ } catch (Exception ex) {
+ Log.e(TAG, "Can't save last drawer tab", ex); //$NON-NLS-1$
+ }
+
+ mClearHistory.setVisibility(View.GONE);
+ }
+ break;
+ case R.id.drawer_history_tab:
+ if (!mHistoryTab.isSelected()) {
+ mHistoryTab.setSelected(true);
+ mBookmarksTab.setSelected(false);
+ mHistoryTab.setTextAppearance(
+ NavigationActivity.this, R.style.primary_text_appearance);
+ mBookmarksTab.setTextAppearance(
+ NavigationActivity.this, R.style.secondary_text_appearance);
+ mBookmarksLayout.setVisibility(View.GONE);
+ mHistoryLayout.setVisibility(View.VISIBLE);
+ applyTabTheme();
+
+ try {
+ Preferences.savePreference(FileManagerSettings.USER_PREF_LAST_DRAWER_TAB,
+ Integer.valueOf(1), true);
+ } catch (Exception ex) {
+ Log.e(TAG, "Can't save last drawer tab", ex); //$NON-NLS-1$
+ }
+
+ mClearHistory.setVisibility(mHistory.size() > 0 ? View.VISIBLE : View.GONE);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private OnClickListener mOnClickDrawerActionBarListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.ab_settings:
+ mDrawerLayout.closeDrawer(mDrawer);
+ openSettings();
+ break;
+ case R.id.ab_clear_history:
+ clearHistory();
+ mClearHistory.setVisibility(View.GONE);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
/**
* @hide
*/
@@ -312,11 +398,19 @@ public class NavigationActivity extends Activity
private SelectionView mSelectionBar;
private DrawerLayout mDrawerLayout;
- private ScrollView mDrawer;
+ private ViewGroup mDrawer;
private ActionBarDrawerToggle mDrawerToggle;
private LinearLayout mDrawerHistory;
private TextView mDrawerHistoryEmpty;
+ private TextView mBookmarksTab;
+ private TextView mHistoryTab;
+ private View mBookmarksLayout;
+ private View mHistoryLayout;
+
+ private ButtonItem mSettings;
+ private ButtonItem mClearHistory;
+
private List<Bookmark> mBookmarks;
private LinearLayout mDrawerBookmarks;
@@ -337,6 +431,8 @@ public class NavigationActivity extends Activity
*/
Handler mHandler;
+ private AsyncTask<Void, Void, Boolean> mBookmarksTask;
+
/**
* {@inheritDoc}
*/
@@ -355,6 +451,7 @@ public class NavigationActivity extends Activity
filter.addAction(Intent.ACTION_DATE_CHANGED);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED);
registerReceiver(this.mNotificationReceiver, filter);
// Set the theme before setContentView
@@ -447,6 +544,22 @@ public class NavigationActivity extends Activity
if (!FileManagerApplication.checkRestrictSecondaryUsersAccess(this, mChRooted)) {
return;
}
+
+ // Check that the current dir is mounted (for virtual filesystems)
+ String curDir = mNavigationViews[mCurrentNavigationView].getCurrentDir();
+ if (curDir != null) {
+ VirtualMountPointConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath(
+ mNavigationViews[mCurrentNavigationView].getCurrentDir());
+ if (vc != null && !vc.isMounted()) {
+ onRequestBookmarksRefresh();
+ removeUnmountedHistory();
+ removeUnmountedSelection();
+
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_ADD_TO_HISTORY, false);
+ initNavigation(NavigationActivity.this.mCurrentNavigationView, false, intent);
+ }
+ }
}
@Override
@@ -536,7 +649,7 @@ public class NavigationActivity extends Activity
((Boolean)FileManagerSettings.SETTINGS_FIRST_USE.getDefaultValue()).booleanValue());
//Display the welcome message?
- if (firstUse && !FileManagerApplication.isDeviceRooted()) {
+ if (firstUse && FileManagerApplication.isDeviceRooted()) {
// open navigation drawer to show user that it exists
mDrawerLayout.openDrawer(mDrawer);
@@ -620,11 +733,10 @@ public class NavigationActivity extends Activity
}
});
- // Have overflow menu?
+ // Have overflow menu? Actually no. There is only a search action, so just hide
+ // the overflow
View overflow = findViewById(R.id.ab_overflow);
- boolean showOptionsMenu = AndroidHelper.showOptionsMenu(getApplicationContext());
- overflow.setVisibility(showOptionsMenu ? View.VISIBLE : View.GONE);
- this.mOptionsAnchorView = showOptionsMenu ? overflow : this.mActionBar;
+ overflow.setVisibility(View.GONE);
// Show the status bar
View statusBar = findViewById(R.id.navigation_statusbar_portrait_holder);
@@ -643,11 +755,30 @@ public class NavigationActivity extends Activity
*/
private void initDrawer() {
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
- mDrawer = (ScrollView) findViewById(R.id.drawer);
+ mDrawer = (ViewGroup) 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);
+ mBookmarksLayout = findViewById(R.id.drawer_bookmarks);
+ mHistoryLayout = findViewById(R.id.drawer_history);
+ mBookmarksTab = (TextView) findViewById(R.id.drawer_bookmarks_tab);
+ mHistoryTab = (TextView) findViewById(R.id.drawer_history_tab);
+ mBookmarksTab.setOnClickListener(mOnClickDrawerTabListener);
+ mHistoryTab.setOnClickListener(mOnClickDrawerTabListener);
+
+ mSettings = (ButtonItem) findViewById(R.id.ab_settings);
+ mSettings.setOnClickListener(mOnClickDrawerActionBarListener);
+ mClearHistory = (ButtonItem) findViewById(R.id.ab_clear_history);
+ mClearHistory.setOnClickListener(mOnClickDrawerActionBarListener);
+
+ // Restore the last tab pressed
+ Integer lastTab = Preferences.getSharedPreferences().getInt(
+ FileManagerSettings.USER_PREF_LAST_DRAWER_TAB.getId(),
+ (Integer) FileManagerSettings.USER_PREF_LAST_DRAWER_TAB
+ .getDefaultValue());
+ mOnClickDrawerTabListener.onClick(lastTab == 0 ? mBookmarksTab : mHistoryTab);
+
// Set the navigation drawer "hamburger" icon
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.ic_holo_light_navigation_drawer,
@@ -661,7 +792,6 @@ public class NavigationActivity extends Activity
| 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. */
@@ -681,8 +811,6 @@ public class NavigationActivity extends Activity
"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()
}
};
@@ -694,7 +822,7 @@ public class NavigationActivity extends Activity
}
/**
- * Method adds a history entry to the history list in the drawer
+ * Method that adds a history entry to the history list in the drawer
*/
private void addHistoryToDrawer(int index, HistoryNavigable navigable) {
// hide empty message
@@ -712,10 +840,7 @@ public class NavigationActivity extends Activity
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$
@@ -729,11 +854,9 @@ public class NavigationActivity extends Activity
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() {
@@ -750,6 +873,9 @@ public class NavigationActivity extends Activity
// add as first child
mDrawerHistory.addView(view, 0);
+
+ // Show clear button if history tab is selected
+ mClearHistory.setVisibility(mHistoryTab.getVisibility());
}
/**
@@ -904,12 +1030,17 @@ public class NavigationActivity extends Activity
/**
* Method that initializes the bookmarks.
*/
- private void initBookmarks() {
+ private synchronized void initBookmarks() {
+ if (mBookmarksTask != null &&
+ !mBookmarksTask.getStatus().equals(AsyncTask.Status.FINISHED)) {
+ return;
+ }
+
// 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>() {
+ mBookmarksTask = new AsyncTask<Void, Void, Boolean>() {
Exception mCause;
@Override
@@ -945,14 +1076,16 @@ public class NavigationActivity extends Activity
NavigationActivity.this, this.mCause);
}
}
+ mBookmarksTask = null;
}
@Override
protected void onCancelled() {
waiting.setVisibility(View.GONE);
+ mBookmarksTask = null;
}
};
- task.execute();
+ mBookmarksTask.execute();
}
/**
@@ -971,6 +1104,7 @@ public class NavigationActivity extends Activity
bookmarks.addAll(loadFilesystemBookmarks());
}
bookmarks.addAll(loadSdStorageBookmarks());
+ bookmarks.addAll(loadVirtualBookmarks());
bookmarks.addAll(loadUserBookmarks());
return bookmarks;
}
@@ -1103,6 +1237,32 @@ public class NavigationActivity extends Activity
}
/**
+ * Method that loads all virtual mount points.
+ *
+ * @return List<Bookmark> The bookmarks loaded
+ */
+ private List<Bookmark> loadVirtualBookmarks() {
+ // Initialize the bookmarks
+ List<Bookmark> bookmarks = new ArrayList<Bookmark>();
+ List<MountPoint> mps = VirtualMountPointConsole.getVirtualMountPoints();
+ for (MountPoint mp : mps) {
+ BOOKMARK_TYPE type = null;
+ String name = null;
+ if (mp.isSecure()) {
+ type = BOOKMARK_TYPE.SECURE;
+ name = getString(R.string.bookmarks_secure);
+ } else if (mp.isRemote()) {
+ type = BOOKMARK_TYPE.REMOTE;
+ name = getString(R.string.bookmarks_remote);
+ } else {
+ continue;
+ }
+ bookmarks.add(new Bookmark(type, name, mp.getMountPoint()));
+ }
+ return bookmarks;
+ }
+
+ /**
* Method that loads the user bookmarks (added by the user).
*
* @return List<Bookmark> The bookmarks loaded
@@ -1133,6 +1293,17 @@ public class NavigationActivity extends Activity
/** NON BLOCK **/
}
}
+
+ // Remove bookmarks from virtual storage if the filesystem is not mount
+ int c = bookmarks.size() - 1;
+ for (int i = c; i >= 0; i--) {
+ VirtualMountPointConsole vc =
+ VirtualMountPointConsole.getVirtualConsoleForPath(bookmarks.get(i).mPath);
+ if (vc != null && !vc.isMounted()) {
+ bookmarks.remove(i);
+ }
+ }
+
return bookmarks;
}
@@ -1223,6 +1394,15 @@ public class NavigationActivity extends Activity
initialDir = navigateTo;
}
+ // Add to history
+ final boolean addToHistory = intent.getBooleanExtra(EXTRA_ADD_TO_HISTORY, true);
+
+ // We cannot navigate to a secure console if is unmount, go to root in that case
+ VirtualConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath(initialDir);
+ if (vc != null && vc instanceof SecureConsole && !((SecureConsole) vc).isMounted()) {
+ initialDir = FileHelper.ROOT_DIRECTORY;
+ }
+
if (this.mChRooted) {
// Initial directory is the first external sdcard (sdcard, emmc, usb, ...)
if (!StorageHelper.isPathInStorageVolume(initialDir)) {
@@ -1257,17 +1437,19 @@ public class NavigationActivity extends Activity
this, ipex, false, true, new OnRelaunchCommandResult() {
@Override
public void onSuccess() {
- navigationView.changeCurrentDir(absInitialDir);
+ navigationView.changeCurrentDir(absInitialDir, addToHistory);
}
@Override
public void onFailed(Throwable cause) {
showInitialInvalidDirectoryMsg(userInitialDir);
- navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY);
+ navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY,
+ addToHistory);
}
@Override
public void onCancelled() {
showInitialInvalidDirectoryMsg(userInitialDir);
- navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY);
+ navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY,
+ addToHistory);
}
});
@@ -1289,7 +1471,7 @@ public class NavigationActivity extends Activity
}
// Change the current directory to the user-defined initial directory
- navigationView.changeCurrentDir(initialDir);
+ navigationView.changeCurrentDir(initialDir, addToHistory);
}
/**
@@ -1368,65 +1550,6 @@ public class NavigationActivity extends Activity
}
/**
- * {@inheritDoc}
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem 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);
- }
-
- /**
* Method invoked when an action item is clicked.
*
* @param view The button pushed
@@ -1535,14 +1658,16 @@ public class NavigationActivity extends Activity
case INTENT_REQUEST_SEARCH:
if (resultCode == RESULT_OK) {
//Change directory?
- FileSystemObject fso =
- (FileSystemObject)data.
- getSerializableExtra(EXTRA_SEARCH_ENTRY_SELECTION);
- SearchInfoParcelable searchInfo =
- data.getParcelableExtra(EXTRA_SEARCH_LAST_SEARCH_DATA);
- if (fso != null) {
- //Goto to new directory
- getCurrentNavigationView().open(fso, searchInfo);
+ Bundle bundle = data.getExtras();
+ if (bundle != null) {
+ FileSystemObject fso = (FileSystemObject) bundle.getSerializable(
+ EXTRA_SEARCH_ENTRY_SELECTION);
+ SearchInfoParcelable searchInfo =
+ bundle.getParcelable(EXTRA_SEARCH_LAST_SEARCH_DATA);
+ if (fso != null) {
+ //Goto to new directory
+ getCurrentNavigationView().open(fso, searchInfo);
+ }
}
} else if (resultCode == RESULT_CANCELED) {
SearchInfoParcelable searchInfo =
@@ -1606,6 +1731,14 @@ public class NavigationActivity extends Activity
* {@inheritDoc}
*/
@Override
+ public void onRequestBookmarksRefresh() {
+ initBookmarks();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void onRequestRemove(Object o, boolean clearSelection) {
if (o instanceof FileSystemObject) {
// Remove from view
@@ -1797,6 +1930,13 @@ public class NavigationActivity extends Activity
if (breadcrumb.getMountPointInfo().compareTo(mountPoint) == 0) {
breadcrumb.updateMountPointInfo();
}
+ if (mountPoint.isSecure()) {
+ // Secure mountpoints only can be unmount, so we need to move the navigation
+ // to a secure storage (do not add to history)
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_ADD_TO_HISTORY, false);
+ initNavigation(NavigationActivity.this.mCurrentNavigationView, false, intent);
+ }
}
});
dialog.show();
@@ -2044,17 +2184,19 @@ public class NavigationActivity extends Activity
* 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--) {
+ int cc = this.mHistory.size() - 1;
+ for (int i = cc; i >= 0 ; i--) {
History history = this.mHistory.get(i);
if (history.getItem() instanceof NavigationViewInfoParcelable) {
String p0 = fso.getFullPath();
- String p1 =
- ((NavigationViewInfoParcelable)history.getItem()).getCurrentDir();
+ String p1 = ((NavigationViewInfoParcelable) history.getItem()).getCurrentDir();
if (p0.compareTo(p1) == 0) {
this.mHistory.remove(i);
+ mDrawerHistory.removeViewAt(mDrawerHistory.getChildCount() - i - 1);
+ mDrawerHistoryEmpty.setVisibility(
+ mDrawerHistory.getChildCount() == 0 ? View.VISIBLE : View.GONE);
+ updateHistoryPositions();
}
}
}
@@ -2062,6 +2204,17 @@ public class NavigationActivity extends Activity
}
/**
+ * Update the history positions after one of the history is removed from drawer
+ */
+ private void updateHistoryPositions() {
+ int cc = this.mHistory.size() - 1;
+ for (int i = 0; i <= cc ; i++) {
+ History history = this.mHistory.get(i);
+ history.setPosition(i + 1);
+ }
+ }
+
+ /**
* Method that ask the user to change the access mode prior to crash.
* @hide
*/
@@ -2208,11 +2361,8 @@ public class NavigationActivity extends Activity
rbw += bw;
}
}
- int w = abw + rbw;
- boolean showOptionsMenu = AndroidHelper.showOptionsMenu(getApplicationContext());
- if (!showOptionsMenu) {
- w -= bw;
- }
+ // Currently there isn't overflow menu
+ int w = abw + rbw - bw;
// Add to the new location
ViewGroup newParent = (ViewGroup)findViewById(R.id.navigation_title_landscape_holder);
@@ -2262,6 +2412,40 @@ public class NavigationActivity extends Activity
}
/**
+ * Method that removes all the history items that refers to virtual unmounted filesystems
+ */
+ private void removeUnmountedHistory() {
+ int cc = mHistory.size() - 1;
+ for (int i = cc; i >= 0; i--) {
+ History history = mHistory.get(i);
+ if (history.getItem() instanceof NavigationViewInfoParcelable) {
+ NavigationViewInfoParcelable navigableInfo =
+ ((NavigationViewInfoParcelable) history.getItem());
+ VirtualMountPointConsole vc =
+ VirtualMountPointConsole.getVirtualConsoleForPath(
+ navigableInfo.getCurrentDir());
+ if (vc != null && !vc.isMounted()) {
+ mHistory.remove(i);
+ mDrawerHistory.removeViewAt(mDrawerHistory.getChildCount() - i - 1);
+ }
+ }
+ }
+ mDrawerHistoryEmpty.setVisibility(
+ mDrawerHistory.getChildCount() == 0 ? View.VISIBLE : View.GONE);
+ updateHistoryPositions();
+ }
+
+ /**
+ * Method that removes all the selection items that refers to virtual unmounted filesystems
+ */
+ private void removeUnmountedSelection() {
+ for (NavigationView view : mNavigationViews) {
+ view.removeUnmountedSelection();
+ }
+ mSelectionBar.setSelection(getNavigationView(mCurrentNavigationView).getSelectedFiles());
+ }
+
+ /**
* Method that applies the current theme to the activity
* @hide
*/
@@ -2269,6 +2453,7 @@ public class NavigationActivity extends Activity
int orientation = getResources().getConfiguration().orientation;
Theme theme = ThemeManager.getCurrentTheme(this);
theme.setBaseTheme(this, false);
+ applyTabTheme();
// imitate a closed drawer while layout is rebuilt to avoid NullPointerException
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer);
@@ -2318,11 +2503,6 @@ public class NavigationActivity extends Activity
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"));
@@ -2334,8 +2514,6 @@ public class NavigationActivity extends Activity
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
@@ -2350,4 +2528,25 @@ public class NavigationActivity extends Activity
}
}
+ /**
+ * Method that applies the current theme to the tab host
+ */
+ private void applyTabTheme() {
+ // Apply the theme
+ Theme theme = ThemeManager.getCurrentTheme(this);
+
+ View v = findViewById(R.id.drawer);
+ theme.setBackgroundDrawable(this, v, "background_drawable"); //$NON-NLS-1$
+
+ v = findViewById(R.id.drawer_bookmarks_tab);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+ v = findViewById(R.id.drawer_history_tab);
+ theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+
+ v = findViewById(R.id.ab_settings);
+ theme.setImageDrawable(this, (ButtonItem) v, "ab_settings_drawable"); //$NON-NLS-1$
+ v = findViewById(R.id.ab_clear_history);
+ theme.setImageDrawable(this, (ButtonItem) v, "ab_delete_drawable"); //$NON-NLS-1$
+ }
+
}
diff --git a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java
index 4fbbec5b..ca16924e 100644
--- a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java
+++ b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java
@@ -52,7 +52,7 @@ import com.cyanogenmod.filemanager.activities.preferences.SearchPreferenceFragme
import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences;
import com.cyanogenmod.filemanager.adapters.SearchResultAdapter;
import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
-import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
import com.cyanogenmod.filemanager.console.RelaunchableException;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
@@ -93,8 +93,7 @@ import java.util.List;
* An activity for search files and folders.
*/
public class SearchActivity extends Activity
- implements AsyncResultListener, OnItemClickListener,
- OnItemLongClickListener, OnRequestRefreshListener {
+ implements OnItemClickListener, OnItemLongClickListener, OnRequestRefreshListener {
private static final String TAG = "SearchActivity"; //$NON-NLS-1$
@@ -211,6 +210,89 @@ public class SearchActivity extends Activity
}
};
+ private ConcurrentAsyncResultListener mAsyncListener = new ConcurrentAsyncResultListener() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onConcurrentAsyncStart() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ SearchActivity.this.toggleResults(false, false);
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onConcurrentAsyncEnd(boolean cancelled) {
+ mSearchListView.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ //Dismiss the dialog
+ if (SearchActivity.this.mDialog != null) {
+ SearchActivity.this.mDialog.dismiss();
+ }
+
+ // Resolve the symlinks
+ FileHelper.resolveSymlinks(
+ SearchActivity.this, SearchActivity.this.mResultList);
+
+ // Draw the results
+ drawResults();
+
+ } catch (Throwable ex) {
+ Log.e(TAG, "onAsyncEnd method fails", ex); //$NON-NLS-1$
+ }
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void onConcurrentPartialResult(final Object partialResults) {
+ //Saved in the global result list, for save at the end
+ if (partialResults instanceof FileSystemObject) {
+ SearchActivity.this.mResultList.add((FileSystemObject)partialResults);
+ } else {
+ SearchActivity.this.mResultList.addAll((List<FileSystemObject>)partialResults);
+ }
+
+ //Notify progress
+ mSearchListView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (SearchActivity.this.mDialog != null) {
+ int progress = SearchActivity.this.mResultList.size();
+ setProgressMsg(progress);
+ }
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onConcurrentAsyncExitCode(int exitCode) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onConcurrentException(Exception cause) {
+ //Capture the exception
+ ExceptionUtil.translateException(SearchActivity.this, cause);
+ }
+ };
+
/**
* @hide
*/
@@ -670,13 +752,13 @@ public class SearchActivity extends Activity
});
SearchActivity.this.mDialog.show();
- //Execute the query (search are process in background)
+ // Execute the query (search in background)
SearchActivity.this.mExecutable =
CommandHelper.findFiles(
SearchActivity.this,
searchDirectory,
- SearchActivity.this.mQuery,
- SearchActivity.this,
+ mQuery,
+ mAsyncListener,
null);
} catch (Throwable ex) {
@@ -1000,6 +1082,14 @@ public class SearchActivity extends Activity
* {@inheritDoc}
*/
@Override
+ public void onRequestBookmarksRefresh() {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void onRequestRemove(Object o, boolean clearSelection) {
if (o instanceof FileSystemObject) {
removeItem((FileSystemObject)o);
@@ -1027,17 +1117,18 @@ public class SearchActivity extends Activity
*/
void back(final boolean cancelled, FileSystemObject item, boolean isChecked) {
final Context ctx = SearchActivity.this;
- final Intent intent = new Intent();
boolean finish = true;
if (cancelled) {
+ final Intent intent = new Intent();
if (SearchActivity.this.mDrawingSearchResultTask != null
&& SearchActivity.this.mDrawingSearchResultTask.isRunning()) {
SearchActivity.this.mDrawingSearchResultTask.cancel(true);
}
if (this.mRestoreState != null) {
- intent.putExtra(
- NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
(Parcelable)this.mRestoreState);
+ intent.putExtras(bundle);
}
setResult(RESULT_CANCELED, intent);
} else {
@@ -1047,7 +1138,7 @@ public class SearchActivity extends Activity
if (!isChecked) {
fso = CommandHelper.getFileInfo(ctx, item.getFullPath(), null);
}
- finish = navigateTo(fso, intent);
+ finish = navigateTo(fso);
} catch (Exception e) {
// Capture the exception
@@ -1055,7 +1146,7 @@ public class SearchActivity extends Activity
final OnRelaunchCommandResult relaunchListener = new OnRelaunchCommandResult() {
@Override
public void onSuccess() {
- if (navigateTo(fFso, intent)) {
+ if (navigateTo(fFso)) {
exit();
}
}
@@ -1102,13 +1193,15 @@ public class SearchActivity extends Activity
* @param intent The intent used to navigate to
* @return boolean If the action implies finish this activity
*/
- boolean navigateTo(FileSystemObject fso, Intent intent) {
+ boolean navigateTo(FileSystemObject fso) {
if (fso != null) {
if (FileHelper.isDirectory(fso)) {
- intent.putExtra(NavigationActivity.EXTRA_SEARCH_ENTRY_SELECTION, fso);
- intent.putExtra(
- NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
+ final Intent intent = new Intent();
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(NavigationActivity.EXTRA_SEARCH_ENTRY_SELECTION, fso);
+ bundle.putParcelable(NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA,
(Parcelable)createSearchInfo());
+ intent.putExtras(bundle);
setResult(RESULT_OK, intent);
return true;
}
@@ -1126,87 +1219,6 @@ public class SearchActivity extends Activity
}
/**
- * {@inheritDoc}
- */
- @Override
- public void onAsyncStart() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- SearchActivity.this.toggleResults(false, false);
- }
- });
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onAsyncEnd(boolean cancelled) {
- this.mSearchListView.post(new Runnable() {
- @Override
- public void run() {
- try {
- //Dismiss the dialog
- if (SearchActivity.this.mDialog != null) {
- SearchActivity.this.mDialog.dismiss();
- }
-
- // Resolve the symlinks
- FileHelper.resolveSymlinks(
- SearchActivity.this, SearchActivity.this.mResultList);
-
- // Draw the results
- drawResults();
-
- } catch (Throwable ex) {
- Log.e(TAG, "onAsyncEnd method fails", ex); //$NON-NLS-1$
- }
- }
- });
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- @SuppressWarnings("unchecked")
- public void onPartialResult(final Object partialResults) {
- //Saved in the global result list, for save at the end
- if (partialResults instanceof FileSystemObject) {
- SearchActivity.this.mResultList.add((FileSystemObject)partialResults);
- } else {
- SearchActivity.this.mResultList.addAll((List<FileSystemObject>)partialResults);
- }
-
- //Notify progress
- this.mSearchListView.post(new Runnable() {
- @Override
- public void run() {
- if (SearchActivity.this.mDialog != null) {
- int progress = SearchActivity.this.mResultList.size();
- setProgressMsg(progress);
- }
- }
- });
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onAsyncExitCode(int exitCode) {/**NON BLOCK**/}
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onException(Exception cause) {
- //Capture the exception
- ExceptionUtil.translateException(this, cause);
- }
-
- /**
* Method that draw the results in the listview
* @hide
*/
@@ -1232,11 +1244,10 @@ public class SearchActivity extends Activity
* @return SearchInfoParcelable The search info reference
*/
private SearchInfoParcelable createSearchInfo() {
- SearchInfoParcelable parcel = new SearchInfoParcelable();
- parcel.setSearchDirectory(this.mSearchDirectory);
- parcel.setSearchResultList(
- ((SearchResultAdapter)this.mSearchListView.getAdapter()).getData());
- parcel.setSearchQuery(this.mQuery);
+ SearchInfoParcelable parcel = new SearchInfoParcelable(
+ mSearchDirectory,
+ ((SearchResultAdapter)this.mSearchListView.getAdapter()).getData(),
+ mQuery);
return parcel;
}
diff --git a/src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java b/src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java
new file mode 100644
index 00000000..d664dd47
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java
@@ -0,0 +1,180 @@
+/*
+ * 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.preferences;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
+import com.cyanogenmod.filemanager.preferences.Preferences;
+
+/**
+ * A class that manages the storage options
+ */
+public class StoragePreferenceFragment extends TitlePreferenceFragment {
+
+ private static final String TAG = "StoragePreferenceFragment"; //$NON-NLS-1$
+
+ private static final boolean DEBUG = false;
+
+ private static final String KEY_RESET_PASSWORD = "secure_storage_reset_password";
+ private static final String KEY_DELETE_STORAGE = "secure_storage_delete_storage";
+
+ private Preference mResetPassword;
+ private Preference mDeleteStorage;
+ private CheckBoxPreference mDelayedSync;
+
+ private final BroadcastReceiver mMountStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().compareTo(
+ FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED) == 0) {
+ updatePreferences();
+ }
+ }
+ };
+
+ private final OnPreferenceChangeListener mOnChangeListener =
+ new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ String key = preference.getKey();
+ if (DEBUG) {
+ Log.d(TAG,
+ String.format("New value for %s: %s", //$NON-NLS-1$
+ key,
+ String.valueOf(newValue)));
+ }
+
+ return true;
+ }
+ };
+
+ private final OnPreferenceClickListener mOnClickListener = new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference.equals(mResetPassword)) {
+ getSecureConsole().requestReset(getActivity());
+ } else if (preference.equals(mDeleteStorage)) {
+ getSecureConsole().requestDelete(getActivity());
+ }
+ return false;
+ }
+ };
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED);
+ getActivity().registerReceiver(mMountStatusReceiver, filter);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ getActivity().unregisterReceiver(mMountStatusReceiver);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Update the preferences
+ updatePreferences();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Change the preference manager
+ getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME);
+ getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+
+ // Add the preferences
+ addPreferencesFromResource(R.xml.preferences_storage);
+
+ // Reset password
+ mResetPassword = findPreference(KEY_RESET_PASSWORD);
+ mResetPassword.setOnPreferenceClickListener(mOnClickListener);
+
+ // Delete storage
+ mDeleteStorage = findPreference(KEY_DELETE_STORAGE);
+ mDeleteStorage.setOnPreferenceClickListener(mOnClickListener);
+
+ // Delayed sync
+ this.mDelayedSync =
+ (CheckBoxPreference)findPreference(
+ FileManagerSettings.SETTINGS_SECURE_STORAGE_DELAYED_SYNC.getId());
+ this.mDelayedSync.setOnPreferenceChangeListener(this.mOnChangeListener);
+
+ // Update the preferences
+ updatePreferences();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CharSequence getTitle() {
+ return getString(R.string.pref_storage);
+ }
+
+ /**
+ * Method that returns the secure console instance
+ *
+ * @return SecureConsole The secure console
+ */
+ private SecureConsole getSecureConsole() {
+ int bufferSize = getActivity().getResources().getInteger(R.integer.buffer_size);
+ return SecureConsole.getInstance(getActivity(), bufferSize);
+ }
+
+ /**
+ * Check the preferences status
+ */
+ @SuppressWarnings("deprecation")
+ private void updatePreferences() {
+ boolean secureStorageExists = SecureConsole.getSecureStorageRoot().getFile().exists();
+ if (mResetPassword != null) {
+ mResetPassword.setEnabled(secureStorageExists);
+ }
+ if (mDeleteStorage != null) {
+ mDeleteStorage.setEnabled(secureStorageExists);
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java b/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java
index 0caf438b..5ae541b3 100644
--- a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java
+++ b/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java
@@ -80,8 +80,6 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> {
Float mRelevance;
}
- private static final int MESSAGE_REDRAW = 1;
-
private DataHolder[] mData;
private IconHolder mIconHolder;
private final int mItemViewResourceId;
diff --git a/src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java b/src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java
new file mode 100644
index 00000000..31ea0f06
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java
@@ -0,0 +1,167 @@
+/*
+ * 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.commands;
+
+/**
+ * An interface for communicate partial results in concurrent mode.
+ */
+public abstract class ConcurrentAsyncResultListener implements AsyncResultListener {
+
+ private final Object mSync = new Object();
+ private int mRefs;
+ private boolean mStartNotified = false;
+ private boolean mCancelled = false;
+
+ /**
+ * Constructor of {@code ConcurrentAsyncResultListener}
+ */
+ public ConcurrentAsyncResultListener() {
+ super();
+ mRefs = 0;
+ }
+
+ /**
+ * Method invoked when the partial data has initialized.
+ */
+ public abstract void onConcurrentAsyncStart();
+
+ /**
+ * Method invoked when the partial data has finalized.
+ *
+ * @param cancelled Indicates if the program was cancelled
+ */
+ public abstract void onConcurrentAsyncEnd(boolean cancelled);
+
+ /**
+ * Method invoked when the program is ended.
+ *
+ * @param exitCode The exit code of the program
+ */
+ public abstract void onConcurrentAsyncExitCode(int exitCode);
+
+ /**
+ * Method invoked when new partial data are ready.
+ *
+ * @param result New data result
+ */
+ public abstract void onConcurrentPartialResult(Object result);
+
+ /**
+ * Method invoked when an exception occurs while executing the program.
+ *
+ * @param cause The cause that raise the exception
+ */
+ public abstract void onConcurrentException(Exception cause);
+
+ /**
+ * Return if the operation was cancelled by other listener
+ *
+ * @return boolean If the operation was cancelled
+ */
+ public boolean isCancelled() {
+ return mCancelled;
+ }
+
+ /**
+ * Method invoked when an object want to be part of this concurrent listener
+ */
+ public void onRegister() {
+ synchronized (mSync) {
+ mRefs++;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onAsyncStart() {
+ boolean notify = false;
+ synchronized (mSync) {
+ if (!mStartNotified) {
+ notify = true;
+ }
+ mStartNotified = true;
+ }
+ if (notify) {
+ onConcurrentAsyncStart();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onAsyncEnd(boolean cancelled) {
+ boolean notify = false;
+ if (cancelled) {
+ mCancelled = true;
+ }
+ synchronized (mSync) {
+ if (mRefs <= 1) {
+ notify = true;
+ }
+ mRefs--;
+ mStartNotified = true;
+ }
+ if (notify) {
+ onConcurrentAsyncEnd(mCancelled);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onAsyncExitCode(int exitCode) {
+ boolean notify = false;
+ synchronized (mSync) {
+ if (mRefs <= 0) {
+ notify = true;
+ }
+ mStartNotified = true;
+ }
+ if (notify) {
+ onConcurrentAsyncExitCode(exitCode);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onPartialResult(Object result) {
+ synchronized (mSync) {
+ if (!mCancelled && mRefs >= 1) {
+ onConcurrentPartialResult(result);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void onException(Exception cause) {
+ synchronized (mSync) {
+ if (!mCancelled && mRefs >= 1) {
+ onConcurrentException(cause);
+ }
+ }
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java
index f5c2f169..83fdf188 100644
--- a/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java
+++ b/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java
@@ -196,7 +196,7 @@ public interface ExecutableCreator {
* @throws InsufficientPermissionsException If an operation requires elevated permissions
*/
FindExecutable createFindExecutable(
- String directory, Query query, AsyncResultListener asyncResultListener)
+ String directory, Query query, ConcurrentAsyncResultListener asyncResultListener)
throws CommandNotFoundException,
NoSuchFileOrDirectory, InsufficientPermissionsException;
diff --git a/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java b/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java
index 793dc616..2a7ccf54 100644
--- a/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java
+++ b/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java
@@ -19,6 +19,7 @@ package com.cyanogenmod.filemanager.commands.java;
import android.util.Log;
import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
import com.cyanogenmod.filemanager.commands.FindExecutable;
import com.cyanogenmod.filemanager.console.ExecutionException;
import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
@@ -40,7 +41,7 @@ public class FindCommand extends Program implements FindExecutable {
private final String mDirectory;
private final String[] mQueryRegExp;
- private final AsyncResultListener mAsyncResultListener;
+ private final ConcurrentAsyncResultListener mAsyncResultListener;
private boolean mCancelled;
private boolean mEnded;
@@ -53,11 +54,15 @@ public class FindCommand extends Program implements FindExecutable {
* @param query The terms to be searched
* @param asyncResultListener The partial result listener
*/
- public FindCommand(String directory, Query query, AsyncResultListener asyncResultListener) {
+ public FindCommand(String directory, Query query,
+ ConcurrentAsyncResultListener asyncResultListener) {
super();
this.mDirectory = directory;
this.mQueryRegExp = createRegexp(directory, query);
this.mAsyncResultListener = asyncResultListener;
+ if (mAsyncResultListener instanceof ConcurrentAsyncResultListener) {
+ ((ConcurrentAsyncResultListener) mAsyncResultListener).onRegister();
+ }
this.mCancelled = false;
this.mEnded = false;
}
@@ -85,27 +90,25 @@ public class FindCommand extends Program implements FindExecutable {
this.mAsyncResultListener.onAsyncStart();
}
+ boolean ready = true;
File f = new File(this.mDirectory);
if (!f.exists()) {
if (isTrace()) {
Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
}
- if (this.mAsyncResultListener != null) {
- this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory));
- }
+ ready = false;
}
- if (!f.isDirectory()) {
+ if (ready && !f.isDirectory()) {
if (isTrace()) {
Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
}
- if (this.mAsyncResultListener != null) {
- this.mAsyncResultListener.onException(
- new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$
- }
+ ready = false;
}
// Find the data
- findRecursive(f);
+ if (ready) {
+ findRecursive(f);
+ }
if (this.mAsyncResultListener != null) {
this.mAsyncResultListener.onAsyncEnd(this.mCancelled);
@@ -156,7 +159,8 @@ public class FindCommand extends Program implements FindExecutable {
// Check if the process was cancelled
try {
synchronized (this.mSync) {
- if (this.mCancelled || this.mEnded) {
+ if (this.mCancelled || this.mEnded || (mAsyncResultListener != null
+ && mAsyncResultListener.isCancelled())) {
this.mSync.notify();
break;
}
diff --git a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java
index 94856ba9..a76c9fef 100644
--- a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java
+++ b/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java
@@ -22,6 +22,7 @@ import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable;
import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable;
import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
import com.cyanogenmod.filemanager.commands.CompressExecutable;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
import com.cyanogenmod.filemanager.commands.CopyExecutable;
import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
import com.cyanogenmod.filemanager.commands.CreateFileExecutable;
@@ -180,7 +181,7 @@ public class JavaExecutableCreator implements ExecutableCreator {
*/
@Override
public FindExecutable createFindExecutable(
- String directory, Query query, AsyncResultListener asyncResultListener)
+ String directory, Query query, ConcurrentAsyncResultListener asyncResultListener)
throws CommandNotFoundException {
return new FindCommand(directory, query, asyncResultListener);
}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java
new file mode 100644
index 00000000..39e623bb
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java
@@ -0,0 +1,278 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.android.internal.util.HexDump;
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+
+import de.schlichtherle.truezip.file.TFile;
+import de.schlichtherle.truezip.file.TFileInputStream;
+
+import java.io.File;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.util.Locale;
+
+/**
+ * A class for calculate MD5 and SHA-1 checksums of a file system object.<br />
+ * <br />
+ * Partial results are returned in order (MD5 -> SHA1)
+ */
+public class ChecksumCommand extends Program implements ChecksumExecutable {
+
+ private static final String TAG = "ChecksumCommand"; //$NON-NLS-1$
+
+ private final File mSrc;
+ private final String[] mChecksums;
+ private final AsyncResultListener mAsyncResultListener;
+
+ private boolean mCancelled;
+ private final Object mSync = new Object();
+
+ /**
+ * Constructor of <code>ChecksumCommand</code>.
+ *
+ * @param console The current console
+ * @param src The source file
+ * @param asyncResultListener The partial result listener
+ */
+ public ChecksumCommand(SecureConsole console, String src,
+ AsyncResultListener asyncResultListener) {
+ super(console);
+ this.mAsyncResultListener = asyncResultListener;
+ this.mChecksums = new String[]{null, null};
+ this.mSrc = new File(src);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAsynchronous() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Calculating checksums of file %s", this.mSrc)); //$NON-NLS-1$
+ }
+
+ // Check that the file exists
+ TFile f = getConsole().buildRealFile(this.mSrc.getAbsolutePath());
+ if (!f.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ throw new NoSuchFileOrDirectory(this.mSrc.getAbsolutePath());
+ }
+
+ CHECKSUMS checksum = CHECKSUMS.MD5;
+ try {
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncStart();
+ }
+
+ // Calculate digests
+ calculateDigest(checksum, f);
+ checksum = CHECKSUMS.SHA1;
+ calculateDigest(checksum, f);
+
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncEnd(false);
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncExitCode(0);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+
+ } catch (InterruptedException ie) {
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncEnd(true);
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncExitCode(143);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: CANCELLED"); //$NON-NLS-1$
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG,
+ String.format(
+ "Fail to calculate %s checksum of file %s", //$NON-NLS-1$
+ checksum.name(),
+ this.mSrc.getAbsolutePath()),
+ e);
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(e);
+ }
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancelled() {
+ synchronized (this.mSync) {
+ return this.mCancelled;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean cancel() {
+ try {
+ synchronized (this.mSync) {
+ this.mCancelled = true;
+ }
+ } catch (Throwable _throw) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean end() {
+ return cancel();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnEndListener(OnEndListener onEndListener) {
+ //Ignore. Java console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnCancelListener(OnCancelListener onCancelListener) {
+ //Ignore. Java console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getResult() {
+ return this.mChecksums;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getChecksum(CHECKSUMS checksum) {
+ return getResult()[checksum.ordinal()];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancellable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AsyncResultListener getAsyncResultListener() {
+ return this.mAsyncResultListener;
+ }
+
+ /**
+ * Method that calculate a digest of the file for the source file
+ *
+ * @param type The type of digest to obtain
+ * @pa
+ * @throws InterruptedException If the operation was cancelled
+ * @throws Exception If an error occurs
+ */
+ private void calculateDigest(CHECKSUMS type, TFile file)
+ throws InterruptedException, Exception {
+
+ InputStream is = null;
+ try {
+ MessageDigest md = MessageDigest.getInstance(type.name());
+ is = new TFileInputStream(file);
+
+ // Start digesting
+ byte[] data = new byte[getBufferSize()];
+ int read = 0;
+ while ((read = is.read(data, 0, getBufferSize())) != -1) {
+ checkCancelled();
+ md.update(data, 0, read);
+ }
+ checkCancelled();
+
+ // Finally digest
+ this.mChecksums[type.ordinal()] =
+ HexDump.toHexString(md.digest()).toLowerCase(Locale.ROOT);
+ checkCancelled();
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onPartialResult(this.mChecksums[type.ordinal()]);
+ }
+
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ }
+ }
+
+ /**
+ * Checks if the operation was cancelled
+ *
+ * @throws InterruptedException If the operation was cancelled
+ */
+ private void checkCancelled() throws InterruptedException {
+ synchronized (this.mSync) {
+ if (this.mCancelled) {
+ throw new InterruptedException();
+ }
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java
new file mode 100644
index 00000000..3b7d8563
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java
@@ -0,0 +1,117 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.CopyExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.MountPoint;
+
+import de.schlichtherle.truezip.file.TFile;
+
+import java.io.IOException;
+
+
+/**
+ * A class for copy a file or directory.
+ */
+public class CopyCommand extends Program implements CopyExecutable {
+
+ private static final String TAG = "CopyCommand"; //$NON-NLS-1$
+
+ private final String mSrc;
+ private final String mDst;
+
+ /**
+ * Constructor of <code>CopyCommand</code>.
+ *
+ * @param console The current console
+ * @param src The name of the file or directory to be copied
+ * @param dst The name of the file or directory in which copy the source file or directory
+ */
+ public CopyCommand(SecureConsole console, String src, String dst) {
+ super(console);
+ this.mSrc = src;
+ this.mDst = dst;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresSync() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean getResult() {
+ return Boolean.TRUE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Moving from %s to %s", //$NON-NLS-1$
+ this.mSrc, this.mDst));
+ }
+
+ TFile s = getConsole().buildRealFile(this.mSrc);
+ TFile d = getConsole().buildRealFile(this.mDst);
+ if (!s.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ throw new NoSuchFileOrDirectory(this.mSrc);
+ }
+
+ try {
+ TFile.cp_r(s, d, SecureConsole.DETECTOR, SecureConsole.DETECTOR);
+ } catch (IOException ex) {
+ throw new ExecutionException("Failed to copy file or directory", ex);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getSrcWritableMountPoint() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getDstWritableMountPoint() {
+ return null;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java
new file mode 100644
index 00000000..8b87c464
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java
@@ -0,0 +1,116 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.MountPoint;
+
+import de.schlichtherle.truezip.file.TFile;
+
+
+/**
+ * A class for create a directory.
+ */
+public class CreateDirCommand extends Program implements CreateDirExecutable {
+
+ private static final String TAG = "CreateDirCommand"; //$NON-NLS-1$
+
+ private final String mPath;
+
+ /**
+ * Constructor of <code>CreateDirCommand</code>.
+ *
+ * @param console The current console
+ * @param path The name of the new directory
+ */
+ public CreateDirCommand(SecureConsole console, String path) {
+ super(console);
+ this.mPath = path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresSync() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean getResult() {
+ return Boolean.TRUE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Creating directory: %s", this.mPath)); //$NON-NLS-1$
+ }
+
+ TFile f = getConsole().buildRealFile(this.mPath);
+ // Check that if the path exist, it need to be a directory. Otherwise something is
+ // wrong
+ if (f.exists() && !f.isDirectory()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$
+ }
+
+ // Only create the directory if the folder not exists. Otherwise mkdir will return false
+ if (!f.exists()) {
+ if (!f.mkdir()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. IOException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("Failed to create directory");
+ }
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getSrcWritableMountPoint() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getDstWritableMountPoint() {
+ return null;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java
new file mode 100644
index 00000000..dfac1be6
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java
@@ -0,0 +1,125 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.CreateFileExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.MountPoint;
+
+import de.schlichtherle.truezip.file.TFile;
+
+import java.io.IOException;
+
+
+/**
+ * A class for create a file.
+ */
+public class CreateFileCommand extends Program implements CreateFileExecutable {
+
+ private static final String TAG = "CreateFileCommand"; //$NON-NLS-1$
+
+
+ private final String mPath;
+
+ /**
+ * Constructor of <code>CreateFileCommand</code>.
+ *
+ * @param console The current console
+ * @param path The name of the new file
+ */
+ public CreateFileCommand(SecureConsole console, String path) {
+ super(console);
+ this.mPath = path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresSync() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean getResult() {
+ return Boolean.TRUE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Creating file: %s", this.mPath)); //$NON-NLS-1$
+ }
+
+ TFile f = getConsole().buildRealFile(this.mPath);
+ // Check that if the path exist, it need to be a file. Otherwise
+ // something is wrong
+ if (f.exists() && !f.isFile()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("the path exists but is not a file"); //$NON-NLS-1$
+ }
+
+ // Only create the file if the file not exists. Otherwise createNewFile
+ // will return false
+ if (!f.exists()) {
+ try {
+ if (!f.createNewFile()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("Failed to create file");
+ }
+ } catch (IOException ex) {
+ throw new ExecutionException("Failed to create file", ex);
+ }
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getSrcWritableMountPoint() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getDstWritableMountPoint() {
+ return null;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java
new file mode 100644
index 00000000..47ca0947
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java
@@ -0,0 +1,122 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.DeleteDirExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.MountPoint;
+import com.cyanogenmod.filemanager.util.FileHelper;
+
+import de.schlichtherle.truezip.file.TFile;
+
+
+/**
+ * A class for delete a folder.
+ */
+public class DeleteDirCommand extends Program implements DeleteDirExecutable {
+
+ private static final String TAG = "DeleteDirCommand"; //$NON-NLS-1$
+
+ private final String mPath;
+
+ /**
+ * Constructor of <code>DeleteDirCommand</code>.
+ *
+ * @param console The current console
+ * @param path The name of the new folder
+ */
+ public DeleteDirCommand(SecureConsole console, String path) {
+ super(console);
+ this.mPath = path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresSync() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean getResult() {
+ return Boolean.TRUE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Deleting directory: %s", this.mPath)); //$NON-NLS-1$
+ }
+
+ TFile f = getConsole().buildRealFile(this.mPath);
+ if (!f.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ throw new NoSuchFileOrDirectory(this.mPath);
+ }
+
+ // Check that if the path exist, it need to be a folder. Otherwise something is
+ // wrong
+ if (f.exists() && !f.isDirectory()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$
+ }
+
+ // Delete the file
+ if (!FileHelper.deleteFolder(f)) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("Failed to delete directory");
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getSrcWritableMountPoint() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getDstWritableMountPoint() {
+ return null;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java
new file mode 100644
index 00000000..a8e6e077
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java
@@ -0,0 +1,125 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.DeleteFileExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.MountPoint;
+
+import de.schlichtherle.truezip.file.TFile;
+
+import java.io.IOException;
+
+
+/**
+ * A class for delete a file.
+ */
+public class DeleteFileCommand extends Program implements DeleteFileExecutable {
+
+ private static final String TAG = "DeleteFileCommand"; //$NON-NLS-1$
+
+ private final String mPath;
+
+ /**
+ * Constructor of <code>DeleteFileCommand</code>.
+ *
+ * @param console The current console
+ * @param path The name of the new file
+ */
+ public DeleteFileCommand(SecureConsole console, String path) {
+ super(console);
+ this.mPath = path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresSync() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean getResult() {
+ return Boolean.TRUE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Deleting file: %s", this.mPath)); //$NON-NLS-1$
+ }
+
+ TFile f = getConsole().buildRealFile(this.mPath);
+ if (!f.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ throw new NoSuchFileOrDirectory(this.mPath);
+ }
+
+ // Check that if the path exist, it need to be a file. Otherwise something is
+ // wrong
+ if (f.exists() && !f.isFile()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("the path exists but is not a file"); //$NON-NLS-1$
+ }
+
+ // Delete the file
+ try {
+ TFile.rm(f);
+ } catch (IOException ex) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. IOException"); //$NON-NLS-1$
+ }
+ throw new ExecutionException("Failed to delete file", ex);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getSrcWritableMountPoint() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getDstWritableMountPoint() {
+ return null;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java
new file mode 100644
index 00000000..33d0ace0
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java
@@ -0,0 +1,271 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
+import com.cyanogenmod.filemanager.commands.FindExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.FileSystemObject;
+import com.cyanogenmod.filemanager.model.Query;
+import com.cyanogenmod.filemanager.util.FileHelper;
+import com.cyanogenmod.filemanager.util.SearchHelper;
+
+import de.schlichtherle.truezip.file.TFile;
+
+import java.util.Arrays;
+
+/**
+ * A class for search files.
+ */
+public class FindCommand extends Program implements FindExecutable {
+
+ private static final String TAG = "FindCommand"; //$NON-NLS-1$
+
+ private final String mDirectory;
+ private final String[] mQueryRegExp;
+ private final ConcurrentAsyncResultListener mAsyncResultListener;
+
+ private boolean mCancelled;
+ private boolean mEnded;
+ private final Object mSync = new Object();
+
+ /**
+ * Constructor of <code>FindCommand</code>.
+ *
+ * @param console The secure console
+ * @param directory The absolute directory where start the search
+ * @param query The terms to be searched
+ * @param asyncResultListener The partial result listener
+ */
+ public FindCommand(SecureConsole console, String directory, Query query,
+ ConcurrentAsyncResultListener asyncResultListener) {
+ super(console);
+ // This command should start the search in the root directory or in a descendent folder
+ if (!getConsole().isSecureStorageResource(directory)) {
+ this.mDirectory = getConsole().getVirtualMountPoint().getAbsolutePath();
+ } else {
+ this.mDirectory = directory;
+ }
+ this.mQueryRegExp = createRegexp(directory, query);
+ this.mAsyncResultListener = asyncResultListener;
+ if (mAsyncResultListener instanceof ConcurrentAsyncResultListener) {
+ ((ConcurrentAsyncResultListener) mAsyncResultListener).onRegister();
+ }
+ this.mCancelled = false;
+ this.mEnded = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAsynchronous() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Finding in %s the query %s", //$NON-NLS-1$
+ this.mDirectory, Arrays.toString(this.mQueryRegExp)));
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncStart();
+ }
+
+ TFile f = getConsole().buildRealFile(mDirectory);
+ if (!f.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory));
+ }
+ }
+ if (!f.isDirectory()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(
+ new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$
+ }
+ }
+
+ // Find the data
+ findRecursive(f);
+
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncEnd(this.mCancelled);
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncExitCode(0);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Method that search files recursively
+ *
+ * @param folder The folder where to start the search
+ */
+ private void findRecursive(TFile folder) {
+ // Obtains the files and folders of the folders
+ TFile[] files = folder.listFiles();
+ if (files != null) {
+ int cc = files.length;
+ for (int i = 0; i < cc; i++) {
+ if (files[i].isDirectory()) {
+ findRecursive(files[i]);
+ }
+
+ // Check if the file or folder matches the regexp
+ try {
+ int ccc = this.mQueryRegExp.length;
+ for (int j = 0; j < ccc; j++) {
+ if (files[i].getName().matches(this.mQueryRegExp[j])) {
+ FileSystemObject fso = FileHelper.createFileSystemObject(files[i]);
+ if (fso != null) {
+ // Convert to virtual
+ fso.setParent(getConsole().buildVirtualPath(
+ files[i].getParentFile()));
+ fso.setSecure(true);
+
+ if (isTrace()) {
+ Log.v(TAG, String.valueOf(fso));
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onPartialResult(fso);
+ }
+ }
+ }
+ }
+ } catch (Exception e) {/**NON-BLOCK**/}
+
+ // Check if the process was cancelled
+ try {
+ synchronized (this.mSync) {
+ if (this.mCancelled || this.mEnded || (mAsyncResultListener != null
+ && mAsyncResultListener.isCancelled())) {
+ this.mSync.notify();
+ break;
+ }
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancelled() {
+ synchronized (this.mSync) {
+ return this.mCancelled;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean cancel() {
+ try {
+ synchronized (this.mSync) {
+ this.mCancelled = true;
+ this.mSync.wait(5000L);
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean end() {
+ try {
+ synchronized (this.mSync) {
+ this.mEnded = true;
+ this.mSync.wait(5000L);
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnEndListener(OnEndListener onEndListener) {
+ //Ignore. secure console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnCancelListener(OnCancelListener onCancelListener) {
+ //Ignore. secure console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancellable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AsyncResultListener getAsyncResultListener() {
+ return this.mAsyncResultListener;
+ }
+
+ /**
+ * Method that create the regexp of this command, using the directory and
+ * arguments and creating the regular expressions of the search.
+ *
+ * @param directory The directory where to search
+ * @param query The query make for user
+ * @return String[] The regexp for filtering files
+ */
+ private static String[] createRegexp(String directory, Query query) {
+ String[] args = new String[query.getSlotsCount()];
+ int cc = query.getSlotsCount();
+ for (int i = 0; i < cc; i++) {
+ args[i] = SearchHelper.toIgnoreCaseRegExp(query.getSlot(i), true);
+ }
+ return args;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java
new file mode 100644
index 00000000..5049a01a
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java
@@ -0,0 +1,258 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.FolderUsageExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.FolderUsage;
+import com.cyanogenmod.filemanager.util.MimeTypeHelper;
+import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
+
+import de.schlichtherle.truezip.file.TFile;
+
+/**
+ * A class for retrieve the disk usage of a folder.
+ */
+public class FolderUsageCommand extends Program implements FolderUsageExecutable {
+
+ private static final String TAG = "FolderUsage"; //$NON-NLS-1$
+
+ private final String mDirectory;
+ private final AsyncResultListener mAsyncResultListener;
+ private final FolderUsage mFolderUsage;
+
+ private boolean mCancelled;
+ private boolean mEnded;
+ private final Object mSync = new Object();
+
+ /**
+ * Constructor of <code>FolderUsageCommand</code>.
+ *
+ * @param console The secure console
+ * @param directory The absolute directory to compute
+ * @param asyncResultListener The partial result listener
+ */
+ public FolderUsageCommand(SecureConsole console, String directory,
+ AsyncResultListener asyncResultListener) {
+ super(console);
+ this.mDirectory = directory;
+ this.mAsyncResultListener = asyncResultListener;
+ this.mFolderUsage = new FolderUsage(directory);
+ this.mCancelled = false;
+ this.mEnded = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAsynchronous() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FolderUsage getFolderUsage() {
+ return this.mFolderUsage;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Computing folder usage for folder %s", //$NON-NLS-1$
+ this.mDirectory));
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncStart();
+ }
+
+ TFile f = getConsole().buildRealFile(mDirectory);
+ if (!f.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory));
+ }
+ }
+ if (!f.isDirectory()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(
+ new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$
+ }
+ }
+
+ // Compute data recursively
+ computeRecursive(f);
+
+ synchronized (this.mSync) {
+ this.mEnded = true;
+ this.mSync.notify();
+ }
+
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncEnd(this.mCancelled);
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncExitCode(0);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Method that computes the folder usage recursively
+ *
+ * @param folder The folder where to start the computation
+ */
+ private void computeRecursive(TFile folder) {
+ // Obtains the files and folders of the folders
+ try {
+ TFile[] files = folder.listFiles();
+ int c = 0;
+ if (files != null) {
+ int cc = files.length;
+ for (int i = 0; i < cc; i++) {
+ if (files[i].isDirectory()) {
+ this.mFolderUsage.addFolder();
+ computeRecursive(files[i]);
+ } else {
+ this.mFolderUsage.addFile();
+ // Compute statistics and size
+ MimeTypeCategory category =
+ MimeTypeHelper.getCategory(null, files[i]);
+ this.mFolderUsage.addFileToCategory(category);
+ this.mFolderUsage.addSize(files[i].length());
+ }
+
+ // Partial notification
+ if (c % 5 == 0) {
+ //If a listener is defined, then send the partial result
+ if (getAsyncResultListener() != null) {
+ getAsyncResultListener().onPartialResult(this.mFolderUsage);
+ }
+ }
+
+ // Check if the process was cancelled
+ try {
+ synchronized (this.mSync) {
+ if (this.mCancelled || this.mEnded) {
+ this.mSync.notify();
+ break;
+ }
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ }
+ }
+ } finally {
+ //If a listener is defined, then send the partial result
+ if (getAsyncResultListener() != null) {
+ getAsyncResultListener().onPartialResult(this.mFolderUsage);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancelled() {
+ synchronized (this.mSync) {
+ return this.mCancelled;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean cancel() {
+ try {
+ synchronized (this.mSync) {
+ if (this.mEnded || this.mCancelled) {
+ this.mCancelled = true;
+ return true;
+ }
+ this.mCancelled = true;
+ this.mSync.wait(5000L);
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean end() {
+ try {
+ synchronized (this.mSync) {
+ this.mEnded = true;
+ this.mSync.wait(5000L);
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnEndListener(OnEndListener onEndListener) {
+ //Ignore. secure console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnCancelListener(OnCancelListener onCancelListener) {
+ //Ignore. secure console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancellable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AsyncResultListener getAsyncResultListener() {
+ return this.mAsyncResultListener;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java
new file mode 100644
index 00000000..3fc95465
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java
@@ -0,0 +1,167 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.ListExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.Directory;
+import com.cyanogenmod.filemanager.model.FileSystemObject;
+import com.cyanogenmod.filemanager.model.ParentDirectory;
+import com.cyanogenmod.filemanager.util.FileHelper;
+
+import de.schlichtherle.truezip.file.TFile;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * A class for list information about files and directories.
+ */
+public class ListCommand extends Program implements ListExecutable {
+
+ private static final String TAG = "ListCommand"; //$NON-NLS-1$
+
+ private final String mSrc;
+ private final LIST_MODE mMode;
+ private final List<FileSystemObject> mFiles;
+
+ /**
+ * Constructor of <code>ListCommand</code>. List mode.
+ *
+ * @param src The file system object to be listed
+ * @param mode The mode of listing
+ */
+ public ListCommand(SecureConsole console, String src, LIST_MODE mode) {
+ super(console);
+ this.mSrc = src;
+ this.mMode = mode;
+ this.mFiles = new ArrayList<FileSystemObject>();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresOpen() {
+ if (this.mMode.compareTo(LIST_MODE.FILEINFO) == 0) {
+ return !getConsole().getVirtualMountPoint().equals(new File(mSrc));
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<FileSystemObject> getResult() {
+ return this.mFiles;
+ }
+
+ /**
+ * Method that returns a single result of the program invocation.
+ * Only must be called within a <code>FILEINFO</code> mode listing.
+ *
+ * @return FileSystemObject The file system object reference
+ */
+ public FileSystemObject getSingleResult() {
+ return this.mFiles.get(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Listing %s. Mode: %s", //$NON-NLS-1$
+ this.mSrc, this.mMode));
+ }
+
+ TFile f = getConsole().buildRealFile(mSrc);
+ boolean isSecureStorage = SecureConsole.isSecureStorageDir(f);
+ File javaFile = f.getFile();
+ if (!isSecureStorage && !f.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ throw new NoSuchFileOrDirectory(this.mSrc);
+ }
+ if (this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) {
+ // List files in directory
+ TFile[] files = f.listFiles();
+ if (files != null) {
+ for (int i = 0; i < files.length; i++) {
+ FileSystemObject fso = FileHelper.createFileSystemObject(files[i]);
+ if (fso != null) {
+ // Convert to virtual
+ fso.setParent(getConsole().buildVirtualPath(files[i].getParentFile()));
+ fso.setSecure(true);
+
+ if (isTrace()) {
+ Log.v(TAG, String.valueOf(fso));
+ }
+ this.mFiles.add(fso);
+ }
+ }
+ }
+
+ //Now if not is the root directory, add the parent directory
+ if (this.mSrc.compareTo(FileHelper.ROOT_DIRECTORY) != 0 &&
+ this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) {
+ this.mFiles.add(0, new ParentDirectory(new File(this.mSrc).getParent()));
+ }
+ } else {
+ // Build the source file information
+ FileSystemObject fso = FileHelper.createFileSystemObject(
+ isSecureStorage ? javaFile : f);
+ if (fso != null) {
+ // Convert to virtual
+ if (isSecureStorage) {
+ File virtualMountPoint = getConsole().getVirtualMountPoint();
+ fso = new Directory(
+ virtualMountPoint.getName(),
+ getConsole().getVirtualMountPoint().getParent(),
+ fso.getUser(), fso.getGroup(), fso.getPermissions(),
+ fso.getLastAccessedTime(),
+ fso.getLastModifiedTime(),
+ fso.getLastChangedTime());
+ fso.setSecure(true);
+ } else {
+ fso.setParent(getConsole().buildVirtualPath(f.getParentFile()));
+ }
+ fso.setSecure(true);
+ if (isTrace()) {
+ Log.v(TAG, String.valueOf(fso));
+ }
+ this.mFiles.add(fso);
+ }
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java
new file mode 100644
index 00000000..3cd9748d
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java
@@ -0,0 +1,135 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.MoveExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.MountPoint;
+import com.cyanogenmod.filemanager.util.FileHelper;
+
+import java.io.IOException;
+
+import de.schlichtherle.truezip.file.TFile;
+
+
+
+/**
+ * A class for move a file or directory.
+ */
+public class MoveCommand extends Program implements MoveExecutable {
+
+ private static final String TAG = "MoveCommand"; //$NON-NLS-1$
+
+ private final String mSrc;
+ private final String mDst;
+
+ /**
+ * Constructor of <code>MoveCommand</code>.
+ *
+ * @param console The current console
+ * @param src The name of the file or directory to be moved
+ * @param dst The name of the file or directory in which move the source file or directory
+ */
+ public MoveCommand(SecureConsole console, String src, String dst) {
+ super(console);
+ this.mSrc = src;
+ this.mDst = dst;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresSync() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean getResult() {
+ return Boolean.TRUE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Creating from %s to %s", this.mSrc, this.mDst)); //$NON-NLS-1$
+ }
+
+ TFile s = getConsole().buildRealFile(this.mSrc);
+ TFile d = getConsole().buildRealFile(this.mDst);
+ if (!s.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ throw new NoSuchFileOrDirectory(this.mSrc);
+ }
+
+ //Move or copy recursively
+ if (d.exists()) {
+ try {
+ TFile.cp_r(s, d, SecureConsole.DETECTOR, SecureConsole.DETECTOR);
+ } catch (IOException ex) {
+ throw new ExecutionException("Failed to move file or directory", ex);
+ }
+ if (!FileHelper.deleteFolder(s)) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK. WARNING. Source not deleted."); //$NON-NLS-1$
+ }
+ }
+ } else {
+ // Use rename. We are not cross filesystem with this console, so this operation
+ // should be safe
+ try {
+ TFile.mv(s, d, SecureConsole.DETECTOR);
+ } catch (IOException ex) {
+ throw new ExecutionException("Failed to rename file or directory", ex);
+ }
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getSrcWritableMountPoint() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPoint getDstWritableMountPoint() {
+ return null;
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java
new file mode 100644
index 00000000..7789120f
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java
@@ -0,0 +1,89 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.ParentDirExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+
+import de.schlichtherle.truezip.file.TFile;
+
+
+/**
+ * A class for returns the parent directory.
+ */
+public class ParentDirCommand extends Program implements ParentDirExecutable {
+
+ private static final String TAG = "ParentDirCommand"; //$NON-NLS-1$
+
+ private final String mSrc;
+ private String mParentDir;
+
+ /**
+ * Constructor of <code>ParentDirCommand</code>.
+ *
+ * @param console The current console
+ * @param src The source file
+ */
+ public ParentDirCommand(SecureConsole console, String src) {
+ super(console);
+ this.mSrc = src;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getResult() {
+ return this.mParentDir;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Getting parent directory of %s", //$NON-NLS-1$
+ this.mSrc));
+ }
+
+ // Build the source file information
+ TFile f = getConsole().buildRealFile(mSrc).getParentFile();
+ boolean isSecureStorage = SecureConsole.isSecureStorageDir(f);
+ if (isSecureStorage) {
+ this.mParentDir = getConsole().getVirtualMountPoint().getAbsolutePath();
+ } else {
+ this.mParentDir = getConsole().buildVirtualPath(f);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Parent directory: %S", //$NON-NLS-1$
+ this.mParentDir));
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/Program.java b/src/com/cyanogenmod/filemanager/commands/secure/Program.java
new file mode 100644
index 00000000..d25cf18a
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/Program.java
@@ -0,0 +1,125 @@
+/*
+ * 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.commands.secure;
+
+import com.cyanogenmod.filemanager.commands.Executable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+
+
+/**
+ * An abstract base class for all secure executables.
+ */
+public abstract class Program implements Executable {
+
+ private SecureConsole mConsole;
+ private boolean mTrace;
+ private int mBufferSize;
+
+ /**
+ * Constructor of <code>Program</code>
+ */
+ public Program(SecureConsole console) {
+ super();
+ mConsole = console;
+ }
+
+ /**
+ * Method that return if the command has to trace his operations
+ *
+ * @return boolean If the command has to trace
+ */
+ public boolean isTrace() {
+ return this.mTrace;
+ }
+
+ /**
+ * Method that sets if the command has to trace his operations
+ *
+ * @param trace If the command has to trace
+ */
+ public void setTrace(boolean trace) {
+ this.mTrace = trace;
+ }
+
+ /**
+ * Method that return the buffer size of the program
+ *
+ * @return int The buffer size of the program
+ */
+ public int getBufferSize() {
+ return this.mBufferSize;
+ }
+
+ /**
+ * Method that sets the buffer size of the program
+ *
+ * @param bufferSize The buffer size of the program
+ */
+ public void setBufferSize(int bufferSize) {
+ this.mBufferSize = bufferSize;
+ }
+
+ /**
+ * Method that returns the current console of the program
+ *
+ * @return SecureConsole The current console
+ */
+ public SecureConsole getConsole() {
+ return mConsole;
+ }
+
+ /**
+ * Method that returns if this program uses an asynchronous model. <code>false</code>
+ * by default.
+ *
+ * @return boolean If this program uses an asynchronous model
+ */
+ @SuppressWarnings("static-method")
+ public boolean isAsynchronous() {
+ return false;
+ }
+
+ /**
+ * Method that returns if the program requires a sync of the underlying storage
+ *
+ * @return boolean if the program requires a sync operation
+ */
+ public boolean requiresSync() {
+ return false;
+ }
+
+ /**
+ * Method that returns if the program requires that the file system is mounted
+ *
+ * @return boolean If the program requires that the file system is mounted
+ */
+ public boolean requiresOpen() {
+ return true;
+ }
+
+ /**
+ * Method that executes the program
+ *
+ * @throws NoSuchFileOrDirectory If the file or directory was not found
+ * @throws ExecutionException If the operation returns a invalid exit code
+ */
+ public abstract void execute()
+ throws NoSuchFileOrDirectory, ExecutionException;
+
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java
new file mode 100644
index 00000000..851d10e5
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java
@@ -0,0 +1,235 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ReadExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+
+import de.schlichtherle.truezip.file.TFile;
+import de.schlichtherle.truezip.file.TFileInputStream;
+
+import java.io.BufferedInputStream;
+
+/**
+ * A class for read a file.
+ */
+public class ReadCommand extends Program implements ReadExecutable {
+
+ private static final String TAG = "ReadCommand"; //$NON-NLS-1$
+
+ private final String mFile;
+ private final AsyncResultListener mAsyncResultListener;
+
+ private boolean mCancelled;
+ private boolean mEnded;
+ private final Object mSync = new Object();
+
+ /**
+ * Constructor of <code>ExecCommand</code>.
+ *
+ * @param console The current console
+ * @param file The file to read
+ * @param asyncResultListener The partial result listener
+ */
+ public ReadCommand(SecureConsole console, String file,
+ AsyncResultListener asyncResultListener) {
+ super(console);
+ this.mFile = file;
+ this.mAsyncResultListener = asyncResultListener;
+ this.mCancelled = false;
+ this.mEnded = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAsynchronous() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Reading file %s", this.mFile)); //$NON-NLS-1$
+
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncStart();
+ }
+
+ TFile f = getConsole().buildRealFile(mFile);
+ if (!f.exists()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mFile));
+ }
+ }
+ if (!f.isFile()) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(
+ new ExecutionException("path exists but it's not a file")); //$NON-NLS-1$
+ }
+ }
+
+ // Read the file
+ read(f);
+
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncEnd(this.mCancelled);
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncExitCode(0);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Method that read the file
+ *
+ * @param file The file to read
+ */
+ private void read(TFile file) {
+ // Read the file
+ BufferedInputStream bis = null;
+ try {
+ bis = new BufferedInputStream(new TFileInputStream(file), getBufferSize());
+ int read = 0;
+ byte[] data = new byte[getBufferSize()];
+ while ((read = bis.read(data, 0, getBufferSize())) != -1) {
+ if (this.mAsyncResultListener != null) {
+ byte[] readData = new byte[read];
+ System.arraycopy(data, 0, readData, 0, read);
+ this.mAsyncResultListener.onPartialResult(readData);
+
+ // Check if the process was cancelled
+ try {
+ synchronized (this.mSync) {
+ if (this.mCancelled || this.mEnded) {
+ this.mSync.notify();
+ break;
+ }
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ }
+ }
+
+ } catch (Exception e) {
+ if (isTrace()) {
+ Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onException(new ExecutionException(
+ "failed to read file", e));
+ }
+
+ } finally {
+ try {
+ if (bis != null) {
+ bis.close();
+ }
+ } catch (Throwable _throw) {/**NON BLOCK**/}
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancelled() {
+ synchronized (this.mSync) {
+ return this.mCancelled;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean cancel() {
+ try {
+ synchronized (this.mSync) {
+ this.mCancelled = true;
+ this.mSync.wait(5000L);
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean end() {
+ try {
+ synchronized (this.mSync) {
+ this.mEnded = true;
+ this.mSync.wait(5000L);
+ }
+ } catch (Exception e) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnEndListener(OnEndListener onEndListener) {
+ //Ignore. Java console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnCancelListener(OnCancelListener onCancelListener) {
+ //Ignore. Java console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancellable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AsyncResultListener getAsyncResultListener() {
+ return this.mAsyncResultListener;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java
new file mode 100644
index 00000000..530f26a2
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java
@@ -0,0 +1,401 @@
+/*
+ * 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.commands.secure;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable;
+import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
+import com.cyanogenmod.filemanager.commands.CompressExecutable;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
+import com.cyanogenmod.filemanager.commands.CopyExecutable;
+import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
+import com.cyanogenmod.filemanager.commands.CreateFileExecutable;
+import com.cyanogenmod.filemanager.commands.DeleteDirExecutable;
+import com.cyanogenmod.filemanager.commands.DeleteFileExecutable;
+import com.cyanogenmod.filemanager.commands.DiskUsageExecutable;
+import com.cyanogenmod.filemanager.commands.EchoExecutable;
+import com.cyanogenmod.filemanager.commands.ExecExecutable;
+import com.cyanogenmod.filemanager.commands.ExecutableCreator;
+import com.cyanogenmod.filemanager.commands.FindExecutable;
+import com.cyanogenmod.filemanager.commands.FolderUsageExecutable;
+import com.cyanogenmod.filemanager.commands.GroupsExecutable;
+import com.cyanogenmod.filemanager.commands.IdentityExecutable;
+import com.cyanogenmod.filemanager.commands.LinkExecutable;
+import com.cyanogenmod.filemanager.commands.ListExecutable;
+import com.cyanogenmod.filemanager.commands.MountExecutable;
+import com.cyanogenmod.filemanager.commands.MountPointInfoExecutable;
+import com.cyanogenmod.filemanager.commands.MoveExecutable;
+import com.cyanogenmod.filemanager.commands.ParentDirExecutable;
+import com.cyanogenmod.filemanager.commands.ProcessIdExecutable;
+import com.cyanogenmod.filemanager.commands.QuickFolderSearchExecutable;
+import com.cyanogenmod.filemanager.commands.ReadExecutable;
+import com.cyanogenmod.filemanager.commands.ResolveLinkExecutable;
+import com.cyanogenmod.filemanager.commands.SIGNAL;
+import com.cyanogenmod.filemanager.commands.SendSignalExecutable;
+import com.cyanogenmod.filemanager.commands.UncompressExecutable;
+import com.cyanogenmod.filemanager.commands.WriteExecutable;
+import com.cyanogenmod.filemanager.commands.ListExecutable.LIST_MODE;
+import com.cyanogenmod.filemanager.console.CommandNotFoundException;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.Group;
+import com.cyanogenmod.filemanager.model.MountPoint;
+import com.cyanogenmod.filemanager.model.Permissions;
+import com.cyanogenmod.filemanager.model.Query;
+import com.cyanogenmod.filemanager.model.User;
+import com.cyanogenmod.filemanager.preferences.CompressionMode;
+
+/**
+ * A class for create shell {@link "Executable"} objects.
+ */
+public class SecureExecutableCreator implements ExecutableCreator {
+
+ private final SecureConsole mConsole;
+
+ /**
+ * Constructor of <code>SecureExecutableCreator</code>.
+ *
+ * @param console A shell console that use for create objects
+ */
+ SecureExecutableCreator(SecureConsole console) {
+ super();
+ this.mConsole = console;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ChangeOwnerExecutable createChangeOwnerExecutable(
+ String fso, User newUser, Group newGroup) throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ChangePermissionsExecutable createChangePermissionsExecutable(
+ String fso, Permissions newPermissions) throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CopyExecutable createCopyExecutable(String src, String dst)
+ throws CommandNotFoundException {
+ return new CopyCommand(mConsole, src, dst);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CreateDirExecutable createCreateDirectoryExecutable(String dir)
+ throws CommandNotFoundException {
+ return new CreateDirCommand(mConsole, dir);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CreateFileExecutable createCreateFileExecutable(String file)
+ throws CommandNotFoundException {
+ return new CreateFileCommand(mConsole, file);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DeleteDirExecutable createDeleteDirExecutable(String dir)
+ throws CommandNotFoundException {
+ return new DeleteDirCommand(mConsole, dir);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DeleteFileExecutable createDeleteFileExecutable(String file)
+ throws CommandNotFoundException {
+ return new DeleteFileCommand(mConsole, file);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DiskUsageExecutable createDiskUsageExecutable() throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DiskUsageExecutable createDiskUsageExecutable(String dir)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public EchoExecutable createEchoExecutable(String msg) throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ExecExecutable createExecExecutable(
+ String cmd, AsyncResultListener asyncResultListener) throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FindExecutable createFindExecutable(
+ String directory, Query query, ConcurrentAsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ return new FindCommand(mConsole, directory, query, asyncResultListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FolderUsageExecutable createFolderUsageExecutable(
+ String directory, AsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ return new FolderUsageCommand(mConsole, directory, asyncResultListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public GroupsExecutable createGroupsExecutable() throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IdentityExecutable createIdentityExecutable() throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public LinkExecutable createLinkExecutable(String src, String link)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ListExecutable createListExecutable(String src)
+ throws CommandNotFoundException {
+ return new ListCommand(mConsole, src, LIST_MODE.DIRECTORY);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ListExecutable createFileInfoExecutable(String src, boolean followSymlinks)
+ throws CommandNotFoundException {
+ return new ListCommand(mConsole, src, LIST_MODE.FILEINFO);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountExecutable createMountExecutable(MountPoint mp, boolean rw)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MountPointInfoExecutable createMountPointInfoExecutable()
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MoveExecutable createMoveExecutable(String src, String dst)
+ throws CommandNotFoundException {
+ return new MoveCommand(mConsole, src, dst);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ParentDirExecutable createParentDirExecutable(String fso)
+ throws CommandNotFoundException {
+ return new ParentDirCommand(mConsole, fso);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ProcessIdExecutable createShellProcessIdExecutable() throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ProcessIdExecutable createProcessIdExecutable(int pid)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ProcessIdExecutable createProcessIdExecutable(int pid, String processName)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public QuickFolderSearchExecutable createQuickFolderSearchExecutable(String regexp)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ReadExecutable createReadExecutable(
+ String file, AsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ return new ReadCommand(mConsole, file, asyncResultListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ResolveLinkExecutable createResolveLinkExecutable(String fso)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SendSignalExecutable createSendSignalExecutable(int process, SIGNAL signal)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SendSignalExecutable createKillExecutable(int process)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WriteExecutable createWriteExecutable(
+ String file, AsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ return new WriteCommand(mConsole, file, asyncResultListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CompressExecutable createCompressExecutable(
+ CompressionMode mode, String dst, String[] src,
+ AsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CompressExecutable createCompressExecutable(
+ CompressionMode mode, String src,
+ AsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public UncompressExecutable createUncompressExecutable(
+ String src, String dst,
+ AsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ChecksumExecutable createChecksumExecutable(
+ String src, AsyncResultListener asyncResultListener)
+ throws CommandNotFoundException {
+ return new ChecksumCommand(mConsole, src, asyncResultListener);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java
new file mode 100644
index 00000000..3eaa44e9
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.commands.secure;
+
+import com.cyanogenmod.filemanager.commands.ExecutableCreator;
+import com.cyanogenmod.filemanager.commands.ExecutableFactory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+/**
+ * A class that represents a factory for creating java {@link "Executable"} objects.
+ */
+public class SecureExecutableFactory extends ExecutableFactory {
+
+ private final SecureConsole mConsole;
+
+ /**
+ * Constructor of <code>SecureExecutableFactory</code>.
+ *
+ * @param console A secure console that use for create objects
+ */
+ public SecureExecutableFactory(SecureConsole console) {
+ super();
+ this.mConsole = console;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ExecutableCreator newCreator() {
+ return new SecureExecutableCreator(this.mConsole);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java
new file mode 100644
index 00000000..7d3504b7
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java
@@ -0,0 +1,228 @@
+/*
+ * 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.commands.secure;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.WriteExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+
+import de.schlichtherle.truezip.file.TFile;
+import de.schlichtherle.truezip.file.TFileOutputStream;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A class for write data to disk.<br/>
+ * <br/>
+ * User MUST call the {@link #createOutputStream()} to get the output stream where
+ * write the data.<br/>. When no more exist then user MUST call the onEnd method
+ * of the asynchronous listener.<br/>
+ */
+public class WriteCommand extends Program implements WriteExecutable {
+
+ private static final String TAG = "WriteCommand"; //$NON-NLS-1$
+
+ private final String mFile;
+ private BufferedOutputStream mBuffer;
+ private final AsyncResultListener mAsyncResultListener;
+
+ private boolean mCancelled;
+ private final Object mSync = new Object();
+
+ private static final long TIMEOUT = 1000L;
+
+ private final Object mWriteSync = new Object();
+ private boolean mReady;
+
+ /**
+ * Constructor of <code>WriteCommand</code>.
+ *
+ * @param console The current console
+ * @param file The file where to write the data
+ * @param asyncResultListener The partial result listener
+ */
+ public WriteCommand(SecureConsole console, String file,
+ AsyncResultListener asyncResultListener) {
+ super(console);
+ this.mFile = file;
+ this.mAsyncResultListener = asyncResultListener;
+ this.mCancelled = false;
+ this.mReady = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAsynchronous() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public OutputStream createOutputStream() throws IOException {
+ try {
+ // Wait until command is ready
+ synchronized (this.mWriteSync) {
+ if (!this.mReady) {
+ try {
+ this.mWriteSync.wait(TIMEOUT);
+ } catch (Exception e) {/**NON BLOCK**/}
+ }
+ }
+ TFile f = getConsole().buildRealFile(mFile);
+ this.mBuffer = new BufferedOutputStream(new TFileOutputStream(f), getBufferSize());
+ return this.mBuffer;
+ } catch (IOException ioEx) {
+ if (isTrace()) {
+ Log.e(TAG, "Result: FAILED. IOException", ioEx); //$NON-NLS-1$
+ }
+ throw ioEx;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute() throws NoSuchFileOrDirectory, ExecutionException {
+ synchronized (this.mSync) {
+ this.mReady = true;
+ this.mSync.notify();
+ }
+
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Writing file %s", this.mFile)); //$NON-NLS-1$
+
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncStart();
+ }
+
+ // Wait the finalization
+ try {
+ synchronized (this.mSync) {
+ this.mSync.wait();
+ }
+ } catch (Throwable _throw) {/**NON BLOCK**/}
+
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncEnd(this.mCancelled);
+ }
+ if (this.mAsyncResultListener != null) {
+ this.mAsyncResultListener.onAsyncExitCode(0);
+ }
+
+ if (isTrace()) {
+ Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancelled() {
+ synchronized (this.mSync) {
+ return this.mCancelled;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean cancel() {
+ closeBuffer();
+ this.mCancelled = true;
+ try {
+ synchronized (this.mSync) {
+ this.mSync.notify();
+ }
+ } catch (Throwable _throw) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean end() {
+ closeBuffer();
+ try {
+ synchronized (this.mSync) {
+ this.mSync.notify();
+ }
+ } catch (Throwable _throw) {/**NON BLOCK**/}
+ return true;
+ }
+
+ /**
+ * Method that close the buffer
+ */
+ private void closeBuffer() {
+ try {
+ if (this.mBuffer != null) {
+ this.mBuffer.close();
+ }
+ } catch (Exception ex) {/**NON BLOCK**/}
+ try {
+ Thread.yield();
+ } catch (Exception ex) {/**NON BLOCK**/}
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnEndListener(OnEndListener onEndListener) {
+ //Ignore. Java console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnCancelListener(OnCancelListener onCancelListener) {
+ //Ignore. Java console don't use this
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isCancellable() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AsyncResultListener getAsyncResultListener() {
+ return this.mAsyncResultListener;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java b/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java
index 908b6b13..7c0a6f78 100644
--- a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java
+++ b/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java
@@ -18,6 +18,7 @@ package com.cyanogenmod.filemanager.commands.shell;
import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
import com.cyanogenmod.filemanager.commands.SIGNAL;
import com.cyanogenmod.filemanager.util.FileHelper;
@@ -93,6 +94,9 @@ public abstract class AsyncResultProgram
throws InvalidCommandDefinitionException {
super(id, prepare, args);
this.mAsyncResultListener = asyncResultListener;
+ if (mAsyncResultListener instanceof ConcurrentAsyncResultListener) {
+ ((ConcurrentAsyncResultListener) mAsyncResultListener).onRegister();
+ }
this.mPartialData = Collections.synchronizedList(new ArrayList<String>());
this.mPartialDataType = Collections.synchronizedList(new ArrayList<Byte>());
this.mTempBuffer = new StringBuffer();
diff --git a/src/com/cyanogenmod/filemanager/commands/shell/Program.java b/src/com/cyanogenmod/filemanager/commands/shell/Program.java
index b6a736df..4cb8105b 100644
--- a/src/com/cyanogenmod/filemanager/commands/shell/Program.java
+++ b/src/com/cyanogenmod/filemanager/commands/shell/Program.java
@@ -177,7 +177,6 @@ public abstract class Program extends Command implements Executable {
* @throws ExecutionException If the another exception is detected in the standard error
* @hide
*/
- @SuppressWarnings("unused")
public void checkStdErr(int exitCode, String err)
throws InsufficientPermissionsException, NoSuchFileOrDirectory,
CommandNotFoundException, ExecutionException {
diff --git a/src/com/cyanogenmod/filemanager/commands/shell/Shell.java b/src/com/cyanogenmod/filemanager/commands/shell/Shell.java
index 43e43368..1e4f27ec 100644
--- a/src/com/cyanogenmod/filemanager/commands/shell/Shell.java
+++ b/src/com/cyanogenmod/filemanager/commands/shell/Shell.java
@@ -109,7 +109,7 @@ public abstract class Shell extends Command {
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
* @hide
*/
- @SuppressWarnings({ "static-method", "unused" })
+ @SuppressWarnings("static-method")
public void checkStdErr(Program program, int exitCode, String err)
throws InsufficientPermissionsException, NoSuchFileOrDirectory,
CommandNotFoundException, ExecutionException, ReadOnlyFilesystemException {
diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java
index ca304f5f..57fafd16 100644
--- a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java
+++ b/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java
@@ -21,6 +21,7 @@ import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable;
import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable;
import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
import com.cyanogenmod.filemanager.commands.CompressExecutable;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
import com.cyanogenmod.filemanager.commands.CopyExecutable;
import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
import com.cyanogenmod.filemanager.commands.CreateFileExecutable;
@@ -222,7 +223,7 @@ public class ShellExecutableCreator implements ExecutableCreator {
*/
@Override
public FindExecutable createFindExecutable(
- String directory, Query query, AsyncResultListener asyncResultListener)
+ String directory, Query query, ConcurrentAsyncResultListener asyncResultListener)
throws CommandNotFoundException {
try {
return new FindCommand(directory, query, asyncResultListener);
diff --git a/src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java b/src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java
new file mode 100644
index 00000000..795111e9
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.console;
+
+import java.io.IOException;
+
+/**
+ * An exception that indicates that the operation failed because an authentication failure
+ */
+public class AuthenticationFailedException extends IOException {
+ private static final long serialVersionUID = -2199496556437722726L;
+
+ /**
+ * Constructor of <code>AuthenticationFailedException</code>.
+ *
+ * @param msg The associated message
+ */
+ public AuthenticationFailedException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/CancelledOperationException.java b/src/com/cyanogenmod/filemanager/console/CancelledOperationException.java
new file mode 100644
index 00000000..e19d0dc3
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/CancelledOperationException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.console;
+
+import java.io.IOException;
+
+/**
+ * An exception that indicates that the operation was cancelled
+ */
+public class CancelledOperationException extends IOException {
+ private static final long serialVersionUID = 2999554355110192173L;
+
+ /**
+ * Constructor of <code>CancelledOperationException</code>.
+ */
+ public CancelledOperationException() {
+ super();
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/Console.java b/src/com/cyanogenmod/filemanager/console/Console.java
index ba28db55..6404431f 100644
--- a/src/com/cyanogenmod/filemanager/console/Console.java
+++ b/src/com/cyanogenmod/filemanager/console/Console.java
@@ -15,6 +15,8 @@
*/
package com.cyanogenmod.filemanager.console;
+import android.content.Context;
+
import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
import com.cyanogenmod.filemanager.commands.Executable;
import com.cyanogenmod.filemanager.commands.ExecutableFactory;
@@ -46,7 +48,7 @@ public abstract class Console
*
* @return boolean If the console has to trace
*/
- public boolean isTrace() {
+ public final boolean isTrace() {
return this.mTrace;
}
@@ -111,6 +113,7 @@ public abstract class Console
* Method for execute a command in the operating system layer.
*
* @param executable The executable command to be executed
+ * @param ctx The current context
* @throws ConsoleAllocException If the console is not allocated
* @throws InsufficientPermissionsException If an operation requires elevated permissions
* @throws NoSuchFileOrDirectory If the file or directory was not found
@@ -118,10 +121,14 @@ public abstract class Console
* @throws CommandNotFoundException If the executable program was not found
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
+ * @throws AuthenticationFailedException If the operation failed because an
+ * authentication failure
+ * @throws AuthenticationFailedException
*/
- public abstract void execute(final Executable executable)
+ public abstract void execute(final Executable executable, final Context ctx)
throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
OperationTimeoutException, ExecutionException, CommandNotFoundException,
- ReadOnlyFilesystemException;
+ ReadOnlyFilesystemException, CancelledOperationException, AuthenticationFailedException;
}
diff --git a/src/com/cyanogenmod/filemanager/console/VirtualConsole.java b/src/com/cyanogenmod/filemanager/console/VirtualConsole.java
new file mode 100644
index 00000000..8512bc77
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/VirtualConsole.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 20124 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.console;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.SIGNAL;
+import com.cyanogenmod.filemanager.console.Console;
+import com.cyanogenmod.filemanager.console.ConsoleAllocException;
+import com.cyanogenmod.filemanager.model.Identity;
+import com.cyanogenmod.filemanager.util.AIDHelper;
+
+/**
+ * An abstract base class for all the virtual {@link Console}.
+ */
+public abstract class VirtualConsole extends Console {
+
+ public static final String TAG = "VirtualConsole";
+
+ private boolean mActive;
+ private final Context mCtx;
+ private final Identity mIdentity;
+
+ /**
+ * Constructor of <code>VirtualConsole</code>
+ *
+ * @param ctx The current context
+ */
+ public VirtualConsole(Context ctx) {
+ super();
+ mCtx = ctx;
+ mIdentity = AIDHelper.createVirtualIdentity();
+ }
+
+ public abstract String getName();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void alloc() throws ConsoleAllocException {
+ try {
+ if (isTrace()) {
+ Log.v(TAG, "Allocating " + getName() + " console");
+ }
+ mActive = true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to allocate " + getName() + " console", e);
+ throw new ConsoleAllocException("failed to build console", e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dealloc() {
+ if (isTrace()) {
+ Log.v(TAG, "Deallocating Java console");
+ }
+ mActive = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void realloc() throws ConsoleAllocException {
+ dealloc();
+ alloc();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Identity getIdentity() {
+ return mIdentity;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isPrivileged() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * Method that returns the current context
+ *
+ * @return Context The current context
+ */
+ public Context getCtx() {
+ return mCtx;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onCancel() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onSendSignal(SIGNAL signal) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onEnd() {
+ return false;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java b/src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java
new file mode 100644
index 00000000..ba730606
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 20124 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.console;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.SystemClock;
+
+import com.cyanogenmod.filemanager.FileManagerApplication;
+import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
+import com.cyanogenmod.filemanager.model.Directory;
+import com.cyanogenmod.filemanager.model.DiskUsage;
+import com.cyanogenmod.filemanager.model.Identity;
+import com.cyanogenmod.filemanager.model.MountPoint;
+import com.cyanogenmod.filemanager.model.Permissions;
+import com.cyanogenmod.filemanager.preferences.AccessMode;
+import com.cyanogenmod.filemanager.util.AIDHelper;
+import com.cyanogenmod.filemanager.util.FileHelper;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * An abstract base class for of a {@link VirtualConsole} that has a virtual mount point
+ * in the filesystem.
+ */
+public abstract class VirtualMountPointConsole extends VirtualConsole {
+
+ private static final String DEFAULT_STORAGE_NAME = "storage";
+
+// private static File sVirtualStorageDir;
+
+ private static List<VirtualMountPointConsole> sVirtualConsoles;
+ private static Identity sVirtualIdentity;
+ private static Permissions sVirtualFolderPermissions;
+
+ public VirtualMountPointConsole(Context ctx) {
+ super(ctx);
+ }
+
+ /**
+ * Should return the name of the mount point name
+ *
+ * @return String The name of the mount point name of this console.
+ */
+ public abstract String getMountPointName();
+
+ /**
+ * Method that returns if the console is secure
+ *
+ * @return boolean If the console is secure
+ */
+ public abstract boolean isSecure();
+
+ /**
+ * Method that returns if the console is remote
+ *
+ * @return boolean If the console is remote
+ */
+ public abstract boolean isRemote();
+
+ /**
+ * Method that returns if the console is mounted
+ *
+ * @return boolean If the console is mounted
+ */
+ public abstract boolean isMounted();
+
+ /**
+ * Method that unmounts the filesystem
+ *
+ * @return boolean If the filesystem was unmounted
+ */
+ public abstract boolean unmount();
+
+ /**
+ * Returns the mountpoints for the console
+ *
+ * @return List<MountPoint> The list of mountpoints handled by the console
+ */
+ public abstract List<MountPoint> getMountPoints();
+
+ /**
+ * Returns the disk usage of every mountpoint for the console
+ *
+ * @return List<DiskUsage> The list of disk usage of the mountpoints handled by the console
+ */
+ public abstract List<DiskUsage> getDiskUsage();
+
+ /**
+ * Returns the disk usage of the path
+ *
+ * @param path The path to check
+ * @return DiskUsage The disk usage for the passed path
+ */
+ public abstract DiskUsage getDiskUsage(String path);
+
+ /**
+ * Method that register all the implemented virtual consoles. This method should
+ * be called only once on the application instantiation.
+ *
+ * @param context The current context
+ */
+ public static void registerVirtualConsoles(Context context) {
+ if (sVirtualConsoles != null) return;
+ sVirtualConsoles = new ArrayList<VirtualMountPointConsole>();
+ sVirtualIdentity = AIDHelper.createVirtualIdentity();
+ sVirtualFolderPermissions = Permissions.createDefaultFolderPermissions();
+
+ int bufferSize = context.getResources().getInteger(R.integer.buffer_size);
+
+ // Register every known virtual mountable console
+ sVirtualConsoles.add(SecureConsole.getInstance(context, bufferSize));
+ // TODO Add remote consoles. Not ready for now.
+ // sVirtualConsoles.add(new RemoteConsole(context));
+ }
+
+ /**
+ * Method that returns the virtual storage directory
+ * @return
+ */
+ private static File getVirtualStorageDir() {
+ final Context context = FileManagerApplication.getInstance().getApplicationContext();
+ File dir = new File(context.getString(R.string.virtual_storage_dir));
+ AccessMode mode = FileManagerApplication.getAccessMode();
+ if (mode.equals(AccessMode.SAFE) || !dir.isDirectory()) {
+ // Chroot environment (create a folder inside the external storage)
+ return getChrootedVirtualStorageDir();
+ }
+ return dir;
+ }
+
+ /**
+ * Method that returns the chrooted virtual storage directory
+ *
+ * @return File The Virtual storage directory
+ */
+ private static File getChrootedVirtualStorageDir() {
+ File root = new File(Environment.getExternalStorageDirectory(), DEFAULT_STORAGE_NAME);
+ root.mkdir();
+ return root;
+ }
+
+ /**
+ * Method that list all the virtual directories
+ *
+ * @return List<Directory> The list of virtual directories
+ */
+ public static List<Directory> getVirtualMountableDirectories() {
+ final Date date = new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime());
+ List<Directory> directories = new ArrayList<Directory>();
+ for (VirtualMountPointConsole console : sVirtualConsoles) {
+ File dir = null;
+ do {
+ dir = console.getVirtualMountPoint();
+ } while (dir.getParentFile() != null && !isVirtualStorageDir(dir.getParent()));
+
+ if (dir != null) {
+ Directory directory = new Directory(
+ dir.getName(),
+ getVirtualStorageDir().getAbsolutePath(),
+ sVirtualIdentity.getUser(),
+ sVirtualIdentity.getGroup(),
+ sVirtualFolderPermissions,
+ date, date, date);
+ directory.setSecure(console.isSecure());
+ directory.setRemote(console.isRemote());
+
+ if (!directories.contains(directory)) {
+ directories.add(directory);
+ }
+ }
+ }
+ return directories;
+ }
+
+ /**
+ * Method that returns the virtual mountpoints of every register console
+ * @return
+ */
+ public static List<MountPoint> getVirtualMountPoints() {
+ List<MountPoint> mountPoints = new ArrayList<MountPoint>();
+ for (VirtualMountPointConsole console : sVirtualConsoles) {
+ mountPoints.addAll(console.getMountPoints());
+ }
+ return mountPoints;
+ }
+
+ /**
+ * Method that returns the virtual disk usage of the mountpoints of every register console
+ * @return
+ */
+ public static List<DiskUsage> getVirtualDiskUsage() {
+ List<DiskUsage> diskUsage = new ArrayList<DiskUsage>();
+ for (VirtualMountPointConsole console : sVirtualConsoles) {
+ diskUsage.addAll(console.getDiskUsage());
+ }
+ return diskUsage;
+ }
+
+ /**
+ * Returns if the passed directory is the current virtual storage directory
+ *
+ * @param directory The directory to check
+ * @return boolean If is the current virtual storage directory
+ */
+ public static boolean isVirtualStorageDir(String directory) {
+ return getVirtualStorageDir().equals(new File(directory));
+ }
+
+ /**
+ * Returns if the passed resource belongs to a virtual filesystem
+ *
+ * @param path The path to check
+ * @return boolean If is the resource belongs to a virtual filesystem
+ */
+ public static boolean isVirtualStorageResource(String path) {
+ for (VirtualMountPointConsole console : sVirtualConsoles) {
+ if (FileHelper.belongsToDirectory(new File(path), console.getVirtualMountPoint())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Method that returns the virtual console for the path or null if the path
+ * is not a virtual filesystem
+ *
+ * @param path the path to check
+ * @return VirtualMountPointConsole The found console
+ */
+ public static VirtualMountPointConsole getVirtualConsoleForPath(String path) {
+ File file = new File(path);
+ for (VirtualMountPointConsole console : sVirtualConsoles) {
+ if (FileHelper.belongsToDirectory(file, console.getVirtualMountPoint())) {
+ return console;
+ }
+ }
+ return null;
+ }
+
+ public static List<Console> getVirtualConsoleForSearchPath(String path) {
+ List<Console> consoles = new ArrayList<Console>();
+ File dir = new File(path);
+ for (VirtualMountPointConsole console : sVirtualConsoles) {
+ if (FileHelper.belongsToDirectory(console.getVirtualMountPoint(), dir)) {
+ // Only mount consoles can participate in the search
+ if (console.isMounted()) {
+ consoles.add(console);
+ }
+ }
+ }
+ return consoles;
+ }
+
+ /**
+ * Returns if the passed directory is the virtual mountpoint directory of the virtual console
+ *
+ * @param directory The directory to check
+ * @return boolean If is the virtual mountpoint directory of the virtual console
+ */
+ public boolean isVirtualMountPointDir(String directory) {
+ return getVirtualMountPoint().equals(new File(directory));
+ }
+
+ /**
+ * Method that returns the virtual mount point for this console
+ *
+ * @return String The virtual mount point
+ */
+ public final File getVirtualMountPoint() {
+ return new File(getVirtualStorageDir(), getMountPointName());
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java
index daf30525..f59f7461 100644
--- a/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java
+++ b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java
@@ -17,43 +17,31 @@
package com.cyanogenmod.filemanager.console.java;
import android.content.Context;
-import android.os.Process;
import android.util.Log;
import com.cyanogenmod.filemanager.commands.Executable;
import com.cyanogenmod.filemanager.commands.ExecutableFactory;
-import com.cyanogenmod.filemanager.commands.SIGNAL;
import com.cyanogenmod.filemanager.commands.java.JavaExecutableFactory;
import com.cyanogenmod.filemanager.commands.java.Program;
import com.cyanogenmod.filemanager.console.CommandNotFoundException;
-import com.cyanogenmod.filemanager.console.Console;
import com.cyanogenmod.filemanager.console.ConsoleAllocException;
import com.cyanogenmod.filemanager.console.ExecutionException;
import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
import com.cyanogenmod.filemanager.console.OperationTimeoutException;
import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
-import com.cyanogenmod.filemanager.model.AID;
-import com.cyanogenmod.filemanager.model.Group;
-import com.cyanogenmod.filemanager.model.Identity;
-import com.cyanogenmod.filemanager.model.User;
-import com.cyanogenmod.filemanager.util.AIDHelper;
-
-import java.util.ArrayList;
+import com.cyanogenmod.filemanager.console.VirtualConsole;
/**
- * An implementation of a {@link Console} based on a java implementation.<br/>
+ * An implementation of a {@link VirtualConsole} based on a java implementation.<br/>
* <br/>
* This console is a non-privileged console an many of the functionality is not implemented
* because can't be obtain from java api.
*/
-public final class JavaConsole extends Console {
+public final class JavaConsole extends VirtualConsole {
private static final String TAG = "JavaConsole"; //$NON-NLS-1$
- private boolean mActive;
-
- private final Context mCtx;
private final int mBufferSize;
/**
@@ -63,45 +51,17 @@ public final class JavaConsole extends Console {
* @param bufferSize The buffer size
*/
public JavaConsole(Context ctx, int bufferSize) {
- super();
- this.mCtx = ctx;
+ super(ctx);
this.mBufferSize = bufferSize;
}
- /**
- * {@inheritDoc}
- */
- @Override
- public void alloc() throws ConsoleAllocException {
- try {
- if (isTrace()) {
- Log.v(TAG, "Allocating Java console"); //$NON-NLS-1$
- }
- this.mActive = true;
- } catch (Exception e) {
- Log.e(TAG, "Failed to allocate Java console", e); //$NON-NLS-1$
- throw new ConsoleAllocException("failed to build console", e); //$NON-NLS-1$
- }
- }
/**
* {@inheritDoc}
*/
@Override
- public void dealloc() {
- if (isTrace()) {
- Log.v(TAG, "Deallocating Java console"); //$NON-NLS-1$
- }
- this.mActive = true;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void realloc() throws ConsoleAllocException {
- dealloc();
- alloc();
+ public String getName() {
+ return "Java";
}
/**
@@ -116,48 +76,10 @@ public final class JavaConsole extends Console {
* {@inheritDoc}
*/
@Override
- public Identity getIdentity() {
- AID aid = AIDHelper.getAID(Process.myUid());
- if (aid == null) return null;
- return new Identity(
- new User(aid.getId(), aid.getName()),
- new Group(aid.getId(), aid.getName()),
- new ArrayList<Group>());
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean isPrivileged() {
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean isActive() {
- return this.mActive;
- }
-
- /**
- * Method that returns the current context
- *
- * @return Context The current context
- */
- public Context getCtx() {
- return this.mCtx;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public synchronized void execute(Executable executable) throws ConsoleAllocException,
- InsufficientPermissionsException, NoSuchFileOrDirectory,
- OperationTimeoutException, ExecutionException,
- CommandNotFoundException, ReadOnlyFilesystemException {
+ public synchronized void execute(Executable executable, Context ctx)
+ throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException, CommandNotFoundException,
+ ReadOnlyFilesystemException {
// Check that the program is a java program
try {
Program p = (Program)executable;
@@ -201,28 +123,4 @@ public final class JavaConsole extends Console {
}
}
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onCancel() {
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onSendSignal(SIGNAL signal) {
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean onEnd() {
- return false;
- }
-
} \ No newline at end of file
diff --git a/src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java b/src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java
new file mode 100644
index 00000000..a57b5d51
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 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.console.remote;
+
+import android.content.Context;
+
+import com.cyanogenmod.filemanager.commands.Executable;
+import com.cyanogenmod.filemanager.commands.ExecutableFactory;
+import com.cyanogenmod.filemanager.console.CommandNotFoundException;
+import com.cyanogenmod.filemanager.console.ConsoleAllocException;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.OperationTimeoutException;
+import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
+import com.cyanogenmod.filemanager.model.DiskUsage;
+import com.cyanogenmod.filemanager.model.MountPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An implementation of a {@link VirtualMountPointConsole} for remote filesystems
+ */
+public class RemoteConsole extends VirtualMountPointConsole {
+
+ /**
+ * Constructor of <code>RemoteConsole</code>
+ *
+ * @param ctx The current context
+ */
+ public RemoteConsole(Context ctx) {
+ super(ctx);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getName() {
+ return "Remote";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRemote() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMounted() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean unmount() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<MountPoint> getMountPoints() {
+ List<MountPoint> mountPoints = new ArrayList<MountPoint>();
+ return mountPoints;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<DiskUsage> getDiskUsage() {
+ List<DiskUsage> diskUsage = new ArrayList<DiskUsage>();
+ return diskUsage;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DiskUsage getDiskUsage(String path) {
+ // TODO Fix when remote console will be implemented
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getMountPointName() {
+ return "remote";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ExecutableFactory getExecutableFactory() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void execute(Executable executable, Context ctx)
+ throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException, CommandNotFoundException,
+ ReadOnlyFilesystemException {
+
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java b/src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java
new file mode 100644
index 00000000..be019a66
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2014 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.console.secure;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.Handler.Callback;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.cyanogenmod.filemanager.FileManagerApplication;
+import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.commands.Executable;
+import com.cyanogenmod.filemanager.commands.ExecutableFactory;
+import com.cyanogenmod.filemanager.commands.MountExecutable;
+import com.cyanogenmod.filemanager.commands.secure.Program;
+import com.cyanogenmod.filemanager.commands.secure.SecureExecutableFactory;
+import com.cyanogenmod.filemanager.console.AuthenticationFailedException;
+import com.cyanogenmod.filemanager.console.CancelledOperationException;
+import com.cyanogenmod.filemanager.console.CommandNotFoundException;
+import com.cyanogenmod.filemanager.console.Console;
+import com.cyanogenmod.filemanager.console.ConsoleAllocException;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.OperationTimeoutException;
+import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
+import com.cyanogenmod.filemanager.model.DiskUsage;
+import com.cyanogenmod.filemanager.model.MountPoint;
+import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
+import com.cyanogenmod.filemanager.preferences.Preferences;
+import com.cyanogenmod.filemanager.util.DialogHelper;
+import com.cyanogenmod.filemanager.util.ExceptionUtil;
+import com.cyanogenmod.filemanager.util.FileHelper;
+
+import org.apache.http.auth.AuthenticationException;
+
+import de.schlichtherle.truezip.crypto.raes.RaesAuthenticationException;
+import de.schlichtherle.truezip.file.TArchiveDetector;
+import de.schlichtherle.truezip.file.TFile;
+import de.schlichtherle.truezip.file.TVFS;
+import de.schlichtherle.truezip.key.CancelledOperation;
+import static de.schlichtherle.truezip.fs.FsSyncOption.CLEAR_CACHE;
+import static de.schlichtherle.truezip.fs.FsSyncOption.FORCE_CLOSE_INPUT;
+import static de.schlichtherle.truezip.fs.FsSyncOption.FORCE_CLOSE_OUTPUT;
+import de.schlichtherle.truezip.util.BitField;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * A secure implementation of a {@link VirtualMountPointConsole} that uses a
+ * secure filesystem backend
+ */
+public class SecureConsole extends VirtualMountPointConsole {
+
+ public static final String TAG = "SecureConsole";
+
+ /** The singleton TArchiveDetector which enclosure this driver **/
+ public static final TArchiveDetector DETECTOR = new TArchiveDetector(
+ SecureStorageDriverProvider.SINGLETON, SecureStorageDriverProvider.SINGLETON.get());
+
+ public static String getSecureStorageName() {
+ return String.format("storage.%s.%s",
+ String.valueOf(UserHandle.myUserId()),
+ SecureStorageDriverProvider.SECURE_STORAGE_SCHEME);
+ }
+
+ public static TFile getSecureStorageRoot() {
+ return new TFile(FileManagerApplication.getInstance().getExternalFilesDir(null),
+ getSecureStorageName(), DETECTOR);
+ }
+
+ public static URI getSecureStorageRootUri() {
+ return new File(FileManagerApplication.getInstance().getExternalFilesDir(null),
+ getSecureStorageName()).toURI();
+ }
+
+ private static SecureConsole sConsole = null;
+
+ public final Handler mSyncHandler;
+
+ private boolean mIsMounted;
+ private boolean mRequiresSync;
+
+ private final int mBufferSize;
+
+ private static final long SYNC_WAIT = 10000L;
+
+ private static final int MSG_SYNC_FS = 0;
+
+ private final ExecutorService mExecutorService = Executors.newFixedThreadPool(1);
+
+ private final Callback mSyncCallback = new Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SYNC_FS:
+ mExecutorService.execute(new Runnable() {
+ @Override
+ public void run() {
+ sync();
+ }
+ });
+ break;
+
+ default:
+ break;
+ }
+ return true;
+ }
+ };
+
+ /**
+ * Return an instance of the current console
+ * @return
+ */
+ public static synchronized SecureConsole getInstance(Context ctx, int bufferSize) {
+ if (sConsole == null) {
+ sConsole = new SecureConsole(ctx, bufferSize);
+ }
+ return sConsole;
+ }
+
+ private final TFile mStorageRoot;
+ private final String mStorageName;
+
+ /**
+ * Constructor of <code>SecureConsole</code>
+ *
+ * @param ctx The current context
+ */
+ private SecureConsole(Context ctx, int bufferSize) {
+ super(ctx);
+ mIsMounted = false;
+ mBufferSize = bufferSize;
+ mSyncHandler = new Handler(mSyncCallback);
+ mStorageRoot = getSecureStorageRoot();
+ mStorageName = getSecureStorageName();
+
+ // Save a copy of the console. This has a unique instance for all the app
+ if (sConsole != null) {
+ sConsole = this;
+ }
+ }
+
+ @Override
+ public void dealloc() {
+ super.dealloc();
+
+ // Synchronize the underlaying storage
+ mSyncHandler.removeMessages(MSG_SYNC_FS);
+ sync();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getName() {
+ return "Secure";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isSecure() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMounted() {
+ return mIsMounted;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<MountPoint> getMountPoints() {
+ // This console only has one mountpoint
+ List<MountPoint> mountPoints = new ArrayList<MountPoint>();
+ String status = mIsMounted ? MountExecutable.READWRITE : MountExecutable.READONLY;
+ mountPoints.add(new MountPoint(getVirtualMountPoint().getAbsolutePath(),
+ "securestorage", "securestoragefs", status, 0, 0, true, false));
+ return mountPoints;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public List<DiskUsage> getDiskUsage() {
+ // This console only has one mountpoint, and is fully usage
+ List<DiskUsage> diskUsage = new ArrayList<DiskUsage>();
+ File mp = mStorageRoot.getFile();
+ diskUsage.add(new DiskUsage(mp.getAbsolutePath(),
+ mp.getTotalSpace(),
+ mp.length(),
+ mp.getTotalSpace() - mp.length()));
+ return diskUsage;
+ }
+
+ /**
+ * Method that returns if the path belongs to the secure storage
+ *
+ * @param path The path to check
+ * @return
+ */
+ public boolean isSecureStorageResource(String path) {
+ return FileHelper.belongsToDirectory(new File(path), getVirtualMountPoint());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DiskUsage getDiskUsage(String path) {
+ if (isSecureStorageResource(path)) {
+ return getDiskUsage().get(0);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getMountPointName() {
+ return "secure";
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRemote() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ExecutableFactory getExecutableFactory() {
+ return new SecureExecutableFactory(this);
+ }
+
+ /**
+ * Method that request a reset of the current password
+ */
+ public void requestReset(final Context ctx) {
+ AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ boolean result = false;
+
+ // Unmount the filesystem
+ if (mIsMounted) {
+ unmount();
+ }
+ try {
+ SecureStorageKeyManagerProvider.SINGLETON.reset();
+
+ // Mount with the new key
+ mount(ctx);
+
+ // In order to claim a write, we need to be sure that an operation is
+ // done to disk before unmount the device.
+ try {
+ String testName = UUID.randomUUID().toString();
+ TFile test = new TFile(getSecureStorageRoot(), testName);
+ test.createNewFile();
+ test.rm();
+ result = true;
+ } catch (IOException ex) {
+ ExceptionUtil.translateException(ctx, ex);
+ }
+
+ } catch (Exception ex) {
+ ExceptionUtil.translateException(ctx, ex);
+ } finally {
+ unmount();
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (result) {
+ // Success
+ DialogHelper.showToast(ctx, R.string.msgs_success, Toast.LENGTH_SHORT);
+ }
+ }
+
+ };
+ task.execute();
+ }
+
+ /**
+ * Method that request a delete of the current password
+ */
+ @SuppressWarnings("deprecation")
+ public void requestDelete(final Context ctx) {
+ AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ boolean result = false;
+
+ // Unmount the filesystem
+ if (mIsMounted) {
+ unmount();
+ }
+ try {
+ SecureStorageKeyManagerProvider.SINGLETON.delete();
+
+ // Test mount/unmount
+ mount(ctx);
+ unmount();
+
+ // Password is valid. Delete the storage
+ mStorageRoot.getFile().delete();
+
+ // Send an broadcast to notify that the mount state of this filesystem changed
+ Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED);
+ intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT,
+ getVirtualMountPoint().toString());
+ intent.putExtra(FileManagerSettings.EXTRA_STATUS, MountExecutable.READONLY);
+ getCtx().sendBroadcast(intent);
+
+ result = true;
+
+ } catch (Exception ex) {
+ ExceptionUtil.translateException(ctx, ex);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (result) {
+ // Success
+ DialogHelper.showToast(ctx, R.string.msgs_success, Toast.LENGTH_SHORT);
+ }
+ }
+
+ };
+ task.execute();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean unmount() {
+ // Unmount the filesystem and cancel the cached key
+ mRequiresSync = true;
+ boolean ret = sync();
+ if (ret) {
+ SecureStorageKeyManagerProvider.SINGLETON.unmount();
+ }
+ mIsMounted = false;
+
+ // Send an broadcast to notify that the mount state of this filesystem changed
+ Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED);
+ intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT,
+ getVirtualMountPoint().toString());
+ intent.putExtra(FileManagerSettings.EXTRA_STATUS, MountExecutable.READONLY);
+ getCtx().sendBroadcast(intent);
+
+ return mIsMounted;
+ }
+
+ /**
+ * Method that verifies if the current storage is open and mount it
+ *
+ * @param ctx The current context
+ * @throws CancelledOperationException If the operation was cancelled (by the user)
+ * @throws AuthenticationException If the secure storage isn't unlocked
+ * @throws NoSuchFileOrDirectory If the secure storage isn't accessible
+ */
+ @SuppressWarnings("deprecation")
+ public synchronized void mount(Context ctx)
+ throws CancelledOperationException, AuthenticationFailedException,
+ NoSuchFileOrDirectory {
+ if (!mIsMounted) {
+ File root = mStorageRoot.getFile();
+ try {
+ boolean newStorage = !root.exists();
+ mStorageRoot.mount();
+ if (newStorage) {
+ // Force a synchronization
+ mRequiresSync = true;
+ sync();
+ } else {
+ // Remove any previous cache files (if not sync invoked)
+ clearCache(ctx);
+ }
+
+ // The device is mounted
+ mIsMounted = true;
+
+ // Send an broadcast to notify that the mount state of this filesystem changed
+ Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED);
+ intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT,
+ getVirtualMountPoint().toString());
+ intent.putExtra(FileManagerSettings.EXTRA_STATUS, MountExecutable.READWRITE);
+ getCtx().sendBroadcast(intent);
+
+ } catch (IOException ex) {
+ if (ex.getCause() != null && ex.getCause() instanceof CancelledOperation) {
+ throw new CancelledOperationException();
+ }
+ if (ex.getCause() != null && ex.getCause() instanceof RaesAuthenticationException) {
+ throw new AuthenticationFailedException(ctx.getString(
+ R.string.secure_storage_unlock_failed));
+ }
+ Log.e(TAG, String.format("Failed to open secure storage: %s", root, ex));
+ throw new NoSuchFileOrDirectory();
+ }
+ }
+ }
+
+ /**
+ * Method that returns if the path is the real secure storage file
+ *
+ * @param path The path to check
+ * @return boolean If the path is the secure storage
+ */
+ public static boolean isSecureStorageDir(String path) {
+ Console vc = getVirtualConsoleForPath(path);
+ if (vc != null && vc instanceof SecureConsole) {
+ return isSecureStorageDir(((SecureConsole) vc).buildRealFile(path));
+ }
+ return false;
+ }
+
+ /**
+ * Method that returns if the path is the real secure storage file
+ *
+ * @param path The path to check
+ * @return boolean If the path is the secure storage
+ */
+ public static boolean isSecureStorageDir(TFile path) {
+ return getSecureStorageRoot().equals(path);
+ }
+
+ /**
+ * Method that build a real file from a virtual path
+ *
+ * @param path The path from build the real file
+ * @return TFile The real file
+ */
+ public TFile buildRealFile(String path) {
+ String real = mStorageRoot.toString();
+ String virtual = getVirtualMountPoint().toString();
+ String src = path.replace(virtual, real);
+ return new TFile(src, DETECTOR);
+ }
+
+ /**
+ * Method that build a virtual file from a real path
+ *
+ * @param path The path from build the virtual file
+ * @return TFile The virtual file
+ */
+ public String buildVirtualPath(TFile path) {
+ String real = mStorageRoot.toString();
+ String virtual = getVirtualMountPoint().toString();
+ String dst = path.toString().replace(real, virtual);
+ return dst;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void execute(Executable executable, Context ctx)
+ throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException, CommandNotFoundException,
+ ReadOnlyFilesystemException, CancelledOperationException,
+ AuthenticationFailedException {
+ // Check that the program is a secure program
+ try {
+ Program p = (Program) executable;
+ p.setBufferSize(mBufferSize);
+ } catch (Throwable e) {
+ Log.e(TAG, String.format("Failed to resolve program: %s", //$NON-NLS-1$
+ executable.getClass().toString()), e);
+ throw new CommandNotFoundException("executable is not a program", e); //$NON-NLS-1$
+ }
+
+ //Auditing program execution
+ if (isTrace()) {
+ Log.v(TAG, String.format("Executing program: %s", //$NON-NLS-1$
+ executable.getClass().toString()));
+ }
+
+
+ final Program program = (Program) executable;
+
+ // Open storage encryption (if required)
+ if (program.requiresOpen()) {
+ mount(ctx);
+ }
+
+ // Execute the program
+ program.setTrace(isTrace());
+ if (program.isAsynchronous()) {
+ // Execute in a thread
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ try {
+ program.execute();
+ requestSync(program);
+ } catch (Exception e) {
+ // Program must use onException to communicate exceptions
+ Log.v(TAG,
+ String.format("Async execute failed program: %s", //$NON-NLS-1$
+ program.getClass().toString()));
+ }
+ }
+ };
+ t.start();
+
+ } else {
+ // Synchronous execution
+ program.execute();
+ requestSync(program);
+ }
+ }
+
+ /**
+ * Request a synchronization of the underlying filesystem
+ *
+ * @param program The last called program
+ */
+ private void requestSync(Program program) {
+ if (program.requiresSync()) {
+ mRequiresSync = true;
+ }
+
+ // There is some changes to synchronize?
+ if (mRequiresSync) {
+ Boolean defaultValue = ((Boolean)FileManagerSettings.
+ SETTINGS_SECURE_STORAGE_DELAYED_SYNC.getDefaultValue());
+ Boolean delayedSync =
+ Boolean.valueOf(
+ Preferences.getSharedPreferences().getBoolean(
+ FileManagerSettings.SETTINGS_SECURE_STORAGE_DELAYED_SYNC.getId(),
+ defaultValue.booleanValue()));
+ mSyncHandler.removeMessages(MSG_SYNC_FS);
+ if (delayedSync) {
+ // Request a sync in 30 seconds, if users is not doing any operation
+ mSyncHandler.sendEmptyMessageDelayed(MSG_SYNC_FS, SYNC_WAIT);
+ } else {
+ // Do the synchronization now
+ mSyncHandler.sendEmptyMessage(MSG_SYNC_FS);
+ }
+ }
+ }
+
+ /**
+ * Synchronize the underlying filesystem
+ *
+ * @retun boolean If the unmount success
+ */
+ public synchronized boolean sync() {
+ if (mRequiresSync) {
+ Log.i(TAG, "Syncing underlaying storage");
+ mRequiresSync = false;
+ // Sync the underlying storage
+ try {
+ TVFS.sync(mStorageRoot,
+ BitField.of(CLEAR_CACHE)
+ .set(FORCE_CLOSE_INPUT, true)
+ .set(FORCE_CLOSE_OUTPUT, true));
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, String.format("Failed to sync secure storage: %s", mStorageRoot, e));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Method that clear the cache
+ *
+ * @param ctx The current context
+ */
+ private void clearCache(Context ctx) {
+ File filesDir = ctx.getExternalFilesDir(null);
+ File[] cacheFiles = filesDir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String filename) {
+ return filename.startsWith(mStorageName)
+ && filename.endsWith(".tmp");
+ }
+ });
+ for (File cacheFile : cacheFiles) {
+ cacheFile.delete();
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java
new file mode 100644
index 00000000..df2e4822
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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.console.secure;
+
+import de.schlichtherle.truezip.fs.archive.zip.raes.SafeZipRaesDriver;
+import de.schlichtherle.truezip.socket.sl.IOPoolLocator;
+
+/**
+ * Custom implementation of {@code SafeZipRaesDriver}
+ */
+public class SecureStorageDriver extends SafeZipRaesDriver {
+
+ // The singleton FsDriver reference
+ static final SecureStorageDriver SINGLETON = new SecureStorageDriver();
+
+ /**
+ * Constructor of {@code SecureStorageDriver}
+ */
+ private SecureStorageDriver() {
+ super(IOPoolLocator.SINGLETON, SecureStorageKeyManagerProvider.SINGLETON);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java
new file mode 100644
index 00000000..17555286
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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.console.secure;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.schlichtherle.truezip.fs.FsDriver;
+import de.schlichtherle.truezip.fs.FsDriverProvider;
+import de.schlichtherle.truezip.fs.FsScheme;
+import de.schlichtherle.truezip.fs.file.FileDriver;
+
+/**
+ * The SecureStorage driver provider which handles {@code "secure"} data schemes
+ */
+public class SecureStorageDriverProvider implements FsDriverProvider {
+
+ /** File scheme **/
+ public static final String FILE_SCHEME = "file";
+
+ /** SecureStorage scheme **/
+ public static final String SECURE_STORAGE_SCHEME = "secure";
+
+ /** The singleton instance of this class. */
+ static final SecureStorageDriverProvider SINGLETON = new SecureStorageDriverProvider();
+
+ /** You cannot instantiate this class. */
+ private SecureStorageDriverProvider() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Map<FsScheme, FsDriver> get() {
+ return Boot.DRIVERS;
+ }
+
+ /** A static data utility class used for lazy initialization. */
+ private static final class Boot {
+ static final Map<FsScheme, FsDriver> DRIVERS;
+ static {
+ final Map<FsScheme, FsDriver> fast = new LinkedHashMap<FsScheme, FsDriver>();
+ fast.put(FsScheme.create(FILE_SCHEME), new FileDriver());
+ fast.put(FsScheme.create(SECURE_STORAGE_SCHEME), SecureStorageDriver.SINGLETON);
+ DRIVERS = Collections.unmodifiableMap(fast);
+ }
+ } // Boot
+}
diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java
new file mode 100644
index 00000000..810b94dd
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 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.console.secure;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.schlichtherle.truezip.crypto.raes.param.AesCipherParameters;
+import de.schlichtherle.truezip.key.AbstractKeyManagerProvider;
+import de.schlichtherle.truezip.key.KeyManager;
+import de.schlichtherle.truezip.key.PromptingKeyManager;
+import de.schlichtherle.truezip.key.PromptingKeyProvider;
+
+/**
+ * The SecureStorage KeyManager provider
+ */
+public class SecureStorageKeyManagerProvider extends AbstractKeyManagerProvider {
+
+ /** The singleton instance of this class. */
+ static final SecureStorageKeyManagerProvider SINGLETON =
+ new SecureStorageKeyManagerProvider();
+
+ private final static SecureStorageKeyPromptDialog PROMPT_DIALOG =
+ new SecureStorageKeyPromptDialog();
+
+ /** You cannot instantiate this class. */
+ private SecureStorageKeyManagerProvider() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Map<Class<?>, KeyManager<?>> get() {
+ return Boot.MANAGERS;
+ }
+
+ /**
+ * @hide
+ */
+ void unmount() {
+ PROMPT_DIALOG.umount();
+ getKeyProvider().setKey(null);
+ }
+
+ /**
+ * @hide
+ */
+ void reset() {
+ PROMPT_DIALOG.reset();
+ getKeyProvider().setKey(null);
+ }
+
+ /**
+ * @hide
+ */
+ void delete() {
+ PROMPT_DIALOG.delete();
+ getKeyProvider().setKey(null);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static PromptingKeyProvider<AesCipherParameters> getKeyProvider() {
+ PromptingKeyManager<AesCipherParameters> keyManager =
+ (PromptingKeyManager<AesCipherParameters>) Boot.MANAGERS.get(
+ AesCipherParameters.class);
+ return (PromptingKeyProvider<AesCipherParameters>) keyManager.getKeyProvider(
+ SecureConsole.getSecureStorageRootUri());
+ }
+
+ /** A static data utility class used for lazy initialization. */
+ private static final class Boot {
+ static final Map<Class<?>, KeyManager<?>> MANAGERS;
+ static {
+ final PromptingKeyManager<AesCipherParameters> promptKeyManager =
+ new PromptingKeyManager<AesCipherParameters>(PROMPT_DIALOG);
+ final Map<Class<?>, KeyManager<?>> fast = new LinkedHashMap<Class<?>, KeyManager<?>>();
+ fast.put(AesCipherParameters.class, promptKeyManager);
+ MANAGERS = Collections.unmodifiableMap(fast);
+
+ // We need that the provider ask always for a password
+ getKeyProvider().setAskAlwaysForWriteKey(true);
+ }
+ } // class Boot
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java
new file mode 100644
index 00000000..2db530c4
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2014 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.console.secure;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.cyanogenmod.filemanager.FileManagerApplication;
+import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.ui.ThemeManager;
+import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
+import com.cyanogenmod.filemanager.util.DialogHelper;
+
+import de.schlichtherle.truezip.crypto.raes.param.AesCipherParameters;
+import de.schlichtherle.truezip.crypto.raes.Type0RaesParameters.KeyStrength;
+import de.schlichtherle.truezip.key.KeyPromptingCancelledException;
+import de.schlichtherle.truezip.key.KeyPromptingInterruptedException;
+import de.schlichtherle.truezip.key.PromptingKeyProvider.Controller;
+import de.schlichtherle.truezip.key.UnknownKeyException;
+
+/**
+ * A class that remembers all the secure storage
+ */
+public class SecureStorageKeyPromptDialog
+ implements de.schlichtherle.truezip.key.PromptingKeyProvider.View<AesCipherParameters> {
+
+ private static final int MIN_PASSWORD_LENGTH = 8;
+
+ private static final int MSG_REQUEST_UNLOCK_DIALOG = 1;
+
+ private static boolean sResetInProgress;
+ private static boolean sDeleteInProgress;
+ private static transient AesCipherParameters sOldUnlockKey = null;
+ private static transient AesCipherParameters sUnlockKey = null;
+ private static transient AesCipherParameters sOldUnlockKeyTemp = null;
+ private static transient AesCipherParameters sUnlockKeyTemp = null;
+ private static final Object WAIT_SYNC = new Object();
+
+ /**
+ * An activity that simulates a dialog over the activity that requested the key prompt.
+ */
+ public static class SecureStorageKeyPromptActivity extends Activity
+ implements OnClickListener, TextWatcher {
+
+ private AlertDialog mDialog;
+
+ private TextView mMessage;
+ private EditText mOldKey;
+ private EditText mKey;
+ private EditText mRepeatKey;
+ private TextView mValidationMsg;
+ private Button mUnlock;
+
+ private boolean mNewStorage;
+ private boolean mResetPassword;
+ private boolean mDeleteStorage;
+
+ AesCipherParameters mOldKeyParams;
+ AesCipherParameters mKeyParams;
+
+ @Override
+ @SuppressWarnings("deprecation")
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Check with java.io.File instead of TFile because TFile#exists() will
+ // check for password key, which is currently locked
+ mNewStorage = !SecureConsole.getSecureStorageRoot().getFile().exists();
+ mResetPassword = sResetInProgress;
+ mDeleteStorage = sDeleteInProgress;
+
+ // Set the theme before setContentView
+ Theme theme = ThemeManager.getCurrentTheme(this);
+ theme.setBaseTheme(this, true);
+
+ // Load the dialog's custom layout
+ ViewGroup v = (ViewGroup) LayoutInflater.from(this).inflate(
+ R.layout.unlock_dialog_message, null);
+ mMessage = (TextView) v.findViewById(R.id.unlock_dialog_message);
+ mOldKey = (EditText) v.findViewById(R.id.unlock_old_password);
+ mOldKey.addTextChangedListener(this);
+ mKey = (EditText) v.findViewById(R.id.unlock_password);
+ mKey.addTextChangedListener(this);
+ mRepeatKey = (EditText) v.findViewById(R.id.unlock_repeat);
+ mRepeatKey.addTextChangedListener(this);
+ View oldPasswordLayout = v.findViewById(R.id.unlock_old_password_layout);
+ View repeatLayout = v.findViewById(R.id.unlock_repeat_layout);
+ mValidationMsg = (TextView) v.findViewById(R.id.unlock_validation_msg);
+
+ // Load resources
+ int messageResourceId = R.string.secure_storage_unlock_key_prompt_msg;
+ int positiveButtonLabelResourceId = R.string.secure_storage_unlock_button;
+ String title = getString(R.string.secure_storage_unlock_title);
+ if (mNewStorage) {
+ positiveButtonLabelResourceId = R.string.secure_storage_create_button;
+ title = getString(R.string.secure_storage_create_title);
+ messageResourceId = R.string.secure_storage_unlock_key_new_msg;
+ } else if (mResetPassword) {
+ positiveButtonLabelResourceId = R.string.secure_storage_reset_button;
+ title = getString(R.string.secure_storage_reset_title);
+ messageResourceId = R.string.secure_storage_unlock_key_reset_msg;
+ TextView passwordLabel = (TextView) v.findViewById(R.id.unlock_password_title);
+ passwordLabel.setText(R.string.secure_storage_unlock_new_key_title);
+ } else if (mDeleteStorage) {
+ positiveButtonLabelResourceId = R.string.secure_storage_delete_button;
+ title = getString(R.string.secure_storage_delete_title);
+ messageResourceId = R.string.secure_storage_unlock_key_delete_msg;
+ }
+
+ // Set the message according to the storage creation status
+ mMessage.setText(messageResourceId);
+ repeatLayout.setVisibility(mNewStorage || mResetPassword ? View.VISIBLE : View.GONE);
+ oldPasswordLayout.setVisibility(mResetPassword ? View.VISIBLE : View.GONE);
+
+ // Set validation msg
+ mValidationMsg.setText(getString(R.string.secure_storage_unlock_validation_length,
+ MIN_PASSWORD_LENGTH));
+ mValidationMsg.setVisibility(View.VISIBLE);
+
+ // Create the dialog
+ mDialog = DialogHelper.createTwoButtonsDialog(this,
+ positiveButtonLabelResourceId, R.string.cancel,
+ theme.getResourceId(this,"ic_secure_drawable"), title, v, this);
+ mDialog.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mDialog.dismiss();
+ finish();
+ }
+ });
+ mDialog.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ sUnlockKeyTemp = null;
+ mDialog.cancel();
+ finish();
+ }
+ });
+ mDialog.setCanceledOnTouchOutside(false);
+
+ // Apply the theme to the custom view of the dialog
+ applyTheme(this, v);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ DialogHelper.delegateDialogShow(this, mDialog);
+ mUnlock = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ mUnlock.setEnabled(false);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // Unlock the wait
+ synchronized (WAIT_SYNC) {
+ WAIT_SYNC.notify();
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ // Create the AES parameter and set to the prompting view
+ if (mResetPassword) {
+ AesCipherParameters params = new AesCipherParameters();
+ params.setPassword(mOldKey.getText().toString().toCharArray());
+ params.setKeyStrength(KeyStrength.BITS_128);
+ sOldUnlockKeyTemp = params;
+ }
+ AesCipherParameters params = new AesCipherParameters();
+ params.setPassword(mKey.getText().toString().toCharArray());
+ params.setKeyStrength(KeyStrength.BITS_128);
+ sUnlockKeyTemp = params;
+
+ // We ended with this dialog
+ dialog.dismiss();
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ // User had cancelled the dialog
+ dialog.cancel();
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Ignore
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Ignore
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // Validations:
+ // * Key must be MIN_PASSWORD_LENGTH characters or more
+ // * Repeat == Key
+ String oldkey = mOldKey.getText().toString();
+ String key = mKey.getText().toString();
+ String repeatKey = mRepeatKey.getText().toString();
+ boolean validLength = key.length() >= MIN_PASSWORD_LENGTH &&
+ (!mResetPassword || (mResetPassword && oldkey.length() >= MIN_PASSWORD_LENGTH));
+ boolean validEquals = key.equals(repeatKey);
+ boolean valid = validLength && ((mNewStorage && validEquals) || !mNewStorage);
+ mUnlock.setEnabled(valid);
+
+ if (!validLength) {
+ mValidationMsg.setText(getString(R.string.secure_storage_unlock_validation_length,
+ MIN_PASSWORD_LENGTH));
+ mValidationMsg.setVisibility(View.VISIBLE);
+ } else if (mNewStorage && !validEquals) {
+ mValidationMsg.setText(R.string.secure_storage_unlock_validation_equals);
+ mValidationMsg.setVisibility(View.VISIBLE);
+ } else {
+ mValidationMsg.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ private void applyTheme(Context ctx, ViewGroup root) {
+ // Apply the current theme
+ Theme theme = ThemeManager.getCurrentTheme(ctx);
+ theme.setBackgroundDrawable(ctx, root, "background_drawable");
+ theme.setTextColor(ctx, mMessage, "text_color");
+ theme.setTextColor(ctx, mOldKey, "text_color");
+ theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_old_password_title),
+ "text_color");
+ theme.setTextColor(ctx, mKey, "text_color");
+ theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_password_title),
+ "text_color");
+ theme.setTextColor(ctx, mRepeatKey, "text_color");
+ theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_repeat_title),
+ "text_color");
+ theme.setTextColor(ctx, mValidationMsg, "text_color");
+ mValidationMsg.setCompoundDrawablesWithIntrinsicBounds(
+ theme.getDrawable(ctx, "filesystem_warning_drawable"), //$NON-NLS-1$
+ null, null, null);
+ }
+ }
+
+ SecureStorageKeyPromptDialog() {
+ super();
+ sResetInProgress = false;
+ sDeleteInProgress = false;
+ sOldUnlockKey = null;
+ sUnlockKey = null;
+ }
+
+ @Override
+ public void promptWriteKey(Controller<AesCipherParameters> controller)
+ throws UnknownKeyException {
+ controller.setKey(getOrPromptForKey(false));
+ if (sResetInProgress) {
+ // Not needed any more. Reads are now done with new key
+ sOldUnlockKey = null;
+ sResetInProgress = false;
+ }
+ }
+
+ @Override
+ public void promptReadKey(Controller<AesCipherParameters> controller, boolean invalid)
+ throws UnknownKeyException {
+ if (!sResetInProgress && invalid) {
+ sUnlockKey = null;
+ }
+ controller.setKey(getOrPromptForKey(true));
+ }
+
+ /**
+ * {@hide}
+ */
+ void umount() {
+ // Discard current keys
+ sResetInProgress = false;
+ sDeleteInProgress = false;
+ sOldUnlockKey = null;
+ sUnlockKey = null;
+ }
+
+ /**
+ * {@hide}
+ */
+ void reset() {
+ // Discard current keys
+ sResetInProgress = true;
+ sDeleteInProgress = false;
+ sOldUnlockKey = null;
+ sUnlockKey = null;
+ }
+
+ /**
+ * {@hide}
+ */
+ void delete() {
+ sDeleteInProgress = true;
+ sResetInProgress = false;
+ sOldUnlockKey = null;
+ }
+
+ /**
+ * Method that return or prompt the user for the secure storage key
+ *
+ * @param read If should return the read or write key
+ * @return AesCipherParameters The AES cipher parameters
+ */
+ private static synchronized AesCipherParameters getOrPromptForKey(boolean read)
+ throws UnknownKeyException {
+ // Check if we have a cached key
+ if (read && sResetInProgress && sOldUnlockKey != null) {
+ return sOldUnlockKey;
+ }
+ if (sUnlockKey != null) {
+ return sUnlockKey;
+ }
+
+ // Need to prompt the user for the secure storage key, so we open a overlay activity
+ // to show the prompt dialog
+ Handler handler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message inputMessage) {
+ Context ctx = FileManagerApplication.getInstance();
+ Intent intent = new Intent(ctx, SecureStorageKeyPromptActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ctx.startActivity(intent);
+ }
+ };
+ handler.sendEmptyMessage(MSG_REQUEST_UNLOCK_DIALOG);
+
+ // Wait for the response
+ synchronized (WAIT_SYNC) {
+ try {
+ WAIT_SYNC.wait();
+ } catch (InterruptedException ex) {
+ throw new KeyPromptingInterruptedException(ex);
+ }
+ }
+
+ // Request for authentication is done. We need to exit from delete status
+ sDeleteInProgress = false;
+
+ // Check if the user cancelled the dialog
+ if (sUnlockKeyTemp == null) {
+ throw new KeyPromptingCancelledException();
+ }
+
+ // Move temporary params to real params
+ sUnlockKey = sUnlockKeyTemp;
+ sOldUnlockKey = sOldUnlockKeyTemp;
+
+ AesCipherParameters key = sUnlockKey;
+ if (sResetInProgress && read) {
+ key = sOldUnlockKey;
+ }
+ return key;
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java
index bc97b25a..7a1f5a9e 100644
--- a/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java
+++ b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java
@@ -16,6 +16,7 @@
package com.cyanogenmod.filemanager.console.shell;
+import android.content.Context;
import android.util.Log;
import com.cyanogenmod.filemanager.FileManagerApplication;
@@ -286,7 +287,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
//Retrieve identity
IdentityExecutable identityCmd =
getExecutableFactory().newCreator().createIdentityExecutable();
- execute(identityCmd);
+ execute(identityCmd, null);
this.mIdentity = identityCmd.getResult();
// Identity command is required for root console detection,
// but Groups command is not used for now. Also, this command is causing
@@ -297,7 +298,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
//Try with groups
GroupsExecutable groupsCmd =
getExecutableFactory().newCreator().createGroupsExecutable();
- execute(groupsCmd);
+ execute(groupsCmd, null);
this.mIdentity.setGroups(groupsCmd.getResult());
}
} catch (Exception ex) {
@@ -372,10 +373,10 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
* {@inheritDoc}
*/
@Override
- public final synchronized void execute(final Executable executable)
- throws ConsoleAllocException, InsufficientPermissionsException,
- CommandNotFoundException, NoSuchFileOrDirectory,
- OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException {
+ public synchronized void execute(Executable executable, Context ctx)
+ throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException, CommandNotFoundException,
+ ReadOnlyFilesystemException {
execute(executable, false);
}
@@ -1192,7 +1193,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
return false;
}
- if (this.mActiveCommand.getCommand() != null) {
+ if (this.mActiveCommand != null && this.mActiveCommand.getCommand() != null) {
try {
boolean isCancellable = true;
if (this.mActiveCommand instanceof AsyncResultProgram) {
diff --git a/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java b/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java
index 0de97198..817bdffd 100644
--- a/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java
+++ b/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java
@@ -30,6 +30,11 @@ public interface OnRequestRefreshListener {
void onRequestRefresh(Object o, boolean clearSelection);
/**
+ * Invoked when bookmarks need a refresh
+ */
+ void onRequestBookmarksRefresh();
+
+ /**
* Invoked when the object was removed.
*
* @param o The object that was removed
diff --git a/src/com/cyanogenmod/filemanager/model/Bookmark.java b/src/com/cyanogenmod/filemanager/model/Bookmark.java
index 192f2d9c..f922a602 100644
--- a/src/com/cyanogenmod/filemanager/model/Bookmark.java
+++ b/src/com/cyanogenmod/filemanager/model/Bookmark.java
@@ -32,6 +32,8 @@ import java.io.Serializable;
*/
public class Bookmark implements Serializable, Comparable<Bookmark>, Parcelable {
+ private static final long serialVersionUID = -2271268193370651368L;
+
/**
* Enumeration for types of bookmarks.
*/
@@ -53,13 +55,19 @@ public class Bookmark implements Serializable, Comparable<Bookmark>, Parcelable
*/
USB,
/**
+ * An SECURE mount point.
+ */
+ SECURE,
+ /**
+ * An REMOTE mount point.
+ */
+ REMOTE,
+ /**
* A bookmark added by the user.
*/
USER_DEFINED
}
- private static final long serialVersionUID = -7524744999056506867L;
-
/**
* Columns of the database
*/
diff --git a/src/com/cyanogenmod/filemanager/model/FileSystemObject.java b/src/com/cyanogenmod/filemanager/model/FileSystemObject.java
index 99110a7d..c87c07cc 100644
--- a/src/com/cyanogenmod/filemanager/model/FileSystemObject.java
+++ b/src/com/cyanogenmod/filemanager/model/FileSystemObject.java
@@ -33,7 +33,7 @@ import java.util.Date;
*/
public abstract class FileSystemObject implements Serializable, Comparable<FileSystemObject> {
- private static final long serialVersionUID = 5877049750925761305L;
+ private static final long serialVersionUID = -571144166609728391L;
//Resource identifier for default icon
private static final int RESOURCE_ICON_DEFAULT = R.drawable.ic_fso_default;
@@ -48,7 +48,8 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS
private Date mLastAccessedTime;
private Date mLastModifiedTime;
private Date mLastChangedTime;
-
+ private boolean mIsSecure;
+ private boolean mIsRemote;
/**
* Constructor of <code>FileSystemObject</code>.
@@ -77,6 +78,8 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS
this.mLastModifiedTime = lastModifiedTime;
this.mLastChangedTime = lastChangedTime;
this.mResourceIconId = RESOURCE_ICON_DEFAULT;
+ this.mIsSecure = false;
+ this.mIsRemote = false;
}
/**
@@ -249,7 +252,7 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS
}
/**
- * Method that returns of the object is hidden object.
+ * Method that returns if the object is hidden object.
*
* @return boolean If the object is hidden object
*/
@@ -258,6 +261,42 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS
}
/**
+ * Method that returns if the object is secure
+ *
+ * @return boolean If the object is secure
+ */
+ public boolean isSecure() {
+ return mIsSecure;
+ }
+
+ /**
+ * Mehtod that sets if the object is secure
+ *
+ * @param secure if the object is secure
+ */
+ public void setSecure(boolean secure) {
+ mIsSecure = secure;
+ }
+
+ /**
+ * Method that returns if the object is remote
+ *
+ * @return boolean If the object is remote
+ */
+ public boolean isRemote() {
+ return mIsRemote;
+ }
+
+ /**
+ * Mehtod that sets if the object is remote
+ *
+ * @param remote if the object is remote
+ */
+ public void setRemote(boolean remote) {
+ mIsRemote = remote;
+ }
+
+ /**
* Method that returns the identifier of the drawable icon associated
* to the object.
*
@@ -365,6 +404,8 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS
+ ", mLastAccessedTime=" + this.mLastAccessedTime //$NON-NLS-1$
+ ", mLastModifiedTime=" + this.mLastModifiedTime //$NON-NLS-1$
+ ", mLastChangedTime=" + this.mLastChangedTime //$NON-NLS-1$
+ + ", mIsSecure=" + mIsSecure //$NON-NLS-1$
+ + ", mIsRemote=" + mIsRemote //$NON-NLS-1$
+ "]"; //$NON-NLS-1$
}
diff --git a/src/com/cyanogenmod/filemanager/model/History.java b/src/com/cyanogenmod/filemanager/model/History.java
index 32787836..2c07b450 100644
--- a/src/com/cyanogenmod/filemanager/model/History.java
+++ b/src/com/cyanogenmod/filemanager/model/History.java
@@ -27,7 +27,7 @@ public class History implements Serializable, Comparable<History> {
private static final long serialVersionUID = -8891185225878742265L;
- private final int mPosition;
+ private int mPosition;
private final HistoryNavigable mItem;
/**
@@ -52,6 +52,15 @@ public class History implements Serializable, Comparable<History> {
}
/**
+ * Method that sets the current position of the history
+ *
+ * @param position The current position
+ */
+ public void setPosition(int position) {
+ this.mPosition = position;
+ }
+
+ /**
* Method that returns the item that holds the history information.
*
* @return HistoryNavigable The item that holds the history information
diff --git a/src/com/cyanogenmod/filemanager/model/MountPoint.java b/src/com/cyanogenmod/filemanager/model/MountPoint.java
index 92576a87..a2abebc6 100644
--- a/src/com/cyanogenmod/filemanager/model/MountPoint.java
+++ b/src/com/cyanogenmod/filemanager/model/MountPoint.java
@@ -23,7 +23,7 @@ import java.io.Serializable;
*/
public class MountPoint implements Serializable, Comparable<MountPoint> {
- private static final long serialVersionUID = 6283618345819358175L;
+ private static final long serialVersionUID = -2598921174356702897L;
private final String mMountPoint;
private final String mDevice;
@@ -31,6 +31,8 @@ public class MountPoint implements Serializable, Comparable<MountPoint> {
private final String mOptions;
private final int mDump;
private final int mPass;
+ private boolean mSecure;
+ private boolean mRemote;
/**
* Constructor of <code>MountPoint</code>.
@@ -41,9 +43,12 @@ public class MountPoint implements Serializable, Comparable<MountPoint> {
* @param options The mount options
* @param dump The frequency to determine if the filesystem need to be dumped
* @param pass The order in which filesystem checks are done at reboot time
+ * @param secure If the device is a secure virtual filesystem
+ * @param remote If the device is a remote virtual filesystem
*/
public MountPoint(
- String mountPoint, String device, String type, String options, int dump, int pass) {
+ String mountPoint, String device, String type, String options, int dump,
+ int pass, boolean secure, boolean remote) {
super();
this.mMountPoint = mountPoint;
this.mDevice = device;
@@ -51,6 +56,8 @@ public class MountPoint implements Serializable, Comparable<MountPoint> {
this.mOptions = options;
this.mDump = dump;
this.mPass = pass;
+ this.mSecure = secure;
+ this.mRemote = remote;
}
/**
@@ -109,18 +116,49 @@ public class MountPoint implements Serializable, Comparable<MountPoint> {
}
/**
+ * Method that returns if the mountpoint belongs to a virtual filesystem.
+ *
+ * @return boolean If the mountpoint belongs to a virtual filesystem.
+ */
+ public boolean isVirtual() {
+ return mSecure || mRemote;
+ }
+
+ /**
+ * Method that returns if the mountpoint belongs to a secure virtual filesystem.
+ *
+ * @return boolean If the mountpoint belongs to a secure virtual filesystem.
+ */
+ public boolean isSecure() {
+ return mSecure;
+ }
+
+ /**
+ * Method that returns if the mountpoint belongs to a remote virtual filesystem.
+ *
+ * @return boolean If the mountpoint belongs to a remote virtual filesystem.
+ */
+ public boolean isRemote() {
+ return mRemote;
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + this.mDump;
- result = prime * result + ((this.mDevice == null) ? 0 : this.mDevice.hashCode());
- result = prime * result + ((this.mMountPoint == null) ? 0 : this.mMountPoint.hashCode());
- result = prime * result + ((this.mOptions == null) ? 0 : this.mOptions.hashCode());
- result = prime * result + this.mPass;
- result = prime * result + ((this.mType == null) ? 0 : this.mType.hashCode());
+ result = prime * result + ((mDevice == null) ? 0 : mDevice.hashCode());
+ result = prime * result + mDump;
+ result = prime * result
+ + ((mMountPoint == null) ? 0 : mMountPoint.hashCode());
+ result = prime * result
+ + ((mOptions == null) ? 0 : mOptions.hashCode());
+ result = prime * result + mPass;
+ result = prime * result + (mRemote ? 1231 : 1237);
+ result = prime * result + (mSecure ? 1231 : 1237);
+ result = prime * result + ((mType == null) ? 0 : mType.hashCode());
return result;
}
@@ -129,50 +167,41 @@ public class MountPoint implements Serializable, Comparable<MountPoint> {
*/
@Override
public boolean equals(Object obj) {
- if (this == obj) {
+ if (this == obj)
return true;
- }
- if (obj == null) {
+ if (obj == null)
return false;
- }
- if (getClass() != obj.getClass()) {
+ if (getClass() != obj.getClass())
return false;
- }
MountPoint other = (MountPoint) obj;
- if (this.mDump != other.mDump) {
- return false;
- }
- if (this.mDevice == null) {
- if (other.mDevice != null) {
+ if (mDevice == null) {
+ if (other.mDevice != null)
return false;
- }
- } else if (!this.mDevice.equals(other.mDevice)) {
+ } else if (!mDevice.equals(other.mDevice))
+ return false;
+ if (mDump != other.mDump)
return false;
- }
- if (this.mMountPoint == null) {
- if (other.mMountPoint != null) {
+ if (mMountPoint == null) {
+ if (other.mMountPoint != null)
return false;
- }
- } else if (!this.mMountPoint.equals(other.mMountPoint)) {
+ } else if (!mMountPoint.equals(other.mMountPoint))
return false;
- }
- if (this.mOptions == null) {
- if (other.mOptions != null) {
+ if (mOptions == null) {
+ if (other.mOptions != null)
return false;
- }
- } else if (!this.mOptions.equals(other.mOptions)) {
+ } else if (!mOptions.equals(other.mOptions))
+ return false;
+ if (mPass != other.mPass)
+ return false;
+ if (mRemote != other.mRemote)
return false;
- }
- if (this.mPass != other.mPass) {
+ if (mSecure != other.mSecure)
return false;
- }
- if (this.mType == null) {
- if (other.mType != null) {
+ if (mType == null) {
+ if (other.mType != null)
return false;
- }
- } else if (!this.mType.equals(other.mType)) {
+ } else if (!mType.equals(other.mType))
return false;
- }
return true;
}
@@ -181,11 +210,10 @@ public class MountPoint implements Serializable, Comparable<MountPoint> {
*/
@Override
public String toString() {
- return "MountPoint [mountPoint=" + this.mMountPoint + ", device=" //$NON-NLS-1$//$NON-NLS-2$
- + this.mDevice + ", type=" //$NON-NLS-1$
- + this.mType + ", options=" + this.mOptions //$NON-NLS-1$
- + ", dump=" + this.mDump + ", pass=" //$NON-NLS-1$//$NON-NLS-2$
- + this.mPass + "]"; //$NON-NLS-1$
+ return "MountPoint [mMountPoint=" + mMountPoint + ", mDevice="
+ + mDevice + ", mType=" + mType + ", mOptions=" + mOptions
+ + ", mDump=" + mDump + ", mPass=" + mPass + ", mSecure="
+ + mSecure + ", mRemote=" + mRemote + "]";
}
/**
diff --git a/src/com/cyanogenmod/filemanager/model/Permissions.java b/src/com/cyanogenmod/filemanager/model/Permissions.java
index 95000865..9fdab8d9 100644
--- a/src/com/cyanogenmod/filemanager/model/Permissions.java
+++ b/src/com/cyanogenmod/filemanager/model/Permissions.java
@@ -29,7 +29,7 @@ import java.text.ParseException;
*/
public class Permissions implements Serializable, Comparable<Permissions> {
- private static final long serialVersionUID = -8268598363293965341L;
+ private static final long serialVersionUID = -3995246732859872806L;
private UserPermission mUser;
private GroupPermission mGroup;
@@ -245,6 +245,30 @@ public class Permissions implements Serializable, Comparable<Permissions> {
}
/**
+ * Method that returns the default permissions for folder
+ *
+ * @return Permissions The default permissions for folder
+ */
+ public static Permissions createDefaultFolderPermissions() {
+ return new Permissions(
+ new UserPermission(true, true, true, false),
+ new GroupPermission(true, false, true, false),
+ new OthersPermission(false, false, false, false));
+ }
+
+ /**
+ * Method that returns the default permissions for folder
+ *
+ * @return Permissions The default permissions for folder
+ */
+ public static Permissions createDefaultFilePermissions() {
+ return new Permissions(
+ new UserPermission(true, true, false, false),
+ new GroupPermission(true, true, false, false),
+ new OthersPermission(false, false, false, false));
+ }
+
+ /**
* Method that parses and extracts the permissions from a unix string format.
*
* @param rawPermissions The raw permissions
diff --git a/src/com/cyanogenmod/filemanager/model/Query.java b/src/com/cyanogenmod/filemanager/model/Query.java
index 612bd68e..ac1f5e67 100644
--- a/src/com/cyanogenmod/filemanager/model/Query.java
+++ b/src/com/cyanogenmod/filemanager/model/Query.java
@@ -145,7 +145,11 @@ public class Query implements Serializable, Parcelable {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeStringArray(this.mQUERIES);
+ int cc = this.mQUERIES.length;
+ dest.writeInt(cc);
+ for (int i = 0; i < cc; i++) {
+ dest.writeString(mQUERIES[i] != null ? mQUERIES[i] : "");
+ }
}
/**
@@ -154,11 +158,15 @@ public class Query implements Serializable, Parcelable {
* @param in The parcel information to recreate the object
*/
private void readFromParcel(Parcel in) {
- String[] queries = in.readStringArray();
- if (queries != null) {
- int count = Math.min(SLOTS_COUNT, queries.length);
- for (int i = 0; i < count; i++) {
- mQUERIES[i] = queries[i];
+ int len = mQUERIES.length;
+ int cc = in.readInt();
+ for (int i = 0; i < cc; i++) {
+ String query = in.readString();
+ if (i >= len) {
+ continue;
+ }
+ if (!TextUtils.isEmpty(query)) {
+ mQUERIES[i] = query;
}
}
}
diff --git a/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java b/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java
index a6da308e..dcfa7af6 100644
--- a/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java
+++ b/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java
@@ -32,7 +32,7 @@ import java.util.List;
*/
public class SearchInfoParcelable extends HistoryNavigable {
- private static final long serialVersionUID = 3051428434374087971L;
+ private static final long serialVersionUID = -124315348462060329L;
private String mSearchDirectory;
private List<SearchResult> mSearchResultList;
@@ -43,8 +43,11 @@ public class SearchInfoParcelable extends HistoryNavigable {
/**
* Constructor of <code>SearchInfoParcelable</code>.
*/
- public SearchInfoParcelable() {
+ public SearchInfoParcelable(String searchDirectory, List<SearchResult> searchResultList, Query searchQuery) {
super();
+ mSearchDirectory = searchDirectory;
+ mSearchResultList = searchResultList;
+ mSearchQuery = searchQuery;
setTitle();
}
@@ -92,15 +95,6 @@ public class SearchInfoParcelable extends HistoryNavigable {
}
/**
- * Method that sets the directory where to search.
- *
- * @param searchDirectory The directory where to search
- */
- public void setSearchDirectory(String searchDirectory) {
- this.mSearchDirectory = searchDirectory;
- }
-
- /**
* Method that returns the search result list.
*
* @return List<SearchResult> The search result list
@@ -110,15 +104,6 @@ public class SearchInfoParcelable extends HistoryNavigable {
}
/**
- * Method that sets the search result list.
- *
- * @param searchResultList The search result list
- */
- public void setSearchResultList(List<SearchResult> searchResultList) {
- this.mSearchResultList = searchResultList;
- }
-
- /**
* Method that returns the query terms of the search.
*
* @return Query The query terms of the search
@@ -128,16 +113,6 @@ public class SearchInfoParcelable extends HistoryNavigable {
}
/**
- * Method that sets the query terms of the search.
- *
- * @param searchQuery The query terms of the search
- */
- public void setSearchQuery(Query searchQuery) {
- this.mSearchQuery = searchQuery;
- setTitle();
- }
-
- /**
* Method that returns if the search navigation was success.
*
* @return boolean If the search navigation was success
@@ -208,7 +183,8 @@ public class SearchInfoParcelable extends HistoryNavigable {
//- 2
int hasSearchQuery = in.readInt();
if (hasSearchQuery == 1) {
- this.mSearchQuery = (Query)in.readParcelable(getClass().getClassLoader());
+ this.mSearchQuery = (Query)in.readParcelable(
+ SearchInfoParcelable.class.getClassLoader());
}
setTitle();
//- 3
diff --git a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java
index 93309dae..caf64f3f 100644
--- a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java
+++ b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java
@@ -143,6 +143,13 @@ public enum FileManagerSettings {
SETTINGS_SAVE_SEARCH_TERMS("cm_filemanager_save_search_terms", Boolean.TRUE), //$NON-NLS-1$
/**
+ * When to delayed filesystem synchronization in secure storages
+ * @hide
+ */
+ SETTINGS_SECURE_STORAGE_DELAYED_SYNC("cm_filemanager_secure_storage_delayed_sync",
+ Boolean.TRUE), //$NON-NLS-1$
+
+ /**
* When to show debug traces
* @hide
*/
@@ -193,7 +200,16 @@ public enum FileManagerSettings {
* @hide
*/
SETTINGS_THEME("cm_filemanager_theme", //$NON-NLS-1$
- "com.cyanogenmod.filemanager:light"); //$NON-NLS-1$
+ "com.cyanogenmod.filemanager:light"),
+
+ /**
+ * The current theme to use in the app
+ * @hide
+ */
+ USER_PREF_LAST_DRAWER_TAB("last_drawer_tab", //$NON-NLS-1$
+ Integer.valueOf(0));
+
+
/**
* A broadcast intent that is sent when a setting was changed
@@ -208,6 +224,12 @@ public enum FileManagerSettings {
"com.cyanogenmod.filemanager.INTENT_THEME_CHANGED"; //$NON-NLS-1$
/**
+ * A broadcast intent that is sent when a setting was changed
+ */
+ public final static String INTENT_MOUNT_STATUS_CHANGED =
+ "com.cyanogenmod.filemanager.INTENT_MOUNT_STATUS_CHANGED"; //$NON-NLS-1$
+
+ /**
* A broadcast intent that is sent when a file was changed
*/
public final static String INTENT_FILE_CHANGED =
@@ -233,6 +255,16 @@ public enum FileManagerSettings {
*/
public final static String EXTRA_THEME_ID = "id"; //$NON-NLS-1$
+ /**
+ * The extra key with the identifier a mountpoint event
+ */
+ public final static String EXTRA_MOUNTPOINT = "mount_point"; //$NON-NLS-1$
+
+ /**
+ * The extra key with the notify the status of an object
+ */
+ public final static String EXTRA_STATUS = "status"; //$NON-NLS-1$
+
diff --git a/src/com/cyanogenmod/filemanager/preferences/Preferences.java b/src/com/cyanogenmod/filemanager/preferences/Preferences.java
index 3cdc595e..b07ec394 100644
--- a/src/com/cyanogenmod/filemanager/preferences/Preferences.java
+++ b/src/com/cyanogenmod/filemanager/preferences/Preferences.java
@@ -282,6 +282,8 @@ public final class Preferences {
editor.putBoolean(pref.getId(), ((Boolean)value).booleanValue());
} else if (value instanceof String && pref.getDefaultValue() instanceof String) {
editor.putString(pref.getId(), (String)value);
+ } else if (value instanceof Integer && pref.getDefaultValue() instanceof Integer) {
+ editor.putInt(pref.getId(), (Integer)value);
} else if (value instanceof Set && pref.getDefaultValue() instanceof Set) {
editor.putStringSet(pref.getId(), (Set<String>)value);
} else if (value instanceof ObjectIdentifier
diff --git a/src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java b/src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java
new file mode 100644
index 00000000..b06fc738
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2014 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.providers;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Handler.Callback;
+import android.os.ParcelFileDescriptor;
+import android.provider.OpenableColumns;
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.model.FileSystemObject;
+import com.cyanogenmod.filemanager.model.RegularFile;
+import com.cyanogenmod.filemanager.util.CommandHelper;
+import com.cyanogenmod.filemanager.util.MimeTypeHelper;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * A {@link ContentProvider} to allow access secure filesystems.
+ */
+public final class SecureResourceProvider extends ContentProvider {
+
+ private static final String TAG = "SecureResourceProvider";
+
+ public static final String AUTHORITY =
+ "com.cyanogenmod.filemanager.providers.resources";
+
+ private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY;
+
+ private static final String COLUMS_ID = "auth_id";
+ private static final String COLUMS_NAME = OpenableColumns.DISPLAY_NAME;
+ private static final String COLUMS_SIZE = OpenableColumns.SIZE;
+
+ private static final String[] COLUMN_PROJECTION = {
+ COLUMS_ID, COLUMS_NAME, COLUMS_SIZE
+ };
+
+ public static class AuthorizationResource {
+ public final RegularFile mFile;
+ private String mPackage;
+
+ private AuthorizationResource(RegularFile file) {
+ mFile = file;
+ mPackage = null;
+ }
+ }
+
+ /**
+ * An implementation of an {@code AsyncResultListener}
+ */
+ private static class AsyncReader implements AsyncResultListener {
+
+ private final CancellationSignal mSignal;
+ private final ParcelFileDescriptor mFdIn;
+ private final ParcelFileDescriptor mFdOut;
+ private final OutputStream mOut;
+
+ public AsyncReader(ParcelFileDescriptor fdIn, ParcelFileDescriptor fdOut,
+ CancellationSignal signal) throws IOException {
+ super();
+ mFdIn = fdIn;
+ mFdOut = fdOut;
+ mOut = new ParcelFileDescriptor.AutoCloseOutputStream(fdOut);
+ mSignal = signal;
+ }
+
+ @Override
+ public void onAsyncStart() {
+ // Ignore
+ }
+
+ @Override
+ public void onAsyncEnd(boolean cancelled) {
+ // Ignore
+ }
+
+ @Override
+ public void onAsyncExitCode(int exitCode) {
+ close();
+ }
+
+ @Override
+ public void onPartialResult(Object result) {
+ try {
+ if (result == null) return;
+ byte[] partial = (byte[])result;
+ mOut.write(partial);
+ mOut.flush();
+ } catch (Exception ex) {
+ Log.w(TAG, "Failed to parse partial result data", ex);
+ closeWithError("Failed to parse partial result data: " + ex.getMessage());
+ if (mSignal != null) {
+ mSignal.cancel();
+ }
+ }
+ }
+
+ @Override
+ public void onException(Exception cause) {
+ Log.w(TAG, "Got exception while reading data", cause);
+ closeWithError("Got exception while reading data: " + cause.getMessage());
+ if (mSignal != null) {
+ mSignal.cancel();
+ }
+ }
+
+ private void close() {
+ try {
+ mOut.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ try {
+ mFdOut.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+
+ private void closeWithError(String msg) {
+ try {
+ mOut.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ try {
+ mFdOut.closeWithError(msg);
+ } catch (IOException ex) {
+ // Ignore
+ }
+ try {
+ mFdIn.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+
+ private static final Callback CLEAR_AUTH_CALLBACK = new Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CLEAR_AUTHORIZATIONS:
+ // Remove authorization
+ UUID uuid = UUID.fromString(msg.getData().getString(EXTRA_AUTH_ID));
+ AUTHORIZATIONS.remove(uuid);
+ break;
+
+ default:
+ break;
+ }
+ return true;
+ }
+ };
+
+ private static final long MAX_AUTH_LIVE_TIME = 20000L;
+ private static final int MSG_CLEAR_AUTHORIZATIONS = 1;
+ private static final String EXTRA_AUTH_ID = "auth_id";
+ private static final Handler CLEAR_AUTH_HANDLER = new Handler(CLEAR_AUTH_CALLBACK);
+
+ private static Map<UUID, AuthorizationResource> AUTHORIZATIONS =
+ (Map<UUID, AuthorizationResource>) Collections.synchronizedMap(
+ new HashMap<UUID, AuthorizationResource>());
+
+ private final ExecutorService mExecutorService = Executors.newFixedThreadPool(1);
+
+ /**
+ * This method creates an authorization uri for a file, but this not grants
+ * access to this file. Callers must explicitly call to grantAuthorization in
+ * order to set the package associated with this grant
+ *
+ * @param fso The file to authorize
+ * @return Uri The uri of this authorized resource
+ */
+ public static Uri createAuthorizationUri(RegularFile file) {
+ // Generate a new authorization for the filesystem
+ UUID uuid = null;
+ do {
+ uuid = UUID.randomUUID();
+ if (!AUTHORIZATIONS.containsKey(uuid)) {
+ AuthorizationResource resource = new AuthorizationResource(file);
+ AUTHORIZATIONS.put(uuid, resource);
+ break;
+ }
+ } while(true);
+
+ // Post a message to clear authorization after an interval of time
+ Message msg = Message.obtain(CLEAR_AUTH_HANDLER, MSG_CLEAR_AUTHORIZATIONS);
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_AUTH_ID, uuid.toString());
+ msg.setData(bundle);
+ CLEAR_AUTH_HANDLER.sendMessageDelayed(msg, MAX_AUTH_LIVE_TIME);
+ return createAuthorizationUri(uuid);
+ }
+
+ /**
+ * Method that register the {@link FileSystemObject} that allow external apps to access
+ * private files. An authorization MUST be explicit done by this app. Third party apps
+ * can register
+ *
+ * @param uri The authorized uri
+ * @param pkg The package to authorize
+ */
+ public static void grantAuthorizationUri(Uri uri, String pkg) {
+ // Check that exists that authorization
+ AuthorizationResource authResource = getAuthorizacionResourceForUri(uri);
+ if (authResource == null) {
+ throw new SecurityException("Authorization not exists");
+ }
+
+ // Check that the authorization doesn't was granted before
+ if (authResource.mPackage != null) {
+ throw new SecurityException("The authorization was granted before");
+ }
+
+ // And now grant the access
+ Log.i(TAG, "grant authorization of uri " + uri.toString() + " to package " + pkg);
+ authResource.mPackage = pkg;
+ }
+
+ /**
+ * Method that unregister un-granted authorizations.
+ *
+ * @param uri The authorized uri
+ */
+ public static AuthorizationResource revertAuthorization(Uri uri) {
+ // Check that exists that authorization
+ AuthorizationResource authResource = getAuthorizacionResourceForUri(uri);
+ if (authResource == null) {
+ throw new SecurityException("Authorization not exists");
+ }
+
+ // Check that the authorization was granted before
+ if (authResource.mPackage != null) {
+ throw new SecurityException("The authorization was granted before");
+ }
+
+ // And now remove the un-granted authorization
+ UUID uuid = UUID.fromString(uri.getLastPathSegment());
+ return AUTHORIZATIONS.remove(uuid);
+ }
+
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ // Retrieve the authorization
+ AuthorizationResource authResource = getAuthorizacionResourceForUri(uri);
+ if (authResource == null) {
+ throw new SecurityException("Authorization not exists");
+ }
+
+ // Create an in-memory cursor
+ String[] cols = new String[COLUMN_PROJECTION.length];
+ Object[] values = new Object[COLUMN_PROJECTION.length];
+ for (int i = 0; i < COLUMN_PROJECTION.length; i++) {
+ cols[i] = COLUMN_PROJECTION[i];
+ switch (i) {
+ case 0:
+ values[i] = uri.getLastPathSegment();
+ break;
+ case 1:
+ values[i] = authResource.mFile.getName();
+ break;
+ case 2:
+ values[i] = authResource.mFile.getSize();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ final MatrixCursor cursor = new MatrixCursor(cols, 1);
+ cursor.addRow(values);
+ return cursor;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ // Retrieve the authorization
+ AuthorizationResource authResource = getAuthorizacionResourceForUri(uri);
+ if (authResource == null) {
+ throw new SecurityException("Authorization not exists");
+ }
+
+ return MimeTypeHelper.getMimeType(getContext(), authResource.mFile);
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new SecurityException("Insert is not allowed");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new SecurityException("Delete is not allowed");
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new SecurityException("Update is not allowed");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ return this.openFile(uri, mode, null);
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode, final CancellationSignal signal)
+ throws FileNotFoundException {
+ // Retrieve the authorization
+ final AuthorizationResource authResource = getAuthorizacionResourceForUri(uri);
+ if (authResource == null) {
+ throw new SecurityException("Authorization not exists");
+ }
+
+ // Check that the request comes from the authorized package
+ String[] pkgs = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ if (pkgs == null) {
+ throw new SecurityException("Authorization denied. No packages");
+ }
+ boolean isPackageAuthorized = false;
+ for (String pkg : pkgs) {
+ if (pkg.equals(authResource.mPackage)) {
+ isPackageAuthorized = true;
+ break;
+ }
+ }
+ if (!isPackageAuthorized) {
+ throw new SecurityException("Authorization denied. Package mismatch");
+ }
+
+ // Open a pipe between the package and this provider
+ try {
+ final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe();
+ mExecutorService.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ AsyncReader reader = new AsyncReader(fds[0], fds[1], signal);
+ CommandHelper.read(getContext(), authResource.mFile.getFullPath(),
+ reader, null);
+ } catch (Exception e) {
+ Log.w(TAG, "Failure writing pipe. ", e);
+ }
+ }
+ });
+ return fds[0];
+
+ } catch (IOException ex) {
+ Log.w(TAG, "Failed to create pipe descriptors. ", ex);
+ }
+ return null;
+ }
+
+ /**
+ * Method that returns an authorization for the passed Uri.
+ *
+ * @param uri The uri to check
+ * @param revoke Whether revoke the grant
+ * @return AuthorizationResource The authorization resource or null if not there is not
+ * authorization
+ */
+ private static AuthorizationResource getAuthorizacionResourceForUri(Uri uri) {
+ UUID uuid = UUID.fromString(uri.getLastPathSegment());
+ if (uuid == null || !AUTHORIZATIONS.containsKey(uuid)) {
+ return null;
+ }
+ return AUTHORIZATIONS.get(uuid);
+ }
+
+ /**
+ * Method that returns an authorization URI from the authorization UUID
+ *
+ * @param uuid The UUID of the authorization
+ * @return Uri The authorization Uri
+ */
+ private static Uri createAuthorizationUri(UUID uuid) {
+ return Uri.withAppendedPath(Uri.parse(CONTENT_AUTHORITY),
+ uuid.toString());
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
index c789fca5..5f076548 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
@@ -36,13 +36,17 @@ 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.console.VirtualMountPointConsole;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
import com.cyanogenmod.filemanager.model.Bookmark;
+import com.cyanogenmod.filemanager.model.Directory;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.Symlink;
import com.cyanogenmod.filemanager.model.SystemFile;
import com.cyanogenmod.filemanager.preferences.AccessMode;
+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.policy.BookmarksActionPolicy;
@@ -57,6 +61,7 @@ import com.cyanogenmod.filemanager.ui.policy.NavigationActionPolicy;
import com.cyanogenmod.filemanager.ui.policy.NewActionPolicy;
import com.cyanogenmod.filemanager.ui.policy.PrintActionPolicy;
import com.cyanogenmod.filemanager.util.DialogHelper;
+import com.cyanogenmod.filemanager.util.ExceptionUtil;
import com.cyanogenmod.filemanager.util.FileHelper;
import com.cyanogenmod.filemanager.util.MimeTypeHelper;
import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
@@ -64,6 +69,7 @@ import com.cyanogenmod.filemanager.util.SelectionHelper;
import com.cyanogenmod.filemanager.util.StorageHelper;
import java.io.File;
+import java.io.InvalidClassException;
import java.util.ArrayList;
import java.util.List;
@@ -437,6 +443,19 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
this.mContext, this.mFso, this.mOnRequestRefreshListener);
break;
+ // Set as home
+ case R.id.mnu_actions_set_as_home:
+ case R.id.mnu_actions_global_set_as_home:
+ try {
+ Preferences.savePreference(
+ FileManagerSettings.SETTINGS_INITIAL_DIR, mFso.getFullPath(), true);
+ mOnRequestRefreshListener.onRequestBookmarksRefresh();
+ DialogHelper.showToast(mContext, R.string.msgs_success, Toast.LENGTH_SHORT);
+ } catch (InvalidClassException e) {
+ ExceptionUtil.translateException(mContext, e);
+ }
+ break;
+
default:
break;
}
@@ -651,8 +670,7 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
}
//- Print (only for text and image categories)
- if (category.compareTo(MimeTypeCategory.TEXT) != 0 &&
- category.compareTo(MimeTypeCategory.IMAGE) != 0) {
+ if (!PrintActionPolicy.isPrintedAllowed(mContext, mFso)) {
menu.removeItem(R.id.mnu_actions_print);
}
}
@@ -680,18 +698,21 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
}
}
//- Create link
- if (this.mGlobal && (selection == null || selection.size() == 0 || selection.size() > 1)) {
+ if (this.mGlobal && (selection == null || selection.size() == 0
+ || selection.size() > 1)) {
// Only when one item is selected
menu.removeItem(R.id.mnu_actions_create_link_global);
} else if (this.mGlobal && selection != null) {
- // Create link (not allow in storage volume)
+ // Create link (not allow in sdcard, secure or remote storage volumes)
FileSystemObject fso = selection.get(0);
- if (StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
- menu.removeItem(R.id.mnu_actions_create_link);
+ if (StorageHelper.isPathInStorageVolume(fso.getFullPath())
+ || fso.isSecure() || fso.isRemote()) {
+ menu.removeItem(R.id.mnu_actions_create_link_global);
}
} else if (!this.mGlobal) {
- // Create link (not allow in storage volume)
- if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) {
+ // Create link (not allow in sdcard, secure or remote storage volumes)
+ if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())
+ || mFso.isSecure() || mFso.isRemote()) {
menu.removeItem(R.id.mnu_actions_create_link);
}
}
@@ -702,10 +723,18 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
if (this.mGlobal) {
if (selection == null || selection.size() == 0) {
menu.removeItem(R.id.mnu_actions_compress_selection);
+ } else {
+ for (FileSystemObject fso : selection) {
+ // Ignore for system, secure or remote files
+ if (fso instanceof SystemFile || fso.isSecure() || fso.isRemote()) {
+ menu.removeItem(R.id.mnu_actions_compress_selection);
+ break;
+ }
+ }
}
} else {
- // Ignore for system files
- if (this.mFso instanceof SystemFile) {
+ // Ignore for system, secure or remote files
+ if (this.mFso instanceof SystemFile || mFso.isSecure() || mFso.isRemote()) {
menu.removeItem(R.id.mnu_actions_compress);
}
}
@@ -735,16 +764,68 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
}
}
+ // Shotcuts and Bookmarks (not available in virtual filesystems)
+ if (!mGlobal && (mFso.isSecure() || mFso.isRemote())) {
+ menu.removeItem(R.id.mnu_actions_add_shortcut);
+ menu.removeItem(R.id.mnu_actions_add_to_bookmarks);
+ } else if (mGlobal) {
+ if (selection != null && selection.size() > 0) {
+ for (FileSystemObject fso : selection) {
+ if (fso.isSecure() || fso.isRemote()) {
+ menu.removeItem(R.id.mnu_actions_add_shortcut_current_folder);
+ menu.removeItem(R.id.mnu_actions_add_to_bookmarks_current_folder);
+ break;
+ }
+ }
+ }
+ }
+
+ // Set as home
+ if (!mGlobal && !FileHelper.isDirectory(mFso)) {
+ menu.removeItem(R.id.mnu_actions_set_as_home);
+ } else if (mGlobal && (selection != null && selection.size() > 0)) {
+ menu.removeItem(R.id.mnu_actions_global_set_as_home);
+ }
+
// Not allowed in search
if (this.mSearch) {
menu.removeItem(R.id.mnu_actions_extract);
menu.removeItem(R.id.mnu_actions_compress);
menu.removeItem(R.id.mnu_actions_create_link);
+ } else {
+ // Not allowed if not in search
+ menu.removeItem(R.id.mnu_actions_open_parent_folder);
}
- // Not allowed if not in search
- if (!this.mSearch) {
- menu.removeItem(R.id.mnu_actions_open_parent_folder);
+ // Remove unsafe operations over virtual mountpoint directories
+ List<Directory> virtualDirs = VirtualMountPointConsole.getVirtualMountableDirectories();
+ if (!mGlobal && FileHelper.isDirectory(mFso) && virtualDirs.contains(mFso)) {
+ menu.removeItem(R.id.mnu_actions_delete);
+ menu.removeItem(R.id.mnu_actions_rename);
+ menu.removeItem(R.id.mnu_actions_compress);
+ menu.removeItem(R.id.mnu_actions_create_copy);
+ menu.removeItem(R.id.mnu_actions_create_link);
+ menu.removeItem(R.id.mnu_actions_add_shortcut);
+ menu.removeItem(R.id.mnu_actions_add_to_bookmarks);
+ } else if (mGlobal) {
+ if (selection != null && selection.size() > 0) {
+ for (FileSystemObject fso : selection) {
+ if (FileHelper.isDirectory(fso) && virtualDirs.contains(fso)) {
+ menu.removeItem(R.id.mnu_actions_paste_selection);
+ menu.removeItem(R.id.mnu_actions_move_selection);
+ menu.removeItem(R.id.mnu_actions_delete_selection);
+ menu.removeItem(R.id.mnu_actions_compress_selection);
+ menu.removeItem(R.id.mnu_actions_create_link_global);
+ menu.removeItem(R.id.mnu_actions_send_selection);
+ menu.removeItem(R.id.mnu_actions_create_link_global);
+ menu.removeItem(R.id.mnu_actions_create_link_global);
+ menu.removeItem(R.id.mnu_actions_create_link_global);
+ menu.removeItem(R.id.mnu_actions_add_shortcut_current_folder);
+ menu.removeItem(R.id.mnu_actions_add_to_bookmarks_current_folder);
+ break;
+ }
+ }
+ }
}
// Remove not-ChRooted actions (actions that can't be present when running in
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java
index cd689330..80d05c13 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java
@@ -480,7 +480,7 @@ public class AssociationsDialog implements OnItemClickListener {
if (intent != null) {
// Capture security exceptions
try {
- this.mContext.startActivity(intent);
+ mContext.startActivity(intent);
} catch (Exception e) {
ExceptionUtil.translateException(this.mContext, e);
}
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java
index f7b34bb7..b1e0219c 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java
@@ -123,7 +123,7 @@ public class ComputeChecksumDialog implements
title,
layout);
this.mDialog.setButton(
- DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this);
+ DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.ok), this);
// Start checksum compute
try {
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
index 7c63d0a0..392f36fd 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
@@ -213,13 +213,14 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
}
//Configure status switch
+ boolean isVirtual = this.mMountPoint.isVirtual();
boolean hasPrivileged = false;
try {
hasPrivileged = ConsoleBuilder.isPrivileged();
} catch (Throwable ex) {/**NON BLOCK**/}
boolean mountAllowed =
MountPointHelper.isMountAllowed(this.mMountPoint);
- if (this.mIsAdvancedMode) {
+ if (!isVirtual || this.mIsAdvancedMode) {
if (hasPrivileged) {
if (!mountAllowed) {
this.mInfoMsgView.setText(
@@ -236,7 +237,7 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
this.mInfoMsgView.setVisibility(View.GONE);
this.mInfoMsgView.setOnClickListener(null);
}
- this.mIsMountAllowed = hasPrivileged && mountAllowed && this.mIsAdvancedMode;
+ this.mIsMountAllowed = isVirtual || (hasPrivileged && mountAllowed && this.mIsAdvancedMode);
this.mSwStatus.setEnabled(this.mIsMountAllowed);
this.mSwStatus.setChecked(MountPointHelper.isReadWrite(this.mMountPoint));
@@ -339,8 +340,7 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
ret = CommandHelper.remount(
this.mContext,
this.mMountPoint, isChecked, null);
-
- if (ret) {
+ if (ret && !mMountPoint.isSecure()) {
Console bgConsole = FileManagerApplication.getBackgroundConsole();
if (bgConsole != null) {
ret = CommandHelper.remount(
@@ -367,6 +367,8 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
this.mInfoMsgView.setText(R.string.filesystem_info_mount_failed_msg);
this.mInfoMsgView.setVisibility(View.VISIBLE);
sw.setChecked(!isChecked);
+ } else if (mMountPoint.isSecure()) {
+ mDialog.dismiss();
}
break;
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java
index 975d335d..1ac6d001 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java
@@ -43,6 +43,7 @@ import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.commands.AsyncResultListener;
import com.cyanogenmod.filemanager.commands.FolderUsageExecutable;
import com.cyanogenmod.filemanager.console.ConsoleBuilder;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
import com.cyanogenmod.filemanager.model.AID;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.FolderUsage;
@@ -139,6 +140,7 @@ public class FsoPropertiesDialog
* @hide
*/
boolean mIgnoreCheckEvents;
+ private boolean mIsVirtual;
private boolean mHasPrivileged;
private final boolean mIsAdvancedMode;
@@ -335,13 +337,14 @@ public class FsoPropertiesDialog
}
// Check if permissions operations are allowed
+ mIsVirtual = VirtualMountPointConsole.isVirtualStorageResource(mFso.getFullPath());
try {
this.mHasPrivileged = ConsoleBuilder.getConsole(this.mContext).isPrivileged();
} catch (Throwable ex) {/**NON BLOCK**/}
this.mSpnOwner.setEnabled(this.mHasPrivileged);
this.mSpnGroup.setEnabled(this.mHasPrivileged);
// Not allowed for symlinks
- if (!(this.mFso instanceof Symlink)) {
+ if (!mIsVirtual && !(this.mFso instanceof Symlink)) {
setCheckBoxesPermissionsEnable(this.mChkUserPermission, this.mHasPrivileged);
setCheckBoxesPermissionsEnable(this.mChkGroupPermission, this.mHasPrivileged);
setCheckBoxesPermissionsEnable(this.mChkOthersPermission, this.mHasPrivileged);
@@ -350,7 +353,7 @@ public class FsoPropertiesDialog
setCheckBoxesPermissionsEnable(this.mChkGroupPermission, false);
setCheckBoxesPermissionsEnable(this.mChkOthersPermission, false);
}
- if (!this.mHasPrivileged && this.mIsAdvancedMode) {
+ if (!mIsVirtual && !this.mHasPrivileged && this.mIsAdvancedMode) {
this.mInfoMsgView.setVisibility(View.VISIBLE);
this.mInfoMsgView.setOnClickListener(this);
}
@@ -523,7 +526,8 @@ public class FsoPropertiesDialog
adjustSpinnerSize(this.mSpnGroup);
}
this.mInfoMsgView.setVisibility(
- this.mHasPrivileged || !this.mIsAdvancedMode ? View.GONE : View.VISIBLE);
+ mIsVirtual || this.mHasPrivileged || !this.mIsAdvancedMode
+ ? View.GONE : View.VISIBLE);
break;
case R.id.fso_info_msg:
@@ -1028,7 +1032,7 @@ public class FsoPropertiesDialog
void setMsg(String msg) {
this.mInfoMsgView.setText(msg);
this.mInfoMsgView.setVisibility(
- !this.mIsAdvancedMode || (this.mHasPrivileged && msg == null) ?
+ mIsVirtual || !this.mIsAdvancedMode || (this.mHasPrivileged && msg == null) ?
View.GONE :
View.VISIBLE);
}
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java
index ab4f8ec9..9ddc3002 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.text.Spanned;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
diff --git a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java
index 4d687dfe..01020622 100644
--- a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java
+++ b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java
@@ -32,8 +32,11 @@ import android.widget.Toast;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.activities.ShortcutActivity;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.RegularFile;
+import com.cyanogenmod.filemanager.providers.SecureResourceProvider;
+import com.cyanogenmod.filemanager.providers.SecureResourceProvider.AuthorizationResource;
import com.cyanogenmod.filemanager.ui.dialogs.AssociationsDialog;
import com.cyanogenmod.filemanager.util.DialogHelper;
import com.cyanogenmod.filemanager.util.ExceptionUtil;
@@ -99,11 +102,10 @@ public final class IntentsActionPolicy extends ActionsPolicy {
// Obtain the mime/type and passed it to intent
String mime = MimeTypeHelper.getMimeType(ctx, fso);
- File file = new File(fso.getFullPath());
if (mime != null) {
- intent.setDataAndType(getUriFromFile(ctx, file), mime);
+ intent.setDataAndType(getUriFromFile(ctx, fso), mime);
} else {
- intent.setData(getUriFromFile(ctx, file));
+ intent.setData(getUriFromFile(ctx, fso));
}
// Resolve the intent
@@ -140,7 +142,7 @@ public final class IntentsActionPolicy extends ActionsPolicy {
intent.setAction(android.content.Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType(MimeTypeHelper.getMimeType(ctx, fso));
- Uri uri = getUriFromFile(ctx, new File(fso.getFullPath()));
+ Uri uri = getUriFromFile(ctx, fso);
intent.putExtra(Intent.EXTRA_STREAM, uri);
// Resolve the intent
@@ -201,7 +203,7 @@ public final class IntentsActionPolicy extends ActionsPolicy {
lastMimeType = mimeType;
// Add the uri
- uris.add(getUriFromFile(ctx, new File(fso.getFullPath())));
+ uris.add(getUriFromFile(ctx, fso));
}
if (sameMimeType) {
intent.setType(lastMimeType);
@@ -291,6 +293,15 @@ public final class IntentsActionPolicy extends ActionsPolicy {
rie.activityInfo.packageName) == 0 &&
ri.activityInfo.name.compareTo(
rie.activityInfo.name) == 0) {
+
+ // Mark as internal
+ if (ri.activityInfo.metaData == null) {
+ ri.activityInfo.metaData = new Bundle();
+ ri.activityInfo.metaData.putString(
+ EXTRA_INTERNAL_ACTION, ii.getAction());
+ ri.activityInfo.metaData.putBoolean(
+ CATEGORY_INTERNAL_VIEWER, true);
+ }
exists = true;
break;
}
@@ -463,7 +474,8 @@ public final class IntentsActionPolicy extends ActionsPolicy {
ri.activityInfo.applicationInfo.packageName,
ri.activityInfo.name),
request);
- if (isInternalEditor(ri)) {
+ boolean isInternalEditor = isInternalEditor(ri);
+ if (isInternalEditor) {
String a = Intent.ACTION_VIEW;
if (ri.activityInfo.metaData != null) {
a = ri.activityInfo.metaData.getString(
@@ -476,10 +488,47 @@ public final class IntentsActionPolicy extends ActionsPolicy {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
+
+ // Grant access to resources if needed
+ grantSecureAccessIfNeeded(intent, ri);
+
return intent;
}
/**
+ * Method that add grant access to secure resources if needed
+ *
+ * @param intent The intent to grant access
+ * @param ri The resolved info associated with the intent
+ */
+ public static final void grantSecureAccessIfNeeded(Intent intent, ResolveInfo ri) {
+ // If this intent will be serve by the SecureResourceProvider then this uri must
+ // be granted before we start it, only for external apps. The internal editor
+ // must receive an file scheme uri
+ Uri uri = intent.getData();
+ String authority = null;
+ if (uri != null) {
+ authority = uri.getAuthority();
+ } else if (intent.getExtras() != null) {
+ uri = (Uri) intent.getExtras().get(Intent.EXTRA_STREAM);
+ authority = uri.getAuthority();
+ }
+ if (authority != null && authority.equals(SecureResourceProvider.AUTHORITY)) {
+ boolean isInternalEditor = isInternalEditor(ri);
+ if (isInternalEditor) {
+ // remove the authorization and change request to file scheme
+ AuthorizationResource auth = SecureResourceProvider.revertAuthorization(uri);
+ intent.setData(Uri.fromFile(new File(auth.mFile.getFullPath())));
+
+ } else {
+ // Grant access to the package
+ SecureResourceProvider.grantAuthorizationUri(uri,
+ ri.activityInfo.applicationInfo.packageName);
+ }
+ }
+ }
+
+ /**
* Method that returns an {@link Intent} from his {@link ComponentName}
*
* @param cn The ComponentName
@@ -593,7 +642,17 @@ public final class IntentsActionPolicy extends ActionsPolicy {
* @param ctx The current context
* @param file The file to resolve
*/
- private static Uri getUriFromFile(Context ctx, File file) {
+ private static Uri getUriFromFile(Context ctx, FileSystemObject fso) {
+ // If the passed object is secure file then we have to provide access with
+ // the internal resource provider
+ if (fso.isSecure() && SecureConsole.isVirtualStorageResource(fso.getFullPath())
+ && fso instanceof RegularFile) {
+ RegularFile file = (RegularFile) fso;
+ return SecureResourceProvider.createAuthorizationUri(file);
+ }
+
+ // Try to resolve media data or return a file uri
+ final File file = new File(fso.getFullPath());
ContentResolver cr = ctx.getContentResolver();
Uri uri = MediaHelper.fileToContentUri(cr, file);
if (uri == null) {
diff --git a/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java
index 0f998739..6f7087c8 100644
--- a/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java
+++ b/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java
@@ -41,9 +41,13 @@ import android.util.Log;
import android.widget.Toast;
import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ReadExecutable;
import com.cyanogenmod.filemanager.model.FileSystemObject;
+import com.cyanogenmod.filemanager.util.CommandHelper;
import com.cyanogenmod.filemanager.util.DialogHelper;
import com.cyanogenmod.filemanager.util.ExceptionUtil;
+import com.cyanogenmod.filemanager.util.FileHelper;
import com.cyanogenmod.filemanager.util.MimeTypeHelper;
import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
import com.cyanogenmod.filemanager.util.StringHelper;
@@ -51,11 +55,12 @@ import com.cyanogenmod.filemanager.util.StringHelper;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
@@ -67,6 +72,23 @@ public final class PrintActionPolicy extends ActionsPolicy {
private static final String TAG = "PrintActionPolicy"; //$NON-NLS-1$
+ private static final String PDF_FILE_EXT = "pdf";
+
+ /**
+ * Method that returns if the {@code FileSystemObject} can be printed
+ *
+ * @param ctx The current context
+ * @param fso The fso to check
+ * @return boolean If the fso can be printed
+ */
+ public static boolean isPrintedAllowed(Context ctx, FileSystemObject fso) {
+ MimeTypeCategory category = MimeTypeHelper.getCategory(ctx, fso);
+ String extension = FileHelper.getExtension(fso);
+ return category.compareTo(MimeTypeCategory.TEXT) == 0
+ || category.compareTo(MimeTypeCategory.IMAGE) == 0
+ || (extension != null && extension.toLowerCase().equals(PDF_FILE_EXT));
+ }
+
/**
* Method that prints the passed document
*
@@ -83,335 +105,446 @@ public final class PrintActionPolicy extends ActionsPolicy {
printImage(ctx, fso);
return;
}
+ String ext = FileHelper.getExtension(fso);
+ if (ext != null && ext.toLowerCase().equals(PDF_FILE_EXT)) {
+ printPdfDocument(ctx, fso);
+ return;
+ }
DialogHelper.showToast(ctx, R.string.print_unsupported_document, Toast.LENGTH_SHORT);
}
+ public static abstract class DocumentAdapterReader {
+ /**
+ * Read the document to an string array
+ *
+ * @param lines The array where to put the document
+ * @param adjustedLines The array where to put the document
+ */
+ public abstract void read(List<String> lines, List<String> adjustedLines);
+
+ /**
+ * Read the document mode [0-Invalid; 1-Text; 2-Binary]
+ *
+ * @return int The document mode
+ */
+ public abstract int getDocumentMode();
+ }
+
/**
- * Method that prints the document as a text document
- *
- * @param ctx The current context
- * @param fso The document to print
+ * A document adapter
*/
- private static void printTextDocument(final Context ctx, final FileSystemObject document) {
- final int printPageMargins = ctx.getResources().getDimensionPixelSize(
- R.dimen.print_page_margins);
+ private static class DocumentAdapter extends PrintDocumentAdapter {
+ private PrintAttributes mAttributes;
+ private Paint mPaint;
+ private RectF mTextBounds;
+ private List<String> mLines;
+ private List<String> mAdjustedLines;
- PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE);
- PrintAttributes attr = new PrintAttributes.Builder()
- .setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT)
- .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
- .build();
- printManager.print(document.getName(), new PrintDocumentAdapter() {
- private PrintAttributes mAttributes;
- private Paint mPaint;
- private RectF mTextBounds;
- private boolean mIsBinaryDocument;
- private List<String> mLines;
- private List<String> mAdjustedLines;
+ private static final int MILS_PER_INCH = 1000;
+ private static final int POINTS_IN_INCH = 72;
- private static final int MILS_PER_INCH = 1000;
- private static final int POINTS_IN_INCH = 72;
+ private final Context mCtx;
+ private final FileSystemObject mDocument;
+ private final int mPrintPageMargin;
+ private final DocumentAdapterReader mReader;
- @Override
- public void onStart() {
- super.onStart();
+ public DocumentAdapter(Context ctx, FileSystemObject document,
+ DocumentAdapterReader reader) {
+ super();
+ mCtx = ctx;
+ mDocument = document;
+ mPrintPageMargin = ctx.getResources().getDimensionPixelSize(
+ R.dimen.print_page_margins);
+ mReader = reader;
+ }
- // Create the paint used for draw text
- Typeface courier = Typeface.createFromAsset(ctx.getAssets(),
- "fonts/Courier-Prime.ttf");
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setTypeface(courier);
- mPaint.setTextSize(ctx.getResources().getDimensionPixelSize(
- R.dimen.print_text_size));
- mPaint.setColor(Color.BLACK);
+ @Override
+ public void onStart() {
+ super.onStart();
- // Get the text width and height
- mTextBounds = new RectF();
- mTextBounds.right = mPaint.measureText(new char[]{'A'}, 0, 1);
- mTextBounds.bottom = mPaint.getFontMetrics().descent
- - mPaint.getFontMetrics().ascent + mPaint.getFontMetrics().leading;
+ // Create the paint used for draw text
+ Typeface courier = Typeface.createFromAsset(mCtx.getAssets(),
+ "fonts/Courier-Prime.ttf");
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaint.setTypeface(courier);
+ mPaint.setTextSize(mCtx.getResources().getDimensionPixelSize(
+ R.dimen.print_text_size));
+ mPaint.setColor(Color.BLACK);
- mLines = new ArrayList<String>();
- readFile();
- }
+ // Get the text width and height
+ mTextBounds = new RectF();
+ mTextBounds.right = mPaint.measureText(new char[]{'A'}, 0, 1);
+ mTextBounds.bottom = mPaint.getFontMetrics().descent
+ - mPaint.getFontMetrics().ascent + mPaint.getFontMetrics().leading;
- @Override
- public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
- CancellationSignal cancellationSignal, WriteResultCallback callback) {
- PrintedPdfDocument pdfDocument = new PrintedPdfDocument(ctx,
- mAttributes);
- try {
- Rect pageContentRect = getContentRect(mAttributes);
- int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width());
- int rowsPerPage = rowsPerPage(pageContentRect);
-
- int currentPage = 0;
- int currentLine = 0;
- Page page = null;
- if (mAdjustedLines.size() > 0) {
- page = pdfDocument.startPage(currentPage++);
- printHeader(ctx, page, pageContentRect, charsPerRow);
- }
- // Top (with margin) + header
- float top = pageContentRect.top + (mTextBounds.height() * 2);
- for (String line : mAdjustedLines) {
- currentLine++;
- page.getCanvas().drawText(line, pageContentRect.left,
- top + (currentLine * mTextBounds.height()), mPaint);
-
- if (currentLine >= rowsPerPage) {
- if (page != null) {
- printFooter(ctx, page, pageContentRect, currentPage);
- pdfDocument.finishPage(page);
- }
- currentLine = 0;
- page = pdfDocument.startPage(currentPage++);
- printHeader(ctx, page, pageContentRect, charsPerRow);
+ mLines = new ArrayList<String>();
+ mAdjustedLines = new ArrayList<String>();
+ mReader.read(mLines, mAdjustedLines);
+ }
+
+ @Override
+ public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
+ CancellationSignal cancellationSignal, WriteResultCallback callback) {
+ PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mCtx,
+ mAttributes);
+ try {
+ Rect pageContentRect = getContentRect(mAttributes);
+ int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width());
+ int rowsPerPage = rowsPerPage(pageContentRect);
+
+ int currentPage = 0;
+ int currentLine = 0;
+ Page page = null;
+ if (mAdjustedLines.size() > 0) {
+ page = pdfDocument.startPage(currentPage++);
+ printHeader(mCtx, page, pageContentRect, charsPerRow);
+ }
+ // Top (with margin) + header
+ float top = pageContentRect.top + (mTextBounds.height() * 2);
+ for (String line : mAdjustedLines) {
+ currentLine++;
+ page.getCanvas().drawText(line, pageContentRect.left,
+ top + (currentLine * mTextBounds.height()), mPaint);
+
+ if (currentLine >= rowsPerPage) {
+ if (page != null) {
+ printFooter(mCtx, page, pageContentRect, currentPage);
+ pdfDocument.finishPage(page);
}
+ currentLine = 0;
+ page = pdfDocument.startPage(currentPage++);
+ printHeader(mCtx, page, pageContentRect, charsPerRow);
}
+ }
- // Finish the last page
- printFooter(ctx, page, pageContentRect, currentPage);
+ // Finish the last page
+ if (page != null) {
+ printFooter(mCtx, page, pageContentRect, currentPage);
pdfDocument.finishPage(page);
+ } else {
+ page = pdfDocument.startPage(1);
+ printHeader(mCtx, page, pageContentRect, charsPerRow);
+ printFooter(mCtx, page, pageContentRect, currentPage);
+ pdfDocument.finishPage(page);
+ }
- try {
- // Write the document
- pdfDocument.writeTo(new FileOutputStream(destination.getFileDescriptor()));
+ try {
+ // Write the document
+ pdfDocument.writeTo(new FileOutputStream(destination.getFileDescriptor()));
- // Done
- callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
+ // Done
+ callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
+ } catch (IOException ioe) {
+ // Failed.
+ ExceptionUtil.translateException(mCtx, ioe);
+ callback.onWriteFailed("Failed to print image");
+ }
+ } finally {
+ if (destination != null) {
+ try {
+ destination.close();
} catch (IOException ioe) {
- // Failed.
- ExceptionUtil.translateException(ctx, ioe);
- callback.onWriteFailed(null);
- }
- } finally {
- if (destination != null) {
- try {
- destination.close();
- } catch (IOException ioe) {
- /* ignore */
- }
+ /* ignore */
}
}
}
+ }
- @Override
- public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- CancellationSignal cancellationSignal, LayoutResultCallback callback,
- Bundle extras) {
-
- mAttributes = newAttributes;
- Rect pageContentRect = getContentRect(newAttributes);
- int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width());
- int rowsPerPage = rowsPerPage(pageContentRect);
- adjustLines(pageContentRect, charsPerRow);
+ @Override
+ public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ CancellationSignal cancellationSignal, LayoutResultCallback callback,
+ Bundle extras) {
- PrintDocumentInfo info = new PrintDocumentInfo.Builder(document.getName())
- .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
- .setPageCount(calculatePageCount(rowsPerPage))
- .build();
- info.setDataSize(document.getSize());
- boolean changed = !newAttributes.equals(oldAttributes);
- callback.onLayoutFinished(info, changed);
+ // Check if document is valid
+ if (mReader.getDocumentMode() == 0) {
+ callback.onLayoutFailed("Failed to read document");
+ return;
}
- private Rect getContentRect(PrintAttributes attributes) {
- MediaSize mediaSize = attributes.getMediaSize();
-
- // Compute the size of the target canvas from the attributes.
- int pageWidth = (int) (((float) mediaSize.getWidthMils() / MILS_PER_INCH)
- * POINTS_IN_INCH);
- int pageHeight = (int) (((float) mediaSize.getHeightMils() / MILS_PER_INCH)
- * POINTS_IN_INCH);
-
- // Compute the content size from the attributes.
- Margins minMargins = attributes.getMinMargins();
- final int marginLeft = (int) (((float) minMargins.getLeftMils() / MILS_PER_INCH)
- * POINTS_IN_INCH);
- final int marginTop = (int) (((float) minMargins.getTopMils() / MILS_PER_INCH)
- * POINTS_IN_INCH);
- final int marginRight = (int) (((float) minMargins.getRightMils() / MILS_PER_INCH)
- * POINTS_IN_INCH);
- final int marginBottom = (int) (((float) minMargins.getBottomMils() / MILS_PER_INCH)
- * POINTS_IN_INCH);
- return new Rect(
- Math.max(marginLeft, printPageMargins),
- Math.max(marginTop, printPageMargins),
- pageWidth - Math.max(marginRight, printPageMargins),
- pageHeight - Math.max(marginBottom, printPageMargins));
- }
-
- private void printHeader(Context ctx, Page page, Rect pageContentRect,
- int charsPerRow) {
- String header = ctx.getString(R.string.print_document_header, document.getName());
- if (header.length() >= charsPerRow) {
- header = header.substring(header.length() - 3) + "...";
- }
- page.getCanvas().drawText(header,
- (int) (pageContentRect.width() / 2) - (mPaint.measureText(header) / 2),
- pageContentRect.top + mTextBounds.height(), mPaint);
+ if (cancellationSignal.isCanceled()) {
+ callback.onLayoutCancelled();
+ return;
}
+ mAttributes = newAttributes;
+ Rect pageContentRect = getContentRect(newAttributes);
+ int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width());
+ int rowsPerPage = rowsPerPage(pageContentRect);
+ adjustLines(pageContentRect, charsPerRow);
- private void printFooter(Context ctx, Page page, Rect pageContentRect, int pageNumber) {
- String footer = ctx.getString(R.string.print_document_footer, pageNumber);
- page.getCanvas().drawText(footer,
- (int) (pageContentRect.width() / 2) - (mPaint.measureText(footer) / 2),
- pageContentRect.bottom - mTextBounds.height(), mPaint);
- }
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(mDocument.getName())
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(calculatePageCount(rowsPerPage))
+ .build();
+ info.setDataSize(mDocument.getSize());
+ boolean changed = !newAttributes.equals(oldAttributes);
+ callback.onLayoutFinished(info, changed);
+ }
- private void adjustLines(Rect pageRect, int charsPerRow) {
- if (mIsBinaryDocument) {
- return;
- }
- mAdjustedLines = new ArrayList<String>(mLines);
- for (int i = 0; i < mAdjustedLines.size(); i++) {
- String line = mAdjustedLines.get(i);
- if (line.length() > charsPerRow) {
- int prevSpace = line.lastIndexOf(" ", charsPerRow);
- if (prevSpace != -1) {
- // Split in the previous word
- String currentLine = line.substring(0, prevSpace + 1);
- String nextLine = line.substring(prevSpace + 1);
- mAdjustedLines.set(i, currentLine);
- mAdjustedLines.add(i + 1, nextLine);
- } else {
- // Just split at margin
- String currentLine = line.substring(0, charsPerRow);
- String nextLine = line.substring(charsPerRow);
- mAdjustedLines.set(i, currentLine);
- mAdjustedLines.add(i + 1, nextLine);
- }
- }
- }
- }
+ private Rect getContentRect(PrintAttributes attributes) {
+ MediaSize mediaSize = attributes.getMediaSize();
- private int calculatePageCount(int rowsPerPage) {
- int pages = mAdjustedLines.size() / rowsPerPage;
- return pages <= 0 ? PrintDocumentInfo.PAGE_COUNT_UNKNOWN : pages;
- }
+ // Compute the size of the target canvas from the attributes.
+ int pageWidth = (int) (((float) mediaSize.getWidthMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ int pageHeight = (int) (((float) mediaSize.getHeightMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
- private int rowsPerPage(Rect pageContentRect) {
- // Text height - header - footer
- return (int) ((pageContentRect.height() / mTextBounds.height()) - 4);
- }
+ // Compute the content size from the attributes.
+ Margins minMargins = attributes.getMinMargins();
+ final int marginLeft = (int) (((float) minMargins.getLeftMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ final int marginTop = (int) (((float) minMargins.getTopMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ final int marginRight = (int) (((float) minMargins.getRightMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ final int marginBottom = (int) (((float) minMargins.getBottomMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ return new Rect(
+ Math.max(marginLeft, mPrintPageMargin),
+ Math.max(marginTop, mPrintPageMargin),
+ pageWidth - Math.max(marginRight, mPrintPageMargin),
+ pageHeight - Math.max(marginBottom, mPrintPageMargin));
+ }
- private void readFile() {
- mIsBinaryDocument = isBinaryDocument();
- if (mIsBinaryDocument) {
- readHexDumpDocumentFile();
- } else {
- readDocumentFile();
- }
+ private void printHeader(Context ctx, Page page, Rect pageContentRect,
+ int charsPerRow) {
+ String header = ctx.getString(R.string.print_document_header, mDocument.getName());
+ if (header.length() >= charsPerRow) {
+ header = header.substring(header.length() - 3) + "...";
}
+ page.getCanvas().drawText(header,
+ (int) (pageContentRect.width() / 2) - (mPaint.measureText(header) / 2),
+ pageContentRect.top + mTextBounds.height(), mPaint);
+ }
- private boolean isBinaryDocument() {
- BufferedReader br = null;
- try {
- br = new BufferedReader(new FileReader(document.getFullPath()));
- char[] data = new char[50];
- int read = br.read(data);
- for (int i = 0; i < read; i++) {
- if (!StringHelper.isPrintableCharacter(data[i])) {
- return true;
- }
- }
- } catch (IOException ex) {
- //Ignore
- } finally {
- if (br != null) {
- try {
- br.close();
- } catch (IOException ex) {
- //Ignore
- }
+ private void printFooter(Context ctx, Page page, Rect pageContentRect, int pageNumber) {
+ String footer = ctx.getString(R.string.print_document_footer, pageNumber);
+ page.getCanvas().drawText(footer,
+ (int) (pageContentRect.width() / 2) - (mPaint.measureText(footer) / 2),
+ pageContentRect.bottom - mTextBounds.height(), mPaint);
+ }
+
+ private void adjustLines(Rect pageRect, int charsPerRow) {
+ if (mReader.getDocumentMode() == 2) {
+ return;
+ }
+ mAdjustedLines = new ArrayList<String>(mLines);
+ for (int i = 0; i < mAdjustedLines.size(); i++) {
+ String line = mAdjustedLines.get(i);
+ if (line.length() > charsPerRow) {
+ int prevSpace = line.lastIndexOf(" ", charsPerRow);
+ if (prevSpace != -1) {
+ // Split in the previous word
+ String currentLine = line.substring(0, prevSpace + 1);
+ String nextLine = line.substring(prevSpace + 1);
+ mAdjustedLines.set(i, currentLine);
+ mAdjustedLines.add(i + 1, nextLine);
+ } else {
+ // Just split at margin
+ String currentLine = line.substring(0, charsPerRow);
+ String nextLine = line.substring(charsPerRow);
+ mAdjustedLines.set(i, currentLine);
+ mAdjustedLines.add(i + 1, nextLine);
}
}
- return false;
}
+ }
+
+ private int calculatePageCount(int rowsPerPage) {
+ int pages = mAdjustedLines.size() / rowsPerPage;
+ return pages <= 0 ? PrintDocumentInfo.PAGE_COUNT_UNKNOWN : pages;
+ }
+
+ private int rowsPerPage(Rect pageContentRect) {
+ // Text height - header - footer
+ return (int) ((pageContentRect.height() / mTextBounds.height()) - 4);
+ }
+ }
- private void readDocumentFile() {
+ /**
+ * Method that prints the document from a string buffer
+ *
+ * @param ctx The current context
+ * @param fso The document to print
+ * @param sb The buffer to print
+ * @param adjustLines If document must be adjusted
+ */
+ public static void printStringDocument(final Context ctx, final FileSystemObject document,
+ final StringBuilder sb) {
+ PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE);
+ PrintAttributes attr = new PrintAttributes.Builder()
+ .setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT)
+ .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
+ .build();
+ final DocumentAdapterReader reader = new DocumentAdapterReader() {
+ @Override
+ public void read(List<String> lines, List<String> adjustedLines) {
BufferedReader br = null;
try {
- br = new BufferedReader(new FileReader(document.getFullPath()));
+ int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size);
+ br = new BufferedReader(new StringReader(sb.toString()), bufferSize);
String line = null;
- while((line = br.readLine()) != null) {
- mLines.add(line);
+ while ((line = br.readLine()) != null) {
+ lines.add(line);
}
+
} catch (IOException ex) {
- mLines.clear();
Log.e(TAG, "Failed to read file " + document.getFullPath(), ex);
+ lines.clear();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException ex) {
- //Ignore
+ // Ignore
}
}
}
}
- private void readHexDumpDocumentFile() {
- InputStream is = null;
- ByteArrayOutputStream baos;
+ @Override
+ public int getDocumentMode() {
+ // Always is text
+ return 1;
+ }
+ };
+ printManager.print(document.getName(), new DocumentAdapter(ctx, document, reader), attr);
+ }
+
+ /**
+ * Method that prints the document as a text document
+ *
+ * @param ctx The current context
+ * @param fso The document to print
+ */
+ private static void printTextDocument(final Context ctx, final FileSystemObject document) {
+ PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE);
+ PrintAttributes attr = new PrintAttributes.Builder()
+ .setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT)
+ .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
+ .build();
+ final DocumentAdapterReader reader = new DocumentAdapterReader() {
+ private int mDocumentMode = -1;
+
+ @Override
+ public void read(List<String> lines, List<String> adjustedLines) {
+ mDocumentMode = getDocumentMode();
+ if (mDocumentMode <= 0) {
+ lines.clear();
+ } else if (mDocumentMode == 2) {
+ adjustedLines.addAll(readHexDumpDocumentFile(ctx, document, lines));
+ } else {
+ readDocumentFile(ctx, document, lines);
+ }
+ }
+
+ @Override
+ public int getDocumentMode() {
+ if (mDocumentMode == -1) {
+ String mimeType = MimeTypeHelper.getMimeType(ctx, document);
+ if (mimeType == null) {
+ mDocumentMode = 0; // Invalid
+ } else {
+ mDocumentMode = isBinaryDocument(ctx, document) ? 2 : 1; // binary / text
+ }
+ }
+ return mDocumentMode;
+ }
+ };
+ printManager.print(document.getName(), new DocumentAdapter(ctx, document, reader), attr);
+ }
+
+ /**
+ * Method that prints the document as a Pdf
+ *
+ * @param ctx The current context
+ * @param fso The pdf to print
+ */
+ private static void printPdfDocument(final Context ctx, final FileSystemObject document) {
+ PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE);
+ PrintAttributes.MediaSize mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
+ PrintAttributes attr = new PrintAttributes.Builder()
+ .setMediaSize(mediaSize)
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ printManager.print(document.getName(), new PrintDocumentAdapter() {
+ @Override
+ public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
+ CancellationSignal cancellationSignal, WriteResultCallback callback) {
+ FileInputStream fis = null;
+ FileOutputStream fos = null;
+ AsyncDocumentReader reader = null;
+
try {
- int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size);
+ // Try first with java.io before using pipes
- baos = new ByteArrayOutputStream();
- is = new BufferedInputStream(new FileInputStream(document.getFullPath()));
+ File file = new File(document.getFullPath());
+ if (file.isFile() && file.canRead()) {
+ fis = new FileInputStream(file);
+ } else {
+ reader = new AsyncDocumentReader(ctx);
+ CommandHelper.read(ctx, document.getFullPath(), reader, null);
+ fis = reader.mIn;
+ }
+ fos = new FileOutputStream(destination.getFileDescriptor());
+
+ // Write the document
+ int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size);
byte[] data = new byte[bufferSize];
int read = 0;
- while((read = is.read(data, 0, bufferSize)) != -1) {
- baos.write(data, 0, read);
+ while ((read = fis.read(data)) > 0) {
+ fos.write(data, 0, read);
}
- } catch (IOException ex) {
- mLines.clear();
- Log.e(TAG, "Failed to read file " + document.getFullPath(), ex);
- return;
+
+ // All was ok
+ callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
+
+ } catch (Exception ex) {
+ // Failed.
+ ExceptionUtil.translateException(ctx, ex);
+ callback.onWriteFailed("Failed to print image");
+
} finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ex) {
- //Ignore
+ try {
+ if (fis != null) {
+ fis.close();
}
+ } catch (IOException e) {
+ // Ignore
}
- }
-
- // Convert the bytes to a hex printable string and free resources
- String documentBuffer = StringHelper.toHexPrintableString(baos.toByteArray());
- try {
- baos.close();
- } catch (IOException ex) {
- //Ignore
- }
-
- BufferedReader br = null;
- try {
- br = new BufferedReader(new StringReader(documentBuffer));
- String line = null;
- while((line = br.readLine()) != null) {
- mLines.add(line);
+ try {
+ if (fos != null) {
+ fos.close();
+ }
+ } catch (IOException e) {
+ // Ignore
}
- } catch (IOException ex) {
- mLines.clear();
- Log.e(TAG, "Failed to read file " + document.getFullPath(), ex);
- } finally {
- if (br != null) {
+ if (reader != null && reader.mIn != null) {
try {
- br.close();
+ reader.mIn.close();
} catch (IOException ex) {
//Ignore
}
}
}
-
- // Use the final array and clear the original (we don't use it anymore)
- mAdjustedLines = new ArrayList<String>(mLines);
- mLines.clear();
}
+ @Override
+ public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ CancellationSignal cancellationSignal, LayoutResultCallback callback,
+ Bundle extras) {
+
+ if (cancellationSignal.isCanceled()) {
+ callback.onLayoutCancelled();
+ return;
+ }
+
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(document.getName())
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .build();
+ boolean changed = !newAttributes.equals(oldAttributes);
+ callback.onLayoutFinished(info, changed);
+ }
}, attr);
}
@@ -428,13 +561,52 @@ public final class PrintActionPolicy extends ActionsPolicy {
return;
}
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inPreferredConfig = Bitmap.Config.RGB_565;
- final Bitmap bitmap = BitmapFactory.decodeFile(image.getFullPath(), options);
+ Bitmap bitmap = null;
+ AsyncDocumentReader reader = null;
+ try {
+ // Try first with java.io before using pipes
+ File file = new File(image.getFullPath());
+ if (file.isFile() && file.canRead()) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ bitmap = BitmapFactory.decodeFile(image.getFullPath(), options);
+ } else {
+ reader = new AsyncDocumentReader(ctx);
+ CommandHelper.read(ctx, image.getFullPath(), reader, null);
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ bitmap = BitmapFactory.decodeStream(reader.mIn);
+ }
+ if (bitmap == null) {
+ throw new IOException("Failed to load image");
+ }
+
+ } catch (Exception ex) {
+ ExceptionUtil.translateException(ctx, ex);
+ return;
+ } finally {
+ if (reader != null && reader.mIn != null) {
+ try {
+ reader.mIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ if (reader != null && reader.mFdIn != null) {
+ try {
+ reader.mFdIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ }
+
+ final Bitmap fBitmap = bitmap;
PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE);
PrintAttributes.MediaSize mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
- if (bitmap.getWidth() > bitmap.getHeight()) {
+ if (fBitmap.getWidth() > fBitmap.getHeight()) {
mediaSize = PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE;
}
PrintAttributes attr = new PrintAttributes.Builder()
@@ -451,13 +623,11 @@ public final class PrintActionPolicy extends ActionsPolicy {
mAttributes);
try {
Page page = pdfDocument.startPage(1);
-
RectF content = new RectF(page.getInfo().getContentRect());
-
- Matrix matrix = getMatrix(bitmap.getWidth(), bitmap.getHeight(), content);
+ Matrix matrix = getMatrix(fBitmap.getWidth(), fBitmap.getHeight(), content);
// Draw the bitmap.
- page.getCanvas().drawBitmap(bitmap, matrix, null);
+ page.getCanvas().drawBitmap(fBitmap, matrix, null);
// Finish the page.
pdfDocument.finishPage(page);
@@ -471,7 +641,7 @@ public final class PrintActionPolicy extends ActionsPolicy {
} catch (IOException ioe) {
// Failed.
ExceptionUtil.translateException(ctx, ioe);
- callback.onWriteFailed(null);
+ callback.onWriteFailed("Failed to print image");
}
} finally {
if (pdfDocument != null) {
@@ -492,6 +662,10 @@ public final class PrintActionPolicy extends ActionsPolicy {
CancellationSignal cancellationSignal, LayoutResultCallback callback,
Bundle extras) {
+ if (cancellationSignal.isCanceled()) {
+ callback.onLayoutCancelled();
+ return;
+ }
mAttributes = newAttributes;
PrintDocumentInfo info = new PrintDocumentInfo.Builder(image.getName())
@@ -505,8 +679,8 @@ public final class PrintActionPolicy extends ActionsPolicy {
@Override
public void onFinish() {
super.onFinish();
- if (bitmap != null) {
- bitmap.recycle();
+ if (fBitmap != null) {
+ fBitmap.recycle();
}
}
@@ -546,4 +720,305 @@ public final class PrintActionPolicy extends ActionsPolicy {
}
return bitmap != null;
}
-} \ No newline at end of file
+
+ /**
+ * Method that checks if the file has a binary format
+ *
+ * @param ctx The current context
+ * @param document The document to read
+ * @return boolean If the document has a binary format
+ */
+ private static boolean isBinaryDocument(Context ctx, FileSystemObject document) {
+ BufferedReader br = null;
+ boolean binary = false;
+ AsyncDocumentReader reader = null;
+ try {
+ reader = new AsyncDocumentReader(ctx);
+ ReadExecutable command = CommandHelper.read(ctx, document.getFullPath(), reader, null);
+ br = new BufferedReader(new InputStreamReader(reader.mIn));
+
+ char[] data = new char[50];
+ int read = br.read(data);
+ for (int i = 0; i < read; i++) {
+ if (!StringHelper.isPrintableCharacter(data[i])) {
+ binary = true;
+ break;
+ }
+ }
+ command.cancel();
+
+ } catch (Exception ex) {
+ //Ignore
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ if (reader != null && reader.mIn != null) {
+ try {
+ reader.mIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ if (reader != null && reader.mFdIn != null) {
+ try {
+ reader.mFdIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ }
+ return binary;
+ }
+
+ /**
+ * Read a file as document
+ *
+ * @param ctx The current context
+ * @param document The document to read
+ * @param lines The output
+ */
+ private static void readDocumentFile(Context ctx, FileSystemObject document,
+ List<String> lines) {
+ BufferedReader br = null;
+ AsyncDocumentReader reader = null;
+ try {
+ // Async read the document while blocking with a buffered reader
+ int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size);
+ reader = new AsyncDocumentReader(ctx);
+ CommandHelper.read(ctx, document.getFullPath(), reader, null);
+ br = new BufferedReader(new InputStreamReader(reader.mIn), bufferSize);
+
+ String line = null;
+ while((line = br.readLine()) != null) {
+ lines.add(line);
+ }
+
+ // Got an exception?
+ if (reader.mCause != null) {
+ lines.clear();
+ Log.e(TAG, "Failed to read file " + document.getFullPath(), reader.mCause);
+ }
+
+ } catch (Exception ex) {
+ lines.clear();
+ Log.e(TAG, "Failed to read file " + document.getFullPath(), ex);
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ if (reader != null && reader.mIn != null) {
+ try {
+ reader.mIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ if (reader != null && reader.mFdIn != null) {
+ try {
+ reader.mFdIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Read a file as hex document
+ *
+ * @param ctx The current context
+ * @param document The document to read
+ * @param lines The internal output
+ * @return output The output
+ */
+ private static List<String> readHexDumpDocumentFile(Context ctx, FileSystemObject document,
+ List<String> lines) {
+ InputStream is = null;
+ ByteArrayOutputStream baos;
+ AsyncDocumentReader reader = null;
+ try {
+ // Async read the document while blocking with a buffered stream
+ reader = new AsyncDocumentReader(ctx);
+ CommandHelper.read(ctx, document.getFullPath(), reader, null);
+
+ int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size);
+ baos = new ByteArrayOutputStream();
+ is = new BufferedInputStream(reader.mIn);
+
+ byte[] data = new byte[bufferSize];
+ int read = 0;
+ while((read = is.read(data, 0, bufferSize)) != -1) {
+ baos.write(data, 0, read);
+ }
+
+ // Got an exception?
+ if (reader.mCause != null) {
+ lines.clear();
+ Log.e(TAG, "Failed to read file " + document.getFullPath(), reader.mCause);
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to read file " + document.getFullPath(), ex);
+ lines.clear();
+ return new ArrayList<String>();
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ if (reader != null && reader.mIn != null) {
+ try {
+ reader.mIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ if (reader != null && reader.mFdIn != null) {
+ try {
+ reader.mFdIn.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ }
+
+ // Convert the bytes to a hex printable string and free resources
+ String documentBuffer = StringHelper.toHexPrintableString(baos.toByteArray());
+ try {
+ baos.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new StringReader(documentBuffer));
+ String line = null;
+ while((line = br.readLine()) != null) {
+ lines.add(line);
+ }
+ } catch (IOException ex) {
+ lines.clear();
+ Log.e(TAG, "Failed to read file " + document.getFullPath(), ex);
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException ex) {
+ //Ignore
+ }
+ }
+ }
+
+ // Use the final array and clear the original (we don't use it anymore)
+ List<String> output = new ArrayList<String>(lines);
+ lines.clear();
+ return output;
+ }
+
+ /**
+ * An implementation of an {@code AsyncResultListener} based on pipes for readers
+ */
+ private static class AsyncDocumentReader implements AsyncResultListener {
+
+ final FileInputStream mIn;
+ private final FileOutputStream mOut;
+ final ParcelFileDescriptor mFdIn;
+ private final ParcelFileDescriptor mFdOut;
+ Exception mCause;
+
+ public AsyncDocumentReader(Context ctx) throws IOException {
+ super();
+
+ ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe();
+ mFdIn = fds[0];
+ mFdOut = fds[1];
+ mIn = new ParcelFileDescriptor.AutoCloseInputStream(mFdIn);
+ mOut = new ParcelFileDescriptor.AutoCloseOutputStream(mFdOut);
+ mCause = null;
+ }
+
+ @Override
+ public void onAsyncStart() {
+ // Ignore
+ }
+
+ @Override
+ public void onAsyncEnd(boolean cancelled) {
+ // Ignore
+ }
+
+ @Override
+ public void onAsyncExitCode(int exitCode) {
+ close();
+ }
+
+ @Override
+ public void onPartialResult(Object result) {
+ try {
+ if (result == null) return;
+ byte[] partial = (byte[])result;
+ mOut.write(partial);
+ mOut.flush();
+ } catch (Exception ex) {
+ Log.w(TAG, "Failed to parse partial result data", ex);
+ closeWithError("Failed to parse partial result data: " + ex.getMessage());
+ mCause = ex;
+ }
+ }
+
+ @Override
+ public void onException(Exception cause) {
+ Log.w(TAG, "Got exception while reading data", cause);
+ closeWithError("Got exception while reading data: " + cause.getMessage());
+ mCause = cause;
+ }
+
+ private void close() {
+ try {
+ mOut.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ try {
+ mFdOut.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+
+ private void closeWithError(String msg) {
+ try {
+ mOut.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ try {
+ mIn.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ try {
+ mFdOut.closeWithError(msg);
+ } catch (IOException ex) {
+ // Ignore
+ }
+ try {
+ mFdIn.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java b/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java
index 22b5b261..38f4a951 100644
--- a/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java
+++ b/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java
@@ -229,7 +229,6 @@ public class ColorPickerPreference extends DialogPreference {
/**
* A class that generates instances of the <code>SavedState</code> class from a Parcel.
*/
- @SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
diff --git a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java b/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java
index bb70bfbd..164ec6ec 100644
--- a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java
+++ b/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java
@@ -306,7 +306,6 @@ public class ThemeSelectorPreference extends Preference implements OnClickListen
/**
* A class for create the saved state
*/
- @SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java b/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java
index 4ef9e483..2397ac32 100644
--- a/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java
@@ -31,12 +31,10 @@ 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
@@ -71,7 +69,6 @@ import android.widget.ImageView;
* </p>
*/
public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
- private static final String TAG = "ActionBarDrawerToggle";
/**
* Allows an implementing Activity to return an
@@ -148,16 +145,13 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
}
}
+ @SuppressWarnings("unused")
private static class SetIndicatorInfo {
- public Method setHomeAsUpIndicator;
- public Method setHomeActionContentDescription;
- public ImageView upIndicatorView;
-
SetIndicatorInfo(Activity activity) {
try {
- setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod(
+ Method setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod(
"setHomeAsUpIndicator", Drawable.class);
- setHomeActionContentDescription = ActionBar.class
+ Method setHomeActionContentDescription = ActionBar.class
.getDeclaredMethod("setHomeActionContentDescription",
Integer.TYPE);
@@ -185,16 +179,9 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
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;
@@ -462,7 +449,7 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
if (mActivityImpl != null) {
return mActivityImpl.getThemeUpIndicator();
}
- return IMPL.getThemeUpIndicator(mActivity);
+ return ActionBarDrawerToggleImpl.getThemeUpIndicator(mActivity);
}
void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
@@ -470,7 +457,7 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes);
return;
}
- mSetIndicatorInfo = IMPL.setActionBarUpIndicator(mSetIndicatorInfo,
+ mSetIndicatorInfo = ActionBarDrawerToggleImpl.setActionBarUpIndicator(mSetIndicatorInfo,
mActivity, upDrawable, contentDescRes);
}
@@ -479,7 +466,7 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener {
mActivityImpl.setActionBarDescription(contentDescRes);
return;
}
- mSetIndicatorInfo = IMPL.setActionBarDescription(mSetIndicatorInfo,
+ mSetIndicatorInfo = ActionBarDrawerToggleImpl.setActionBarDescription(mSetIndicatorInfo,
mActivity, contentDescRes);
}
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java b/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java
index 41071056..8f06c4ef 100644
--- a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java
@@ -35,6 +35,7 @@ import com.cyanogenmod.filemanager.tasks.FilesystemAsyncTask;
import com.cyanogenmod.filemanager.ui.ThemeManager;
import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
import com.cyanogenmod.filemanager.util.FileHelper;
+import com.cyanogenmod.filemanager.util.MountPointHelper;
import com.cyanogenmod.filemanager.util.StorageHelper;
import java.io.File;
@@ -370,5 +371,18 @@ public class BreadcrumbView extends RelativeLayout implements Breadcrumb, OnClic
Drawable dw = theme.getDrawable(getContext(), "horizontal_progress_bar"); //$NON-NLS-1$
this.mDiskUsageInfo.setProgressDrawable(dw);
}
+ final ImageView fsInfo = (ImageView)findViewById(R.id.ab_filesystem_info);
+ if (fsInfo != null) {
+ MountPoint mp = (MountPoint) fsInfo.getTag();
+ if (mp == null) {
+ theme.setImageDrawable(getContext(), fsInfo, "filesystem_warning_drawable");
+ } else {
+ String resource =
+ MountPointHelper.isReadOnly(mp)
+ ? "filesystem_locked_drawable"
+ : "filesystem_unlocked_drawable";
+ theme.setImageDrawable(getContext(), fsInfo, resource);
+ }
+ }
}
}
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java b/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java
index 0787e64d..ecd0cfa0 100644
--- a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java
@@ -162,7 +162,6 @@ public class DiskUsageGraph extends View {
* {@inheritDoc}
*/
@Override
- @SuppressWarnings("null")
public void run() {
//Get information about the drawing zone, and adjust the size
Rect rect = new Rect();
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java b/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java
index 04a9130b..641d8f18 100644
--- a/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java
@@ -463,7 +463,7 @@ public class FlingerListView extends ListView {
// What is the motion
if (!this.mScrolling && this.mFlingingView != null) {
- if(!this.mMoveStarted && !this.mLongPress) {
+ if (!this.mMoveStarted && !this.mLongPress) {
this.mFlingingView.removeCallbacks(this.mLongPressDetection);
this.mFlingingView.setPressed(true);
this.mFlingingViewPressed = true;
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java
index 1f1ce3ec..9573f682 100644
--- a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java
@@ -38,7 +38,9 @@ import com.cyanogenmod.filemanager.FileManagerApplication;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter;
import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter.OnSelectionChangedListener;
+import com.cyanogenmod.filemanager.console.CancelledOperationException;
import com.cyanogenmod.filemanager.console.ConsoleAllocException;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
import com.cyanogenmod.filemanager.listeners.OnHistoryListener;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
@@ -299,6 +301,9 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe
/**NON BLOCK**/
}
}
+ if (ex instanceof CancelledOperationException) {
+ return null;
+ }
//Capture exception (attach task, and use listener to do the anim)
ExceptionUtil.attachAsyncTask(
@@ -973,6 +978,16 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe
* Method that changes the current directory of the view.
*
* @param newDir The new directory location
+ * @param addToHistory Add the directory to history
+ */
+ public void changeCurrentDir(final String newDir, boolean addToHistory) {
+ changeCurrentDir(newDir, addToHistory, false, false, null, null);
+ }
+
+ /**
+ * Method that changes the current directory of the view.
+ *
+ * @param newDir The new directory location
* @param searchInfo The search information (if calling activity is {@link "SearchActivity"})
*/
public void changeCurrentDir(final String newDir, SearchInfoParcelable searchInfo) {
@@ -998,6 +1013,27 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, newDir);
}
+ /**
+ * Remove all unmounted files in the current selection
+ */
+ public void removeUnmountedSelection() {
+ List<FileSystemObject> selection = mAdapter.getSelectedItems();
+ int cc = selection.size() - 1;
+ for (int i = cc; i >= 0; i--) {
+ FileSystemObject item = selection.get(i);
+ VirtualMountPointConsole vc =
+ VirtualMountPointConsole.getVirtualConsoleForPath(item.getFullPath());
+ if (vc != null && !vc.isMounted()) {
+ selection.remove(i);
+ }
+ }
+ mAdapter.setSelectedItems(selection);
+ mAdapter.notifyDataSetChanged();
+
+ // Do not call the selection listener. This method is supposed to be called by the
+ // listener itself
+ }
+
/**
* Method invoked when a execution ends.
@@ -1203,6 +1239,14 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe
* {@inheritDoc}
*/
@Override
+ public void onRequestBookmarksRefresh() {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void onRequestRemove(Object o, boolean clearSelection) {
if (o != null && o instanceof FileSystemObject) {
removeItem((FileSystemObject)o);
diff --git a/src/com/cyanogenmod/filemanager/util/AIDHelper.java b/src/com/cyanogenmod/filemanager/util/AIDHelper.java
index d49f1687..c117cfdb 100644
--- a/src/com/cyanogenmod/filemanager/util/AIDHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/AIDHelper.java
@@ -19,12 +19,17 @@ package com.cyanogenmod.filemanager.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Process;
import android.util.Log;
import android.util.SparseArray;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.model.AID;
+import com.cyanogenmod.filemanager.model.Group;
+import com.cyanogenmod.filemanager.model.Identity;
+import com.cyanogenmod.filemanager.model.User;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
@@ -137,4 +142,18 @@ public final class AIDHelper {
return null;
}
+ /**
+ * Method that return a virtual identity composed by the name of the current process
+ *
+ * @return Identity The virtual identity
+ */
+ public static Identity createVirtualIdentity() {
+ AID aid = AIDHelper.getAID(Process.myUid());
+ if (aid == null) return null;
+ return new Identity(
+ new User(aid.getId(), aid.getName()),
+ new Group(aid.getId(), aid.getName()),
+ new ArrayList<Group>());
+ }
+
}
diff --git a/src/com/cyanogenmod/filemanager/util/AndroidHelper.java b/src/com/cyanogenmod/filemanager/util/AndroidHelper.java
index 34194923..891e6e3a 100644
--- a/src/com/cyanogenmod/filemanager/util/AndroidHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/AndroidHelper.java
@@ -19,7 +19,11 @@ package com.cyanogenmod.filemanager.util;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.UserHandle;
@@ -27,13 +31,21 @@ import android.os.UserManager;
import android.util.DisplayMetrics;
import android.view.ViewConfiguration;
-import com.cyanogenmod.filemanager.R;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayInputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
/**
* A helper class with useful methods for deal with android.
*/
public final class AndroidHelper {
+ private static Boolean sIsAppPlatformSigned;
+
/**
* Method that returns if the device is a tablet
*
@@ -91,18 +103,47 @@ public final class AndroidHelper {
* @return boolean If the app is signed with the platform signature
*/
public static boolean isAppPlatformSignature(Context ctx) {
- // TODO This need to be improved, checking if the app is really with the platform signature
- try {
- // For now only check that the app is installed in system directory
- PackageManager pm = ctx.getPackageManager();
- String appDir = pm.getApplicationInfo(ctx.getPackageName(), 0).sourceDir;
- String systemDir = ctx.getString(R.string.system_dir);
- return appDir.startsWith(systemDir);
-
- } catch (Exception e) {
- ExceptionUtil.translateException(ctx, e, true, false);
+ if (sIsAppPlatformSigned == null) {
+ try {
+ // First check that the app is installed as a system app
+ PackageManager pm = ctx.getPackageManager();
+ ApplicationInfo ai = pm.getApplicationInfo(ctx.getPackageName(),
+ PackageManager.GET_SIGNATURES);
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ sIsAppPlatformSigned = Boolean.FALSE;
+ } else {
+ final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+ // Get signature of the current package
+ PackageInfo info = pm.getPackageInfo(ctx.getPackageName(),
+ PackageManager.GET_SIGNATURES);
+ Signature[] signatures = info.signatures;
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(signatures[0].toByteArray()));
+ sha1.update(cert.getEncoded());
+ String appHash = HexDump.toHexString(sha1.digest());
+
+ // Get the signature of the system package
+ info = pm.getPackageInfo("android",
+ PackageManager.GET_SIGNATURES);
+ signatures = info.signatures;
+ cert = (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(signatures[0].toByteArray()));
+ sha1.update(cert.getEncoded());
+ String systemHash = HexDump.toHexString(sha1.digest());
+
+ // Is platform signed?
+ sIsAppPlatformSigned = appHash.equals(systemHash);
+ }
+
+ } catch (NameNotFoundException e) {
+ sIsAppPlatformSigned = Boolean.FALSE;
+ } catch (GeneralSecurityException e) {
+ sIsAppPlatformSigned = Boolean.FALSE;
+ }
}
- return false;
+ return sIsAppPlatformSigned.booleanValue();
}
public static boolean hasSupportForMultipleUsers(Context context) {
diff --git a/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java b/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java
index 0bc5dfee..8f35895d 100644
--- a/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java
@@ -49,6 +49,12 @@ public final class BookmarksHelper {
if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.USB) == 0) {
return "ic_usb_drawable"; //$NON-NLS-1$
}
+ if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.SECURE) == 0) {
+ return "ic_secure_drawable"; //$NON-NLS-1$
+ }
+ if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.REMOTE) == 0) {
+ return "ic_remote_drawable"; //$NON-NLS-1$
+ }
//Bookmark add by the user
return "ic_user_defined_bookmark_drawable"; //$NON-NLS-1$
}
diff --git a/src/com/cyanogenmod/filemanager/util/CommandHelper.java b/src/com/cyanogenmod/filemanager/util/CommandHelper.java
index 7d1b236d..4c98e95d 100644
--- a/src/com/cyanogenmod/filemanager/util/CommandHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/CommandHelper.java
@@ -17,6 +17,7 @@
package com.cyanogenmod.filemanager.util;
import android.content.Context;
+import android.content.Intent;
import android.media.MediaScannerConnection;
import com.cyanogenmod.filemanager.commands.AsyncResultListener;
@@ -24,6 +25,7 @@ import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable;
import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable;
import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
import com.cyanogenmod.filemanager.commands.CompressExecutable;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
import com.cyanogenmod.filemanager.commands.CopyExecutable;
import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
import com.cyanogenmod.filemanager.commands.CreateFileExecutable;
@@ -54,6 +56,8 @@ import com.cyanogenmod.filemanager.commands.UncompressExecutable;
import com.cyanogenmod.filemanager.commands.WritableExecutable;
import com.cyanogenmod.filemanager.commands.WriteExecutable;
import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
+import com.cyanogenmod.filemanager.console.AuthenticationFailedException;
+import com.cyanogenmod.filemanager.console.CancelledOperationException;
import com.cyanogenmod.filemanager.console.CommandNotFoundException;
import com.cyanogenmod.filemanager.console.Console;
import com.cyanogenmod.filemanager.console.ConsoleAllocException;
@@ -63,6 +67,8 @@ import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
import com.cyanogenmod.filemanager.console.OperationTimeoutException;
import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
import com.cyanogenmod.filemanager.model.DiskUsage;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.FolderUsage;
@@ -74,10 +80,12 @@ import com.cyanogenmod.filemanager.model.Query;
import com.cyanogenmod.filemanager.model.SearchResult;
import com.cyanogenmod.filemanager.model.User;
import com.cyanogenmod.filemanager.preferences.CompressionMode;
+import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
@@ -150,7 +158,8 @@ public final class CommandHelper {
createMountExecutable(
UnmountAsyncResultListener.this.mMountPoint,
false);
- UnmountAsyncResultListener.this.mConsole.execute(unmountExecutable);
+ UnmountAsyncResultListener.this.mConsole.execute(
+ unmountExecutable, mCtx);
} catch (Exception e) {
// Capture the exception but not show to the user
ExceptionUtil.translateException(
@@ -210,14 +219,16 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see ChangeOwnerExecutable
*/
public static boolean changeOwner(
Context context, String src, User user, Group group, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
- CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
+ CommandNotFoundException, OperationTimeoutException, ExecutionException,
+ InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
Console c = ensureConsole(context, console);
ChangeOwnerExecutable executable =
c.getExecutableFactory().
@@ -245,6 +256,7 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see ChangePermissionsExecutable
*/
public static boolean changePermissions(
@@ -252,7 +264,8 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
Console c = ensureConsole(context, console);
ChangePermissionsExecutable executable =
c.getExecutableFactory().newCreator().
@@ -279,14 +292,16 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see CreateDirExecutable
*/
public static boolean createDirectory(Context context, String directory, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, directory);
CreateDirExecutable executable =
c.getExecutableFactory().newCreator().createCreateDirectoryExecutable(directory);
writableExecute(context, executable, c);
@@ -311,14 +326,16 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see CreateFileExecutable
*/
public static boolean createFile(Context context, String file, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, file);
CreateFileExecutable executable =
c.getExecutableFactory().newCreator().createCreateFileExecutable(file);
writableExecute(context, executable, c);
@@ -348,14 +365,16 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see DeleteDirExecutable
*/
public static boolean deleteDirectory(Context context, String directory, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, directory);
DeleteDirExecutable executable =
c.getExecutableFactory().newCreator().createDeleteDirExecutable(directory);
writableExecute(context, executable, c);
@@ -388,14 +407,16 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see DeleteFileExecutable
*/
public static boolean deleteFile(Context context, String file, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, file);
DeleteFileExecutable executable =
c.getExecutableFactory().newCreator().createDeleteFileExecutable(file);
writableExecute(context, executable, c);
@@ -427,13 +448,14 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ResolveLinkExecutable
*/
public static FileSystemObject resolveSymlink(Context context, String symlink, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
ResolveLinkExecutable executable =
c.getExecutableFactory().newCreator().createResolveLinkExecutable(symlink);
@@ -458,13 +480,14 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ListExecutable
*/
public static FileSystemObject getFileInfo(Context context, String src, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
return getFileInfo(context, src, true, console);
}
@@ -486,6 +509,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ListExecutable
*/
public static FileSystemObject getFileInfo(
@@ -493,8 +517,8 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, src);
ListExecutable executable =
c.getExecutableFactory().
newCreator().createFileInfoExecutable(src, followSymlinks);
@@ -526,13 +550,14 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see GroupsExecutable
*/
public static List<Group> getGroups(Context context, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
GroupsExecutable executable =
c.getExecutableFactory().newCreator().createGroupsExecutable();
@@ -541,28 +566,29 @@ public final class CommandHelper {
}
/**
- * Method that retrieves the identity of the current user.
- *
- * @param context The current context (needed if console == null)
- * @param console The console in which execute the program. <code>null</code>
- * to attach to the default console
- * @return Identity The identity of the current user
- * @throws FileNotFoundException If the initial directory not exists
- * @throws IOException If initial directory couldn't be checked
- * @throws InvalidCommandDefinitionException If the command has an invalid definition
- * @throws NoSuchFileOrDirectory If the file or directory was not found
- * @throws ConsoleAllocException If the console can't be allocated
- * @throws InsufficientPermissionsException If an operation requires elevated permissions
- * @throws CommandNotFoundException If the command was not found
- * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
- * @throws ExecutionException If the operation returns a invalid exit code
- * @see IdentityExecutable
- */
+ * Method that retrieves the identity of the current user.
+ *
+ * @param context The current context (needed if console == null)
+ * @param console The console in which execute the program. <code>null</code>
+ * to attach to the default console
+ * @return Identity The identity of the current user
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws NoSuchFileOrDirectory If the file or directory was not found
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If an operation requires elevated permissions
+ * @throws CommandNotFoundException If the command was not found
+ * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
+ * @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
+ * @see IdentityExecutable
+ */
public static Identity getIdentity(Context context, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
IdentityExecutable executable =
c.getExecutableFactory().newCreator().createIdentityExecutable();
@@ -575,7 +601,7 @@ public final class CommandHelper {
*
* @param context The current context (needed if console == null)
* @param src The absolute path to the source fso
- * @param link The absolute path to the link fso
+ * @param link The absolute path to the link fso
* @param console The console in which execute the program. <code>null</code>
* to attach to the default console
* @return boolean The operation result
@@ -589,13 +615,15 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see LinkExecutable
*/
public static boolean createLink(Context context, String src, String link, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
Console c = ensureConsole(context, console);
LinkExecutable executable =
c.getExecutableFactory().newCreator().createLinkExecutable(src, link);
@@ -620,14 +648,15 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ParentDirExecutable
*/
public static String getParentDir(Context context, String src, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, src);
ParentDirExecutable executable =
c.getExecutableFactory().newCreator().createParentDirExecutable(src);
execute(context, executable, c);
@@ -652,13 +681,14 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see EchoExecutable
*/
public static String getVariable(Context context, String msg, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
EchoExecutable executable =
c.getExecutableFactory().newCreator().createEchoExecutable(msg);
@@ -683,6 +713,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ListExecutable
*/
public static List<FileSystemObject> listFiles(
@@ -690,14 +721,21 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException,
+ CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, directory);
ListExecutable executable =
c.getExecutableFactory().newCreator().
createListExecutable(directory);
execute(context, executable, c);
List<FileSystemObject> result = executable.getResult();
FileHelper.resolveSymlinks(context, result);
+
+ // And now we need to verify if the directory is the
+ if (VirtualMountPointConsole.isVirtualStorageDir(directory)) {
+ result.addAll(VirtualMountPointConsole.getVirtualMountableDirectories());
+ }
+
return result;
}
@@ -720,28 +758,72 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see MoveExecutable
*/
public static boolean move(Context context, String src, String dst, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
- Console c = ensureConsole(context, console);
- MoveExecutable executable =
- c.getExecutableFactory().newCreator().createMoveExecutable(src, dst);
- writableExecute(context, executable, c);
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
+ Console cSrc = ensureConsoleForFile(context, console, src);
+ Console cDst = ensureConsoleForFile(context, console, dst);
+ boolean ret = true;
+ if (cSrc.equals(cDst)) {
+ // Is safe to use the same console
+ MoveExecutable executable =
+ cSrc.getExecutableFactory().newCreator().createMoveExecutable(src, dst);
+ writableExecute(context, executable, cSrc);
+ ret = executable.getResult().booleanValue();
+ } else {
+ // We need to create a temporary file in the external filesystem to make it
+ // available to virtual consoles
+
+ // 1.- Move to a temporary file with the source console (destination
+ // is a safe location)
+ File tmp = FileHelper.createTempFilename(context, true);
+ try {
+ MoveExecutable moveExecutable =
+ cSrc.getExecutableFactory().newCreator().createMoveExecutable(
+ src, tmp.getAbsolutePath());
+ writableExecute(context, moveExecutable, cSrc);
+ if (!moveExecutable.getResult().booleanValue()) {
+ ret = false;
+ }
- // Do media scan
- File parent = new File(src).getParentFile();
- if (parent != null) {
- MediaScannerConnection.scanFile(context, new String[]{
- MediaHelper.normalizeMediaPath(parent.getAbsolutePath())}, null, null);
+ // 2.- Move the temporary file to the final filesystem with the destination console
+ if (ret) {
+ moveExecutable =
+ cDst.getExecutableFactory().newCreator().createMoveExecutable(
+ tmp.getAbsolutePath(), dst);
+ writableExecute(context, moveExecutable, cDst);
+ if (!moveExecutable.getResult().booleanValue()) {
+ ret = false;
+ }
+ }
+
+ } finally {
+ FileHelper.deleteFileOrFolder(tmp);
+ }
}
- MediaScannerConnection.scanFile(context, new String[]{
- MediaHelper.normalizeMediaPath(dst)}, null, null);
- return executable.getResult().booleanValue();
+ // Do media scan (don't scan the file if is virtual file)
+ if (ret) {
+ File parent = new File(src).getParentFile();
+ if (parent != null) {
+ if (!VirtualMountPointConsole.isVirtualStorageResource(parent.getAbsolutePath())) {
+ MediaScannerConnection.scanFile(context, new String[]{
+ MediaHelper.normalizeMediaPath(parent.getAbsolutePath())}, null, null);
+ }
+ }
+ if (!VirtualMountPointConsole.isVirtualStorageResource(parent.getAbsolutePath())) {
+ MediaScannerConnection.scanFile(context, new String[]{
+ MediaHelper.normalizeMediaPath(dst)}, null, null);
+ }
+ }
+
+ return ret;
}
/**
@@ -763,23 +845,65 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see CopyExecutable
*/
public static boolean copy(Context context, String src, String dst, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
- Console c = ensureConsole(context, console);
- CopyExecutable executable =
- c.getExecutableFactory().newCreator().createCopyExecutable(src, dst);
- writableExecute(context, executable, c);
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
+ Console cSrc = ensureConsoleForFile(context, console, src);
+ Console cDst = ensureConsoleForFile(context, console, dst);
+ boolean ret = true;
+ if (cSrc.equals(cDst)) {
+ // Is safe to use the same console
+ CopyExecutable executable =
+ cSrc.getExecutableFactory().newCreator().createCopyExecutable(src, dst);
+ writableExecute(context, executable, cSrc);
+ ret = executable.getResult().booleanValue();
+ } else {
+ // We need to create a temporary file in the external filesystem to make it
+ // available to virtual consoles
+
+ // 1.- Copy to a temporary file with the source console (destination
+ // is a safe location)
+ File tmp = FileHelper.createTempFilename(context, true);
+ try {
+ CopyExecutable copyExecutable =
+ cSrc.getExecutableFactory().newCreator().createCopyExecutable(
+ src, tmp.getAbsolutePath());
+ writableExecute(context, copyExecutable, cSrc);
+ if (!copyExecutable.getResult().booleanValue()) {
+ ret = false;
+ }
- // Do media scan
- MediaScannerConnection.scanFile(context, new String[]{
- MediaHelper.normalizeMediaPath(dst)}, null, null);
+ // 2.- Move the temporary file to the final filesystem with the destination console
+ if (ret) {
+ MoveExecutable moveExecutable =
+ cDst.getExecutableFactory().newCreator().createMoveExecutable(
+ tmp.getAbsolutePath(), dst);
+ writableExecute(context, moveExecutable, cDst);
+ if (!moveExecutable.getResult().booleanValue()) {
+ ret = false;
+ }
+ }
- return executable.getResult().booleanValue();
+ } finally {
+ FileHelper.deleteFileOrFolder(tmp);
+ }
+ }
+
+ // Do media scan (don't scan the file if is virtual file)
+ if (ret) {
+ if (!VirtualMountPointConsole.isVirtualStorageResource(dst)) {
+ MediaScannerConnection.scanFile(context, new String[]{
+ MediaHelper.normalizeMediaPath(dst)}, null, null);
+ }
+ }
+
+ return ret;
}
/**
@@ -800,6 +924,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ExecExecutable
*/
public static ExecExecutable exec(
@@ -807,7 +932,7 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
ExecExecutable executable =
c.getExecutableFactory().newCreator().
@@ -835,22 +960,48 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see SearchResult
* @see FindExecutable
*/
public static FindExecutable findFiles(
Context context, String directory, Query search,
- AsyncResultListener asyncResultListener, Console console)
+ ConcurrentAsyncResultListener asyncResultListener, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
- FindExecutable executable =
- c.getExecutableFactory().newCreator().
- createFindExecutable(directory, search, asyncResultListener);
- execute(context, executable, c);
- return executable;
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+ List<Console> consoles = new ArrayList<Console>();
+ List<FindExecutable> executables = new ArrayList<FindExecutable>();
+ Console c = ensureConsoleForFile(context, console, directory);
+ consoles.add(c);
+
+ // Obtain all the rest of console that will participate in the search, that aren't the
+ // current console
+ List<Console> vcs = VirtualMountPointConsole.getVirtualConsoleForSearchPath(directory);
+ for (int i = vcs.size() - 1; i >= 0; i--) {
+ Console vc = vcs.get(i);
+ if (vc.equals(c)) {
+ vcs.remove(i);
+ }
+ }
+ consoles.addAll(vcs);
+
+ // Register all the executables
+ for (Console cc : consoles) {
+ executables.add(
+ cc.getExecutableFactory().newCreator().
+ createFindExecutable(directory, search, asyncResultListener));
+ }
+
+ // Launch every executable
+ int count = executables.size();
+ for (int i = 0; i < count; i++) {
+ execute(context, executables.get(i), consoles.get(i));
+ }
+
+ // Return the first of the executables
+ return executables.get(0);
}
/**
@@ -871,6 +1022,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see FolderUsage
* @see FolderUsageExecutable
*/
@@ -880,8 +1032,8 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, directory);
FolderUsageExecutable executable =
c.getExecutableFactory().newCreator().
createFolderUsageExecutable(directory, asyncResultListener);
@@ -905,18 +1057,21 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see DiskUsageExecutable
*/
public static List<DiskUsage> getDiskUsage(Context context, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
DiskUsageExecutable executable =
c.getExecutableFactory().newCreator().createDiskUsageExecutable();
execute(context, executable, c);
- return executable.getResult();
+ List<DiskUsage> diskUsage = executable.getResult();
+ diskUsage.addAll(VirtualMountPointConsole.getVirtualDiskUsage());
+ return diskUsage;
}
/**
@@ -936,20 +1091,29 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see DiskUsageExecutable
*/
public static DiskUsage getDiskUsage(Context context, String dir, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
- DiskUsageExecutable executable =
- c.getExecutableFactory().newCreator().createDiskUsageExecutable(dir);
- execute(context, executable, c);
- List<DiskUsage> du = executable.getResult();
- if (du != null && du.size() > 0) {
- return du.get(0);
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+
+ // Virtual directories don't implement a disk usage command, just return the data if
+ // the directory belongs to a virtual filesystem
+ VirtualMountPointConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath(dir);
+ if (vc != null) {
+ return vc.getDiskUsage(dir);
+ } else {
+ Console c = ensureConsole(context, console);
+ DiskUsageExecutable executable =
+ c.getExecutableFactory().newCreator().createDiskUsageExecutable(dir);
+ execute(context, executable, c);
+ List<DiskUsage> du = executable.getResult();
+ if (du != null && du.size() > 0) {
+ return du.get(0);
+ }
}
return null;
}
@@ -970,18 +1134,21 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see MountPointInfoExecutable
*/
public static List<MountPoint> getMountPoints(Context context, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
MountPointInfoExecutable executable =
c.getExecutableFactory().newCreator().createMountPointInfoExecutable();
execute(context, executable, c);
- return executable.getResult();
+ List<MountPoint> mountPoints = executable.getResult();
+ mountPoints.addAll(VirtualMountPointConsole.getVirtualMountPoints());
+ return mountPoints;
}
/**
@@ -1002,18 +1169,43 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see MountExecutable
*/
public static boolean remount(Context context, MountPoint mp, boolean rw, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
- MountExecutable executable =
- c.getExecutableFactory().newCreator().createMountExecutable(mp, rw);
- execute(context, executable, c);
- return executable.getResult().booleanValue();
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+ boolean ret = false;
+ if (mp.isSecure()) {
+ // Unmount the secure file system
+ SecureConsole sc = (SecureConsole) ensureConsoleForFile(
+ context, console, mp.getMountPoint());
+ if (rw) {
+ sc.mount(context);
+ } else {
+ sc.unmount();
+ }
+ ret = true;
+ } else {
+ Console c = ensureConsole(context, console);
+ MountExecutable executable =
+ c.getExecutableFactory().newCreator().createMountExecutable(mp, rw);
+ execute(context, executable, c);
+ ret = executable.getResult().booleanValue();
+ }
+
+ if (ret) {
+ // Send an broadcast to notify that the mount state of this filesystem changed
+ Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED);
+ intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT, mp.getMountPoint());
+ intent.putExtra(FileManagerSettings.EXTRA_STATUS, rw
+ ? MountExecutable.READWRITE : MountExecutable.READONLY);
+ context.sendBroadcast(intent);
+ }
+
+ return ret;
}
/**
@@ -1033,13 +1225,14 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see QuickFolderSearchExecutable
*/
public static List<String> quickFolderSearch(Context context, String regexp, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
QuickFolderSearchExecutable executable =
c.getExecutableFactory().newCreator().createQuickFolderSearchExecutable(regexp);
@@ -1065,6 +1258,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ProcessIdExecutable
*/
public static List<Integer> getProcessesIds(
@@ -1072,7 +1266,7 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
ProcessIdExecutable executable =
c.getExecutableFactory().newCreator().createProcessIdExecutable(pid);
@@ -1099,6 +1293,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ProcessIdExecutable
*/
public static Integer getProcessId(
@@ -1106,7 +1301,7 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
ProcessIdExecutable executable =
c.getExecutableFactory().newCreator().createProcessIdExecutable(pid, processName);
@@ -1135,6 +1330,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ProcessIdExecutable
*/
public static void sendSignal(
@@ -1142,7 +1338,7 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
SendSignalExecutable executable =
c.getExecutableFactory().newCreator().createSendSignalExecutable(process, signal);
@@ -1165,6 +1361,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see ProcessIdExecutable
*/
public static void sendSignal(
@@ -1172,7 +1369,7 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
Console c = ensureConsole(context, console);
SendSignalExecutable executable =
c.getExecutableFactory().newCreator().createKillExecutable(process);
@@ -1197,6 +1394,7 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
* @see "byte[]"
* @see ReadExecutable
*/
@@ -1206,8 +1404,8 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, file);
ReadExecutable executable =
c.getExecutableFactory().newCreator().
createReadExecutable(file, asyncResultListener);
@@ -1234,6 +1432,7 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see WriteExecutable
*/
public static WriteExecutable write(
@@ -1242,8 +1441,9 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, file);
// Create a wrapper listener, for unmount the filesystem if necessary
UnmountAsyncResultListener wrapperListener = new UnmountAsyncResultListener();
@@ -1293,6 +1493,7 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see CompressExecutable
*/
public static CompressExecutable compress(
@@ -1301,7 +1502,8 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
Console c = ensureConsole(context, console);
// Create a wrapper listener, for unmount the filesystem if necessary
@@ -1365,6 +1567,7 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see CompressExecutable
*/
public static CompressExecutable compress(
@@ -1373,7 +1576,8 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
Console c = ensureConsole(context, console);
// Create a wrapper listener, for unmount the filesystem if necessary
@@ -1429,6 +1633,7 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
* @see CompressExecutable
*/
public static UncompressExecutable uncompress(
@@ -1437,7 +1642,8 @@ public final class CommandHelper {
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException {
+ ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException,
+ CancelledOperationException {
Console c = ensureConsole(context, console);
// Create a wrapper listener, for unmount the filesystem if necessary
@@ -1479,7 +1685,7 @@ public final class CommandHelper {
// Do media scan
MediaScannerConnection.scanFile(context, new String[]{
- MediaHelper.normalizeMediaPath(dst)}, null, null);
+ MediaHelper.normalizeMediaPath(compressOutFile)}, null, null);
return executable1;
}
@@ -1495,7 +1701,7 @@ public final class CommandHelper {
* @param asyncResultListener The partial result listener
* @param console The console in which execute the program.
* <code>null</code> to attach to the default console
- * @return WriteExecutable The command executed in background
+ * @return ChecksumExecutable The command executed in background
* @throws FileNotFoundException If the initial directory not exists
* @throws IOException If initial directory couldn't be checked
* @throws InvalidCommandDefinitionException If the command has an invalid definition
@@ -1505,16 +1711,16 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
- * @see WriteExecutable
+ * @throws CancelledOperationException If the operation was cancelled
+ * @see ChecksumExecutable
*/
- public static ChecksumExecutable checksum(
- Context context, String src,
+ public static ChecksumExecutable checksum(Context context, String src,
AsyncResultListener asyncResultListener, Console console)
throws FileNotFoundException, IOException, ConsoleAllocException,
NoSuchFileOrDirectory, InsufficientPermissionsException,
CommandNotFoundException, OperationTimeoutException,
- ExecutionException, InvalidCommandDefinitionException {
- Console c = ensureConsole(context, console);
+ ExecutionException, InvalidCommandDefinitionException, CancelledOperationException {
+ Console c = ensureConsoleForFile(context, console, src);
ChecksumExecutable executable =
c.getExecutableFactory().newCreator().
createChecksumExecutable(src, asyncResultListener);
@@ -1539,6 +1745,7 @@ public final class CommandHelper {
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
* @throws InvalidCommandDefinitionException If the command has an invalid definition
* @throws IOException If initial directory couldn't be checked
+ * @throws CancelledOperationException If the operation was cancelled
* @throws FileNotFoundException If the initial directory not exists
*/
public static Object reexecute(
@@ -1546,9 +1753,10 @@ public final class CommandHelper {
throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
OperationTimeoutException, ExecutionException,
CommandNotFoundException, ReadOnlyFilesystemException,
- FileNotFoundException, IOException, InvalidCommandDefinitionException {
+ FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ CancelledOperationException {
Console c = ensureConsole(context, console);
- c.execute(executable);
+ c.execute(executable, context);
return executable.getResult();
}
@@ -1566,13 +1774,16 @@ public final class CommandHelper {
* @throws CommandNotFoundException If the command was not found
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
+ * @throws CancelledOperationException If the operation was cancelled
+ * @throws AuthenticationFailedException If the operation failed caused by an
+ * authentication failure
*/
private static void execute(Context context, Executable executable, Console console)
throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
- OperationTimeoutException, ExecutionException,
- CommandNotFoundException {
+ OperationTimeoutException, ExecutionException, CommandNotFoundException,
+ CancelledOperationException, AuthenticationFailedException {
try {
- console.execute(executable);
+ console.execute(executable, context);
} catch (ReadOnlyFilesystemException rofEx) {
// ReadOnlyFilesystemException don't have sense if command is not writable
// WritableExecutable must be used with "writableExecute" method
@@ -1595,12 +1806,15 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
+ * @throws AuthenticationFailedException If the operation failed caused by an
+ * authentication failure
*/
- private static void writableExecute(
- Context context, WritableExecutable executable, Console console)
- throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
- OperationTimeoutException, ExecutionException,
- CommandNotFoundException, ReadOnlyFilesystemException {
+ private static void writableExecute(Context context, WritableExecutable executable,
+ Console console) throws ConsoleAllocException, InsufficientPermissionsException,
+ NoSuchFileOrDirectory, OperationTimeoutException, ExecutionException,
+ CommandNotFoundException, ReadOnlyFilesystemException, CancelledOperationException,
+ AuthenticationFailedException{
writableExecute(context, executable, console, false);
}
@@ -1622,13 +1836,15 @@ public final class CommandHelper {
* @throws OperationTimeoutException If the operation exceeded the maximum time of wait
* @throws ExecutionException If the operation returns a invalid exit code
* @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @throws CancelledOperationException If the operation was cancelled
+ * @throws AuthenticationFailedException If the operation failed caused by an
+ * authentication failure
*/
- private static boolean writableExecute(
- Context context, WritableExecutable executable, Console console,
- boolean leaveDeviceMounted)
- throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
- OperationTimeoutException, ExecutionException,
- CommandNotFoundException, ReadOnlyFilesystemException {
+ private static boolean writableExecute(Context context, WritableExecutable executable,
+ Console console, boolean leaveDeviceMounted) throws ConsoleAllocException,
+ InsufficientPermissionsException, NoSuchFileOrDirectory, OperationTimeoutException,
+ ExecutionException, CommandNotFoundException, ReadOnlyFilesystemException,
+ CancelledOperationException, AuthenticationFailedException {
//Retrieve the mount point information to check if a remount operation is required
//There are 2 mount points: destination and source. Check both
@@ -1699,17 +1915,17 @@ public final class CommandHelper {
try {
if (needMountDst) {
//Execute the mount command
- console.execute(mountDstExecutable);
+ console.execute(mountDstExecutable, context);
mountExecutedDst = true;
}
if (needMountSrc) {
//Execute the mount command
- console.execute(mountSrcExecutable);
+ console.execute(mountSrcExecutable, context);
mountExecutedSrc = true;
}
//Execute the command
- console.execute(executable);
+ console.execute(executable, context);
} catch (InsufficientPermissionsException ipEx) {
//Configure the commands to execute
@@ -1739,11 +1955,11 @@ public final class CommandHelper {
//and unmount operation
if (mountExecutedDst && !leaveDeviceMounted) {
//Execute the unmount command
- console.execute(unmountDstExecutable);
+ console.execute(unmountDstExecutable, context);
}
if (mountExecutedSrc && !leaveDeviceMounted) {
//Execute the unmount command
- console.execute(unmountSrcExecutable);
+ console.execute(unmountSrcExecutable, context);
}
}
@@ -1774,4 +1990,36 @@ public final class CommandHelper {
return c;
}
+ /**
+ * Method that ensure the console retrieve the default console if a console
+ * is not passed.
+ *
+ * @param context The current context (needed if console == null)
+ * @param console The console passed
+ * @param src The source file to check
+ * @return Console The console passed if not is null. Otherwise, the default console
+ * @throws InsufficientPermissionsException If an operation requires elevated permissions
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws IOException If initial directory couldn't be checked
+ * @throws FileNotFoundException If the initial directory not exists
+ */
+ private static Console ensureConsoleForFile(Context context, Console console, String src)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+
+ // Check if the path belongs to a virtual mount point
+ Console c = VirtualMountPointConsole.getVirtualConsoleForPath(src);
+ if (c != null) {
+ return c;
+ }
+
+ // Recover a real console
+ c = console;
+ if (c == null) {
+ c = ConsoleBuilder.getConsole(context);
+ }
+ return c;
+ }
+
}
diff --git a/src/com/cyanogenmod/filemanager/util/DialogHelper.java b/src/com/cyanogenmod/filemanager/util/DialogHelper.java
index 0dc9965c..40464452 100644
--- a/src/com/cyanogenmod/filemanager/util/DialogHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/DialogHelper.java
@@ -416,6 +416,84 @@ public final class DialogHelper {
}
/**
+ * Method that creates a new {@link AlertDialog} with one buttons.
+ *
+ * @param context The current context
+ * @param button1 The resource identifier of the text of the button 1 (POSITIVE)
+ * @param icon The icon resource
+ * @param title The title of the alert dialog
+ * @param content The content layout
+ * @param onClickListener The listener where returns the button pressed
+ * @return AlertDialog The alert dialog reference
+ */
+ public static AlertDialog createOneButtonsDialog(Context context, int button1,
+ int icon, String title, View content, OnClickListener onClickListener) {
+ //Create the alert dialog
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setCustomTitle(createTitle(context, icon, title, false));
+ builder.setView(content);
+ AlertDialog dialog = builder.create();
+ dialog.setButton(
+ DialogInterface.BUTTON_POSITIVE, context.getString(button1), onClickListener);
+ return dialog;
+ }
+
+ /**
+ * Method that creates a new {@link AlertDialog} with two buttons.
+ *
+ * @param context The current context
+ * @param button1 The resource identifier of the text of the button 1 (POSITIVE)
+ * @param button2 The resource identifier of the text of the button 2 (NEUTRAL)
+ * @param icon The icon resource
+ * @param title The title of the alert dialog
+ * @param content The content layout
+ * @param onClickListener The listener where returns the button pressed
+ * @return AlertDialog The alert dialog reference
+ */
+ public static AlertDialog createTwoButtonsDialog(Context context, int button1, int button2,
+ int icon, String title, View content, OnClickListener onClickListener) {
+ //Create the alert dialog
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setCustomTitle(createTitle(context, icon, title, false));
+ builder.setView(content);
+ AlertDialog dialog = builder.create();
+ dialog.setButton(
+ DialogInterface.BUTTON_POSITIVE, context.getString(button1), onClickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_NEGATIVE, context.getString(button2), onClickListener);
+ return dialog;
+ }
+
+ /**
+ * Method that creates a new {@link AlertDialog} with three buttons.
+ *
+ * @param context The current context
+ * @param button1 The resource identifier of the text of the button 1 (POSITIVE)
+ * @param button2 The resource identifier of the text of the button 2 (NEUTRAL)
+ * @param button3 The resource identifier of the text of the button 3 (NEGATIVE)
+ * @param icon The icon resource
+ * @param title The title of the alert dialog
+ * @param content The content layout
+ * @param onClickListener The listener where returns the button pressed
+ * @return AlertDialog The alert dialog reference
+ */
+ public static AlertDialog createThreeButtonsDialog(Context context, int button1, int button2,
+ int button3, int icon, String title, View content, OnClickListener onClickListener) {
+ //Create the alert dialog
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setCustomTitle(createTitle(context, icon, title, false));
+ builder.setView(content);
+ AlertDialog dialog = builder.create();
+ dialog.setButton(
+ DialogInterface.BUTTON_POSITIVE, context.getString(button1), onClickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_NEUTRAL, context.getString(button2), onClickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_NEGATIVE, context.getString(button3), onClickListener);
+ return dialog;
+ }
+
+ /**
* Method that creates and returns the title of the dialog.
*
* @param context The current context
diff --git a/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java b/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java
index 26f084f2..7b7ce745 100644
--- a/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java
+++ b/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java
@@ -29,6 +29,8 @@ import com.cyanogenmod.filemanager.FileManagerApplication;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
+import com.cyanogenmod.filemanager.console.AuthenticationFailedException;
+import com.cyanogenmod.filemanager.console.CancelledOperationException;
import com.cyanogenmod.filemanager.console.CommandNotFoundException;
import com.cyanogenmod.filemanager.console.ConsoleAllocException;
import com.cyanogenmod.filemanager.console.ConsoleBuilder;
@@ -94,7 +96,8 @@ public final class ExceptionUtil {
OperationTimeoutException.class,
ExecutionException.class,
ParseException.class,
- ActivityNotFoundException.class
+ ActivityNotFoundException.class,
+ AuthenticationFailedException.class
};
private static final int[] KNOWN_EXCEPTIONS_IDS = {
R.string.msgs_file_not_found,
@@ -108,7 +111,8 @@ public final class ExceptionUtil {
R.string.msgs_operation_timeout,
R.string.msgs_operation_failure,
R.string.msgs_operation_failure,
- R.string.msgs_not_registered_app
+ R.string.msgs_not_registered_app,
+ 0
};
private static final boolean[] KNOWN_EXCEPTIONS_TOAST = {
false,
@@ -122,7 +126,8 @@ public final class ExceptionUtil {
true,
true,
true,
- false
+ false,
+ true
};
/**
@@ -181,6 +186,11 @@ public final class ExceptionUtil {
final boolean quiet, final boolean askUser,
final OnRelaunchCommandResult listener) {
+ // Is cancellable?
+ if (ex instanceof CancelledOperationException) {
+ return;
+ }
+
//Get the appropriate message for the exception
int msgResId = R.string.msgs_unknown;
boolean toast = true;
@@ -216,12 +226,18 @@ public final class ExceptionUtil {
@Override
public void run() {
try {
+ String msg = null;
+ if (fMsgResId > 0) {
+ msg = context.getString(fMsgResId);
+ } else {
+ msg = ex.getMessage();
+ }
if (fToast) {
- DialogHelper.showToast(context, fMsgResId, Toast.LENGTH_SHORT);
+ DialogHelper.showToast(context, msg, Toast.LENGTH_SHORT);
} else {
AlertDialog dialog =
DialogHelper.createErrorDialog(
- context, R.string.error_title, fMsgResId);
+ context, R.string.error_title, msg);
DialogHelper.delegateDialogShow(context, dialog);
}
} catch (Exception e) {
diff --git a/src/com/cyanogenmod/filemanager/util/FileHelper.java b/src/com/cyanogenmod/filemanager/util/FileHelper.java
index 811225a9..3b43aa83 100644
--- a/src/com/cyanogenmod/filemanager/util/FileHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/FileHelper.java
@@ -66,6 +66,7 @@ import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
/**
* A helper class with useful methods for deal with files.
@@ -877,10 +878,14 @@ public final class FileHelper {
// Check that have a valid file
if (fso == null) return false;
- //Only regular files
+ // Only regular files
if (isDirectory(fso) || fso instanceof Symlink) {
return false;
}
+ // No in virtual filesystems
+ if (fso.isSecure() || fso.isRemote()) {
+ return false;
+ }
String ext = getExtension(fso);
if (ext != null) {
int cc = VALID.length;
@@ -938,11 +943,9 @@ public final class FileHelper {
*/
public static FileSystemObject createFileSystemObject(File file) {
try {
- // The user and group name of the files. In ChRoot, aosp give restrict access to
- // this user and group.
- final String USER = "system"; //$NON-NLS-1$
+ // The user and group name of the files. Use the defaults one for sdcards
+ final String USER = "root"; //$NON-NLS-1$
final String GROUP = "sdcard_r"; //$NON-NLS-1$
- final String PERMISSIONS = "----rwxr-x"; //$NON-NLS-1$
// The user and group name of the files. In ChRoot, aosp give restrict access to
// this user and group. This applies for permission also. This has no really much
@@ -951,7 +954,9 @@ public final class FileHelper {
AID groupAID = AIDHelper.getAIDFromName(GROUP);
User user = new User(userAID.getId(), userAID.getName());
Group group = new Group(groupAID.getId(), groupAID.getName());
- Permissions perm = Permissions.fromRawString(PERMISSIONS);
+ Permissions perm = file.isDirectory()
+ ? Permissions.createDefaultFolderPermissions()
+ : Permissions.createDefaultFilePermissions();
// Build a directory?
Date lastModified = new Date(file.lastModified());
@@ -1318,4 +1323,46 @@ public final class FileHelper {
return sDateFormat.format(filetime);
}
}
+
+ /**
+ * Method that create a new temporary filename
+ *
+ * @param external If the file should be created in the external or the internal cache dir
+ */
+ public static synchronized File createTempFilename(Context context, boolean external) {
+ File tempDirectory = external ? context.getExternalCacheDir() : context.getCacheDir();
+ File tempFile;
+ do {
+ UUID uuid = UUID.randomUUID();
+ tempFile = new File(tempDirectory, uuid.toString());
+ } while (tempFile.exists());
+ return tempFile;
+ }
+
+ /**
+ * Method that delete a file or a folder
+ *
+ * @param src The file or folder to delete
+ * @return boolean If the operation was successfully
+ */
+ public static boolean deleteFileOrFolder(File src) {
+ if (src.isDirectory()) {
+ return FileHelper.deleteFolder(src);
+ }
+ return src.delete();
+ }
+
+ /**
+ * Method that checks if the source file passed belongs to (is under) the directory passed
+ *
+ * @param src The file to check
+ * @param dir The parent file to check
+ * @return boolean If the source belongs to the directory
+ */
+ public static boolean belongsToDirectory(File src, File dir) {
+ if (dir.isFile()) {
+ return false;
+ }
+ return src.getAbsolutePath().startsWith(dir.getAbsolutePath());
+ }
}
diff --git a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java
index 1220272c..02833052 100644
--- a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
import com.cyanogenmod.filemanager.model.BlockDevice;
import com.cyanogenmod.filemanager.model.CharacterDevice;
import com.cyanogenmod.filemanager.model.Directory;
@@ -241,6 +242,11 @@ public final class MimeTypeHelper {
//Check if the argument is a folder
if (fso instanceof Directory) {
+ if (fso.isSecure() && SecureConsole.isSecureStorageDir(fso.getFullPath())) {
+ return "fso_folder_secure"; //$NON-NLS-1$
+ } else if (fso.isRemote()) {
+ return "fso_folder_remote"; //$NON-NLS-1$
+ }
return "ic_fso_folder_drawable"; //$NON-NLS-1$
}
diff --git a/src/com/cyanogenmod/filemanager/util/ParseHelper.java b/src/com/cyanogenmod/filemanager/util/ParseHelper.java
index b593875a..ada51aae 100644
--- a/src/com/cyanogenmod/filemanager/util/ParseHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/ParseHelper.java
@@ -322,7 +322,7 @@ public final class ParseHelper {
//Return the mount point
- return new MountPoint(mountPoint, device, type, options, dump, pass);
+ return new MountPoint(mountPoint, device, type, options, dump, pass, false, false);
} catch (Exception e) {
throw new ParseException(e.getMessage(), 0);
diff --git a/src/com/cyanogenmod/filemanager/util/StringHelper.java b/src/com/cyanogenmod/filemanager/util/StringHelper.java
index 3702746b..89d7d2f0 100644
--- a/src/com/cyanogenmod/filemanager/util/StringHelper.java
+++ b/src/com/cyanogenmod/filemanager/util/StringHelper.java
@@ -47,6 +47,33 @@ public final class StringHelper {
return TextUtils.isGraphic(c);
}
+ public static boolean isBinaryData(byte[] data) {
+ int lastByteTranslated = 0;
+ final int read = Math.min(10 * 1024, data.length);
+ final long max = ((5 * read) / 100); // 5% percent of binary bytes
+ int hits = 0;
+ for (int i = 0; i < read; i++) {
+ final byte b = data[i];
+ int ub = b & (0xff); // unsigned
+ int utf8value = lastByteTranslated + ub;
+ lastByteTranslated = (ub) << 8;
+
+ if (ub == 0x09 /*(tab)*/
+ || ub == 0x0A /*(line feed)*/
+ || ub == 0x0C /*(form feed)*/
+ || ub == 0x0D /*(carriage return)*/
+ || (ub >= 0x20 && ub <= 0x7E) /* Letters, Numbers and other "normal symbols" */
+ || (ub >= 0xA0 && ub <= 0xEE) /* Symbols of Latin-1 */
+ || (utf8value >= 0x2E2E && utf8value <= 0xC3BF)) { /* Latin-1 in UTF-8 encoding */
+ // ok
+ } else {
+ // binary
+ hits++;
+ }
+ }
+ return hits > max;
+ }
+
/**
* Method that converts to a visual printable hex string
*
diff --git a/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java b/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java
index d12b69e4..b8257cd1 100644
--- a/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java
+++ b/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java
@@ -20,7 +20,7 @@ import android.os.Environment;
import android.test.suitebuilder.annotation.LargeTest;
import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
-import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.model.Query;
import com.cyanogenmod.filemanager.util.CommandHelper;
@@ -77,29 +77,31 @@ public class FindCommandTest extends AbstractConsoleTest {
Query query = new Query().setSlot(FIND_TERM_PARTIAL, 0);
final List<FileSystemObject> files = new ArrayList<FileSystemObject>();
AsyncResultExecutable cmd =
- CommandHelper.findFiles(getContext(), FIND_PATH, query, new AsyncResultListener() {
+ CommandHelper.findFiles(getContext(), FIND_PATH,
+ query, new ConcurrentAsyncResultListener() {
+
@Override
- public void onAsyncStart() {
+ public void onConcurrentAsyncStart() {
/**NON BLOCK**/
}
@Override
- public void onAsyncEnd(boolean cancelled) {
+ public void onConcurrentAsyncEnd(boolean cancelled) {
synchronized (FindCommandTest.this.mSync) {
FindCommandTest.this.mNormalEnd = true;
FindCommandTest.this.mSync.notify();
}
}
@Override
- public void onAsyncExitCode(int exitCode) {
+ public void onConcurrentAsyncExitCode(int exitCode) {
/**NON BLOCK**/
}
@Override
- public void onException(Exception cause) {
+ public void onConcurrentException(Exception cause) {
fail(String.valueOf(cause));
}
@Override
@SuppressWarnings("unchecked")
- public void onPartialResult(Object results) {
+ public void onConcurrentPartialResult(Object results) {
FindCommandTest.this.mNewPartialData = true;
files.addAll((List<FileSystemObject>)results);
}
diff --git a/themes/res/drawable-hdpi/ic_holo_dark_delete.png b/themes/res/drawable-hdpi/ic_holo_dark_delete.png
new file mode 100644
index 00000000..32a63ee1
--- /dev/null
+++ b/themes/res/drawable-hdpi/ic_holo_dark_delete.png
Binary files differ
diff --git a/themes/res/drawable-hdpi/ic_holo_dark_print.png b/themes/res/drawable-hdpi/ic_holo_dark_print.png
new file mode 100644
index 00000000..4544c7bf
--- /dev/null
+++ b/themes/res/drawable-hdpi/ic_holo_dark_print.png
Binary files differ
diff --git a/themes/res/drawable-hdpi/ic_holo_dark_remote.png b/themes/res/drawable-hdpi/ic_holo_dark_remote.png
new file mode 100644
index 00000000..17ed77a9
--- /dev/null
+++ b/themes/res/drawable-hdpi/ic_holo_dark_remote.png
Binary files differ
diff --git a/themes/res/drawable-hdpi/ic_holo_dark_secure.png b/themes/res/drawable-hdpi/ic_holo_dark_secure.png
new file mode 100755
index 00000000..f24aed37
--- /dev/null
+++ b/themes/res/drawable-hdpi/ic_holo_dark_secure.png
Binary files differ
diff --git a/themes/res/drawable-hdpi/ic_holo_dark_settings.png b/themes/res/drawable-hdpi/ic_holo_dark_settings.png
new file mode 100644
index 00000000..eee54507
--- /dev/null
+++ b/themes/res/drawable-hdpi/ic_holo_dark_settings.png
Binary files differ
diff --git a/themes/res/drawable-mdpi/ic_holo_dark_delete.png b/themes/res/drawable-mdpi/ic_holo_dark_delete.png
new file mode 100644
index 00000000..274f30ad
--- /dev/null
+++ b/themes/res/drawable-mdpi/ic_holo_dark_delete.png
Binary files differ
diff --git a/themes/res/drawable-mdpi/ic_holo_dark_print.png b/themes/res/drawable-mdpi/ic_holo_dark_print.png
new file mode 100644
index 00000000..947459d1
--- /dev/null
+++ b/themes/res/drawable-mdpi/ic_holo_dark_print.png
Binary files differ
diff --git a/themes/res/drawable-mdpi/ic_holo_dark_remote.png b/themes/res/drawable-mdpi/ic_holo_dark_remote.png
new file mode 100644
index 00000000..0cf01df3
--- /dev/null
+++ b/themes/res/drawable-mdpi/ic_holo_dark_remote.png
Binary files differ
diff --git a/themes/res/drawable-mdpi/ic_holo_dark_secure.png b/themes/res/drawable-mdpi/ic_holo_dark_secure.png
new file mode 100644
index 00000000..1df65e30
--- /dev/null
+++ b/themes/res/drawable-mdpi/ic_holo_dark_secure.png
Binary files differ
diff --git a/themes/res/drawable-mdpi/ic_holo_dark_settings.png b/themes/res/drawable-mdpi/ic_holo_dark_settings.png
new file mode 100644
index 00000000..78c3f3cf
--- /dev/null
+++ b/themes/res/drawable-mdpi/ic_holo_dark_settings.png
Binary files differ
diff --git a/themes/res/drawable-nodpi/dark_background_disabled.9.png b/themes/res/drawable-nodpi/dark_background_disabled.9.png
new file mode 100644
index 00000000..ac4ec10b
--- /dev/null
+++ b/themes/res/drawable-nodpi/dark_background_disabled.9.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 0ba30cd7..0fc9e11c 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_delete.png b/themes/res/drawable-xhdpi/ic_holo_dark_delete.png
new file mode 100644
index 00000000..cff1862f
--- /dev/null
+++ b/themes/res/drawable-xhdpi/ic_holo_dark_delete.png
Binary files differ
diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_print.png b/themes/res/drawable-xhdpi/ic_holo_dark_print.png
new file mode 100644
index 00000000..781564ae
--- /dev/null
+++ b/themes/res/drawable-xhdpi/ic_holo_dark_print.png
Binary files differ
diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_remote.png b/themes/res/drawable-xhdpi/ic_holo_dark_remote.png
new file mode 100644
index 00000000..c8c6297d
--- /dev/null
+++ b/themes/res/drawable-xhdpi/ic_holo_dark_remote.png
Binary files differ
diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_secure.png b/themes/res/drawable-xhdpi/ic_holo_dark_secure.png
new file mode 100644
index 00000000..40a99b75
--- /dev/null
+++ b/themes/res/drawable-xhdpi/ic_holo_dark_secure.png
Binary files differ
diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_settings.png b/themes/res/drawable-xhdpi/ic_holo_dark_settings.png
new file mode 100644
index 00000000..ad69b56f
--- /dev/null
+++ b/themes/res/drawable-xhdpi/ic_holo_dark_settings.png
Binary files differ
diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_delete.png b/themes/res/drawable-xxhdpi/ic_holo_dark_delete.png
new file mode 100644
index 00000000..0555c6a6
--- /dev/null
+++ b/themes/res/drawable-xxhdpi/ic_holo_dark_delete.png
Binary files differ
diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_print.png b/themes/res/drawable-xxhdpi/ic_holo_dark_print.png
new file mode 100644
index 00000000..f5248b9b
--- /dev/null
+++ b/themes/res/drawable-xxhdpi/ic_holo_dark_print.png
Binary files differ
diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_remote.png b/themes/res/drawable-xxhdpi/ic_holo_dark_remote.png
new file mode 100644
index 00000000..a6b57504
--- /dev/null
+++ b/themes/res/drawable-xxhdpi/ic_holo_dark_remote.png
Binary files differ
diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_secure.png b/themes/res/drawable-xxhdpi/ic_holo_dark_secure.png
new file mode 100644
index 00000000..fa79fcb3
--- /dev/null
+++ b/themes/res/drawable-xxhdpi/ic_holo_dark_secure.png
Binary files differ
diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_settings.png b/themes/res/drawable-xxhdpi/ic_holo_dark_settings.png
new file mode 100644
index 00000000..e5a1fbad
--- /dev/null
+++ b/themes/res/drawable-xxhdpi/ic_holo_dark_settings.png
Binary files differ
diff --git a/themes/res/drawable/dark_holo_button_selector.xml b/themes/res/drawable/dark_holo_button_selector.xml
index 76d512d1..efb778c2 100644
--- a/themes/res/drawable/dark_holo_button_selector.xml
+++ b/themes/res/drawable/dark_holo_button_selector.xml
@@ -24,6 +24,9 @@
android:state_enabled="true"
android:state_focused="true"/>
<item
+ android:drawable="@drawable/dark_background_disabled"
+ android:state_enabled="false"/>
+ <item
android:drawable="@drawable/dark_background"/>
</selector>
diff --git a/themes/res/values/dark_theme.xml b/themes/res/values/dark_theme.xml
index c55371bb..9fb44dbe 100644
--- a/themes/res/values/dark_theme.xml
+++ b/themes/res/values/dark_theme.xml
@@ -60,6 +60,12 @@
<drawable name="dark_ab_save_drawable">@drawable/ic_holo_dark_save</drawable>
<!-- The drawable for the tab action bar button -->
<drawable name="dark_ab_tab_drawable">@drawable/ic_holo_dark_tab</drawable>
+ <!-- The drawable for the print action bar button -->
+ <drawable name="dark_ab_print_drawable">@drawable/ic_holo_dark_print</drawable>
+ <!-- The drawable for the settings action bar button -->
+ <drawable name="dark_ab_settings_drawable">@drawable/ic_holo_dark_settings</drawable>
+ <!-- The drawable for the delete action bar button -->
+ <drawable name="dark_ab_delete_drawable">@drawable/ic_holo_dark_delete</drawable>
<!-- The close action drawable from the expander bar -->
<drawable name="dark_expander_close_drawable">@drawable/ic_holo_dark_expander_close</drawable>
@@ -85,6 +91,11 @@
<!-- FileSystem warning drawable -->
<drawable name="dark_filesystem_warning_drawable">@drawable/ic_holo_dark_fs_warning</drawable>
+ <!-- Secure FileSystem icon -->
+ <drawable name="dark_secure_filesystem_drawable">@drawable/ic_holo_dark_secure</drawable>
+ <!-- Remote FileSystem icon -->
+ <drawable name="dark_remote_filesystem_drawable">@drawable/ic_holo_dark_remote</drawable>
+
<!-- The popup menu checkable selector drawable -->
<drawable name="dark_popup_checkable_selector_drawable">@drawable/dark_checkable_selector</drawable>
<!-- The menu checkable selector drawable -->
@@ -116,6 +127,8 @@
<drawable name="dark_ic_usb_drawable">@drawable/ic_holo_dark_usb</drawable>
<drawable name="dark_ic_user_defined_bookmark_drawable">@drawable/ic_holo_dark_user_defined_bookmark</drawable>
<drawable name="dark_ic_copy_drawable">@drawable/ic_holo_dark_copy</drawable>
+ <drawable name="dark_ic_secure_drawable">@drawable/ic_holo_dark_secure</drawable>
+ <drawable name="dark_ic_remote_drawable">@drawable/ic_holo_dark_remote</drawable>
<!-- Disk usage graph -->
<color name="dark_disk_usage_total_color">#7ecccccc</color>