OSDN Git Service

cmfm: secure storage and other improvements
authorJorge Ruesga <jorge@ruesga.com>
Tue, 28 Oct 2014 02:26:42 +0000 (03:26 +0100)
committerJorge Ruesga <jorge@ruesga.com>
Mon, 10 Nov 2014 23:16:25 +0000 (23:16 +0000)
This patch adds support for virtual filesystems and implements a SecureStorage
filesystem (a password protected area) mounted in /storage or /sdcard/storage
(in chrooted environments).
Also includes a better print support and a cleanup of the code and design of
the menu drawer.
Bump version to 2.0.0

Required: https://github.com/jruesga/android_external_libtruezip located
in external/libtruezip

Patchset 4: Fix selection of unmounted virtual storages.
            Fix actions on virtual mount points folders.
            Fix strings and typos. Change drop for delete secure storage.
Patchset 5: Move actionbar buttons to navigation drawer
            Remove history position
Patchset 6: Update theme preview images
            Fix filesystem status image on theme change
Patchset 7: Fix binary file detection in editor (including unicode files)
Patchset 8: Fix unsafe operations in virtual mountpoint logic
Patchset 9: Rebase

Change-Id: I65511352ca649dcbf238c8b07cf8c22465296e8e
Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
157 files changed:
Android.mk
AndroidManifest.xml
CHANGELOG.md
README.md
proguard.flags
res/drawable-hdpi/ic_holo_light_delete.png [new file with mode: 0644]
res/drawable-hdpi/ic_holo_light_print.png [new file with mode: 0644]
res/drawable-hdpi/ic_holo_light_remote.png [new file with mode: 0644]
res/drawable-hdpi/ic_holo_light_secure.png [new file with mode: 0644]
res/drawable-hdpi/ic_holo_light_settings.png [new file with mode: 0644]
res/drawable-hdpi/ic_overlay_remote.png [new file with mode: 0644]
res/drawable-hdpi/ic_overlay_secure.png [new file with mode: 0644]
res/drawable-mdpi/ic_holo_light_delete.png [new file with mode: 0644]
res/drawable-mdpi/ic_holo_light_print.png [new file with mode: 0644]
res/drawable-mdpi/ic_holo_light_remote.png [new file with mode: 0644]
res/drawable-mdpi/ic_holo_light_secure.png [new file with mode: 0644]
res/drawable-mdpi/ic_holo_light_settings.png [new file with mode: 0644]
res/drawable-mdpi/ic_overlay_remote.png [new file with mode: 0644]
res/drawable-mdpi/ic_overlay_secure.png [new file with mode: 0644]
res/drawable-nodpi/theme_preview.png
res/drawable-xhdpi/ic_holo_light_delete.png [new file with mode: 0644]
res/drawable-xhdpi/ic_holo_light_print.png [new file with mode: 0644]
res/drawable-xhdpi/ic_holo_light_remote.png [new file with mode: 0644]
res/drawable-xhdpi/ic_holo_light_secure.png [new file with mode: 0644]
res/drawable-xhdpi/ic_holo_light_settings.png [new file with mode: 0644]
res/drawable-xhdpi/ic_overlay_remote.png [new file with mode: 0644]
res/drawable-xhdpi/ic_overlay_secure.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_holo_light_delete.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_holo_light_print.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_holo_light_remote.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_holo_light_secure.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_holo_light_settings.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_overlay_remote.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_overlay_secure.png [new file with mode: 0644]
res/drawable/fso_folder_remote.xml [new file with mode: 0644]
res/drawable/fso_folder_secure.xml [new file with mode: 0644]
res/drawable/holo_button_selector.xml
res/layout/associations_dialog.xml
res/layout/color_picker_pref_item.xml
res/layout/history_item.xml
res/layout/navigation_drawer.xml
res/layout/simple_customtitle.xml
res/layout/unlock_dialog_message.xml [new file with mode: 0644]
res/menu/actions.xml
res/menu/drawer.xml [deleted file]
res/menu/navigation.xml
res/raw/changelog
res/values/colors.xml
res/values/overlay.xml
res/values/strings.xml
res/values/theme.xml
res/xml/preferences_headers.xml
res/xml/preferences_storage.xml [new file with mode: 0644]
src/com/cyanogenmod/filemanager/FileManagerApplication.java
src/com/cyanogenmod/filemanager/activities/EditorActivity.java
src/com/cyanogenmod/filemanager/activities/NavigationActivity.java
src/com/cyanogenmod/filemanager/activities/SearchActivity.java
src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java
src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java
src/com/cyanogenmod/filemanager/commands/java/FindCommand.java
src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java
src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/Program.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java
src/com/cyanogenmod/filemanager/commands/shell/Program.java
src/com/cyanogenmod/filemanager/commands/shell/Shell.java
src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java
src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/CancelledOperationException.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/Console.java
src/com/cyanogenmod/filemanager/console/VirtualConsole.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/java/JavaConsole.java
src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java
src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java
src/com/cyanogenmod/filemanager/model/Bookmark.java
src/com/cyanogenmod/filemanager/model/FileSystemObject.java
src/com/cyanogenmod/filemanager/model/History.java
src/com/cyanogenmod/filemanager/model/MountPoint.java
src/com/cyanogenmod/filemanager/model/Permissions.java
src/com/cyanogenmod/filemanager/model/Query.java
src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java
src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java
src/com/cyanogenmod/filemanager/preferences/Preferences.java
src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java
src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java
src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java
src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java
src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java
src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java
src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java
src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java
src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java
src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java
src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java
src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java
src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java
src/com/cyanogenmod/filemanager/util/AIDHelper.java
src/com/cyanogenmod/filemanager/util/AndroidHelper.java
src/com/cyanogenmod/filemanager/util/BookmarksHelper.java
src/com/cyanogenmod/filemanager/util/CommandHelper.java
src/com/cyanogenmod/filemanager/util/DialogHelper.java
src/com/cyanogenmod/filemanager/util/ExceptionUtil.java
src/com/cyanogenmod/filemanager/util/FileHelper.java
src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java
src/com/cyanogenmod/filemanager/util/ParseHelper.java
src/com/cyanogenmod/filemanager/util/StringHelper.java
tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java
themes/res/drawable-hdpi/ic_holo_dark_delete.png [new file with mode: 0644]
themes/res/drawable-hdpi/ic_holo_dark_print.png [new file with mode: 0644]
themes/res/drawable-hdpi/ic_holo_dark_remote.png [new file with mode: 0644]
themes/res/drawable-hdpi/ic_holo_dark_secure.png [new file with mode: 0755]
themes/res/drawable-hdpi/ic_holo_dark_settings.png [new file with mode: 0644]
themes/res/drawable-mdpi/ic_holo_dark_delete.png [new file with mode: 0644]
themes/res/drawable-mdpi/ic_holo_dark_print.png [new file with mode: 0644]
themes/res/drawable-mdpi/ic_holo_dark_remote.png [new file with mode: 0644]
themes/res/drawable-mdpi/ic_holo_dark_secure.png [new file with mode: 0644]
themes/res/drawable-mdpi/ic_holo_dark_settings.png [new file with mode: 0644]
themes/res/drawable-nodpi/dark_background_disabled.9.png [new file with mode: 0644]
themes/res/drawable-nodpi/dark_theme_preview.png
themes/res/drawable-xhdpi/ic_holo_dark_delete.png [new file with mode: 0644]
themes/res/drawable-xhdpi/ic_holo_dark_print.png [new file with mode: 0644]
themes/res/drawable-xhdpi/ic_holo_dark_remote.png [new file with mode: 0644]
themes/res/drawable-xhdpi/ic_holo_dark_secure.png [new file with mode: 0644]
themes/res/drawable-xhdpi/ic_holo_dark_settings.png [new file with mode: 0644]
themes/res/drawable-xxhdpi/ic_holo_dark_delete.png [new file with mode: 0644]
themes/res/drawable-xxhdpi/ic_holo_dark_print.png [new file with mode: 0644]
themes/res/drawable-xxhdpi/ic_holo_dark_remote.png [new file with mode: 0644]
themes/res/drawable-xxhdpi/ic_holo_dark_secure.png [new file with mode: 0644]
themes/res/drawable-xxhdpi/ic_holo_dark_settings.png [new file with mode: 0644]
themes/res/drawable/dark_holo_button_selector.xml
themes/res/values/dark_theme.xml

index bbd4b66..7aaf907 100644 (file)
@@ -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
index ac00a7b..528e2a3 100644 (file)
@@ -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"
       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"
     </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"
         <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/*" />
       </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>
index c64271e..bba634b 100644 (file)
@@ -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
index 7b33f94..63ded0a 100644 (file)
--- 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
index 6f04692..c6a4e5b 100644 (file)
   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 (file)
index 0000000..57f9b4d
Binary files /dev/null and b/res/drawable-hdpi/ic_holo_light_delete.png 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 (file)
index 0000000..5b5fc36
Binary files /dev/null and b/res/drawable-hdpi/ic_holo_light_print.png 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 (file)
index 0000000..8223443
Binary files /dev/null and b/res/drawable-hdpi/ic_holo_light_remote.png 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 (file)
index 0000000..75fe8a9
Binary files /dev/null and b/res/drawable-hdpi/ic_holo_light_secure.png 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 (file)
index 0000000..6da677a
Binary files /dev/null and b/res/drawable-hdpi/ic_holo_light_settings.png differ
diff --git a/res/drawable-hdpi/ic_overlay_remote.png b/res/drawable-hdpi/ic_overlay_remote.png
new file mode 100644 (file)
index 0000000..2ee5639
Binary files /dev/null and b/res/drawable-hdpi/ic_overlay_remote.png differ
diff --git a/res/drawable-hdpi/ic_overlay_secure.png b/res/drawable-hdpi/ic_overlay_secure.png
new file mode 100644 (file)
index 0000000..d8e692d
Binary files /dev/null and b/res/drawable-hdpi/ic_overlay_secure.png 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 (file)
index 0000000..e8ea0b1
Binary files /dev/null and b/res/drawable-mdpi/ic_holo_light_delete.png 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 (file)
index 0000000..e4a53d0
Binary files /dev/null and b/res/drawable-mdpi/ic_holo_light_print.png 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 (file)
index 0000000..87eeff5
Binary files /dev/null and b/res/drawable-mdpi/ic_holo_light_remote.png 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 (file)
index 0000000..dcf1b11
Binary files /dev/null and b/res/drawable-mdpi/ic_holo_light_secure.png 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 (file)
index 0000000..be39869
Binary files /dev/null and b/res/drawable-mdpi/ic_holo_light_settings.png differ
diff --git a/res/drawable-mdpi/ic_overlay_remote.png b/res/drawable-mdpi/ic_overlay_remote.png
new file mode 100644 (file)
index 0000000..318dc87
Binary files /dev/null and b/res/drawable-mdpi/ic_overlay_remote.png differ
diff --git a/res/drawable-mdpi/ic_overlay_secure.png b/res/drawable-mdpi/ic_overlay_secure.png
new file mode 100644 (file)
index 0000000..8099896
Binary files /dev/null and b/res/drawable-mdpi/ic_overlay_secure.png differ
index ba98ce0..866ed72 100644 (file)
Binary files a/res/drawable-nodpi/theme_preview.png and b/res/drawable-nodpi/theme_preview.png 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 (file)
index 0000000..a5cbd4f
Binary files /dev/null and b/res/drawable-xhdpi/ic_holo_light_delete.png 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 (file)
index 0000000..1927fcc
Binary files /dev/null and b/res/drawable-xhdpi/ic_holo_light_print.png 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 (file)
index 0000000..a45da43
Binary files /dev/null and b/res/drawable-xhdpi/ic_holo_light_remote.png 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 (file)
index 0000000..5a6d50d
Binary files /dev/null and b/res/drawable-xhdpi/ic_holo_light_secure.png 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 (file)
index 0000000..09f504d
Binary files /dev/null and b/res/drawable-xhdpi/ic_holo_light_settings.png differ
diff --git a/res/drawable-xhdpi/ic_overlay_remote.png b/res/drawable-xhdpi/ic_overlay_remote.png
new file mode 100644 (file)
index 0000000..a0003e8
Binary files /dev/null and b/res/drawable-xhdpi/ic_overlay_remote.png differ
diff --git a/res/drawable-xhdpi/ic_overlay_secure.png b/res/drawable-xhdpi/ic_overlay_secure.png
new file mode 100644 (file)
index 0000000..174a1d8
Binary files /dev/null and b/res/drawable-xhdpi/ic_overlay_secure.png 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 (file)
index 0000000..743fbfd
Binary files /dev/null and b/res/drawable-xxhdpi/ic_holo_light_delete.png 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 (file)
index 0000000..6d1fdf6
Binary files /dev/null and b/res/drawable-xxhdpi/ic_holo_light_print.png 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 (file)
index 0000000..0cb6fb7
Binary files /dev/null and b/res/drawable-xxhdpi/ic_holo_light_remote.png 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 (file)
index 0000000..a959f74
Binary files /dev/null and b/res/drawable-xxhdpi/ic_holo_light_secure.png 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 (file)
index 0000000..f324673
Binary files /dev/null and b/res/drawable-xxhdpi/ic_holo_light_settings.png differ
diff --git a/res/drawable-xxhdpi/ic_overlay_remote.png b/res/drawable-xxhdpi/ic_overlay_remote.png
new file mode 100644 (file)
index 0000000..52fb870
Binary files /dev/null and b/res/drawable-xxhdpi/ic_overlay_remote.png differ
diff --git a/res/drawable-xxhdpi/ic_overlay_secure.png b/res/drawable-xxhdpi/ic_overlay_secure.png
new file mode 100644 (file)
index 0000000..dc9a0b8
Binary files /dev/null and b/res/drawable-xxhdpi/ic_overlay_secure.png differ
diff --git a/res/drawable/fso_folder_remote.xml b/res/drawable/fso_folder_remote.xml
new file mode 100644 (file)
index 0000000..79f40ef
--- /dev/null
@@ -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 (file)
index 0000000..1f27b84
--- /dev/null
@@ -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>
index 97ebf26..d6a5a8d 100644 (file)
@@ -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>
index 210301a..18009c9 100644 (file)
         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
index 02c44c1..e250561 100644 (file)
@@ -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"
index ef02927..9aa4d8a 100644 (file)
             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
index 9398ede..c5e2909 100644 (file)
 <?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
index 97dafca..6828693 100644 (file)
     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 (file)
index 0000000..8c7a465
--- /dev/null
@@ -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
index ba5f50c..bb2d8e5 100644 (file)
       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 -->
       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 (file)
index 7a5ef1d..0000000
+++ /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
index 5111c94..e63074b 100644 (file)
     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
index 447938c..bba634b 100644 (file)
@@ -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
 -------------
index 5dad820..2284184 100644 (file)
@@ -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>
index 1258d25..88c5d68 100644 (file)
@@ -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>
index 2c51d02..0685958 100644 (file)
     <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>
     <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 -->
     <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 -->
     <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>
     <!-- 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 -->
     <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 -->
     <!-- 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>
     <!-- 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 -->
     <string name="ash_quoted_string">Quoted string</string>
     <string name="ash_variable">Variable</string>
 
+    <!-- Secure Storage -->
+    <!-- Secure Storage dialog title - Unlock -->
+    <string name="secure_storage_unlock_title">Unlock storage</string>
+    <!-- Secure Storage dialog title - Create -->
+    <string name="secure_storage_create_title">Create storage</string>
+    <!-- Secure Storage dialog title - Reset -->
+    <string name="secure_storage_reset_title">Reset password</string>
+    <!-- Secure Storage dialog title - Delete -->
+    <string name="secure_storage_delete_title">Delete storage</string>
+    <!-- Secure Storage unlock dialog message (secure storage exists) -->
+    <string name="secure_storage_unlock_key_prompt_msg">Type the password to unlock the secure storage filesystem.</string>
+    <!-- Secure Storage unlock dialog message (new secure storage) -->
+    <string name="secure_storage_unlock_key_new_msg">Type a password to protect the secure storage filesystem.</string>
+    <!-- Secure Storage unlock dialog message (reset the current password) -->
+    <string name="secure_storage_unlock_key_reset_msg">Type the current and new passwords to reset the secure storage filesystem.</string>
+    <!-- Secure Storage unlock dialog message (delete the secure storage) -->
+    <string name="secure_storage_unlock_key_delete_msg">Type the current password to delete the secure storage filesystem.</string>
+    <!-- Secure Storage unlock dialog old key title -->
+    <string name="secure_storage_unlock_old_key_title">Old password:</string>
+    <!-- Secure Storage unlock dialog key title -->
+    <string name="secure_storage_unlock_new_key_title">New Password:</string>
+    <!-- Secure Storage unlock dialog key title -->
+    <string name="secure_storage_unlock_key_title">Password:</string>
+    <!-- Secure Storage unlock dialog repeat key title-->
+    <string name="secure_storage_unlock_repeat_title">Repeat password:</string>
+    <!-- Secure Storage create button -->
+    <string name="secure_storage_create_button">Create</string>
+    <!-- Secure Storage unlock button -->
+    <string name="secure_storage_unlock_button">Unlock</string>
+    <!-- Secure Storage reset button -->
+    <string name="secure_storage_reset_button">Reset</string>
+    <!-- Secure Storage delete button -->
+    <string name="secure_storage_delete_button">Delete</string>
+    <!-- Secure Storage unlock failed toast -->
+    <string name="secure_storage_unlock_failed">Cannot unlock the storage</string>
+    <!-- Secure Storage unlock validation, length -->
+    <string name="secure_storage_unlock_validation_length">Password must have at least <xliff:g id="characters">%1$d</xliff:g> characters.</string>
+    <!-- Secure Storage unlock validation, equal -->
+    <string name="secure_storage_unlock_validation_equals">Passwords are not the same.</string>
+
     <!-- Print messages -->
     <!-- Unsupported document format -->
     <string name="print_unsupported_document">Unsupported document format</string>
index d5128c9..2920801 100644 (file)
     <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>
     <!-- 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 -->
     <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>
     <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>
index 80c4509..2c97dce 100644 (file)
@@ -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 (file)
index 0000000..47f27ee
--- /dev/null
@@ -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>
index be475df..8230c1f 100644 (file)
@@ -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
index 16dd035..8e6c413 100644 (file)
@@ -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
index 97473c0..6770acf 100644 (file)
@@ -69,6 +69,9 @@ import com.cyanogenmod.filemanager.console.ConsoleAllocException;
 import com.cyanogenmod.filemanager.console.ConsoleBuilder;
 import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
 import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.VirtualConsole;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
+import com.cyanogenmod.filemanager.console.secure.SecureConsole;
 import com.cyanogenmod.filemanager.listeners.OnHistoryListener;
 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
 import com.cyanogenmod.filemanager.model.Bookmark;
@@ -170,6 +173,12 @@ public class NavigationActivity extends Activity
     public static final String EXTRA_NAVIGATE_TO =
             "extra_navigate_to"; //$NON-NLS-1$
 
+    /**
+     * Constant for extra information for request to add navigation to the history
+     */
+    public static final String EXTRA_ADD_TO_HISTORY =
+            "extra_add_to_history"; //$NON-NLS-1$
+
     // The timeout needed to reset the exit status for back button
     // After this time user need to tap 2 times the back button to
     // exit, and the toast is shown again after the first tap.
@@ -295,11 +304,88 @@ public class NavigationActivity extends Activity
                         FileHelper.sReloadDateTimeFormats = true;
                         NavigationActivity.this.getCurrentNavigationView().refresh();
                     }
+                } else if (intent.getAction().compareTo(
+                        FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED) == 0) {
+                    onRequestBookmarksRefresh();
+                    removeUnmountedHistory();
+                    removeUnmountedSelection();
                 }
             }
         }
     };
 
+    private OnClickListener mOnClickDrawerTabListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.drawer_bookmarks_tab:
+                    if (!mBookmarksTab.isSelected()) {
+                        mBookmarksTab.setSelected(true);
+                        mHistoryTab.setSelected(false);
+                        mBookmarksTab.setTextAppearance(
+                                NavigationActivity.this, R.style.primary_text_appearance);
+                        mHistoryTab.setTextAppearance(
+                                NavigationActivity.this, R.style.secondary_text_appearance);
+                        mHistoryLayout.setVisibility(View.GONE);
+                        mBookmarksLayout.setVisibility(View.VISIBLE);
+                        applyTabTheme();
+
+                        try {
+                            Preferences.savePreference(FileManagerSettings.USER_PREF_LAST_DRAWER_TAB,
+                                    Integer.valueOf(0), true);
+                        } catch (Exception ex) {
+                            Log.e(TAG, "Can't save last drawer tab", ex); //$NON-NLS-1$
+                        }
+
+                        mClearHistory.setVisibility(View.GONE);
+                    }
+                    break;
+                case R.id.drawer_history_tab:
+                    if (!mHistoryTab.isSelected()) {
+                        mHistoryTab.setSelected(true);
+                        mBookmarksTab.setSelected(false);
+                        mHistoryTab.setTextAppearance(
+                                NavigationActivity.this, R.style.primary_text_appearance);
+                        mBookmarksTab.setTextAppearance(
+                                NavigationActivity.this, R.style.secondary_text_appearance);
+                        mBookmarksLayout.setVisibility(View.GONE);
+                        mHistoryLayout.setVisibility(View.VISIBLE);
+                        applyTabTheme();
+
+                        try {
+                            Preferences.savePreference(FileManagerSettings.USER_PREF_LAST_DRAWER_TAB,
+                                    Integer.valueOf(1), true);
+                        } catch (Exception ex) {
+                            Log.e(TAG, "Can't save last drawer tab", ex); //$NON-NLS-1$
+                        }
+
+                        mClearHistory.setVisibility(mHistory.size() > 0 ? View.VISIBLE : View.GONE);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    private OnClickListener mOnClickDrawerActionBarListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.ab_settings:
+                    mDrawerLayout.closeDrawer(mDrawer);
+                    openSettings();
+                    break;
+                case R.id.ab_clear_history:
+                    clearHistory();
+                    mClearHistory.setVisibility(View.GONE);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
     /**
      * @hide
      */
@@ -312,11 +398,19 @@ public class NavigationActivity extends Activity
     private SelectionView mSelectionBar;
 
     private DrawerLayout mDrawerLayout;
-    private ScrollView mDrawer;
+    private ViewGroup mDrawer;
     private ActionBarDrawerToggle mDrawerToggle;
     private LinearLayout mDrawerHistory;
     private TextView mDrawerHistoryEmpty;
 
+    private TextView mBookmarksTab;
+    private TextView mHistoryTab;
+    private View mBookmarksLayout;
+    private View mHistoryLayout;
+
+    private ButtonItem mSettings;
+    private ButtonItem mClearHistory;
+
     private List<Bookmark> mBookmarks;
     private LinearLayout mDrawerBookmarks;
 
@@ -337,6 +431,8 @@ public class NavigationActivity extends Activity
      */
     Handler mHandler;
 
+    private AsyncTask<Void, Void, Boolean> mBookmarksTask;
+
     /**
      * {@inheritDoc}
      */
@@ -355,6 +451,7 @@ public class NavigationActivity extends Activity
         filter.addAction(Intent.ACTION_DATE_CHANGED);
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        filter.addAction(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED);
         registerReceiver(this.mNotificationReceiver, filter);
 
         // Set the theme before setContentView
@@ -447,6 +544,22 @@ public class NavigationActivity extends Activity
         if (!FileManagerApplication.checkRestrictSecondaryUsersAccess(this, mChRooted)) {
             return;
         }
+
+        // Check that the current dir is mounted (for virtual filesystems)
+        String curDir = mNavigationViews[mCurrentNavigationView].getCurrentDir();
+        if (curDir != null) {
+            VirtualMountPointConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath(
+                    mNavigationViews[mCurrentNavigationView].getCurrentDir());
+            if (vc != null && !vc.isMounted()) {
+                onRequestBookmarksRefresh();
+                removeUnmountedHistory();
+                removeUnmountedSelection();
+
+                Intent intent = new Intent();
+                intent.putExtra(EXTRA_ADD_TO_HISTORY, false);
+                initNavigation(NavigationActivity.this.mCurrentNavigationView, false, intent);
+            }
+        }
     }
 
     @Override
@@ -536,7 +649,7 @@ public class NavigationActivity extends Activity
                 ((Boolean)FileManagerSettings.SETTINGS_FIRST_USE.getDefaultValue()).booleanValue());
 
         //Display the welcome message?
-        if (firstUse && !FileManagerApplication.isDeviceRooted()) {
+        if (firstUse && FileManagerApplication.isDeviceRooted()) {
             // open navigation drawer to show user that it exists
             mDrawerLayout.openDrawer(mDrawer);
 
@@ -620,11 +733,10 @@ public class NavigationActivity extends Activity
             }
         });
 
-        // Have overflow menu?
+        // Have overflow menu? Actually no. There is only a search action, so just hide
+        // the overflow
         View overflow = findViewById(R.id.ab_overflow);
-        boolean showOptionsMenu = AndroidHelper.showOptionsMenu(getApplicationContext());
-        overflow.setVisibility(showOptionsMenu ? View.VISIBLE : View.GONE);
-        this.mOptionsAnchorView = showOptionsMenu ? overflow : this.mActionBar;
+        overflow.setVisibility(View.GONE);
 
         // Show the status bar
         View statusBar = findViewById(R.id.navigation_statusbar_portrait_holder);
@@ -643,11 +755,30 @@ public class NavigationActivity extends Activity
      */
     private void initDrawer() {
         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
-        mDrawer = (ScrollView) findViewById(R.id.drawer);
+        mDrawer = (ViewGroup) findViewById(R.id.drawer);
         mDrawerBookmarks = (LinearLayout) findViewById(R.id.bookmarks_list);
         mDrawerHistory = (LinearLayout) findViewById(R.id.history_list);
         mDrawerHistoryEmpty = (TextView) findViewById(R.id.history_empty);
 
+        mBookmarksLayout = findViewById(R.id.drawer_bookmarks);
+        mHistoryLayout = findViewById(R.id.drawer_history);
+        mBookmarksTab = (TextView) findViewById(R.id.drawer_bookmarks_tab);
+        mHistoryTab = (TextView) findViewById(R.id.drawer_history_tab);
+        mBookmarksTab.setOnClickListener(mOnClickDrawerTabListener);
+        mHistoryTab.setOnClickListener(mOnClickDrawerTabListener);
+
+        mSettings = (ButtonItem) findViewById(R.id.ab_settings);
+        mSettings.setOnClickListener(mOnClickDrawerActionBarListener);
+        mClearHistory = (ButtonItem) findViewById(R.id.ab_clear_history);
+        mClearHistory.setOnClickListener(mOnClickDrawerActionBarListener);
+
+        // Restore the last tab pressed
+        Integer lastTab = Preferences.getSharedPreferences().getInt(
+                FileManagerSettings.USER_PREF_LAST_DRAWER_TAB.getId(),
+                (Integer) FileManagerSettings.USER_PREF_LAST_DRAWER_TAB
+                        .getDefaultValue());
+        mOnClickDrawerTabListener.onClick(lastTab == 0 ? mBookmarksTab : mHistoryTab);
+
         // Set the navigation drawer "hamburger" icon
         mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                 R.drawable.ic_holo_light_navigation_drawer,
@@ -661,7 +792,6 @@ public class NavigationActivity extends Activity
                                 | ActionBar.DISPLAY_SHOW_HOME);
                 getActionBar().setDisplayHomeAsUpEnabled(true);
                 getActionBar().setHomeButtonEnabled(true);
-                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
             }
 
             /** Called when a drawer has settled in a completely open state. */
@@ -681,8 +811,6 @@ public class NavigationActivity extends Activity
                         "action_bar_title", "id", "android");
                 TextView v = (TextView) findViewById(titleId);
                 theme.setTextColor(NavigationActivity.this, v, "text_color"); //$NON-NLS-1$
-
-                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
             }
         };
 
@@ -694,7 +822,7 @@ public class NavigationActivity extends Activity
     }
 
     /**
-     * Method adds a history entry to the history list in the drawer
+     * Method that adds a history entry to the history list in the drawer
      */
     private void addHistoryToDrawer(int index, HistoryNavigable navigable) {
         // hide empty message
@@ -712,10 +840,7 @@ public class NavigationActivity extends Activity
         TextView name = (TextView) view.findViewById(R.id.history_item_name);
         TextView directory = (TextView) view
                 .findViewById(R.id.history_item_directory);
-        TextView position = (TextView) view
-                .findViewById(R.id.history_item_position);
 
-        // if (history.getItem() instanceof NavigationViewInfoParcelable)
         Drawable icon = iconholder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$
         if (navigable instanceof SearchInfoParcelable) {
             icon = iconholder.getDrawable("ic_history_search_drawable"); //$NON-NLS-1$
@@ -729,11 +854,9 @@ public class NavigationActivity extends Activity
 
         name.setText(title);
         directory.setText(navigable.getDescription());
-        position.setText(String.format("#%d", index + 1));
 
         theme.setTextColor(this, name, "text_color");
         theme.setTextColor(this, directory, "text_color");
-        theme.setTextColor(this, position, "text_color");
 
         // handle item click
         view.setOnClickListener(new OnClickListener() {
@@ -750,6 +873,9 @@ public class NavigationActivity extends Activity
 
         // add as first child
         mDrawerHistory.addView(view, 0);
+
+        // Show clear button if history tab is selected
+        mClearHistory.setVisibility(mHistoryTab.getVisibility());
     }
 
     /**
@@ -904,12 +1030,17 @@ public class NavigationActivity extends Activity
     /**
      * Method that initializes the bookmarks.
      */
-    private void initBookmarks() {
+    private synchronized void initBookmarks() {
+        if (mBookmarksTask != null &&
+                !mBookmarksTask.getStatus().equals(AsyncTask.Status.FINISHED)) {
+            return;
+        }
+
         // Retrieve the loading view
         final View waiting = findViewById(R.id.bookmarks_loading);
 
         // Load bookmarks in background
-        AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
+        mBookmarksTask = new AsyncTask<Void, Void, Boolean>() {
             Exception mCause;
 
             @Override
@@ -945,14 +1076,16 @@ public class NavigationActivity extends Activity
                                 NavigationActivity.this, this.mCause);
                     }
                 }
+                mBookmarksTask = null;
             }
 
             @Override
             protected void onCancelled() {
                 waiting.setVisibility(View.GONE);
+                mBookmarksTask = null;
             }
         };
-        task.execute();
+        mBookmarksTask.execute();
     }
 
     /**
@@ -971,6 +1104,7 @@ public class NavigationActivity extends Activity
             bookmarks.addAll(loadFilesystemBookmarks());
         }
         bookmarks.addAll(loadSdStorageBookmarks());
+        bookmarks.addAll(loadVirtualBookmarks());
         bookmarks.addAll(loadUserBookmarks());
         return bookmarks;
     }
@@ -1103,6 +1237,32 @@ public class NavigationActivity extends Activity
     }
 
     /**
+     * Method that loads all virtual mount points.
+     *
+     * @return List<Bookmark> The bookmarks loaded
+     */
+    private List<Bookmark> loadVirtualBookmarks() {
+        // Initialize the bookmarks
+        List<Bookmark> bookmarks = new ArrayList<Bookmark>();
+        List<MountPoint> mps = VirtualMountPointConsole.getVirtualMountPoints();
+        for (MountPoint mp : mps) {
+            BOOKMARK_TYPE type = null;
+            String name = null;
+            if (mp.isSecure()) {
+                type = BOOKMARK_TYPE.SECURE;
+                name = getString(R.string.bookmarks_secure);
+            } else if (mp.isRemote()) {
+                type = BOOKMARK_TYPE.REMOTE;
+                name = getString(R.string.bookmarks_remote);
+            } else {
+                continue;
+            }
+            bookmarks.add(new Bookmark(type, name, mp.getMountPoint()));
+        }
+        return bookmarks;
+    }
+
+    /**
      * Method that loads the user bookmarks (added by the user).
      *
      * @return List<Bookmark> The bookmarks loaded
@@ -1133,6 +1293,17 @@ public class NavigationActivity extends Activity
                 /** NON BLOCK **/
             }
         }
+
+        // Remove bookmarks from virtual storage if the filesystem is not mount
+        int c = bookmarks.size() - 1;
+        for (int i = c; i >= 0; i--) {
+            VirtualMountPointConsole vc =
+                    VirtualMountPointConsole.getVirtualConsoleForPath(bookmarks.get(i).mPath);
+            if (vc != null && !vc.isMounted()) {
+                bookmarks.remove(i);
+            }
+        }
+
         return bookmarks;
     }
 
@@ -1223,6 +1394,15 @@ public class NavigationActivity extends Activity
             initialDir = navigateTo;
         }
 
+        // Add to history
+        final boolean addToHistory = intent.getBooleanExtra(EXTRA_ADD_TO_HISTORY, true);
+
+        // We cannot navigate to a secure console if is unmount, go to root in that case
+        VirtualConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath(initialDir);
+        if (vc != null && vc instanceof SecureConsole && !((SecureConsole) vc).isMounted()) {
+            initialDir = FileHelper.ROOT_DIRECTORY;
+        }
+
         if (this.mChRooted) {
             // Initial directory is the first external sdcard (sdcard, emmc, usb, ...)
             if (!StorageHelper.isPathInStorageVolume(initialDir)) {
@@ -1257,17 +1437,19 @@ public class NavigationActivity extends Activity
                             this, ipex, false, true, new OnRelaunchCommandResult() {
                         @Override
                         public void onSuccess() {
-                            navigationView.changeCurrentDir(absInitialDir);
+                            navigationView.changeCurrentDir(absInitialDir, addToHistory);
                         }
                         @Override
                         public void onFailed(Throwable cause) {
                             showInitialInvalidDirectoryMsg(userInitialDir);
-                            navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY);
+                            navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY,
+                                    addToHistory);
                         }
                         @Override
                         public void onCancelled() {
                             showInitialInvalidDirectoryMsg(userInitialDir);
-                            navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY);
+                            navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY,
+                                    addToHistory);
                         }
                     });
 
@@ -1289,7 +1471,7 @@ public class NavigationActivity extends Activity
         }
 
         // Change the current directory to the user-defined initial directory
-        navigationView.changeCurrentDir(initialDir);
+        navigationView.changeCurrentDir(initialDir, addToHistory);
     }
 
     /**
@@ -1368,65 +1550,6 @@ public class NavigationActivity extends Activity
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        // Pass the event to ActionBarDrawerToggle, if it returns
-        // true, then it has handled the app icon touch event
-        if (mDrawerToggle.onOptionsItemSelected(item)) {
-            return true;
-        }
-
-        // just handle the drawer list here
-        switch (item.getItemId()) {
-            case R.id.mnu_actions_add_to_bookmarks_current_folder:
-                // TODO add bookmark
-                Log.d(TAG, "add bookmark");
-                return true;
-            case R.id.mnu_clear_history:
-                clearHistory();
-                return true;
-            case R.id.mnu_settings:
-                openSettings();
-                return true;
-        }
-
-        return super.onOptionsItemSelected(item);
-    }
-
-    /**
-     * Called when the menu is created. Just includes the drawer's overflow
-     * menu. All entries are hidden until onPrepareOptionsMenu unhides them.
-     */
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        MenuInflater inflater = getMenuInflater();
-        inflater.inflate(R.menu.drawer, menu);
-        return true;
-    }
-
-    /**
-     * Called whenever we call invalidateOptionsMenu()
-     */
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer);
-
-        for (int i = 0; i < menu.size(); i++) {
-            // show all items if drawer is open,
-            // hide them if not
-            menu.getItem(i).setVisible(drawerOpen);
-
-            if (menu.getItem(i).getItemId() == R.id.mnu_clear_history) {
-                menu.getItem(i).setEnabled(mHistory.size() > 0);
-            }
-        }
-
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    /**
      * Method invoked when an action item is clicked.
      *
      * @param view The button pushed
@@ -1535,14 +1658,16 @@ public class NavigationActivity extends Activity
                 case INTENT_REQUEST_SEARCH:
                     if (resultCode == RESULT_OK) {
                         //Change directory?
-                        FileSystemObject fso =
-                                (FileSystemObject)data.
-                                    getSerializableExtra(EXTRA_SEARCH_ENTRY_SELECTION);
-                        SearchInfoParcelable searchInfo =
-                                data.getParcelableExtra(EXTRA_SEARCH_LAST_SEARCH_DATA);
-                        if (fso != null) {
-                            //Goto to new directory
-                            getCurrentNavigationView().open(fso, searchInfo);
+                        Bundle bundle = data.getExtras();
+                        if (bundle != null) {
+                            FileSystemObject fso = (FileSystemObject) bundle.getSerializable(
+                                    EXTRA_SEARCH_ENTRY_SELECTION);
+                            SearchInfoParcelable searchInfo =
+                                    bundle.getParcelable(EXTRA_SEARCH_LAST_SEARCH_DATA);
+                            if (fso != null) {
+                                //Goto to new directory
+                                getCurrentNavigationView().open(fso, searchInfo);
+                            }
                         }
                     } else if (resultCode == RESULT_CANCELED) {
                         SearchInfoParcelable searchInfo =
@@ -1606,6 +1731,14 @@ public class NavigationActivity extends Activity
      * {@inheritDoc}
      */
     @Override
+    public void onRequestBookmarksRefresh() {
+        initBookmarks();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void onRequestRemove(Object o, boolean clearSelection) {
         if (o instanceof FileSystemObject) {
             // Remove from view
@@ -1797,6 +1930,13 @@ public class NavigationActivity extends Activity
                 if (breadcrumb.getMountPointInfo().compareTo(mountPoint) == 0) {
                     breadcrumb.updateMountPointInfo();
                 }
+                if (mountPoint.isSecure()) {
+                    // Secure mountpoints only can be unmount, so we need to move the navigation
+                    // to a secure storage (do not add to history)
+                    Intent intent = new Intent();
+                    intent.putExtra(EXTRA_ADD_TO_HISTORY, false);
+                    initNavigation(NavigationActivity.this.mCurrentNavigationView, false, intent);
+                }
             }
         });
         dialog.show();
@@ -2044,17 +2184,19 @@ public class NavigationActivity extends Activity
      * Method that remove the {@link FileSystemObject} from the history
      */
     private void removeFromHistory(FileSystemObject fso) {
-        // TODO remove drawer entry here, too
         if (this.mHistory != null) {
-            int cc = this.mHistory.size();
-            for (int i = cc-1; i >= 0 ; i--) {
+            int cc = this.mHistory.size() - 1;
+            for (int i = cc; i >= 0 ; i--) {
                 History history = this.mHistory.get(i);
                 if (history.getItem() instanceof NavigationViewInfoParcelable) {
                     String p0 = fso.getFullPath();
-                    String p1 =
-                            ((NavigationViewInfoParcelable)history.getItem()).getCurrentDir();
+                    String p1 = ((NavigationViewInfoParcelable) history.getItem()).getCurrentDir();
                     if (p0.compareTo(p1) == 0) {
                         this.mHistory.remove(i);
+                        mDrawerHistory.removeViewAt(mDrawerHistory.getChildCount() - i - 1);
+                        mDrawerHistoryEmpty.setVisibility(
+                                mDrawerHistory.getChildCount() == 0 ? View.VISIBLE : View.GONE);
+                        updateHistoryPositions();
                     }
                 }
             }
@@ -2062,6 +2204,17 @@ public class NavigationActivity extends Activity
     }
 
     /**
+     * Update the history positions after one of the history is removed from drawer
+     */
+    private void updateHistoryPositions() {
+        int cc = this.mHistory.size() - 1;
+        for (int i = 0; i <= cc ; i++) {
+            History history = this.mHistory.get(i);
+            history.setPosition(i + 1);
+        }
+    }
+
+    /**
      * Method that ask the user to change the access mode prior to crash.
      * @hide
      */
@@ -2208,11 +2361,8 @@ public class NavigationActivity extends Activity
                     rbw += bw;
                 }
             }
-            int w = abw + rbw;
-            boolean showOptionsMenu = AndroidHelper.showOptionsMenu(getApplicationContext());
-            if (!showOptionsMenu) {
-                w -= bw;
-            }
+            // Currently there isn't overflow menu
+            int w = abw + rbw - bw;
 
             // Add to the new location
             ViewGroup newParent = (ViewGroup)findViewById(R.id.navigation_title_landscape_holder);
@@ -2262,6 +2412,40 @@ public class NavigationActivity extends Activity
     }
 
     /**
+     * Method that removes all the history items that refers to virtual unmounted filesystems
+     */
+    private void removeUnmountedHistory() {
+        int cc = mHistory.size() - 1;
+        for (int i = cc; i >= 0; i--) {
+            History history = mHistory.get(i);
+            if (history.getItem() instanceof NavigationViewInfoParcelable) {
+                NavigationViewInfoParcelable navigableInfo =
+                        ((NavigationViewInfoParcelable) history.getItem());
+                VirtualMountPointConsole vc =
+                        VirtualMountPointConsole.getVirtualConsoleForPath(
+                                navigableInfo.getCurrentDir());
+                if (vc != null && !vc.isMounted()) {
+                    mHistory.remove(i);
+                    mDrawerHistory.removeViewAt(mDrawerHistory.getChildCount() - i - 1);
+                }
+            }
+        }
+        mDrawerHistoryEmpty.setVisibility(
+                mDrawerHistory.getChildCount() == 0 ? View.VISIBLE : View.GONE);
+        updateHistoryPositions();
+    }
+
+    /**
+     * Method that removes all the selection items that refers to virtual unmounted filesystems
+     */
+    private void removeUnmountedSelection() {
+        for (NavigationView view : mNavigationViews) {
+            view.removeUnmountedSelection();
+        }
+        mSelectionBar.setSelection(getNavigationView(mCurrentNavigationView).getSelectedFiles());
+    }
+
+    /**
      * Method that applies the current theme to the activity
      * @hide
      */
@@ -2269,6 +2453,7 @@ public class NavigationActivity extends Activity
         int orientation = getResources().getConfiguration().orientation;
         Theme theme = ThemeManager.getCurrentTheme(this);
         theme.setBaseTheme(this, false);
+        applyTabTheme();
 
         // imitate a closed drawer while layout is rebuilt to avoid NullPointerException
         boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer);
@@ -2318,11 +2503,6 @@ public class NavigationActivity extends Activity
         theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
 
         // - Navigation drawer
-        theme.setBackgroundColor(this, mDrawer, "drawer_color");
-        v = findViewById(R.id.bookmarks_header);
-        theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
-        v = findViewById(R.id.history_header);
-        theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
         v = findViewById(R.id.history_empty);
         theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
         mDrawerToggle.setDrawerImageResource(theme.getResourceId(this, "drawer_icon"));
@@ -2334,8 +2514,6 @@ public class NavigationActivity extends Activity
             theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
             v = item.findViewById(R.id.history_item_directory);
             theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
-            v = item.findViewById(R.id.history_item_position);
-            theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
         }
 
         //- NavigationView
@@ -2350,4 +2528,25 @@ public class NavigationActivity extends Activity
         }
     }
 
+    /**
+     * Method that applies the current theme to the tab host
+     */
+    private void applyTabTheme() {
+        // Apply the theme
+        Theme theme = ThemeManager.getCurrentTheme(this);
+
+        View v = findViewById(R.id.drawer);
+        theme.setBackgroundDrawable(this, v, "background_drawable"); //$NON-NLS-1$
+
+        v = findViewById(R.id.drawer_bookmarks_tab);
+        theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+        v = findViewById(R.id.drawer_history_tab);
+        theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
+
+        v = findViewById(R.id.ab_settings);
+        theme.setImageDrawable(this, (ButtonItem) v, "ab_settings_drawable"); //$NON-NLS-1$
+        v = findViewById(R.id.ab_clear_history);
+        theme.setImageDrawable(this, (ButtonItem) v, "ab_delete_drawable"); //$NON-NLS-1$
+    }
+
 }
index 4fbbec5..ca16924 100644 (file)
@@ -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 (file)
index 0000000..d664dd4
--- /dev/null
@@ -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);
+        }
+    }
+}
index 0caf438..5ae541b 100644 (file)
@@ -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 (file)
index 0000000..31ea0f0
--- /dev/null
@@ -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);
+            }
+        }
+    }
+
+}
index f5c2f16..83fdf18 100644 (file)
@@ -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;
 
index 793dc61..2a7ccf5 100644 (file)
@@ -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;
                         }
index 94856ba..a76c9fe 100644 (file)
@@ -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 (file)
index 0000000..39e623b
--- /dev/null
@@ -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 (file)
index 0000000..3b7d856
--- /dev/null
@@ -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 (file)
index 0000000..8b87c46
--- /dev/null
@@ -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 (file)
index 0000000..dfac1be
--- /dev/null
@@ -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 (file)
index 0000000..47ca094
--- /dev/null
@@ -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 (file)
index 0000000..a8e6e07
--- /dev/null
@@ -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 (file)
index 0000000..33d0ace
--- /dev/null
@@ -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 (file)
index 0000000..5049a01
--- /dev/null
@@ -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 (file)
index 0000000..3fc9546
--- /dev/null
@@ -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 (file)
index 0000000..3cd9748
--- /dev/null
@@ -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 (file)
index 0000000..7789120
--- /dev/null
@@ -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 (file)
index 0000000..d25cf18
--- /dev/null
@@ -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 (file)
index 0000000..851d10e
--- /dev/null
@@ -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 (file)
index 0000000..530f26a
--- /dev/null
@@ -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 (file)
index 0000000..3eaa44e
--- /dev/null
@@ -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 (file)
index 0000000..7d3504b
--- /dev/null
@@ -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;
+    }
+}
index 908b6b1..7c0a6f7 100644 (file)
@@ -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();
index b6a736d..4cb8105 100644 (file)
@@ -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 {
index 43e4336..1e4f27e 100644 (file)
@@ -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 {
index ca304f5..57fafd1 100644 (file)
@@ -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 (file)
index 0000000..795111e
--- /dev/null
@@ -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 (file)
index 0000000..e19d0dc
--- /dev/null
@@ -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();
+    }
+
+}
index ba28db5..6404431 100644 (file)
@@ -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 (file)
index 0000000..8512bc7
--- /dev/null
@@ -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 (file)
index 0000000..ba73060
--- /dev/null
@@ -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());
+    }
+}
index daf3052..f59f746 100644 (file)
 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 (file)
index 0000000..a57b5d5
--- /dev/null
@@ -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 (file)
index 0000000..be019a6
--- /dev/null
@@ -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 (file)
index 0000000..df2e482
--- /dev/null
@@ -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 (file)
index 0000000..1755528
--- /dev/null
@@ -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 (file)
index 0000000..810b94d
--- /dev/null
@@ -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 (file)
index 0000000..2db530c
--- /dev/null
@@ -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;
+    }
+}
index bc97b25..7a1f5a9 100644 (file)
@@ -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) {
index 0de9719..817bdff 100644 (file)
@@ -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
index 192f2d9..f922a60 100644 (file)
@@ -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
      */
index 99110a7..c87c07c 100644 (file)
@@ -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$
     }
 
index 3278783..2c07b45 100644 (file)
@@ -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
index 92576a8..a2abebc 100644 (file)
@@ -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 + "]";
     }
 
     /**
index 9500086..9fdab8d 100644 (file)
@@ -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
index 612bd68..ac1f5e6 100644 (file)
@@ -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;
             }
         }
     }
index a6da308..dcfa7af 100644 (file)
@@ -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
index 93309da..caf64f3 100644 (file)
@@ -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$
+
 
 
 
index 3cdc595..b07ec39 100644 (file)
@@ -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 (file)
index 0000000..b06fc73
--- /dev/null
@@ -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());
+    }
+}
index c789fca..5f07654 100644 (file)
@@ -36,13 +36,17 @@ import com.cyanogenmod.filemanager.FileManagerApplication;
 import com.cyanogenmod.filemanager.R;
 import com.cyanogenmod.filemanager.activities.NavigationActivity;
 import com.cyanogenmod.filemanager.adapters.TwoColumnsMenuListAdapter;
+import com.cyanogenmod.filemanager.console.VirtualMountPointConsole;
 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
 import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
 import com.cyanogenmod.filemanager.model.Bookmark;
+import com.cyanogenmod.filemanager.model.Directory;
 import com.cyanogenmod.filemanager.model.FileSystemObject;
 import com.cyanogenmod.filemanager.model.Symlink;
 import com.cyanogenmod.filemanager.model.SystemFile;
 import com.cyanogenmod.filemanager.preferences.AccessMode;
+import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
+import com.cyanogenmod.filemanager.preferences.Preferences;
 import com.cyanogenmod.filemanager.ui.ThemeManager;
 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
 import com.cyanogenmod.filemanager.ui.policy.BookmarksActionPolicy;
@@ -57,6 +61,7 @@ import com.cyanogenmod.filemanager.ui.policy.NavigationActionPolicy;
 import com.cyanogenmod.filemanager.ui.policy.NewActionPolicy;
 import com.cyanogenmod.filemanager.ui.policy.PrintActionPolicy;
 import com.cyanogenmod.filemanager.util.DialogHelper;
+import com.cyanogenmod.filemanager.util.ExceptionUtil;
 import com.cyanogenmod.filemanager.util.FileHelper;
 import com.cyanogenmod.filemanager.util.MimeTypeHelper;
 import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
@@ -64,6 +69,7 @@ import com.cyanogenmod.filemanager.util.SelectionHelper;
 import com.cyanogenmod.filemanager.util.StorageHelper;
 
 import java.io.File;
+import java.io.InvalidClassException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -437,6 +443,19 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
                         this.mContext, this.mFso, this.mOnRequestRefreshListener);
                 break;
 
+                // Set as home
+            case R.id.mnu_actions_set_as_home:
+            case R.id.mnu_actions_global_set_as_home:
+                try {
+                    Preferences.savePreference(
+                            FileManagerSettings.SETTINGS_INITIAL_DIR, mFso.getFullPath(), true);
+                    mOnRequestRefreshListener.onRequestBookmarksRefresh();
+                    DialogHelper.showToast(mContext, R.string.msgs_success, Toast.LENGTH_SHORT);
+                } catch (InvalidClassException e) {
+                    ExceptionUtil.translateException(mContext, e);
+                }
+                break;
+
             default:
                 break;
         }
@@ -651,8 +670,7 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
             }
 
             //- Print (only for text and image categories)
-            if (category.compareTo(MimeTypeCategory.TEXT) != 0 &&
-                    category.compareTo(MimeTypeCategory.IMAGE) != 0) {
+            if (!PrintActionPolicy.isPrintedAllowed(mContext, mFso)) {
                 menu.removeItem(R.id.mnu_actions_print);
             }
         }
@@ -680,18 +698,21 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
             }
         }
         //- Create link
-        if (this.mGlobal && (selection == null || selection.size() == 0 || selection.size() > 1)) {
+        if (this.mGlobal && (selection == null || selection.size() == 0
+                || selection.size() > 1)) {
             // Only when one item is selected
             menu.removeItem(R.id.mnu_actions_create_link_global);
         } else if (this.mGlobal  && selection != null) {
-            // Create link (not allow in storage volume)
+            // Create link (not allow in sdcard, secure or remote storage volumes)
             FileSystemObject fso = selection.get(0);
-            if (StorageHelper.isPathInStorageVolume(fso.getFullPath())) {
-                menu.removeItem(R.id.mnu_actions_create_link);
+            if (StorageHelper.isPathInStorageVolume(fso.getFullPath())
+                    || fso.isSecure() || fso.isRemote()) {
+                menu.removeItem(R.id.mnu_actions_create_link_global);
             }
         } else if (!this.mGlobal) {
-            // Create link (not allow in storage volume)
-            if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) {
+            // Create link (not allow in sdcard, secure or remote storage volumes)
+            if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())
+                    || mFso.isSecure() || mFso.isRemote()) {
                 menu.removeItem(R.id.mnu_actions_create_link);
             }
         }
@@ -702,10 +723,18 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
             if (this.mGlobal) {
                 if (selection == null || selection.size() == 0) {
                     menu.removeItem(R.id.mnu_actions_compress_selection);
+                } else {
+                    for (FileSystemObject fso : selection) {
+                        // Ignore for system, secure or remote files
+                        if (fso instanceof SystemFile || fso.isSecure() || fso.isRemote()) {
+                            menu.removeItem(R.id.mnu_actions_compress_selection);
+                            break;
+                        }
+                    }
                 }
             } else {
-                // Ignore for system files
-                if (this.mFso instanceof SystemFile) {
+                // Ignore for system, secure or remote files
+                if (this.mFso instanceof SystemFile || mFso.isSecure() || mFso.isRemote()) {
                     menu.removeItem(R.id.mnu_actions_compress);
                 }
             }
@@ -735,16 +764,68 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
             }
         }
 
+        // Shotcuts and Bookmarks (not available in virtual filesystems)
+        if (!mGlobal && (mFso.isSecure() || mFso.isRemote())) {
+            menu.removeItem(R.id.mnu_actions_add_shortcut);
+            menu.removeItem(R.id.mnu_actions_add_to_bookmarks);
+        } else if (mGlobal) {
+            if (selection != null && selection.size() > 0) {
+                for (FileSystemObject fso : selection) {
+                    if (fso.isSecure() || fso.isRemote()) {
+                        menu.removeItem(R.id.mnu_actions_add_shortcut_current_folder);
+                        menu.removeItem(R.id.mnu_actions_add_to_bookmarks_current_folder);
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Set as home
+        if (!mGlobal && !FileHelper.isDirectory(mFso)) {
+            menu.removeItem(R.id.mnu_actions_set_as_home);
+        } else if (mGlobal && (selection != null && selection.size() > 0)) {
+            menu.removeItem(R.id.mnu_actions_global_set_as_home);
+        }
+
         // Not allowed in search
         if (this.mSearch) {
             menu.removeItem(R.id.mnu_actions_extract);
             menu.removeItem(R.id.mnu_actions_compress);
             menu.removeItem(R.id.mnu_actions_create_link);
+        } else {
+            // Not allowed if not in search
+            menu.removeItem(R.id.mnu_actions_open_parent_folder);
         }
 
-        // Not allowed if not in search
-        if (!this.mSearch) {
-            menu.removeItem(R.id.mnu_actions_open_parent_folder);
+        // Remove unsafe operations over virtual mountpoint directories
+        List<Directory> virtualDirs = VirtualMountPointConsole.getVirtualMountableDirectories();
+        if (!mGlobal && FileHelper.isDirectory(mFso) && virtualDirs.contains(mFso)) {
+            menu.removeItem(R.id.mnu_actions_delete);
+            menu.removeItem(R.id.mnu_actions_rename);
+            menu.removeItem(R.id.mnu_actions_compress);
+            menu.removeItem(R.id.mnu_actions_create_copy);
+            menu.removeItem(R.id.mnu_actions_create_link);
+            menu.removeItem(R.id.mnu_actions_add_shortcut);
+            menu.removeItem(R.id.mnu_actions_add_to_bookmarks);
+        } else if (mGlobal) {
+            if (selection != null && selection.size() > 0) {
+                for (FileSystemObject fso : selection) {
+                    if (FileHelper.isDirectory(fso) && virtualDirs.contains(fso)) {
+                        menu.removeItem(R.id.mnu_actions_paste_selection);
+                        menu.removeItem(R.id.mnu_actions_move_selection);
+                        menu.removeItem(R.id.mnu_actions_delete_selection);
+                        menu.removeItem(R.id.mnu_actions_compress_selection);
+                        menu.removeItem(R.id.mnu_actions_create_link_global);
+                        menu.removeItem(R.id.mnu_actions_send_selection);
+                        menu.removeItem(R.id.mnu_actions_create_link_global);
+                        menu.removeItem(R.id.mnu_actions_create_link_global);
+                        menu.removeItem(R.id.mnu_actions_create_link_global);
+                        menu.removeItem(R.id.mnu_actions_add_shortcut_current_folder);
+                        menu.removeItem(R.id.mnu_actions_add_to_bookmarks_current_folder);
+                        break;
+                    }
+                }
+            }
         }
 
         // Remove not-ChRooted actions (actions that can't be present when running in
index cd68933..80d05c1 100644 (file)
@@ -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);
             }
index f7b34bb..b1e0219 100644 (file)
@@ -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 {
index 7c63d0a..392f36f 100644 (file)
@@ -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;
 
index 975d335..1ac6d00 100644 (file)
@@ -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);
     }
index ab4f8ec..9ddc300 100644 (file)
@@ -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;
index 4d687df..0102062 100644 (file)
@@ -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) {
index 0f99873..6f7087c 100644 (file)
@@ -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
+            }
+        }
+    }
+}
index 22b5b26..38f4a95 100644 (file)
@@ -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>() {
 
index bb70bfb..164ec6e 100644 (file)
@@ -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
index 4ef9e48..2397ac3 100644 (file)
@@ -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);
     }
 
index 4107105..8f06c4e 100644 (file)
@@ -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);
+            }
+        }
     }
 }
index 0787e64..ecd0cfa 100644 (file)
@@ -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();
index 04a9130..641d8f1 100644 (file)
@@ -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;
index 1f1ce3e..9573f68 100644 (file)
@@ -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);
index d49f168..c117cfd 100644 (file)
@@ -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>());
+    }
+
 }
index 3419492..891e6e3 100644 (file)
@@ -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) {
index 0bc5dfe..8f35895 100644 (file)
@@ -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$
     }
index 7d1b236..4c98e95 100644 (file)
@@ -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;
+    }
+
 }
index 0dc9965..4046445 100644 (file)
@@ -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
index 26f084f..7b7ce74 100644 (file)
@@ -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) {
index 811225a..3b43aa8 100644 (file)
@@ -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());
+    }
 }
index 1220272..0283305 100644 (file)
@@ -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$
         }
 
index b593875..ada51aa 100644 (file)
@@ -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);
index 3702746..89d7d2f 100644 (file)
@@ -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
      *
index d12b69e..b8257cd 100644 (file)
@@ -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 (file)
index 0000000..32a63ee
Binary files /dev/null and b/themes/res/drawable-hdpi/ic_holo_dark_delete.png 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 (file)
index 0000000..4544c7b
Binary files /dev/null and b/themes/res/drawable-hdpi/ic_holo_dark_print.png 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 (file)
index 0000000..17ed77a
Binary files /dev/null and b/themes/res/drawable-hdpi/ic_holo_dark_remote.png 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 (executable)
index 0000000..f24aed3
Binary files /dev/null and b/themes/res/drawable-hdpi/ic_holo_dark_secure.png 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 (file)
index 0000000..eee5450
Binary files /dev/null and b/themes/res/drawable-hdpi/ic_holo_dark_settings.png 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 (file)
index 0000000..274f30a
Binary files /dev/null and b/themes/res/drawable-mdpi/ic_holo_dark_delete.png 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 (file)
index 0000000..947459d
Binary files /dev/null and b/themes/res/drawable-mdpi/ic_holo_dark_print.png 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 (file)
index 0000000..0cf01df
Binary files /dev/null and b/themes/res/drawable-mdpi/ic_holo_dark_remote.png 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 (file)
index 0000000..1df65e3
Binary files /dev/null and b/themes/res/drawable-mdpi/ic_holo_dark_secure.png 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 (file)
index 0000000..78c3f3c
Binary files /dev/null and b/themes/res/drawable-mdpi/ic_holo_dark_settings.png 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 (file)
index 0000000..ac4ec10
Binary files /dev/null and b/themes/res/drawable-nodpi/dark_background_disabled.9.png differ
index 0ba30cd..0fc9e11 100644 (file)
Binary files a/themes/res/drawable-nodpi/dark_theme_preview.png and b/themes/res/drawable-nodpi/dark_theme_preview.png 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 (file)
index 0000000..cff1862
Binary files /dev/null and b/themes/res/drawable-xhdpi/ic_holo_dark_delete.png 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 (file)
index 0000000..781564a
Binary files /dev/null and b/themes/res/drawable-xhdpi/ic_holo_dark_print.png 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 (file)
index 0000000..c8c6297
Binary files /dev/null and b/themes/res/drawable-xhdpi/ic_holo_dark_remote.png 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 (file)
index 0000000..40a99b7
Binary files /dev/null and b/themes/res/drawable-xhdpi/ic_holo_dark_secure.png 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 (file)
index 0000000..ad69b56
Binary files /dev/null and b/themes/res/drawable-xhdpi/ic_holo_dark_settings.png 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 (file)
index 0000000..0555c6a
Binary files /dev/null and b/themes/res/drawable-xxhdpi/ic_holo_dark_delete.png 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 (file)
index 0000000..f5248b9
Binary files /dev/null and b/themes/res/drawable-xxhdpi/ic_holo_dark_print.png 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 (file)
index 0000000..a6b5750
Binary files /dev/null and b/themes/res/drawable-xxhdpi/ic_holo_dark_remote.png 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 (file)
index 0000000..fa79fcb
Binary files /dev/null and b/themes/res/drawable-xxhdpi/ic_holo_dark_secure.png 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 (file)
index 0000000..e5a1fba
Binary files /dev/null and b/themes/res/drawable-xxhdpi/ic_holo_dark_settings.png differ
index 76d512d..efb778c 100644 (file)
@@ -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>
index c55371b..9fb44db 100644 (file)
     <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>
     <!-- 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 -->
     <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>