aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrint E. Kriebel <bekit@cyngn.com>2014-11-18 13:50:03 -0800
committerBrint E. Kriebel <bekit@cyngn.com>2014-11-18 13:50:03 -0800
commit8fe08525949a3a1e87e69cc3212eb8e9b43017e4 (patch)
tree6e8269b28e02f2ea46fb2fbfc55290a7fc85400a
parent9a64a5f377dbf0e6b4260ac7d7bb7dee97730ca6 (diff)
parent62fb78c473cbe578ca24d05d0a636a148bb068e1 (diff)
downloadandroid_packages_apps_CMFileManager-8fe08525949a3a1e87e69cc3212eb8e9b43017e4.tar.gz
android_packages_apps_CMFileManager-8fe08525949a3a1e87e69cc3212eb8e9b43017e4.tar.bz2
android_packages_apps_CMFileManager-8fe08525949a3a1e87e69cc3212eb8e9b43017e4.zip
Merge branch 'cm-11.0' into stable/cm-11.0
-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.java481
-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.java123
-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, 8470 insertions, 1147 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..dc79bc00 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 -->
@@ -277,8 +281,7 @@
<!-- Fso Properties Dialog - Execute Label -->
<string name="fso_properties_dialog_execute" translatable="false">X</string>
<!-- Fso Properties Dialog - Execute Label -->
- <string name="fso_properties_dialog_folder_items" translatable="false">
- <xliff:g id="folders">%1$s</xliff:g> / <xliff:g id="files">%2$s</xliff:g></string>
+ <string name="fso_properties_dialog_folder_items" translatable="false"><xliff:g id="folders">%1$s</xliff:g> / <xliff:g id="files">%2$s</xliff:g></string>
<!-- Fso Properties Dialog - Skip media scan -->
<string name="fso_properties_dialog_include_in_media_scan">Skip media scan:</string>
<string name="fso_failed_to_allow_media_scan">Failed to allow media scanning</string>
@@ -353,6 +356,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 +483,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 +556,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 +597,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 +606,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 +681,17 @@
<!-- 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 +763,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 do not match.</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..3d22c591 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,19 +398,25 @@ 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;
private boolean mExitFlag = false;
private long mExitBackTimeout = -1;
- private View mOptionsAnchorView;
-
private int mOrientation;
/**
@@ -337,6 +429,8 @@ public class NavigationActivity extends Activity
*/
Handler mHandler;
+ private AsyncTask<Void, Void, Boolean> mBookmarksTask;
+
/**
* {@inheritDoc}
*/
@@ -355,6 +449,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 +542,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 +647,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 +731,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 +753,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 +790,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 +809,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 +820,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 +838,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 +852,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 +871,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.isSelected() ? View.VISIBLE : View.GONE);
}
/**
@@ -904,12 +1028,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 +1074,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 +1102,7 @@ public class NavigationActivity extends Activity
bookmarks.addAll(loadFilesystemBookmarks());
}
bookmarks.addAll(loadSdStorageBookmarks());
+ bookmarks.addAll(loadVirtualBookmarks());
bookmarks.addAll(loadUserBookmarks());
return bookmarks;
}
@@ -1103,6 +1235,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 +1291,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 +1392,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 +1435,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 +1469,7 @@ public class NavigationActivity extends Activity
}
// Change the current directory to the user-defined initial directory
- navigationView.changeCurrentDir(initialDir);
+ navigationView.changeCurrentDir(initialDir, addToHistory);
}
/**
@@ -1353,7 +1533,11 @@ public class NavigationActivity extends Activity
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
- showOverflowPopUp(this.mOptionsAnchorView);
+ if (mDrawerLayout.isDrawerOpen(mDrawer)) {
+ mDrawerLayout.closeDrawer(mDrawer);
+ } else {
+ mDrawerLayout.openDrawer(mDrawer);
+ }
return true;
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
@@ -1368,65 +1552,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
@@ -1509,10 +1634,6 @@ public class NavigationActivity extends Activity
openSearch();
break;
- case R.id.ab_overflow:
- showOverflowPopUp(view);
- break;
-
default:
break;
}
@@ -1535,14 +1656,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 +1729,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
@@ -1719,56 +1850,6 @@ public class NavigationActivity extends Activity
}
/**
- * Method that shows a popup with the activity main menu.
- *
- * @param anchor The action button that was pressed
- */
- private void showOverflowPopUp(View anchor) {
- SimpleMenuListAdapter adapter =
- new HighlightedSimpleMenuListAdapter(this, R.menu.navigation);
- Menu menu = adapter.getMenu();
- int cc = this.mActionBar.getChildCount();
- for (int i = 0, j = this.mActionBar.getChildCount() - 1; i < cc; i++, j--) {
- View child = this.mActionBar.getChildAt(i);
- boolean visible = child.getVisibility() == View.VISIBLE;
- if (visible) {
- menu.removeItem(menu.getItem(j).getItemId());
- }
- }
-
- final ListPopupWindow popup = DialogHelper.createListPopupWindow(this, adapter, anchor);
- popup.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(
- final AdapterView<?> parent, final View v, final int position, final long id) {
-
- final int itemId = (int)id;
- NavigationActivity.this.mHandler.post(new Runnable() {
- @Override
- public void run() {
- popup.dismiss();
- switch (itemId) {
- case R.id.mnu_settings:
- //Settings
- openSettings();
- break;
-
- case R.id.mnu_search:
- //Search
- openSearch();
- popup.dismiss();
- break;
- default:
- break;
- }
- }
- });
- }
- });
- popup.show();
- }
-
- /**
* Method that show the information of a filesystem mount point.
*
* @param mp The mount point info
@@ -1797,6 +1878,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();
@@ -1911,6 +1999,8 @@ public class NavigationActivity extends Activity
}
//Navigate
+ boolean clearHistory = mHistoryTab.isSelected() && mHistory.size() > 0;
+ mClearHistory.setVisibility(clearHistory ? View.VISIBLE : View.GONE);
return true;
} catch (Throwable ex) {
@@ -1965,13 +2055,13 @@ public class NavigationActivity extends Activity
}
}
- //Extract a history from the
+ //Navigate to history
if (this.mHistory.size() > 0) {
- //Navigate to history
return navigateToHistory(this.mHistory.get(this.mHistory.size() - 1));
}
//Nothing to apply
+ mClearHistory.setVisibility(View.GONE);
return false;
}
@@ -2044,17 +2134,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 +2154,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 +2311,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 +2362,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 +2403,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 +2453,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 +2464,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 +2478,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 cb2ceab4..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;
@@ -419,8 +425,16 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
//- Properties
case R.id.mnu_actions_properties:
case R.id.mnu_actions_properties_current_folder:
+ FileSystemObject fso = this.mFso;
+ if (this.mOnSelectionListener != null) {
+ List<FileSystemObject> selection = this.mOnSelectionListener
+ .onRequestSelectedFiles();
+ if (selection.size() == 1) {
+ fso = selection.get(0);
+ }
+ }
InfoActionPolicy.showPropertiesDialog(
- this.mContext, this.mFso, this.mOnRequestRefreshListener);
+ this.mContext, fso, this.mOnRequestRefreshListener);
break;
//- Navigate to parent
@@ -429,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;
}
@@ -643,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);
}
}
@@ -655,6 +681,12 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
menu.removeItem(R.id.mnu_actions_add_to_bookmarks_current_folder);
}
+ //- Remove properties option if multiple files selected
+ if (selection != null && selection.size() > 1) {
+ menu.removeItem(R.id.mnu_actions_properties);
+ menu.removeItem(R.id.mnu_actions_properties_current_folder);
+ }
+
//- Paste/Move only when have a selection
if (this.mGlobal) {
if (selection == null || selection.size() == 0 ||
@@ -666,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);
}
}
@@ -688,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);
}
}
@@ -721,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>