diff options
157 files changed, 8448 insertions, 1085 deletions
@@ -21,6 +21,9 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_SRC_FILES += $(call all-java-files-under, themes/src) LOCAL_SRC_FILES += $(call all-java-files-under, libs/android-syntax-highlight/src) LOCAL_SRC_FILES += $(call all-java-files-under, libs/color-picker-view/src) + +LOCAL_STATIC_JAVA_LIBRARIES := libtruezip + LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, themes/res res) LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true LOCAL_AAPT_FLAGS := --auto-add-overlay diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ac00a7b8..528e2a3b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -16,8 +16,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cyanogenmod.filemanager" - android:versionCode="102" - android:versionName="1.0.2"> + android:versionCode="103" + android:versionName="2.0.0"> <original-package android:name="com.cyanogenmod.filemanager" /> @@ -29,6 +29,7 @@ <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" /> <uses-permission android:name="android.permission.ACCESS_SUPERUSER"/> <uses-permission android:name="android.permission.NFC"/> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.cyanogenmod.filemanager.permissions.READ_THEME"/> @@ -38,7 +39,8 @@ android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:largeHeap="true" - android:theme="@style/FileManager.Theme.Holo.Light" > + android:theme="@style/FileManager.Theme.Holo.Light" + android:supportsRtl="true"> <meta-data android:name="android.app.default_searchable" @@ -54,6 +56,12 @@ android:authorities="com.cyanogenmod.filemanager.providers.bookmarks" android:exported="false" /> + <provider + android:name=".providers.SecureResourceProvider" + android:authorities="com.cyanogenmod.filemanager.providers.resources" + android:grantUriPermissions="true" + android:exported="true" /> + <activity android:name=".activities.NavigationActivity" android:label="@string/app_name" @@ -77,24 +85,6 @@ </activity> <activity - android:name=".activities.BookmarksActivity" - android:label="@string/bookmarks" - android:uiOptions="none" - android:windowSoftInputMode="adjustNothing" - android:configChanges="orientation|keyboardHidden|screenSize" - android:exported="false"> - </activity> - - <activity - android:name=".activities.HistoryActivity" - android:label="@string/history" - android:uiOptions="none" - android:configChanges="orientation|keyboardHidden|screenSize" - android:windowSoftInputMode="adjustNothing" - android:exported="false"> - </activity> - - <activity android:name=".activities.SearchActivity" android:label="@string/search" android:launchMode="singleTop" @@ -168,6 +158,8 @@ <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <category android:name="android.intent.category.DEFAULT" /> + <category android:name="com.cyanogenmod.filemanager.category.INTERNAL_VIEWER" /> + <category android:name="com.cyanogenmod.filemanager.category.EDITOR" /> <data android:scheme="file" /> <data android:mimeType="text/*" /> @@ -200,6 +192,18 @@ </intent-filter> </activity> + <activity + android:name=".console.secure.SecureStorageKeyPromptDialog$SecureStorageKeyPromptActivity" + android:label="@string/app_name" + android:uiOptions="none" + android:configChanges="orientation|keyboardHidden|screenSize" + android:theme="@android:style/Theme.Holo.Light.Dialog" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + </intent-filter> + </activity> + </application> </manifest> diff --git a/CHANGELOG.md b/CHANGELOG.md index c64271e6..bba634b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ CyanogenMod File Manager ======================== +Version 2.0.0 +------------- +* Secure storage support +* Print support + +Version 1.0.2 +------------- +* Drawer navigation support (by Florian Edelmann) + Version 1.0.1 ------------- * NFC support @@ -10,7 +10,7 @@ This source was released under the terms of Visit [CyanogenMod Github](https://github.com/CyanogenMod) and [CyanogenMod Code Review](http://review.cyanogenmod.com/) to get the source and patches. -This application uses also third party libraries. Checkout the license individual +This application uses also third party libraries. Checkout the individual license of every library in libs folder. Copyright © 2012 The CyanogenMod Project diff --git a/proguard.flags b/proguard.flags index 6f046926..c6a4e5bf 100644 --- a/proguard.flags +++ b/proguard.flags @@ -57,3 +57,10 @@ public <init>(...); } +#keep library packages +-keep public class de.schlichtherle.truezip.** { + public protected *; +} +-keep public class libtruezip.** { + public protected *; +} diff --git a/res/drawable-hdpi/ic_holo_light_delete.png b/res/drawable-hdpi/ic_holo_light_delete.png Binary files differnew file mode 100644 index 00000000..57f9b4d5 --- /dev/null +++ b/res/drawable-hdpi/ic_holo_light_delete.png diff --git a/res/drawable-hdpi/ic_holo_light_print.png b/res/drawable-hdpi/ic_holo_light_print.png Binary files differnew file mode 100644 index 00000000..5b5fc364 --- /dev/null +++ b/res/drawable-hdpi/ic_holo_light_print.png diff --git a/res/drawable-hdpi/ic_holo_light_remote.png b/res/drawable-hdpi/ic_holo_light_remote.png Binary files differnew file mode 100644 index 00000000..82234433 --- /dev/null +++ b/res/drawable-hdpi/ic_holo_light_remote.png diff --git a/res/drawable-hdpi/ic_holo_light_secure.png b/res/drawable-hdpi/ic_holo_light_secure.png Binary files differnew file mode 100644 index 00000000..75fe8a9e --- /dev/null +++ b/res/drawable-hdpi/ic_holo_light_secure.png diff --git a/res/drawable-hdpi/ic_holo_light_settings.png b/res/drawable-hdpi/ic_holo_light_settings.png Binary files differnew file mode 100644 index 00000000..6da677ad --- /dev/null +++ b/res/drawable-hdpi/ic_holo_light_settings.png diff --git a/res/drawable-hdpi/ic_overlay_remote.png b/res/drawable-hdpi/ic_overlay_remote.png Binary files differnew file mode 100644 index 00000000..2ee56395 --- /dev/null +++ b/res/drawable-hdpi/ic_overlay_remote.png diff --git a/res/drawable-hdpi/ic_overlay_secure.png b/res/drawable-hdpi/ic_overlay_secure.png Binary files differnew file mode 100644 index 00000000..d8e692d9 --- /dev/null +++ b/res/drawable-hdpi/ic_overlay_secure.png diff --git a/res/drawable-mdpi/ic_holo_light_delete.png b/res/drawable-mdpi/ic_holo_light_delete.png Binary files differnew file mode 100644 index 00000000..e8ea0b10 --- /dev/null +++ b/res/drawable-mdpi/ic_holo_light_delete.png diff --git a/res/drawable-mdpi/ic_holo_light_print.png b/res/drawable-mdpi/ic_holo_light_print.png Binary files differnew file mode 100644 index 00000000..e4a53d08 --- /dev/null +++ b/res/drawable-mdpi/ic_holo_light_print.png diff --git a/res/drawable-mdpi/ic_holo_light_remote.png b/res/drawable-mdpi/ic_holo_light_remote.png Binary files differnew file mode 100644 index 00000000..87eeff50 --- /dev/null +++ b/res/drawable-mdpi/ic_holo_light_remote.png diff --git a/res/drawable-mdpi/ic_holo_light_secure.png b/res/drawable-mdpi/ic_holo_light_secure.png Binary files differnew file mode 100644 index 00000000..dcf1b117 --- /dev/null +++ b/res/drawable-mdpi/ic_holo_light_secure.png diff --git a/res/drawable-mdpi/ic_holo_light_settings.png b/res/drawable-mdpi/ic_holo_light_settings.png Binary files differnew file mode 100644 index 00000000..be398696 --- /dev/null +++ b/res/drawable-mdpi/ic_holo_light_settings.png diff --git a/res/drawable-mdpi/ic_overlay_remote.png b/res/drawable-mdpi/ic_overlay_remote.png Binary files differnew file mode 100644 index 00000000..318dc87e --- /dev/null +++ b/res/drawable-mdpi/ic_overlay_remote.png diff --git a/res/drawable-mdpi/ic_overlay_secure.png b/res/drawable-mdpi/ic_overlay_secure.png Binary files differnew file mode 100644 index 00000000..80998961 --- /dev/null +++ b/res/drawable-mdpi/ic_overlay_secure.png diff --git a/res/drawable-nodpi/theme_preview.png b/res/drawable-nodpi/theme_preview.png Binary files differindex ba98ce0b..866ed72e 100644 --- a/res/drawable-nodpi/theme_preview.png +++ b/res/drawable-nodpi/theme_preview.png diff --git a/res/drawable-xhdpi/ic_holo_light_delete.png b/res/drawable-xhdpi/ic_holo_light_delete.png Binary files differnew file mode 100644 index 00000000..a5cbd4f7 --- /dev/null +++ b/res/drawable-xhdpi/ic_holo_light_delete.png diff --git a/res/drawable-xhdpi/ic_holo_light_print.png b/res/drawable-xhdpi/ic_holo_light_print.png Binary files differnew file mode 100644 index 00000000..1927fcc6 --- /dev/null +++ b/res/drawable-xhdpi/ic_holo_light_print.png diff --git a/res/drawable-xhdpi/ic_holo_light_remote.png b/res/drawable-xhdpi/ic_holo_light_remote.png Binary files differnew file mode 100644 index 00000000..a45da433 --- /dev/null +++ b/res/drawable-xhdpi/ic_holo_light_remote.png diff --git a/res/drawable-xhdpi/ic_holo_light_secure.png b/res/drawable-xhdpi/ic_holo_light_secure.png Binary files differnew file mode 100644 index 00000000..5a6d50db --- /dev/null +++ b/res/drawable-xhdpi/ic_holo_light_secure.png diff --git a/res/drawable-xhdpi/ic_holo_light_settings.png b/res/drawable-xhdpi/ic_holo_light_settings.png Binary files differnew file mode 100644 index 00000000..09f504de --- /dev/null +++ b/res/drawable-xhdpi/ic_holo_light_settings.png diff --git a/res/drawable-xhdpi/ic_overlay_remote.png b/res/drawable-xhdpi/ic_overlay_remote.png Binary files differnew file mode 100644 index 00000000..a0003e85 --- /dev/null +++ b/res/drawable-xhdpi/ic_overlay_remote.png diff --git a/res/drawable-xhdpi/ic_overlay_secure.png b/res/drawable-xhdpi/ic_overlay_secure.png Binary files differnew file mode 100644 index 00000000..174a1d84 --- /dev/null +++ b/res/drawable-xhdpi/ic_overlay_secure.png diff --git a/res/drawable-xxhdpi/ic_holo_light_delete.png b/res/drawable-xxhdpi/ic_holo_light_delete.png Binary files differnew file mode 100644 index 00000000..743fbfd5 --- /dev/null +++ b/res/drawable-xxhdpi/ic_holo_light_delete.png diff --git a/res/drawable-xxhdpi/ic_holo_light_print.png b/res/drawable-xxhdpi/ic_holo_light_print.png Binary files differnew file mode 100644 index 00000000..6d1fdf67 --- /dev/null +++ b/res/drawable-xxhdpi/ic_holo_light_print.png diff --git a/res/drawable-xxhdpi/ic_holo_light_remote.png b/res/drawable-xxhdpi/ic_holo_light_remote.png Binary files differnew file mode 100644 index 00000000..0cb6fb7a --- /dev/null +++ b/res/drawable-xxhdpi/ic_holo_light_remote.png diff --git a/res/drawable-xxhdpi/ic_holo_light_secure.png b/res/drawable-xxhdpi/ic_holo_light_secure.png Binary files differnew file mode 100644 index 00000000..a959f743 --- /dev/null +++ b/res/drawable-xxhdpi/ic_holo_light_secure.png diff --git a/res/drawable-xxhdpi/ic_holo_light_settings.png b/res/drawable-xxhdpi/ic_holo_light_settings.png Binary files differnew file mode 100644 index 00000000..f3246735 --- /dev/null +++ b/res/drawable-xxhdpi/ic_holo_light_settings.png diff --git a/res/drawable-xxhdpi/ic_overlay_remote.png b/res/drawable-xxhdpi/ic_overlay_remote.png Binary files differnew file mode 100644 index 00000000..52fb8701 --- /dev/null +++ b/res/drawable-xxhdpi/ic_overlay_remote.png diff --git a/res/drawable-xxhdpi/ic_overlay_secure.png b/res/drawable-xxhdpi/ic_overlay_secure.png Binary files differnew file mode 100644 index 00000000..dc9a0b80 --- /dev/null +++ b/res/drawable-xxhdpi/ic_overlay_secure.png diff --git a/res/drawable/fso_folder_remote.xml b/res/drawable/fso_folder_remote.xml new file mode 100644 index 00000000..79f40efd --- /dev/null +++ b/res/drawable/fso_folder_remote.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:drawable="@drawable/ic_fso_folder_drawable"/> + <item android:drawable="@drawable/ic_overlay_remote_drawable"/> + +</layer-list> diff --git a/res/drawable/fso_folder_secure.xml b/res/drawable/fso_folder_secure.xml new file mode 100644 index 00000000..1f27b843 --- /dev/null +++ b/res/drawable/fso_folder_secure.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:drawable="@drawable/ic_fso_folder_drawable"/> + <item android:drawable="@drawable/ic_overlay_secure_drawable"/> + +</layer-list> diff --git a/res/drawable/holo_button_selector.xml b/res/drawable/holo_button_selector.xml index 97ebf269..d6a5a8d7 100644 --- a/res/drawable/holo_button_selector.xml +++ b/res/drawable/holo_button_selector.xml @@ -24,6 +24,9 @@ android:state_enabled="true" android:state_focused="true"/> <item + android:drawable="@color/default_background_disabled" + android:state_enabled="false"/> + <item android:drawable="@color/default_background"/> </selector> diff --git a/res/layout/associations_dialog.xml b/res/layout/associations_dialog.xml index 210301a9..18009c9d 100644 --- a/res/layout/associations_dialog.xml +++ b/res/layout/associations_dialog.xml @@ -26,13 +26,17 @@ android:stretchMode="columnWidth" android:scrollbars="vertical" android:horizontalSpacing="@dimen/default_margin" + android:layout_marginBottom="@dimen/extra_margin" android:numColumns="@integer/associations_items_per_row" /> <CheckBox android:id="@+id/associations_remember" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="@dimen/extra_margin" + android:layout_marginLeft="@dimen/extra_margin" + android:layout_marginRight="@dimen/extra_margin" + android:layout_marginBottom="@dimen/extra_margin" android:textAppearance="@style/secondary_text_appearance" - android:text="@string/associations_dialog_remember" /> + android:text="@string/associations_dialog_remember" + android:visibility="gone" /> </LinearLayout>
\ No newline at end of file diff --git a/res/layout/color_picker_pref_item.xml b/res/layout/color_picker_pref_item.xml index 02c44c1a..e250561c 100644 --- a/res/layout/color_picker_pref_item.xml +++ b/res/layout/color_picker_pref_item.xml @@ -19,7 +19,7 @@ android:layout_height="32dp" android:background="@android:color/darker_gray"> <afzkl.development.mColorPicker.views.ColorPanelView - android:id="@+android:id/color_picker" + android:id="@+id/color_picker" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="1dp" diff --git a/res/layout/history_item.xml b/res/layout/history_item.xml index ef02927b..9aa4d8a3 100644 --- a/res/layout/history_item.xml +++ b/res/layout/history_item.xml @@ -56,15 +56,4 @@ android:textAppearance="@style/secondary_text_appearance" /> </LinearLayout> - <TextView - android:id="@+id/history_item_position" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginLeft="@dimen/default_margin" - android:paddingRight="@dimen/extra_margin" - android:singleLine="true" - android:textAppearance="@style/primary_text_appearance" - android:textStyle="normal" /> - </LinearLayout>
\ No newline at end of file diff --git a/res/layout/navigation_drawer.xml b/res/layout/navigation_drawer.xml index 9398ede5..c5e29092 100644 --- a/res/layout/navigation_drawer.xml +++ b/res/layout/navigation_drawer.xml @@ -1,62 +1,155 @@ <?xml version="1.0" encoding="utf-8"?> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer" - android:layout_width="240dp" + android:layout_width="280dp" android:layout_height="match_parent" android:layout_gravity="start" - android:background="@android:color/background_light" > + android:background="@android:color/background_light"> + + <RelativeLayout + android:id="@+id/drawer_actionbar" + android:layout_width="match_parent" + android:layout_height="64dp" + android:layout_alignParentBottom="true"> + + <com.cyanogenmod.filemanager.ui.widgets.ButtonItem + android:id="@+id/ab_settings" + android:layout_width="@dimen/default_buttom_width" + android:layout_height="match_parent" + android:layout_alignParentLeft="true" + android:contentDescription="@string/menu_settings" + android:onClick="onActionBarItemClick" + android:src="@drawable/ic_holo_light_settings" /> + + <com.cyanogenmod.filemanager.ui.widgets.ButtonItem + android:id="@+id/ab_clear_history" + android:layout_width="@dimen/default_buttom_width" + android:layout_height="match_parent" + android:layout_toRightOf="@id/ab_settings" + android:contentDescription="@string/menu_clear_history" + android:onClick="onActionBarItemClick" + android:src="@drawable/ic_holo_light_delete" /> + </RelativeLayout> <LinearLayout + android:id="@+id/drawer_drawer_divider" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" > - - <TextView - android:id="@+id/bookmarks_header" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/bookmarks" - style="@style/drawer_header" /> + android:layout_above="@id/drawer_actionbar"> + <include layout="@layout/vertical_divider" /> + </LinearLayout> - <ProgressBar - android:id="@+id/bookmarks_loading" - android:layout_width="@dimen/default_row_height" - android:layout_height="@dimen/default_row_height" - android:indeterminate="true" - android:indeterminateOnly="true" - android:visibility="gone" /> + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_above="@id/drawer_drawer_divider" + android:layout_alignParentTop="true"> <LinearLayout - android:id="@+id/bookmarks_list" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/extra_margin" - android:orientation="vertical" > + android:orientation="vertical"> - </LinearLayout> + <LinearLayout + android:id="@+id/filesystem_info_dialog_tabhost" + android:layout_width="match_parent" + android:layout_height="@dimen/default_row_height" + android:orientation="horizontal" > - <TextView - android:id="@+id/history_header" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/history" - style="@style/drawer_header" /> + <TextView + android:id="@+id/drawer_bookmarks_tab" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="@drawable/holo_selector" + android:clickable="true" + android:gravity="center_horizontal|center_vertical" + android:text="@string/bookmarks" + android:textAllCaps="true" + android:textAppearance="@style/primary_text_appearance" /> - <TextView - android:id="@+id/history_empty" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/msgs_history_empty" - android:paddingLeft="@dimen/extra_margin" - style="@style/secondary_text_appearance" /> + <include + android:id="@+id/drawer_tab_divider1" + layout="@layout/horizontal_divider" /> + + <TextView + android:id="@+id/drawer_history_tab" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="@drawable/holo_selector" + android:clickable="true" + android:gravity="center_horizontal|center_vertical" + android:text="@string/history" + android:textAllCaps="true" + android:textAppearance="@style/primary_text_appearance" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + <include + android:id="@+id/drawer_tab_divider2" + layout="@layout/vertical_divider" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/drawer_bookmarks" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/default_margin" + android:layout_marginRight="@dimen/default_margin" + android:orientation="vertical"> + + <ProgressBar + android:id="@+id/bookmarks_loading" + android:layout_width="@dimen/default_row_height" + android:layout_height="@dimen/default_row_height" + android:indeterminate="true" + android:indeterminateOnly="true" + android:visibility="gone" /> + + <LinearLayout + android:id="@+id/bookmarks_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/extra_margin" + android:orientation="vertical" > + + </LinearLayout> + + </LinearLayout> + + <LinearLayout + android:id="@+id/drawer_history" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/default_margin" + android:layout_marginRight="@dimen/default_margin" + android:orientation="vertical" + android:visibility="gone"> + + <TextView + android:id="@+id/history_empty" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/msgs_history_empty" + android:padding="@dimen/extra_margin" + android:gravity="center_horizontal" + android:textAppearance="@style/primary_text_appearance"/> + + <LinearLayout + android:id="@+id/history_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/extra_margin" + android:orientation="vertical" > + </LinearLayout> + + </LinearLayout> - <LinearLayout - android:id="@+id/history_list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/extra_margin" - android:orientation="vertical" > </LinearLayout> - </LinearLayout> -</ScrollView>
\ No newline at end of file + </ScrollView> +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/simple_customtitle.xml b/res/layout/simple_customtitle.xml index 97dafcaa..68286934 100644 --- a/res/layout/simple_customtitle.xml +++ b/res/layout/simple_customtitle.xml @@ -25,6 +25,14 @@ android:layout_alignParentRight="true"> <com.cyanogenmod.filemanager.ui.widgets.ButtonItem + android:id="@+id/ab_button0" + style="@style/breadcrumb_actionbar_buttom" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:onClick="onActionBarItemClick" + android:visibility="gone" /> + + <com.cyanogenmod.filemanager.ui.widgets.ButtonItem android:id="@+id/ab_button1" style="@style/breadcrumb_actionbar_buttom" android:layout_width="wrap_content" @@ -51,7 +59,7 @@ android:layout_marginRight="@dimen/extra_large_margin" android:textAppearance="@style/title_text_appearance" android:layout_alignParentLeft="true" - android:layout_toLeftOf="@id/ab_button1" + android:layout_toLeftOf="@id/ab_button0" android:layout_alignWithParentIfMissing="true" android:contentDescription="@null" android:gravity="left|center_vertical" diff --git a/res/layout/unlock_dialog_message.xml b/res/layout/unlock_dialog_message.xml new file mode 100644 index 00000000..8c7a465c --- /dev/null +++ b/res/layout/unlock_dialog_message.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <!-- Dialog --> + <TextView + android:id="@+id/unlock_dialog_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/double_margin" + android:layout_marginLeft="@dimen/extra_large_margin" + android:layout_marginRight="@dimen/extra_large_margin" + android:textAppearance="@style/primary_text_appearance" /> + + <!-- Password --> + <LinearLayout + android:id="@+id/unlock_old_password_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/double_margin" + android:layout_marginLeft="@dimen/extra_large_margin" + android:layout_marginRight="@dimen/extra_large_margin" + android:orientation="vertical"> + <TextView + android:id="@+id/unlock_old_password_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/primary_text_appearance" + android:text="@string/secure_storage_unlock_old_key_title" + android:textStyle="normal"/> + <EditText + android:id="@+id/unlock_old_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" + android:textStyle="normal" + android:maxLines="1" /> + </LinearLayout> + + <!-- Password --> + <LinearLayout + android:id="@+id/unlock_password_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/double_margin" + android:layout_marginLeft="@dimen/extra_large_margin" + android:layout_marginRight="@dimen/extra_large_margin" + android:orientation="vertical"> + <TextView + android:id="@+id/unlock_password_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/primary_text_appearance" + android:text="@string/secure_storage_unlock_key_title" + android:textStyle="normal"/> + <EditText + android:id="@+id/unlock_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" + android:textStyle="normal" + android:maxLines="1" /> + </LinearLayout> + + <!-- Repeat Password --> + <LinearLayout + android:id="@+id/unlock_repeat_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/double_margin" + android:layout_marginLeft="@dimen/extra_large_margin" + android:layout_marginRight="@dimen/extra_large_margin" + android:orientation="vertical"> + <TextView + android:id="@+id/unlock_repeat_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/primary_text_appearance" + android:text="@string/secure_storage_unlock_repeat_title" + android:textStyle="normal"/> + <EditText + android:id="@+id/unlock_repeat" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" + android:textStyle="normal" + android:maxLines="1" /> + </LinearLayout> + + <!-- Validation message --> + <TextView + android:id="@+id/unlock_validation_msg" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="@dimen/extra_large_margin" + android:layout_marginRight="@dimen/extra_large_margin" + android:background="@drawable/holo_selector" + android:contentDescription="@null" + android:drawableLeft="@drawable/ic_holo_light_fs_warning" + android:drawablePadding="@dimen/default_margin" + android:gravity="left|center_vertical" + android:singleLine="false" + android:textSize="@dimen/note_text_size" + android:visibility="invisible" /> + +</LinearLayout>
\ No newline at end of file diff --git a/res/menu/actions.xml b/res/menu/actions.xml index ba5f50cf..bb2d8e50 100644 --- a/res/menu/actions.xml +++ b/res/menu/actions.xml @@ -74,6 +74,10 @@ android:id="@+id/mnu_actions_add_shortcut_current_folder" android:showAsAction="ifRoom" android:title="@string/actions_menu_add_shortcut"/> + <item + android:id="@+id/mnu_actions_global_set_as_home" + android:showAsAction="ifRoom" + android:title="@string/actions_menu_set_as_home"/> </group> <!-- FileSystemObject Actions --> @@ -150,6 +154,10 @@ android:id="@+id/mnu_actions_open_parent_folder" android:showAsAction="ifRoom" android:title="@string/actions_menu_open_parent_folder"/> + <item + android:id="@+id/mnu_actions_set_as_home" + android:showAsAction="ifRoom" + android:title="@string/actions_menu_set_as_home"/> </group> </menu>
\ No newline at end of file diff --git a/res/menu/drawer.xml b/res/menu/drawer.xml deleted file mode 100644 index 7a5ef1d5..00000000 --- a/res/menu/drawer.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - ** Copyright (C) 2012 The CyanogenMod Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. ---> -<menu xmlns:android="http://schemas.android.com/apk/res/android" > - - <!-- - set android:visible="false" for every item as it should - not be visible until the drawer gets opened - --> - - <!-- TODO: add functionality to set bookmark in NavigationActivity.java - <item - android:id="@+id/mnu_actions_add_to_bookmarks_current_folder" - android:showAsAction="never" - android:title="@string/actions_menu_add_to_bookmarks" - android:visible="false"/>--> - <item - android:id="@+id/mnu_clear_history" - android:showAsAction="never" - android:title="@string/menu_clear_history" - android:visible="false"/> - <item - android:id="@+id/mnu_settings" - android:showAsAction="never" - android:title="@string/menu_settings" - android:visible="false"/> - -</menu>
\ No newline at end of file diff --git a/res/menu/navigation.xml b/res/menu/navigation.xml index 5111c94f..e63074ba 100644 --- a/res/menu/navigation.xml +++ b/res/menu/navigation.xml @@ -22,10 +22,4 @@ android:showAsAction="ifRoom" android:title="@string/menu_search"/> - <!-- Overflow actions --> - <item - android:id="@+id/mnu_settings" - android:showAsAction="never" - android:title="@string/menu_settings"/> - </menu>
\ No newline at end of file diff --git a/res/raw/changelog b/res/raw/changelog index 447938cc..bba634b3 100644 --- a/res/raw/changelog +++ b/res/raw/changelog @@ -1,9 +1,14 @@ CyanogenMod File Manager ======================== +Version 2.0.0 +------------- +* Secure storage support +* Print support + Version 1.0.2 ------------- -* move bookmarks and history into a navigation drawer (by Florian Edelmann) +* Drawer navigation support (by Florian Edelmann) Version 1.0.1 ------------- diff --git a/res/values/colors.xml b/res/values/colors.xml index 5dad8206..22841846 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -18,6 +18,8 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- The default background color --> <color name="default_background">@android:color/white</color> + <!-- The default dissabled background color --> + <color name="default_background_disabled">#f2f2f2</color> <!-- A black color with some transparency --> <color name="black_transparent">#99000000</color> diff --git a/res/values/overlay.xml b/res/values/overlay.xml index 1258d25b..88c5d683 100644 --- a/res/values/overlay.xml +++ b/res/values/overlay.xml @@ -26,6 +26,9 @@ <!-- The system directory --> <string name="system_dir" translatable="false">/system</string> + <!-- The virtual storage directory (virtual filesystem are listed here) --> + <string name="virtual_storage_dir" translatable="false">/storage</string> + <!-- The shell commands used by this application. All of this commands should exist to allow the use of a shell console (and access to root). If any of this commands are not present in the system, then app will start in @@ -85,8 +88,8 @@ <!-- The system properties --> <string name="system_props_file" translatable="false">/system/build.prop</string> - <!-- The size of the buffers use by the console (in bytes). Default: 4k --> - <integer name="buffer_size">4096</integer> + <!-- The size of the buffers use by the console (in bytes) --> + <integer name="buffer_size">8192</integer> <!-- The number of lines to show in the console dialog --> <integer name="console_max_lines">80</integer> diff --git a/res/values/strings.xml b/res/values/strings.xml index 2c51d02f..06859586 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -167,6 +167,8 @@ <string name="actionbar_button_storage_cd">Storage volumes</string> <!-- ActionBar Buttons - Save --> <string name="actionbar_button_save_cd">Save</string> + <!-- ActionBar Buttons - Print --> + <string name="actionbar_button_print_cd">Print</string> <!-- Navigation View - Sort - Sort by name (ascending) --> <string name="sort_by_name_asc">By name \u25B2</string> @@ -221,6 +223,8 @@ <string name="filesystem_info_dialog_options">Options:</string> <!-- Filesystem Info Dialog - Dump/Pass Label --> <string name="filesystem_info_dialog_dump_pass">Dump / Pass:</string> + <!-- Filesystem Info Dialog - Virtual Label --> + <string name="filesystem_info_dialog_virtual">Virtual:</string> <!-- Filesystem Info Dialog - Total Disk Usage --> <string name="filesystem_info_dialog_total_disk_usage">Total:</string> <!-- Filesystem Info Dialog - Used Disk Usage --> @@ -353,6 +357,10 @@ <string name="bookmarks_root_folder">Root folder</string> <!-- Bookmarks - Bookmark name - System folder --> <string name="bookmarks_system_folder">System folder</string> + <!-- Bookmarks - Bookmark name - Secure storage --> + <string name="bookmarks_secure">Secure storage</string> + <!-- Bookmarks - Bookmark name - Remote storage --> + <string name="bookmarks_remote">Remote storage</string> <!-- Bookmarks - Bookmark name - Button - Initial directory content description --> <string name="bookmarks_button_config_cd">Set the initial folder.</string> <!-- Bookmarks - Bookmark name - Button - Remove bookmark content description --> @@ -476,6 +484,8 @@ <string name="actions_menu_compute_checksum">Compute checksum</string> <!-- Actions Dialog - Menu - Print --> <string name="actions_menu_print">Print</string> + <!-- Actions Dialog - Menu - Set as home --> + <string name="actions_menu_set_as_home">Set as home</string> <!-- Actions - Ask user prior to do an undone operation. Dialog message --> <string name="actions_ask_undone_operation_msg">This action cannot be undone. Do you want to continue?</string> @@ -547,6 +557,23 @@ <!-- For example "2 folders and 1 file selected." --> <string name="selection_folders_and_files"><xliff:g id="folders">%1$s</xliff:g> and <xliff:g id="files">%2$s</xliff:g> selected.</string> + <!-- Category descriptions --> + <string name="category_system">SYSTEM</string> + <string name="category_app">APP</string> + <string name="category_binary">BINARY</string> + <string name="category_text">TEXT</string> + <string name="category_document">DOCUMENT</string> + <string name="category_ebook">EBOOK</string> + <string name="category_mail">MAIL</string> + <string name="category_compress">COMPRESS</string> + <string name="category_exec">EXECUTABLE</string> + <string name="category_database">DATABASE</string> + <string name="category_font">FONT</string> + <string name="category_image">IMAGE</string> + <string name="category_audio">AUDIO</string> + <string name="category_video">VIDEO</string> + <string name="category_security">SECURITY</string> + <!-- Compression - Compression modes dialog title --> <string name="compression_mode_title">Compression mode</string> <!-- Compression - Supported archive and compression modes --> @@ -571,6 +598,8 @@ <string name="pref_general">General settings</string> <!-- Preferences - Search title --> <string name="pref_search">Search options</string> + <!-- Preferences - Storage title --> + <string name="pref_storage">Storage options</string> <!-- Preferences - Editor title --> <string name="pref_editor">Editor options</string> <!-- Preferences - Themes title --> @@ -578,7 +607,7 @@ <!-- Preferences - About title --> <string name="pref_about">About</string> <!-- Preferences - About summary --> - <string name="pref_about_summary">File Manager v<xliff:g id="version">%1$s</xliff:g>\nCopyright \u00A9 2012-2014 The CyanogenMod Project</string> + <string name="pref_about_summary">File Manager v<xliff:g id="version">%1$s</xliff:g>\nCopyright \u00A9 2012–2014 The CyanogenMod Project</string> <!-- Preferences - General - Behaviour category --> <string name="pref_general_behaviour_category">General</string> @@ -653,7 +682,20 @@ <!-- Preferences - Search - Remove saved search terms summary --> <string name="pref_remove_saved_search_terms_summary">Tap to remove all the saved search terms</string> <!-- Preferences - Search - Suggestions were truncated --> - <string name="pref_remove_saved_search_terms_msg">All saved search terms were removed.</string> + <string name="pref_remove_saved_search_terms_msg">All saved search terms were removed</string> + <!-- Preferences - Storage - Secure Storage category --> + <string name="pref_secure_storage_category">Secure storage</string> + <!-- Preferences - Storage - Secure Storage - Delayed sync title --> + <string name="pref_secure_storage_delayed_sync_title">Delayed synchronization</string> + <!-- Preferences - Storage - Secure Storage - Delayed sync summary --> + <string name="pref_secure_storage_delayed_sync_summary">Synchronization of secure file systems + is a costly operation. Enable this option to allow better time responses after every operation, + performing the synchronization when the filesystem is in unused state, but at the expense of + lost the pending information not synced if the app crash.</string> + <!-- Preferences - Storage - Secure Storage - Reset password title --> + <string name="pref_secure_storage_reset_password_title">Reset password</string> + <!-- Preferences - Storage - Secure Storage - Delete storage title --> + <string name="pref_secure_storage_delete_storage_title">Delete storage</string> <!-- Preferences - Editor - Behaviour category --> <string name="pref_editor_behaviour_category">Behaviour</string> <!-- Preferences - Editor - No suggestions --> @@ -725,6 +767,46 @@ <string name="ash_quoted_string">Quoted string</string> <string name="ash_variable">Variable</string> + <!-- Secure Storage --> + <!-- Secure Storage dialog title - Unlock --> + <string name="secure_storage_unlock_title">Unlock storage</string> + <!-- Secure Storage dialog title - Create --> + <string name="secure_storage_create_title">Create storage</string> + <!-- Secure Storage dialog title - Reset --> + <string name="secure_storage_reset_title">Reset password</string> + <!-- Secure Storage dialog title - Delete --> + <string name="secure_storage_delete_title">Delete storage</string> + <!-- Secure Storage unlock dialog message (secure storage exists) --> + <string name="secure_storage_unlock_key_prompt_msg">Type the password to unlock the secure storage filesystem.</string> + <!-- Secure Storage unlock dialog message (new secure storage) --> + <string name="secure_storage_unlock_key_new_msg">Type a password to protect the secure storage filesystem.</string> + <!-- Secure Storage unlock dialog message (reset the current password) --> + <string name="secure_storage_unlock_key_reset_msg">Type the current and new passwords to reset the secure storage filesystem.</string> + <!-- Secure Storage unlock dialog message (delete the secure storage) --> + <string name="secure_storage_unlock_key_delete_msg">Type the current password to delete the secure storage filesystem.</string> + <!-- Secure Storage unlock dialog old key title --> + <string name="secure_storage_unlock_old_key_title">Old password:</string> + <!-- Secure Storage unlock dialog key title --> + <string name="secure_storage_unlock_new_key_title">New Password:</string> + <!-- Secure Storage unlock dialog key title --> + <string name="secure_storage_unlock_key_title">Password:</string> + <!-- Secure Storage unlock dialog repeat key title--> + <string name="secure_storage_unlock_repeat_title">Repeat password:</string> + <!-- Secure Storage create button --> + <string name="secure_storage_create_button">Create</string> + <!-- Secure Storage unlock button --> + <string name="secure_storage_unlock_button">Unlock</string> + <!-- Secure Storage reset button --> + <string name="secure_storage_reset_button">Reset</string> + <!-- Secure Storage delete button --> + <string name="secure_storage_delete_button">Delete</string> + <!-- Secure Storage unlock failed toast --> + <string name="secure_storage_unlock_failed">Cannot unlock the storage</string> + <!-- Secure Storage unlock validation, length --> + <string name="secure_storage_unlock_validation_length">Password must have at least <xliff:g id="characters">%1$d</xliff:g> characters.</string> + <!-- Secure Storage unlock validation, equal --> + <string name="secure_storage_unlock_validation_equals">Passwords are not the same.</string> + <!-- Print messages --> <!-- Unsupported document format --> <string name="print_unsupported_document">Unsupported document format</string> diff --git a/res/values/theme.xml b/res/values/theme.xml index d5128c91..29208011 100644 --- a/res/values/theme.xml +++ b/res/values/theme.xml @@ -69,6 +69,12 @@ <drawable name="ab_save_drawable">@drawable/ic_holo_light_save</drawable> <!-- The drawable for the tab action bar button --> <drawable name="ab_tab_drawable">@drawable/ic_holo_light_tab</drawable> + <!-- The drawable for the print action bar button --> + <drawable name="ab_print_drawable">@drawable/ic_holo_light_print</drawable> + <!-- The drawable for the settings action bar button --> + <drawable name="ab_settings_drawable">@drawable/ic_holo_light_settings</drawable> + <!-- The drawable for the delete action bar button --> + <drawable name="ab_delete_drawable">@drawable/ic_holo_light_delete</drawable> <!-- The close action drawable from the expander bar --> <drawable name="expander_close_drawable">@drawable/ic_holo_light_expander_close</drawable> @@ -99,6 +105,11 @@ <!-- FileSystem warning drawable --> <drawable name="filesystem_warning_drawable">@drawable/ic_holo_light_fs_warning</drawable> + <!-- Secure FileSystem icon --> + <drawable name="secure_filesystem_drawable">@drawable/ic_holo_light_secure</drawable> + <!-- Remote FileSystem icon --> + <drawable name="remote_filesystem_drawable">@drawable/ic_holo_light_remote</drawable> + <!-- The popup menu checkable selector drawable --> <drawable name="popup_checkable_selector_drawable">@drawable/checkable_selector</drawable> <!-- The menu checkable selector drawable --> @@ -131,6 +142,8 @@ <drawable name="ic_user_defined_bookmark_drawable">@drawable/ic_holo_light_user_defined_bookmark</drawable> <drawable name="ic_history_search_drawable">@drawable/ic_holo_light_history_search</drawable> <drawable name="ic_copy_drawable">@drawable/ic_holo_light_copy</drawable> + <drawable name="ic_secure_drawable">@drawable/ic_holo_light_secure</drawable> + <drawable name="ic_remote_drawable">@drawable/ic_holo_light_remote</drawable> <!-- Disk usage graph --> <color name="disk_usage_total_color">@color/disk_usage_total</color> @@ -181,6 +194,10 @@ <drawable name="fso_type_text_drawable">@drawable/fso_type_text</drawable> <drawable name="fso_type_video_drawable">@drawable/fso_type_video</drawable> + <!-- Overlay --> + <drawable name="ic_overlay_secure_drawable">@drawable/ic_overlay_secure</drawable> + <drawable name="ic_overlay_remote_drawable">@drawable/ic_overlay_remote</drawable> + <!-- Syntax Highlight --> <color name="ash_text_color">@color/black_transparent</color> <color name="ash_assignment_color">@color/black_transparent</color> diff --git a/res/xml/preferences_headers.xml b/res/xml/preferences_headers.xml index 80c4509d..2c97dced 100644 --- a/res/xml/preferences_headers.xml +++ b/res/xml/preferences_headers.xml @@ -22,6 +22,9 @@ android:fragment="com.cyanogenmod.filemanager.activities.preferences.SearchPreferenceFragment" android:title="@string/pref_search" /> <header + android:fragment="com.cyanogenmod.filemanager.activities.preferences.StoragePreferenceFragment" + android:title="@string/pref_storage" /> + <header android:fragment="com.cyanogenmod.filemanager.activities.preferences.EditorPreferenceFragment" android:title="@string/pref_editor" /> <header diff --git a/res/xml/preferences_storage.xml b/res/xml/preferences_storage.xml new file mode 100644 index 00000000..47f27eea --- /dev/null +++ b/res/xml/preferences_storage.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<PreferenceScreen + xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- Secure Storage --> + <PreferenceCategory + android:key="secure_storage" + android:title="@string/pref_secure_storage_category"> + + <!-- Reset password --> + <Preference + android:key="secure_storage_reset_password" + android:title="@string/pref_secure_storage_reset_password_title"/> + + <!-- Delete storage --> + <Preference + android:key="secure_storage_delete_storage" + android:title="@string/pref_secure_storage_delete_storage_title"/> + + <!-- Delayed synchronization --> + <CheckBoxPreference + android:key="cm_filemanager_secure_storage_delayed_sync" + android:title="@string/pref_secure_storage_delayed_sync_title" + android:summary="@string/pref_secure_storage_delayed_sync_summary" + android:persistent="true" + android:defaultValue="true" /> + + </PreferenceCategory> + +</PreferenceScreen> diff --git a/src/com/cyanogenmod/filemanager/FileManagerApplication.java b/src/com/cyanogenmod/filemanager/FileManagerApplication.java index be475dff..8230c1fe 100644 --- a/src/com/cyanogenmod/filemanager/FileManagerApplication.java +++ b/src/com/cyanogenmod/filemanager/FileManagerApplication.java @@ -28,6 +28,7 @@ import com.cyanogenmod.filemanager.console.Console; import com.cyanogenmod.filemanager.console.ConsoleAllocException; import com.cyanogenmod.filemanager.console.ConsoleBuilder; import com.cyanogenmod.filemanager.console.ConsoleHolder; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; import com.cyanogenmod.filemanager.console.shell.PrivilegedConsole; import com.cyanogenmod.filemanager.preferences.AccessMode; import com.cyanogenmod.filemanager.preferences.FileManagerSettings; @@ -269,7 +270,9 @@ public final class FileManagerApplication extends Application { Theme theme = ThemeManager.getCurrentTheme(getApplicationContext()); theme.setBaseTheme(getApplicationContext(), false); - //Create a console for background tasks + //Create a console for background tasks. Register the virtual console prior to + // the real console so mount point can be listed properly + VirtualMountPointConsole.registerVirtualConsoles(getApplicationContext()); allocBackgroundConsole(getApplicationContext()); //Force the load of mime types diff --git a/src/com/cyanogenmod/filemanager/activities/EditorActivity.java b/src/com/cyanogenmod/filemanager/activities/EditorActivity.java index 16dd0355..8e6c413a 100644 --- a/src/com/cyanogenmod/filemanager/activities/EditorActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/EditorActivity.java @@ -74,6 +74,7 @@ import com.cyanogenmod.filemanager.preferences.FileManagerSettings; import com.cyanogenmod.filemanager.preferences.Preferences; import com.cyanogenmod.filemanager.ui.ThemeManager; import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import com.cyanogenmod.filemanager.ui.policy.PrintActionPolicy; import com.cyanogenmod.filemanager.ui.widgets.ButtonItem; import com.cyanogenmod.filemanager.util.AndroidHelper; import com.cyanogenmod.filemanager.util.CommandHelper; @@ -236,6 +237,21 @@ public class EditorActivity extends Activity implements TextWatcher { return v; } + + /** + * Return the view as a document + * + * @return StringBuilder a buffer to the document + */ + public StringBuilder toStringDocument() { + StringBuilder sb = new StringBuilder(); + int c = getCount(); + for (int i = 0; i < c; i++) { + sb.append(getItem(i)); + sb.append("\n"); + } + return sb; + } } /** @@ -279,7 +295,12 @@ public class EditorActivity extends Activity implements TextWatcher { * {@inheritDoc} */ @Override - public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/} + public void onAsyncEnd(boolean cancelled) { + if (!cancelled && StringHelper.isBinaryData(mByteBuffer.toByteArray())) { + EditorActivity.this.mBinary = true; + EditorActivity.this.mReadOnly = true; + } + } /** * {@inheritDoc} @@ -298,20 +319,7 @@ public class EditorActivity extends Activity implements TextWatcher { public void onPartialResult(Object result) { try { if (result == null) return; - byte[] partial = (byte[])result; - - // Check if the file is a binary file. In this case the editor - // is read-only - if (!EditorActivity.this.mReadOnly) { - for (int i = 0; i < partial.length-1; i++) { - if (!StringHelper.isPrintableCharacter((char)partial[i])) { - EditorActivity.this.mBinary = true; - EditorActivity.this.mReadOnly = true; - break; - } - } - } - + byte[] partial = (byte[]) result; this.mByteBuffer.write(partial, 0, partial.length); this.mSize += partial.length; if (this.mListener != null && this.mReadFso != null) { @@ -514,6 +522,10 @@ public class EditorActivity extends Activity implements TextWatcher { * @hide */ ButtonItem mSave; + /** + * @hide + */ + ButtonItem mPrint; // No suggestions status /** @@ -557,6 +569,8 @@ public class EditorActivity extends Activity implements TextWatcher { */ String mHexLineSeparator; + private boolean mHexDump; + /** * Intent extra parameter for the path of the file to open. */ @@ -576,6 +590,12 @@ public class EditorActivity extends Activity implements TextWatcher { // Load typeface for hex editor mHexTypeface = Typeface.createFromAsset(getAssets(), "fonts/Courier-Prime.ttf"); + // Save hexdump user preference + mHexDump = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP. + getDefaultValue()).booleanValue()); + // Register the broadcast receiver IntentFilter filter = new IntentFilter(); filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED); @@ -657,11 +677,17 @@ public class EditorActivity extends Activity implements TextWatcher { this.mTitle = (TextView)customTitle.findViewById(R.id.customtitle_title); this.mTitle.setText(R.string.editor); this.mTitle.setContentDescription(getString(R.string.editor)); - this.mSave = (ButtonItem)customTitle.findViewById(R.id.ab_button1); + + this.mSave = (ButtonItem)customTitle.findViewById(R.id.ab_button0); this.mSave.setImageResource(R.drawable.ic_holo_light_save); this.mSave.setContentDescription(getString(R.string.actionbar_button_save_cd)); this.mSave.setVisibility(View.GONE); + this.mPrint = (ButtonItem)customTitle.findViewById(R.id.ab_button1); + this.mPrint.setImageResource(R.drawable.ic_holo_light_print); + this.mPrint.setContentDescription(getString(R.string.actionbar_button_print_cd)); + this.mPrint.setVisibility(View.VISIBLE); + ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button2); configuration.setImageResource(R.drawable.ic_holo_light_overflow); configuration.setContentDescription(getString(R.string.actionbar_button_overflow_cd)); @@ -913,11 +939,19 @@ public class EditorActivity extends Activity implements TextWatcher { */ public void onActionBarItemClick(View view) { switch (view.getId()) { - case R.id.ab_button1: + case R.id.ab_button0: // Save the file checkAndWrite(); break; + case R.id.ab_button1: + // Print the file + StringBuilder sb = mBinary + ? ((HexDumpAdapter)mBinaryEditor.getAdapter()).toStringDocument() + : new StringBuilder(mEditor.getText().toString()); + PrintActionPolicy.printStringDocument(this, mFso, sb); + break; + case R.id.ab_button2: // Show overflow menu showOverflowPopUp(this.mOptionsAnchorView); @@ -1105,12 +1139,7 @@ public class EditorActivity extends Activity implements TextWatcher { // Now we have the byte array with all the data. is a binary file? // Then dump them byte array to hex dump string (only if users settings // to dump file) - boolean hexDump = - Preferences.getSharedPreferences().getBoolean( - FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(), - ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP. - getDefaultValue()).booleanValue()); - if (activity.mBinary && hexDump) { + if (activity.mBinary && mHexDump) { // we do not use the Hexdump helper class, because we need to show the // progress of the dump process final String data = toHexPrintableString(toHexDump( @@ -1158,7 +1187,7 @@ public class EditorActivity extends Activity implements TextWatcher { } } else { // Now we have the buffer, set the text of the editor - if (activity.mBinary) { + if (activity.mBinary && mHexDump) { HexDumpAdapter adapter = new HexDumpAdapter(EditorActivity.this, this.mReader.mBinaryBuffer); mBinaryEditor.setAdapter(adapter); @@ -1522,8 +1551,10 @@ public class EditorActivity extends Activity implements TextWatcher { theme.setTitlebarDrawable(this, getActionBar(), "titlebar_drawable"); //$NON-NLS-1$ View v = getActionBar().getCustomView().findViewById(R.id.customtitle_title); theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ - v = findViewById(R.id.ab_button1); + v = findViewById(R.id.ab_button0); theme.setImageDrawable(this, (ImageView)v, "ab_save_drawable"); //$NON-NLS-1$ + v = findViewById(R.id.ab_button1); + theme.setImageDrawable(this, (ImageView)v, "ab_print_drawable"); //$NON-NLS-1$ v = findViewById(R.id.ab_button2); theme.setImageDrawable(this, (ImageView)v, "ab_overflow_drawable"); //$NON-NLS-1$ //- View diff --git a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java index 97473c06..6770acfa 100644 --- a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java @@ -69,6 +69,9 @@ import com.cyanogenmod.filemanager.console.ConsoleAllocException; import com.cyanogenmod.filemanager.console.ConsoleBuilder; import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.VirtualConsole; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; import com.cyanogenmod.filemanager.listeners.OnHistoryListener; import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; import com.cyanogenmod.filemanager.model.Bookmark; @@ -170,6 +173,12 @@ public class NavigationActivity extends Activity public static final String EXTRA_NAVIGATE_TO = "extra_navigate_to"; //$NON-NLS-1$ + /** + * Constant for extra information for request to add navigation to the history + */ + public static final String EXTRA_ADD_TO_HISTORY = + "extra_add_to_history"; //$NON-NLS-1$ + // The timeout needed to reset the exit status for back button // After this time user need to tap 2 times the back button to // exit, and the toast is shown again after the first tap. @@ -295,11 +304,88 @@ public class NavigationActivity extends Activity FileHelper.sReloadDateTimeFormats = true; NavigationActivity.this.getCurrentNavigationView().refresh(); } + } else if (intent.getAction().compareTo( + FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED) == 0) { + onRequestBookmarksRefresh(); + removeUnmountedHistory(); + removeUnmountedSelection(); } } } }; + private OnClickListener mOnClickDrawerTabListener = new OnClickListener() { + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.drawer_bookmarks_tab: + if (!mBookmarksTab.isSelected()) { + mBookmarksTab.setSelected(true); + mHistoryTab.setSelected(false); + mBookmarksTab.setTextAppearance( + NavigationActivity.this, R.style.primary_text_appearance); + mHistoryTab.setTextAppearance( + NavigationActivity.this, R.style.secondary_text_appearance); + mHistoryLayout.setVisibility(View.GONE); + mBookmarksLayout.setVisibility(View.VISIBLE); + applyTabTheme(); + + try { + Preferences.savePreference(FileManagerSettings.USER_PREF_LAST_DRAWER_TAB, + Integer.valueOf(0), true); + } catch (Exception ex) { + Log.e(TAG, "Can't save last drawer tab", ex); //$NON-NLS-1$ + } + + mClearHistory.setVisibility(View.GONE); + } + break; + case R.id.drawer_history_tab: + if (!mHistoryTab.isSelected()) { + mHistoryTab.setSelected(true); + mBookmarksTab.setSelected(false); + mHistoryTab.setTextAppearance( + NavigationActivity.this, R.style.primary_text_appearance); + mBookmarksTab.setTextAppearance( + NavigationActivity.this, R.style.secondary_text_appearance); + mBookmarksLayout.setVisibility(View.GONE); + mHistoryLayout.setVisibility(View.VISIBLE); + applyTabTheme(); + + try { + Preferences.savePreference(FileManagerSettings.USER_PREF_LAST_DRAWER_TAB, + Integer.valueOf(1), true); + } catch (Exception ex) { + Log.e(TAG, "Can't save last drawer tab", ex); //$NON-NLS-1$ + } + + mClearHistory.setVisibility(mHistory.size() > 0 ? View.VISIBLE : View.GONE); + } + break; + default: + break; + } + } + }; + + private OnClickListener mOnClickDrawerActionBarListener = new OnClickListener() { + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ab_settings: + mDrawerLayout.closeDrawer(mDrawer); + openSettings(); + break; + case R.id.ab_clear_history: + clearHistory(); + mClearHistory.setVisibility(View.GONE); + break; + default: + break; + } + } + }; + /** * @hide */ @@ -312,11 +398,19 @@ public class NavigationActivity extends Activity private SelectionView mSelectionBar; private DrawerLayout mDrawerLayout; - private ScrollView mDrawer; + private ViewGroup mDrawer; private ActionBarDrawerToggle mDrawerToggle; private LinearLayout mDrawerHistory; private TextView mDrawerHistoryEmpty; + private TextView mBookmarksTab; + private TextView mHistoryTab; + private View mBookmarksLayout; + private View mHistoryLayout; + + private ButtonItem mSettings; + private ButtonItem mClearHistory; + private List<Bookmark> mBookmarks; private LinearLayout mDrawerBookmarks; @@ -337,6 +431,8 @@ public class NavigationActivity extends Activity */ Handler mHandler; + private AsyncTask<Void, Void, Boolean> mBookmarksTask; + /** * {@inheritDoc} */ @@ -355,6 +451,7 @@ public class NavigationActivity extends Activity filter.addAction(Intent.ACTION_DATE_CHANGED); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED); registerReceiver(this.mNotificationReceiver, filter); // Set the theme before setContentView @@ -447,6 +544,22 @@ public class NavigationActivity extends Activity if (!FileManagerApplication.checkRestrictSecondaryUsersAccess(this, mChRooted)) { return; } + + // Check that the current dir is mounted (for virtual filesystems) + String curDir = mNavigationViews[mCurrentNavigationView].getCurrentDir(); + if (curDir != null) { + VirtualMountPointConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath( + mNavigationViews[mCurrentNavigationView].getCurrentDir()); + if (vc != null && !vc.isMounted()) { + onRequestBookmarksRefresh(); + removeUnmountedHistory(); + removeUnmountedSelection(); + + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADD_TO_HISTORY, false); + initNavigation(NavigationActivity.this.mCurrentNavigationView, false, intent); + } + } } @Override @@ -536,7 +649,7 @@ public class NavigationActivity extends Activity ((Boolean)FileManagerSettings.SETTINGS_FIRST_USE.getDefaultValue()).booleanValue()); //Display the welcome message? - if (firstUse && !FileManagerApplication.isDeviceRooted()) { + if (firstUse && FileManagerApplication.isDeviceRooted()) { // open navigation drawer to show user that it exists mDrawerLayout.openDrawer(mDrawer); @@ -620,11 +733,10 @@ public class NavigationActivity extends Activity } }); - // Have overflow menu? + // Have overflow menu? Actually no. There is only a search action, so just hide + // the overflow View overflow = findViewById(R.id.ab_overflow); - boolean showOptionsMenu = AndroidHelper.showOptionsMenu(getApplicationContext()); - overflow.setVisibility(showOptionsMenu ? View.VISIBLE : View.GONE); - this.mOptionsAnchorView = showOptionsMenu ? overflow : this.mActionBar; + overflow.setVisibility(View.GONE); // Show the status bar View statusBar = findViewById(R.id.navigation_statusbar_portrait_holder); @@ -643,11 +755,30 @@ public class NavigationActivity extends Activity */ private void initDrawer() { mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - mDrawer = (ScrollView) findViewById(R.id.drawer); + mDrawer = (ViewGroup) findViewById(R.id.drawer); mDrawerBookmarks = (LinearLayout) findViewById(R.id.bookmarks_list); mDrawerHistory = (LinearLayout) findViewById(R.id.history_list); mDrawerHistoryEmpty = (TextView) findViewById(R.id.history_empty); + mBookmarksLayout = findViewById(R.id.drawer_bookmarks); + mHistoryLayout = findViewById(R.id.drawer_history); + mBookmarksTab = (TextView) findViewById(R.id.drawer_bookmarks_tab); + mHistoryTab = (TextView) findViewById(R.id.drawer_history_tab); + mBookmarksTab.setOnClickListener(mOnClickDrawerTabListener); + mHistoryTab.setOnClickListener(mOnClickDrawerTabListener); + + mSettings = (ButtonItem) findViewById(R.id.ab_settings); + mSettings.setOnClickListener(mOnClickDrawerActionBarListener); + mClearHistory = (ButtonItem) findViewById(R.id.ab_clear_history); + mClearHistory.setOnClickListener(mOnClickDrawerActionBarListener); + + // Restore the last tab pressed + Integer lastTab = Preferences.getSharedPreferences().getInt( + FileManagerSettings.USER_PREF_LAST_DRAWER_TAB.getId(), + (Integer) FileManagerSettings.USER_PREF_LAST_DRAWER_TAB + .getDefaultValue()); + mOnClickDrawerTabListener.onClick(lastTab == 0 ? mBookmarksTab : mHistoryTab); + // Set the navigation drawer "hamburger" icon mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.drawable.ic_holo_light_navigation_drawer, @@ -661,7 +792,6 @@ public class NavigationActivity extends Activity | ActionBar.DISPLAY_SHOW_HOME); getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true); - invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } /** Called when a drawer has settled in a completely open state. */ @@ -681,8 +811,6 @@ public class NavigationActivity extends Activity "action_bar_title", "id", "android"); TextView v = (TextView) findViewById(titleId); theme.setTextColor(NavigationActivity.this, v, "text_color"); //$NON-NLS-1$ - - invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } }; @@ -694,7 +822,7 @@ public class NavigationActivity extends Activity } /** - * Method adds a history entry to the history list in the drawer + * Method that adds a history entry to the history list in the drawer */ private void addHistoryToDrawer(int index, HistoryNavigable navigable) { // hide empty message @@ -712,10 +840,7 @@ public class NavigationActivity extends Activity TextView name = (TextView) view.findViewById(R.id.history_item_name); TextView directory = (TextView) view .findViewById(R.id.history_item_directory); - TextView position = (TextView) view - .findViewById(R.id.history_item_position); - // if (history.getItem() instanceof NavigationViewInfoParcelable) Drawable icon = iconholder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$ if (navigable instanceof SearchInfoParcelable) { icon = iconholder.getDrawable("ic_history_search_drawable"); //$NON-NLS-1$ @@ -729,11 +854,9 @@ public class NavigationActivity extends Activity name.setText(title); directory.setText(navigable.getDescription()); - position.setText(String.format("#%d", index + 1)); theme.setTextColor(this, name, "text_color"); theme.setTextColor(this, directory, "text_color"); - theme.setTextColor(this, position, "text_color"); // handle item click view.setOnClickListener(new OnClickListener() { @@ -750,6 +873,9 @@ public class NavigationActivity extends Activity // add as first child mDrawerHistory.addView(view, 0); + + // Show clear button if history tab is selected + mClearHistory.setVisibility(mHistoryTab.getVisibility()); } /** @@ -904,12 +1030,17 @@ public class NavigationActivity extends Activity /** * Method that initializes the bookmarks. */ - private void initBookmarks() { + private synchronized void initBookmarks() { + if (mBookmarksTask != null && + !mBookmarksTask.getStatus().equals(AsyncTask.Status.FINISHED)) { + return; + } + // Retrieve the loading view final View waiting = findViewById(R.id.bookmarks_loading); // Load bookmarks in background - AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { + mBookmarksTask = new AsyncTask<Void, Void, Boolean>() { Exception mCause; @Override @@ -945,14 +1076,16 @@ public class NavigationActivity extends Activity NavigationActivity.this, this.mCause); } } + mBookmarksTask = null; } @Override protected void onCancelled() { waiting.setVisibility(View.GONE); + mBookmarksTask = null; } }; - task.execute(); + mBookmarksTask.execute(); } /** @@ -971,6 +1104,7 @@ public class NavigationActivity extends Activity bookmarks.addAll(loadFilesystemBookmarks()); } bookmarks.addAll(loadSdStorageBookmarks()); + bookmarks.addAll(loadVirtualBookmarks()); bookmarks.addAll(loadUserBookmarks()); return bookmarks; } @@ -1103,6 +1237,32 @@ public class NavigationActivity extends Activity } /** + * Method that loads all virtual mount points. + * + * @return List<Bookmark> The bookmarks loaded + */ + private List<Bookmark> loadVirtualBookmarks() { + // Initialize the bookmarks + List<Bookmark> bookmarks = new ArrayList<Bookmark>(); + List<MountPoint> mps = VirtualMountPointConsole.getVirtualMountPoints(); + for (MountPoint mp : mps) { + BOOKMARK_TYPE type = null; + String name = null; + if (mp.isSecure()) { + type = BOOKMARK_TYPE.SECURE; + name = getString(R.string.bookmarks_secure); + } else if (mp.isRemote()) { + type = BOOKMARK_TYPE.REMOTE; + name = getString(R.string.bookmarks_remote); + } else { + continue; + } + bookmarks.add(new Bookmark(type, name, mp.getMountPoint())); + } + return bookmarks; + } + + /** * Method that loads the user bookmarks (added by the user). * * @return List<Bookmark> The bookmarks loaded @@ -1133,6 +1293,17 @@ public class NavigationActivity extends Activity /** NON BLOCK **/ } } + + // Remove bookmarks from virtual storage if the filesystem is not mount + int c = bookmarks.size() - 1; + for (int i = c; i >= 0; i--) { + VirtualMountPointConsole vc = + VirtualMountPointConsole.getVirtualConsoleForPath(bookmarks.get(i).mPath); + if (vc != null && !vc.isMounted()) { + bookmarks.remove(i); + } + } + return bookmarks; } @@ -1223,6 +1394,15 @@ public class NavigationActivity extends Activity initialDir = navigateTo; } + // Add to history + final boolean addToHistory = intent.getBooleanExtra(EXTRA_ADD_TO_HISTORY, true); + + // We cannot navigate to a secure console if is unmount, go to root in that case + VirtualConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath(initialDir); + if (vc != null && vc instanceof SecureConsole && !((SecureConsole) vc).isMounted()) { + initialDir = FileHelper.ROOT_DIRECTORY; + } + if (this.mChRooted) { // Initial directory is the first external sdcard (sdcard, emmc, usb, ...) if (!StorageHelper.isPathInStorageVolume(initialDir)) { @@ -1257,17 +1437,19 @@ public class NavigationActivity extends Activity this, ipex, false, true, new OnRelaunchCommandResult() { @Override public void onSuccess() { - navigationView.changeCurrentDir(absInitialDir); + navigationView.changeCurrentDir(absInitialDir, addToHistory); } @Override public void onFailed(Throwable cause) { showInitialInvalidDirectoryMsg(userInitialDir); - navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY); + navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY, + addToHistory); } @Override public void onCancelled() { showInitialInvalidDirectoryMsg(userInitialDir); - navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY); + navigationView.changeCurrentDir(FileHelper.ROOT_DIRECTORY, + addToHistory); } }); @@ -1289,7 +1471,7 @@ public class NavigationActivity extends Activity } // Change the current directory to the user-defined initial directory - navigationView.changeCurrentDir(initialDir); + navigationView.changeCurrentDir(initialDir, addToHistory); } /** @@ -1368,65 +1550,6 @@ public class NavigationActivity extends Activity } /** - * {@inheritDoc} - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Pass the event to ActionBarDrawerToggle, if it returns - // true, then it has handled the app icon touch event - if (mDrawerToggle.onOptionsItemSelected(item)) { - return true; - } - - // just handle the drawer list here - switch (item.getItemId()) { - case R.id.mnu_actions_add_to_bookmarks_current_folder: - // TODO add bookmark - Log.d(TAG, "add bookmark"); - return true; - case R.id.mnu_clear_history: - clearHistory(); - return true; - case R.id.mnu_settings: - openSettings(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - /** - * Called when the menu is created. Just includes the drawer's overflow - * menu. All entries are hidden until onPrepareOptionsMenu unhides them. - */ - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.drawer, menu); - return true; - } - - /** - * Called whenever we call invalidateOptionsMenu() - */ - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer); - - for (int i = 0; i < menu.size(); i++) { - // show all items if drawer is open, - // hide them if not - menu.getItem(i).setVisible(drawerOpen); - - if (menu.getItem(i).getItemId() == R.id.mnu_clear_history) { - menu.getItem(i).setEnabled(mHistory.size() > 0); - } - } - - return super.onPrepareOptionsMenu(menu); - } - - /** * Method invoked when an action item is clicked. * * @param view The button pushed @@ -1535,14 +1658,16 @@ public class NavigationActivity extends Activity case INTENT_REQUEST_SEARCH: if (resultCode == RESULT_OK) { //Change directory? - FileSystemObject fso = - (FileSystemObject)data. - getSerializableExtra(EXTRA_SEARCH_ENTRY_SELECTION); - SearchInfoParcelable searchInfo = - data.getParcelableExtra(EXTRA_SEARCH_LAST_SEARCH_DATA); - if (fso != null) { - //Goto to new directory - getCurrentNavigationView().open(fso, searchInfo); + Bundle bundle = data.getExtras(); + if (bundle != null) { + FileSystemObject fso = (FileSystemObject) bundle.getSerializable( + EXTRA_SEARCH_ENTRY_SELECTION); + SearchInfoParcelable searchInfo = + bundle.getParcelable(EXTRA_SEARCH_LAST_SEARCH_DATA); + if (fso != null) { + //Goto to new directory + getCurrentNavigationView().open(fso, searchInfo); + } } } else if (resultCode == RESULT_CANCELED) { SearchInfoParcelable searchInfo = @@ -1606,6 +1731,14 @@ public class NavigationActivity extends Activity * {@inheritDoc} */ @Override + public void onRequestBookmarksRefresh() { + initBookmarks(); + } + + /** + * {@inheritDoc} + */ + @Override public void onRequestRemove(Object o, boolean clearSelection) { if (o instanceof FileSystemObject) { // Remove from view @@ -1797,6 +1930,13 @@ public class NavigationActivity extends Activity if (breadcrumb.getMountPointInfo().compareTo(mountPoint) == 0) { breadcrumb.updateMountPointInfo(); } + if (mountPoint.isSecure()) { + // Secure mountpoints only can be unmount, so we need to move the navigation + // to a secure storage (do not add to history) + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADD_TO_HISTORY, false); + initNavigation(NavigationActivity.this.mCurrentNavigationView, false, intent); + } } }); dialog.show(); @@ -2044,17 +2184,19 @@ public class NavigationActivity extends Activity * Method that remove the {@link FileSystemObject} from the history */ private void removeFromHistory(FileSystemObject fso) { - // TODO remove drawer entry here, too if (this.mHistory != null) { - int cc = this.mHistory.size(); - for (int i = cc-1; i >= 0 ; i--) { + int cc = this.mHistory.size() - 1; + for (int i = cc; i >= 0 ; i--) { History history = this.mHistory.get(i); if (history.getItem() instanceof NavigationViewInfoParcelable) { String p0 = fso.getFullPath(); - String p1 = - ((NavigationViewInfoParcelable)history.getItem()).getCurrentDir(); + String p1 = ((NavigationViewInfoParcelable) history.getItem()).getCurrentDir(); if (p0.compareTo(p1) == 0) { this.mHistory.remove(i); + mDrawerHistory.removeViewAt(mDrawerHistory.getChildCount() - i - 1); + mDrawerHistoryEmpty.setVisibility( + mDrawerHistory.getChildCount() == 0 ? View.VISIBLE : View.GONE); + updateHistoryPositions(); } } } @@ -2062,6 +2204,17 @@ public class NavigationActivity extends Activity } /** + * Update the history positions after one of the history is removed from drawer + */ + private void updateHistoryPositions() { + int cc = this.mHistory.size() - 1; + for (int i = 0; i <= cc ; i++) { + History history = this.mHistory.get(i); + history.setPosition(i + 1); + } + } + + /** * Method that ask the user to change the access mode prior to crash. * @hide */ @@ -2208,11 +2361,8 @@ public class NavigationActivity extends Activity rbw += bw; } } - int w = abw + rbw; - boolean showOptionsMenu = AndroidHelper.showOptionsMenu(getApplicationContext()); - if (!showOptionsMenu) { - w -= bw; - } + // Currently there isn't overflow menu + int w = abw + rbw - bw; // Add to the new location ViewGroup newParent = (ViewGroup)findViewById(R.id.navigation_title_landscape_holder); @@ -2262,6 +2412,40 @@ public class NavigationActivity extends Activity } /** + * Method that removes all the history items that refers to virtual unmounted filesystems + */ + private void removeUnmountedHistory() { + int cc = mHistory.size() - 1; + for (int i = cc; i >= 0; i--) { + History history = mHistory.get(i); + if (history.getItem() instanceof NavigationViewInfoParcelable) { + NavigationViewInfoParcelable navigableInfo = + ((NavigationViewInfoParcelable) history.getItem()); + VirtualMountPointConsole vc = + VirtualMountPointConsole.getVirtualConsoleForPath( + navigableInfo.getCurrentDir()); + if (vc != null && !vc.isMounted()) { + mHistory.remove(i); + mDrawerHistory.removeViewAt(mDrawerHistory.getChildCount() - i - 1); + } + } + } + mDrawerHistoryEmpty.setVisibility( + mDrawerHistory.getChildCount() == 0 ? View.VISIBLE : View.GONE); + updateHistoryPositions(); + } + + /** + * Method that removes all the selection items that refers to virtual unmounted filesystems + */ + private void removeUnmountedSelection() { + for (NavigationView view : mNavigationViews) { + view.removeUnmountedSelection(); + } + mSelectionBar.setSelection(getNavigationView(mCurrentNavigationView).getSelectedFiles()); + } + + /** * Method that applies the current theme to the activity * @hide */ @@ -2269,6 +2453,7 @@ public class NavigationActivity extends Activity int orientation = getResources().getConfiguration().orientation; Theme theme = ThemeManager.getCurrentTheme(this); theme.setBaseTheme(this, false); + applyTabTheme(); // imitate a closed drawer while layout is rebuilt to avoid NullPointerException boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer); @@ -2318,11 +2503,6 @@ public class NavigationActivity extends Activity theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ // - Navigation drawer - theme.setBackgroundColor(this, mDrawer, "drawer_color"); - v = findViewById(R.id.bookmarks_header); - theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ - v = findViewById(R.id.history_header); - theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ v = findViewById(R.id.history_empty); theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ mDrawerToggle.setDrawerImageResource(theme.getResourceId(this, "drawer_icon")); @@ -2334,8 +2514,6 @@ public class NavigationActivity extends Activity theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ v = item.findViewById(R.id.history_item_directory); theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ - v = item.findViewById(R.id.history_item_position); - theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ } //- NavigationView @@ -2350,4 +2528,25 @@ public class NavigationActivity extends Activity } } + /** + * Method that applies the current theme to the tab host + */ + private void applyTabTheme() { + // Apply the theme + Theme theme = ThemeManager.getCurrentTheme(this); + + View v = findViewById(R.id.drawer); + theme.setBackgroundDrawable(this, v, "background_drawable"); //$NON-NLS-1$ + + v = findViewById(R.id.drawer_bookmarks_tab); + theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ + v = findViewById(R.id.drawer_history_tab); + theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ + + v = findViewById(R.id.ab_settings); + theme.setImageDrawable(this, (ButtonItem) v, "ab_settings_drawable"); //$NON-NLS-1$ + v = findViewById(R.id.ab_clear_history); + theme.setImageDrawable(this, (ButtonItem) v, "ab_delete_drawable"); //$NON-NLS-1$ + } + } diff --git a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java index 4fbbec5b..ca16924e 100644 --- a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java @@ -52,7 +52,7 @@ import com.cyanogenmod.filemanager.activities.preferences.SearchPreferenceFragme import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences; import com.cyanogenmod.filemanager.adapters.SearchResultAdapter; import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; import com.cyanogenmod.filemanager.console.RelaunchableException; import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; @@ -93,8 +93,7 @@ import java.util.List; * An activity for search files and folders. */ public class SearchActivity extends Activity - implements AsyncResultListener, OnItemClickListener, - OnItemLongClickListener, OnRequestRefreshListener { + implements OnItemClickListener, OnItemLongClickListener, OnRequestRefreshListener { private static final String TAG = "SearchActivity"; //$NON-NLS-1$ @@ -211,6 +210,89 @@ public class SearchActivity extends Activity } }; + private ConcurrentAsyncResultListener mAsyncListener = new ConcurrentAsyncResultListener() { + /** + * {@inheritDoc} + */ + @Override + public void onConcurrentAsyncStart() { + runOnUiThread(new Runnable() { + @Override + public void run() { + SearchActivity.this.toggleResults(false, false); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void onConcurrentAsyncEnd(boolean cancelled) { + mSearchListView.post(new Runnable() { + @Override + public void run() { + try { + //Dismiss the dialog + if (SearchActivity.this.mDialog != null) { + SearchActivity.this.mDialog.dismiss(); + } + + // Resolve the symlinks + FileHelper.resolveSymlinks( + SearchActivity.this, SearchActivity.this.mResultList); + + // Draw the results + drawResults(); + + } catch (Throwable ex) { + Log.e(TAG, "onAsyncEnd method fails", ex); //$NON-NLS-1$ + } + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public void onConcurrentPartialResult(final Object partialResults) { + //Saved in the global result list, for save at the end + if (partialResults instanceof FileSystemObject) { + SearchActivity.this.mResultList.add((FileSystemObject)partialResults); + } else { + SearchActivity.this.mResultList.addAll((List<FileSystemObject>)partialResults); + } + + //Notify progress + mSearchListView.post(new Runnable() { + @Override + public void run() { + if (SearchActivity.this.mDialog != null) { + int progress = SearchActivity.this.mResultList.size(); + setProgressMsg(progress); + } + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void onConcurrentAsyncExitCode(int exitCode) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onConcurrentException(Exception cause) { + //Capture the exception + ExceptionUtil.translateException(SearchActivity.this, cause); + } + }; + /** * @hide */ @@ -670,13 +752,13 @@ public class SearchActivity extends Activity }); SearchActivity.this.mDialog.show(); - //Execute the query (search are process in background) + // Execute the query (search in background) SearchActivity.this.mExecutable = CommandHelper.findFiles( SearchActivity.this, searchDirectory, - SearchActivity.this.mQuery, - SearchActivity.this, + mQuery, + mAsyncListener, null); } catch (Throwable ex) { @@ -1000,6 +1082,14 @@ public class SearchActivity extends Activity * {@inheritDoc} */ @Override + public void onRequestBookmarksRefresh() { + // Ignore + } + + /** + * {@inheritDoc} + */ + @Override public void onRequestRemove(Object o, boolean clearSelection) { if (o instanceof FileSystemObject) { removeItem((FileSystemObject)o); @@ -1027,17 +1117,18 @@ public class SearchActivity extends Activity */ void back(final boolean cancelled, FileSystemObject item, boolean isChecked) { final Context ctx = SearchActivity.this; - final Intent intent = new Intent(); boolean finish = true; if (cancelled) { + final Intent intent = new Intent(); if (SearchActivity.this.mDrawingSearchResultTask != null && SearchActivity.this.mDrawingSearchResultTask.isRunning()) { SearchActivity.this.mDrawingSearchResultTask.cancel(true); } if (this.mRestoreState != null) { - intent.putExtra( - NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA, + Bundle bundle = new Bundle(); + bundle.putParcelable(NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA, (Parcelable)this.mRestoreState); + intent.putExtras(bundle); } setResult(RESULT_CANCELED, intent); } else { @@ -1047,7 +1138,7 @@ public class SearchActivity extends Activity if (!isChecked) { fso = CommandHelper.getFileInfo(ctx, item.getFullPath(), null); } - finish = navigateTo(fso, intent); + finish = navigateTo(fso); } catch (Exception e) { // Capture the exception @@ -1055,7 +1146,7 @@ public class SearchActivity extends Activity final OnRelaunchCommandResult relaunchListener = new OnRelaunchCommandResult() { @Override public void onSuccess() { - if (navigateTo(fFso, intent)) { + if (navigateTo(fFso)) { exit(); } } @@ -1102,13 +1193,15 @@ public class SearchActivity extends Activity * @param intent The intent used to navigate to * @return boolean If the action implies finish this activity */ - boolean navigateTo(FileSystemObject fso, Intent intent) { + boolean navigateTo(FileSystemObject fso) { if (fso != null) { if (FileHelper.isDirectory(fso)) { - intent.putExtra(NavigationActivity.EXTRA_SEARCH_ENTRY_SELECTION, fso); - intent.putExtra( - NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA, + final Intent intent = new Intent(); + Bundle bundle = new Bundle(); + bundle.putSerializable(NavigationActivity.EXTRA_SEARCH_ENTRY_SELECTION, fso); + bundle.putParcelable(NavigationActivity.EXTRA_SEARCH_LAST_SEARCH_DATA, (Parcelable)createSearchInfo()); + intent.putExtras(bundle); setResult(RESULT_OK, intent); return true; } @@ -1126,87 +1219,6 @@ public class SearchActivity extends Activity } /** - * {@inheritDoc} - */ - @Override - public void onAsyncStart() { - runOnUiThread(new Runnable() { - @Override - public void run() { - SearchActivity.this.toggleResults(false, false); - } - }); - } - - /** - * {@inheritDoc} - */ - @Override - public void onAsyncEnd(boolean cancelled) { - this.mSearchListView.post(new Runnable() { - @Override - public void run() { - try { - //Dismiss the dialog - if (SearchActivity.this.mDialog != null) { - SearchActivity.this.mDialog.dismiss(); - } - - // Resolve the symlinks - FileHelper.resolveSymlinks( - SearchActivity.this, SearchActivity.this.mResultList); - - // Draw the results - drawResults(); - - } catch (Throwable ex) { - Log.e(TAG, "onAsyncEnd method fails", ex); //$NON-NLS-1$ - } - } - }); - } - - /** - * {@inheritDoc} - */ - @Override - @SuppressWarnings("unchecked") - public void onPartialResult(final Object partialResults) { - //Saved in the global result list, for save at the end - if (partialResults instanceof FileSystemObject) { - SearchActivity.this.mResultList.add((FileSystemObject)partialResults); - } else { - SearchActivity.this.mResultList.addAll((List<FileSystemObject>)partialResults); - } - - //Notify progress - this.mSearchListView.post(new Runnable() { - @Override - public void run() { - if (SearchActivity.this.mDialog != null) { - int progress = SearchActivity.this.mResultList.size(); - setProgressMsg(progress); - } - } - }); - } - - /** - * {@inheritDoc} - */ - @Override - public void onAsyncExitCode(int exitCode) {/**NON BLOCK**/} - - /** - * {@inheritDoc} - */ - @Override - public void onException(Exception cause) { - //Capture the exception - ExceptionUtil.translateException(this, cause); - } - - /** * Method that draw the results in the listview * @hide */ @@ -1232,11 +1244,10 @@ public class SearchActivity extends Activity * @return SearchInfoParcelable The search info reference */ private SearchInfoParcelable createSearchInfo() { - SearchInfoParcelable parcel = new SearchInfoParcelable(); - parcel.setSearchDirectory(this.mSearchDirectory); - parcel.setSearchResultList( - ((SearchResultAdapter)this.mSearchListView.getAdapter()).getData()); - parcel.setSearchQuery(this.mQuery); + SearchInfoParcelable parcel = new SearchInfoParcelable( + mSearchDirectory, + ((SearchResultAdapter)this.mSearchListView.getAdapter()).getData(), + mQuery); return parcel; } diff --git a/src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java b/src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java new file mode 100644 index 00000000..d664dd47 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/activities/preferences/StoragePreferenceFragment.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.activities.preferences; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.util.Log; + +import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.preferences.FileManagerSettings; +import com.cyanogenmod.filemanager.preferences.Preferences; + +/** + * A class that manages the storage options + */ +public class StoragePreferenceFragment extends TitlePreferenceFragment { + + private static final String TAG = "StoragePreferenceFragment"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + private static final String KEY_RESET_PASSWORD = "secure_storage_reset_password"; + private static final String KEY_DELETE_STORAGE = "secure_storage_delete_storage"; + + private Preference mResetPassword; + private Preference mDeleteStorage; + private CheckBoxPreference mDelayedSync; + + private final BroadcastReceiver mMountStatusReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().compareTo( + FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED) == 0) { + updatePreferences(); + } + } + }; + + private final OnPreferenceChangeListener mOnChangeListener = + new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + if (DEBUG) { + Log.d(TAG, + String.format("New value for %s: %s", //$NON-NLS-1$ + key, + String.valueOf(newValue))); + } + + return true; + } + }; + + private final OnPreferenceClickListener mOnClickListener = new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference.equals(mResetPassword)) { + getSecureConsole().requestReset(getActivity()); + } else if (preference.equals(mDeleteStorage)) { + getSecureConsole().requestDelete(getActivity()); + } + return false; + } + }; + + @Override + public void onStart() { + super.onStart(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED); + getActivity().registerReceiver(mMountStatusReceiver, filter); + } + + @Override + public void onStop() { + super.onStop(); + getActivity().unregisterReceiver(mMountStatusReceiver); + } + + @Override + public void onResume() { + super.onResume(); + + // Update the preferences + updatePreferences(); + } + + @Override + public void onPause() { + super.onPause(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_storage); + + // Reset password + mResetPassword = findPreference(KEY_RESET_PASSWORD); + mResetPassword.setOnPreferenceClickListener(mOnClickListener); + + // Delete storage + mDeleteStorage = findPreference(KEY_DELETE_STORAGE); + mDeleteStorage.setOnPreferenceClickListener(mOnClickListener); + + // Delayed sync + this.mDelayedSync = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_SECURE_STORAGE_DELAYED_SYNC.getId()); + this.mDelayedSync.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Update the preferences + updatePreferences(); + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getTitle() { + return getString(R.string.pref_storage); + } + + /** + * Method that returns the secure console instance + * + * @return SecureConsole The secure console + */ + private SecureConsole getSecureConsole() { + int bufferSize = getActivity().getResources().getInteger(R.integer.buffer_size); + return SecureConsole.getInstance(getActivity(), bufferSize); + } + + /** + * Check the preferences status + */ + @SuppressWarnings("deprecation") + private void updatePreferences() { + boolean secureStorageExists = SecureConsole.getSecureStorageRoot().getFile().exists(); + if (mResetPassword != null) { + mResetPassword.setEnabled(secureStorageExists); + } + if (mDeleteStorage != null) { + mDeleteStorage.setEnabled(secureStorageExists); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java b/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java index 0caf438b..5ae541b3 100644 --- a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java +++ b/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java @@ -80,8 +80,6 @@ public class SearchResultAdapter extends ArrayAdapter<SearchResult> { Float mRelevance; } - private static final int MESSAGE_REDRAW = 1; - private DataHolder[] mData; private IconHolder mIconHolder; private final int mItemViewResourceId; diff --git a/src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java b/src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java new file mode 100644 index 00000000..31ea0f06 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/ConcurrentAsyncResultListener.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands; + +/** + * An interface for communicate partial results in concurrent mode. + */ +public abstract class ConcurrentAsyncResultListener implements AsyncResultListener { + + private final Object mSync = new Object(); + private int mRefs; + private boolean mStartNotified = false; + private boolean mCancelled = false; + + /** + * Constructor of {@code ConcurrentAsyncResultListener} + */ + public ConcurrentAsyncResultListener() { + super(); + mRefs = 0; + } + + /** + * Method invoked when the partial data has initialized. + */ + public abstract void onConcurrentAsyncStart(); + + /** + * Method invoked when the partial data has finalized. + * + * @param cancelled Indicates if the program was cancelled + */ + public abstract void onConcurrentAsyncEnd(boolean cancelled); + + /** + * Method invoked when the program is ended. + * + * @param exitCode The exit code of the program + */ + public abstract void onConcurrentAsyncExitCode(int exitCode); + + /** + * Method invoked when new partial data are ready. + * + * @param result New data result + */ + public abstract void onConcurrentPartialResult(Object result); + + /** + * Method invoked when an exception occurs while executing the program. + * + * @param cause The cause that raise the exception + */ + public abstract void onConcurrentException(Exception cause); + + /** + * Return if the operation was cancelled by other listener + * + * @return boolean If the operation was cancelled + */ + public boolean isCancelled() { + return mCancelled; + } + + /** + * Method invoked when an object want to be part of this concurrent listener + */ + public void onRegister() { + synchronized (mSync) { + mRefs++; + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void onAsyncStart() { + boolean notify = false; + synchronized (mSync) { + if (!mStartNotified) { + notify = true; + } + mStartNotified = true; + } + if (notify) { + onConcurrentAsyncStart(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void onAsyncEnd(boolean cancelled) { + boolean notify = false; + if (cancelled) { + mCancelled = true; + } + synchronized (mSync) { + if (mRefs <= 1) { + notify = true; + } + mRefs--; + mStartNotified = true; + } + if (notify) { + onConcurrentAsyncEnd(mCancelled); + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void onAsyncExitCode(int exitCode) { + boolean notify = false; + synchronized (mSync) { + if (mRefs <= 0) { + notify = true; + } + mStartNotified = true; + } + if (notify) { + onConcurrentAsyncExitCode(exitCode); + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void onPartialResult(Object result) { + synchronized (mSync) { + if (!mCancelled && mRefs >= 1) { + onConcurrentPartialResult(result); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void onException(Exception cause) { + synchronized (mSync) { + if (!mCancelled && mRefs >= 1) { + onConcurrentException(cause); + } + } + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java index f5c2f169..83fdf188 100644 --- a/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java +++ b/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java @@ -196,7 +196,7 @@ public interface ExecutableCreator { * @throws InsufficientPermissionsException If an operation requires elevated permissions */ FindExecutable createFindExecutable( - String directory, Query query, AsyncResultListener asyncResultListener) + String directory, Query query, ConcurrentAsyncResultListener asyncResultListener) throws CommandNotFoundException, NoSuchFileOrDirectory, InsufficientPermissionsException; diff --git a/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java b/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java index 793dc616..2a7ccf54 100644 --- a/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java +++ b/src/com/cyanogenmod/filemanager/commands/java/FindCommand.java @@ -19,6 +19,7 @@ package com.cyanogenmod.filemanager.commands.java; import android.util.Log; import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; import com.cyanogenmod.filemanager.commands.FindExecutable; import com.cyanogenmod.filemanager.console.ExecutionException; import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; @@ -40,7 +41,7 @@ public class FindCommand extends Program implements FindExecutable { private final String mDirectory; private final String[] mQueryRegExp; - private final AsyncResultListener mAsyncResultListener; + private final ConcurrentAsyncResultListener mAsyncResultListener; private boolean mCancelled; private boolean mEnded; @@ -53,11 +54,15 @@ public class FindCommand extends Program implements FindExecutable { * @param query The terms to be searched * @param asyncResultListener The partial result listener */ - public FindCommand(String directory, Query query, AsyncResultListener asyncResultListener) { + public FindCommand(String directory, Query query, + ConcurrentAsyncResultListener asyncResultListener) { super(); this.mDirectory = directory; this.mQueryRegExp = createRegexp(directory, query); this.mAsyncResultListener = asyncResultListener; + if (mAsyncResultListener instanceof ConcurrentAsyncResultListener) { + ((ConcurrentAsyncResultListener) mAsyncResultListener).onRegister(); + } this.mCancelled = false; this.mEnded = false; } @@ -85,27 +90,25 @@ public class FindCommand extends Program implements FindExecutable { this.mAsyncResultListener.onAsyncStart(); } + boolean ready = true; File f = new File(this.mDirectory); if (!f.exists()) { if (isTrace()) { Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ } - if (this.mAsyncResultListener != null) { - this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory)); - } + ready = false; } - if (!f.isDirectory()) { + if (ready && !f.isDirectory()) { if (isTrace()) { Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ } - if (this.mAsyncResultListener != null) { - this.mAsyncResultListener.onException( - new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$ - } + ready = false; } // Find the data - findRecursive(f); + if (ready) { + findRecursive(f); + } if (this.mAsyncResultListener != null) { this.mAsyncResultListener.onAsyncEnd(this.mCancelled); @@ -156,7 +159,8 @@ public class FindCommand extends Program implements FindExecutable { // Check if the process was cancelled try { synchronized (this.mSync) { - if (this.mCancelled || this.mEnded) { + if (this.mCancelled || this.mEnded || (mAsyncResultListener != null + && mAsyncResultListener.isCancelled())) { this.mSync.notify(); break; } diff --git a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java index 94856ba9..a76c9fef 100644 --- a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java +++ b/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java @@ -22,6 +22,7 @@ import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; import com.cyanogenmod.filemanager.commands.ChecksumExecutable; import com.cyanogenmod.filemanager.commands.CompressExecutable; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; import com.cyanogenmod.filemanager.commands.CopyExecutable; import com.cyanogenmod.filemanager.commands.CreateDirExecutable; import com.cyanogenmod.filemanager.commands.CreateFileExecutable; @@ -180,7 +181,7 @@ public class JavaExecutableCreator implements ExecutableCreator { */ @Override public FindExecutable createFindExecutable( - String directory, Query query, AsyncResultListener asyncResultListener) + String directory, Query query, ConcurrentAsyncResultListener asyncResultListener) throws CommandNotFoundException { return new FindCommand(directory, query, asyncResultListener); } diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java new file mode 100644 index 00000000..39e623bb --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/ChecksumCommand.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.android.internal.util.HexDump; +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ChecksumExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; + +import de.schlichtherle.truezip.file.TFile; +import de.schlichtherle.truezip.file.TFileInputStream; + +import java.io.File; +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.Locale; + +/** + * A class for calculate MD5 and SHA-1 checksums of a file system object.<br /> + * <br /> + * Partial results are returned in order (MD5 -> SHA1) + */ +public class ChecksumCommand extends Program implements ChecksumExecutable { + + private static final String TAG = "ChecksumCommand"; //$NON-NLS-1$ + + private final File mSrc; + private final String[] mChecksums; + private final AsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private final Object mSync = new Object(); + + /** + * Constructor of <code>ChecksumCommand</code>. + * + * @param console The current console + * @param src The source file + * @param asyncResultListener The partial result listener + */ + public ChecksumCommand(SecureConsole console, String src, + AsyncResultListener asyncResultListener) { + super(console); + this.mAsyncResultListener = asyncResultListener; + this.mChecksums = new String[]{null, null}; + this.mSrc = new File(src); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + + if (isTrace()) { + Log.v(TAG, + String.format("Calculating checksums of file %s", this.mSrc)); //$NON-NLS-1$ + } + + // Check that the file exists + TFile f = getConsole().buildRealFile(this.mSrc.getAbsolutePath()); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc.getAbsolutePath()); + } + + CHECKSUMS checksum = CHECKSUMS.MD5; + try { + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + // Calculate digests + calculateDigest(checksum, f); + checksum = CHECKSUMS.SHA1; + calculateDigest(checksum, f); + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(false); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + + } catch (InterruptedException ie) { + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(true); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(143); + } + + if (isTrace()) { + Log.v(TAG, "Result: CANCELLED"); //$NON-NLS-1$ + } + + } catch (Exception e) { + Log.e(TAG, + String.format( + "Fail to calculate %s checksum of file %s", //$NON-NLS-1$ + checksum.name(), + this.mSrc.getAbsolutePath()), + e); + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(e); + } + if (isTrace()) { + Log.v(TAG, "Result: FAIL"); //$NON-NLS-1$ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + this.mCancelled = true; + } + } catch (Throwable _throw) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + return cancel(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getResult() { + return this.mChecksums; + } + + /** + * {@inheritDoc} + */ + @Override + public String getChecksum(CHECKSUMS checksum) { + return getResult()[checksum.ordinal()]; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } + + /** + * Method that calculate a digest of the file for the source file + * + * @param type The type of digest to obtain + * @pa + * @throws InterruptedException If the operation was cancelled + * @throws Exception If an error occurs + */ + private void calculateDigest(CHECKSUMS type, TFile file) + throws InterruptedException, Exception { + + InputStream is = null; + try { + MessageDigest md = MessageDigest.getInstance(type.name()); + is = new TFileInputStream(file); + + // Start digesting + byte[] data = new byte[getBufferSize()]; + int read = 0; + while ((read = is.read(data, 0, getBufferSize())) != -1) { + checkCancelled(); + md.update(data, 0, read); + } + checkCancelled(); + + // Finally digest + this.mChecksums[type.ordinal()] = + HexDump.toHexString(md.digest()).toLowerCase(Locale.ROOT); + checkCancelled(); + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onPartialResult(this.mChecksums[type.ordinal()]); + } + + } finally { + try { + if (is != null) { + is.close(); + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + + /** + * Checks if the operation was cancelled + * + * @throws InterruptedException If the operation was cancelled + */ + private void checkCancelled() throws InterruptedException { + synchronized (this.mSync) { + if (this.mCancelled) { + throw new InterruptedException(); + } + } + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java new file mode 100644 index 00000000..3b7d8563 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/CopyCommand.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.CopyExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.MountPoint; + +import de.schlichtherle.truezip.file.TFile; + +import java.io.IOException; + + +/** + * A class for copy a file or directory. + */ +public class CopyCommand extends Program implements CopyExecutable { + + private static final String TAG = "CopyCommand"; //$NON-NLS-1$ + + private final String mSrc; + private final String mDst; + + /** + * Constructor of <code>CopyCommand</code>. + * + * @param console The current console + * @param src The name of the file or directory to be copied + * @param dst The name of the file or directory in which copy the source file or directory + */ + public CopyCommand(SecureConsole console, String src, String dst) { + super(console); + this.mSrc = src; + this.mDst = dst; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresSync() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Moving from %s to %s", //$NON-NLS-1$ + this.mSrc, this.mDst)); + } + + TFile s = getConsole().buildRealFile(this.mSrc); + TFile d = getConsole().buildRealFile(this.mDst); + if (!s.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc); + } + + try { + TFile.cp_r(s, d, SecureConsole.DETECTOR, SecureConsole.DETECTOR); + } catch (IOException ex) { + throw new ExecutionException("Failed to copy file or directory", ex); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return null; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java new file mode 100644 index 00000000..8b87c464 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/CreateDirCommand.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.CreateDirExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.MountPoint; + +import de.schlichtherle.truezip.file.TFile; + + +/** + * A class for create a directory. + */ +public class CreateDirCommand extends Program implements CreateDirExecutable { + + private static final String TAG = "CreateDirCommand"; //$NON-NLS-1$ + + private final String mPath; + + /** + * Constructor of <code>CreateDirCommand</code>. + * + * @param console The current console + * @param path The name of the new directory + */ + public CreateDirCommand(SecureConsole console, String path) { + super(console); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresSync() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Creating directory: %s", this.mPath)); //$NON-NLS-1$ + } + + TFile f = getConsole().buildRealFile(this.mPath); + // Check that if the path exist, it need to be a directory. Otherwise something is + // wrong + if (f.exists() && !f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$ + } + + // Only create the directory if the folder not exists. Otherwise mkdir will return false + if (!f.exists()) { + if (!f.mkdir()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. IOException"); //$NON-NLS-1$ + } + throw new ExecutionException("Failed to create directory"); + } + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return null; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java new file mode 100644 index 00000000..dfac1be6 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/CreateFileCommand.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.CreateFileExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.MountPoint; + +import de.schlichtherle.truezip.file.TFile; + +import java.io.IOException; + + +/** + * A class for create a file. + */ +public class CreateFileCommand extends Program implements CreateFileExecutable { + + private static final String TAG = "CreateFileCommand"; //$NON-NLS-1$ + + + private final String mPath; + + /** + * Constructor of <code>CreateFileCommand</code>. + * + * @param console The current console + * @param path The name of the new file + */ + public CreateFileCommand(SecureConsole console, String path) { + super(console); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresSync() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + + if (isTrace()) { + Log.v(TAG, + String.format("Creating file: %s", this.mPath)); //$NON-NLS-1$ + } + + TFile f = getConsole().buildRealFile(this.mPath); + // Check that if the path exist, it need to be a file. Otherwise + // something is wrong + if (f.exists() && !f.isFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a file"); //$NON-NLS-1$ + } + + // Only create the file if the file not exists. Otherwise createNewFile + // will return false + if (!f.exists()) { + try { + if (!f.createNewFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("Failed to create file"); + } + } catch (IOException ex) { + throw new ExecutionException("Failed to create file", ex); + } + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return null; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java new file mode 100644 index 00000000..47ca0947 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/DeleteDirCommand.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.DeleteDirExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.MountPoint; +import com.cyanogenmod.filemanager.util.FileHelper; + +import de.schlichtherle.truezip.file.TFile; + + +/** + * A class for delete a folder. + */ +public class DeleteDirCommand extends Program implements DeleteDirExecutable { + + private static final String TAG = "DeleteDirCommand"; //$NON-NLS-1$ + + private final String mPath; + + /** + * Constructor of <code>DeleteDirCommand</code>. + * + * @param console The current console + * @param path The name of the new folder + */ + public DeleteDirCommand(SecureConsole console, String path) { + super(console); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresSync() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Deleting directory: %s", this.mPath)); //$NON-NLS-1$ + } + + TFile f = getConsole().buildRealFile(this.mPath); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mPath); + } + + // Check that if the path exist, it need to be a folder. Otherwise something is + // wrong + if (f.exists() && !f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$ + } + + // Delete the file + if (!FileHelper.deleteFolder(f)) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("Failed to delete directory"); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return null; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java new file mode 100644 index 00000000..a8e6e077 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/DeleteFileCommand.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.DeleteFileExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.MountPoint; + +import de.schlichtherle.truezip.file.TFile; + +import java.io.IOException; + + +/** + * A class for delete a file. + */ +public class DeleteFileCommand extends Program implements DeleteFileExecutable { + + private static final String TAG = "DeleteFileCommand"; //$NON-NLS-1$ + + private final String mPath; + + /** + * Constructor of <code>DeleteFileCommand</code>. + * + * @param console The current console + * @param path The name of the new file + */ + public DeleteFileCommand(SecureConsole console, String path) { + super(console); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresSync() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Deleting file: %s", this.mPath)); //$NON-NLS-1$ + } + + TFile f = getConsole().buildRealFile(this.mPath); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mPath); + } + + // Check that if the path exist, it need to be a file. Otherwise something is + // wrong + if (f.exists() && !f.isFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a file"); //$NON-NLS-1$ + } + + // Delete the file + try { + TFile.rm(f); + } catch (IOException ex) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. IOException"); //$NON-NLS-1$ + } + throw new ExecutionException("Failed to delete file", ex); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return null; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java new file mode 100644 index 00000000..33d0ace0 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/FindCommand.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; +import com.cyanogenmod.filemanager.commands.FindExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.model.Query; +import com.cyanogenmod.filemanager.util.FileHelper; +import com.cyanogenmod.filemanager.util.SearchHelper; + +import de.schlichtherle.truezip.file.TFile; + +import java.util.Arrays; + +/** + * A class for search files. + */ +public class FindCommand extends Program implements FindExecutable { + + private static final String TAG = "FindCommand"; //$NON-NLS-1$ + + private final String mDirectory; + private final String[] mQueryRegExp; + private final ConcurrentAsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private boolean mEnded; + private final Object mSync = new Object(); + + /** + * Constructor of <code>FindCommand</code>. + * + * @param console The secure console + * @param directory The absolute directory where start the search + * @param query The terms to be searched + * @param asyncResultListener The partial result listener + */ + public FindCommand(SecureConsole console, String directory, Query query, + ConcurrentAsyncResultListener asyncResultListener) { + super(console); + // This command should start the search in the root directory or in a descendent folder + if (!getConsole().isSecureStorageResource(directory)) { + this.mDirectory = getConsole().getVirtualMountPoint().getAbsolutePath(); + } else { + this.mDirectory = directory; + } + this.mQueryRegExp = createRegexp(directory, query); + this.mAsyncResultListener = asyncResultListener; + if (mAsyncResultListener instanceof ConcurrentAsyncResultListener) { + ((ConcurrentAsyncResultListener) mAsyncResultListener).onRegister(); + } + this.mCancelled = false; + this.mEnded = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Finding in %s the query %s", //$NON-NLS-1$ + this.mDirectory, Arrays.toString(this.mQueryRegExp))); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + TFile f = getConsole().buildRealFile(mDirectory); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory)); + } + } + if (!f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException( + new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$ + } + } + + // Find the data + findRecursive(f); + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * Method that search files recursively + * + * @param folder The folder where to start the search + */ + private void findRecursive(TFile folder) { + // Obtains the files and folders of the folders + TFile[] files = folder.listFiles(); + if (files != null) { + int cc = files.length; + for (int i = 0; i < cc; i++) { + if (files[i].isDirectory()) { + findRecursive(files[i]); + } + + // Check if the file or folder matches the regexp + try { + int ccc = this.mQueryRegExp.length; + for (int j = 0; j < ccc; j++) { + if (files[i].getName().matches(this.mQueryRegExp[j])) { + FileSystemObject fso = FileHelper.createFileSystemObject(files[i]); + if (fso != null) { + // Convert to virtual + fso.setParent(getConsole().buildVirtualPath( + files[i].getParentFile())); + fso.setSecure(true); + + if (isTrace()) { + Log.v(TAG, String.valueOf(fso)); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onPartialResult(fso); + } + } + } + } + } catch (Exception e) {/**NON-BLOCK**/} + + // Check if the process was cancelled + try { + synchronized (this.mSync) { + if (this.mCancelled || this.mEnded || (mAsyncResultListener != null + && mAsyncResultListener.isCancelled())) { + this.mSync.notify(); + break; + } + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + this.mCancelled = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + try { + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. secure console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. secure console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } + + /** + * Method that create the regexp of this command, using the directory and + * arguments and creating the regular expressions of the search. + * + * @param directory The directory where to search + * @param query The query make for user + * @return String[] The regexp for filtering files + */ + private static String[] createRegexp(String directory, Query query) { + String[] args = new String[query.getSlotsCount()]; + int cc = query.getSlotsCount(); + for (int i = 0; i < cc; i++) { + args[i] = SearchHelper.toIgnoreCaseRegExp(query.getSlot(i), true); + } + return args; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java new file mode 100644 index 00000000..5049a01a --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/FolderUsageCommand.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.FolderUsageExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.FolderUsage; +import com.cyanogenmod.filemanager.util.MimeTypeHelper; +import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory; + +import de.schlichtherle.truezip.file.TFile; + +/** + * A class for retrieve the disk usage of a folder. + */ +public class FolderUsageCommand extends Program implements FolderUsageExecutable { + + private static final String TAG = "FolderUsage"; //$NON-NLS-1$ + + private final String mDirectory; + private final AsyncResultListener mAsyncResultListener; + private final FolderUsage mFolderUsage; + + private boolean mCancelled; + private boolean mEnded; + private final Object mSync = new Object(); + + /** + * Constructor of <code>FolderUsageCommand</code>. + * + * @param console The secure console + * @param directory The absolute directory to compute + * @param asyncResultListener The partial result listener + */ + public FolderUsageCommand(SecureConsole console, String directory, + AsyncResultListener asyncResultListener) { + super(console); + this.mDirectory = directory; + this.mAsyncResultListener = asyncResultListener; + this.mFolderUsage = new FolderUsage(directory); + this.mCancelled = false; + this.mEnded = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public FolderUsage getFolderUsage() { + return this.mFolderUsage; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Computing folder usage for folder %s", //$NON-NLS-1$ + this.mDirectory)); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + TFile f = getConsole().buildRealFile(mDirectory); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory)); + } + } + if (!f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException( + new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$ + } + } + + // Compute data recursively + computeRecursive(f); + + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.notify(); + } + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * Method that computes the folder usage recursively + * + * @param folder The folder where to start the computation + */ + private void computeRecursive(TFile folder) { + // Obtains the files and folders of the folders + try { + TFile[] files = folder.listFiles(); + int c = 0; + if (files != null) { + int cc = files.length; + for (int i = 0; i < cc; i++) { + if (files[i].isDirectory()) { + this.mFolderUsage.addFolder(); + computeRecursive(files[i]); + } else { + this.mFolderUsage.addFile(); + // Compute statistics and size + MimeTypeCategory category = + MimeTypeHelper.getCategory(null, files[i]); + this.mFolderUsage.addFileToCategory(category); + this.mFolderUsage.addSize(files[i].length()); + } + + // Partial notification + if (c % 5 == 0) { + //If a listener is defined, then send the partial result + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(this.mFolderUsage); + } + } + + // Check if the process was cancelled + try { + synchronized (this.mSync) { + if (this.mCancelled || this.mEnded) { + this.mSync.notify(); + break; + } + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + } finally { + //If a listener is defined, then send the partial result + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(this.mFolderUsage); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + if (this.mEnded || this.mCancelled) { + this.mCancelled = true; + return true; + } + this.mCancelled = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + try { + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. secure console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. secure console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java new file mode 100644 index 00000000..3fc95465 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/ListCommand.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.ListExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.Directory; +import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.model.ParentDirectory; +import com.cyanogenmod.filemanager.util.FileHelper; + +import de.schlichtherle.truezip.file.TFile; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + + +/** + * A class for list information about files and directories. + */ +public class ListCommand extends Program implements ListExecutable { + + private static final String TAG = "ListCommand"; //$NON-NLS-1$ + + private final String mSrc; + private final LIST_MODE mMode; + private final List<FileSystemObject> mFiles; + + /** + * Constructor of <code>ListCommand</code>. List mode. + * + * @param src The file system object to be listed + * @param mode The mode of listing + */ + public ListCommand(SecureConsole console, String src, LIST_MODE mode) { + super(console); + this.mSrc = src; + this.mMode = mode; + this.mFiles = new ArrayList<FileSystemObject>(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresOpen() { + if (this.mMode.compareTo(LIST_MODE.FILEINFO) == 0) { + return !getConsole().getVirtualMountPoint().equals(new File(mSrc)); + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public List<FileSystemObject> getResult() { + return this.mFiles; + } + + /** + * Method that returns a single result of the program invocation. + * Only must be called within a <code>FILEINFO</code> mode listing. + * + * @return FileSystemObject The file system object reference + */ + public FileSystemObject getSingleResult() { + return this.mFiles.get(0); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("deprecation") + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Listing %s. Mode: %s", //$NON-NLS-1$ + this.mSrc, this.mMode)); + } + + TFile f = getConsole().buildRealFile(mSrc); + boolean isSecureStorage = SecureConsole.isSecureStorageDir(f); + File javaFile = f.getFile(); + if (!isSecureStorage && !f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc); + } + if (this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) { + // List files in directory + TFile[] files = f.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + FileSystemObject fso = FileHelper.createFileSystemObject(files[i]); + if (fso != null) { + // Convert to virtual + fso.setParent(getConsole().buildVirtualPath(files[i].getParentFile())); + fso.setSecure(true); + + if (isTrace()) { + Log.v(TAG, String.valueOf(fso)); + } + this.mFiles.add(fso); + } + } + } + + //Now if not is the root directory, add the parent directory + if (this.mSrc.compareTo(FileHelper.ROOT_DIRECTORY) != 0 && + this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) { + this.mFiles.add(0, new ParentDirectory(new File(this.mSrc).getParent())); + } + } else { + // Build the source file information + FileSystemObject fso = FileHelper.createFileSystemObject( + isSecureStorage ? javaFile : f); + if (fso != null) { + // Convert to virtual + if (isSecureStorage) { + File virtualMountPoint = getConsole().getVirtualMountPoint(); + fso = new Directory( + virtualMountPoint.getName(), + getConsole().getVirtualMountPoint().getParent(), + fso.getUser(), fso.getGroup(), fso.getPermissions(), + fso.getLastAccessedTime(), + fso.getLastModifiedTime(), + fso.getLastChangedTime()); + fso.setSecure(true); + } else { + fso.setParent(getConsole().buildVirtualPath(f.getParentFile())); + } + fso.setSecure(true); + if (isTrace()) { + Log.v(TAG, String.valueOf(fso)); + } + this.mFiles.add(fso); + } + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java new file mode 100644 index 00000000..3cd9748d --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/MoveCommand.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.MoveExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.MountPoint; +import com.cyanogenmod.filemanager.util.FileHelper; + +import java.io.IOException; + +import de.schlichtherle.truezip.file.TFile; + + + +/** + * A class for move a file or directory. + */ +public class MoveCommand extends Program implements MoveExecutable { + + private static final String TAG = "MoveCommand"; //$NON-NLS-1$ + + private final String mSrc; + private final String mDst; + + /** + * Constructor of <code>MoveCommand</code>. + * + * @param console The current console + * @param src The name of the file or directory to be moved + * @param dst The name of the file or directory in which move the source file or directory + */ + public MoveCommand(SecureConsole console, String src, String dst) { + super(console); + this.mSrc = src; + this.mDst = dst; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresSync() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Creating from %s to %s", this.mSrc, this.mDst)); //$NON-NLS-1$ + } + + TFile s = getConsole().buildRealFile(this.mSrc); + TFile d = getConsole().buildRealFile(this.mDst); + if (!s.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc); + } + + //Move or copy recursively + if (d.exists()) { + try { + TFile.cp_r(s, d, SecureConsole.DETECTOR, SecureConsole.DETECTOR); + } catch (IOException ex) { + throw new ExecutionException("Failed to move file or directory", ex); + } + if (!FileHelper.deleteFolder(s)) { + if (isTrace()) { + Log.v(TAG, "Result: OK. WARNING. Source not deleted."); //$NON-NLS-1$ + } + } + } else { + // Use rename. We are not cross filesystem with this console, so this operation + // should be safe + try { + TFile.mv(s, d, SecureConsole.DETECTOR); + } catch (IOException ex) { + throw new ExecutionException("Failed to rename file or directory", ex); + } + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return null; + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java new file mode 100644 index 00000000..7789120f --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/ParentDirCommand.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.ParentDirExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; + +import de.schlichtherle.truezip.file.TFile; + + +/** + * A class for returns the parent directory. + */ +public class ParentDirCommand extends Program implements ParentDirExecutable { + + private static final String TAG = "ParentDirCommand"; //$NON-NLS-1$ + + private final String mSrc; + private String mParentDir; + + /** + * Constructor of <code>ParentDirCommand</code>. + * + * @param console The current console + * @param src The source file + */ + public ParentDirCommand(SecureConsole console, String src) { + super(console); + this.mSrc = src; + } + + /** + * {@inheritDoc} + */ + @Override + public String getResult() { + return this.mParentDir; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Getting parent directory of %s", //$NON-NLS-1$ + this.mSrc)); + } + + // Build the source file information + TFile f = getConsole().buildRealFile(mSrc).getParentFile(); + boolean isSecureStorage = SecureConsole.isSecureStorageDir(f); + if (isSecureStorage) { + this.mParentDir = getConsole().getVirtualMountPoint().getAbsolutePath(); + } else { + this.mParentDir = getConsole().buildVirtualPath(f); + } + + if (isTrace()) { + Log.v(TAG, + String.format("Parent directory: %S", //$NON-NLS-1$ + this.mParentDir)); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/Program.java b/src/com/cyanogenmod/filemanager/commands/secure/Program.java new file mode 100644 index 00000000..d25cf18a --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/Program.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import com.cyanogenmod.filemanager.commands.Executable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; + + +/** + * An abstract base class for all secure executables. + */ +public abstract class Program implements Executable { + + private SecureConsole mConsole; + private boolean mTrace; + private int mBufferSize; + + /** + * Constructor of <code>Program</code> + */ + public Program(SecureConsole console) { + super(); + mConsole = console; + } + + /** + * Method that return if the command has to trace his operations + * + * @return boolean If the command has to trace + */ + public boolean isTrace() { + return this.mTrace; + } + + /** + * Method that sets if the command has to trace his operations + * + * @param trace If the command has to trace + */ + public void setTrace(boolean trace) { + this.mTrace = trace; + } + + /** + * Method that return the buffer size of the program + * + * @return int The buffer size of the program + */ + public int getBufferSize() { + return this.mBufferSize; + } + + /** + * Method that sets the buffer size of the program + * + * @param bufferSize The buffer size of the program + */ + public void setBufferSize(int bufferSize) { + this.mBufferSize = bufferSize; + } + + /** + * Method that returns the current console of the program + * + * @return SecureConsole The current console + */ + public SecureConsole getConsole() { + return mConsole; + } + + /** + * Method that returns if this program uses an asynchronous model. <code>false</code> + * by default. + * + * @return boolean If this program uses an asynchronous model + */ + @SuppressWarnings("static-method") + public boolean isAsynchronous() { + return false; + } + + /** + * Method that returns if the program requires a sync of the underlying storage + * + * @return boolean if the program requires a sync operation + */ + public boolean requiresSync() { + return false; + } + + /** + * Method that returns if the program requires that the file system is mounted + * + * @return boolean If the program requires that the file system is mounted + */ + public boolean requiresOpen() { + return true; + } + + /** + * Method that executes the program + * + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws ExecutionException If the operation returns a invalid exit code + */ + public abstract void execute() + throws NoSuchFileOrDirectory, ExecutionException; + +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java new file mode 100644 index 00000000..851d10e5 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/ReadCommand.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ReadExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; + +import de.schlichtherle.truezip.file.TFile; +import de.schlichtherle.truezip.file.TFileInputStream; + +import java.io.BufferedInputStream; + +/** + * A class for read a file. + */ +public class ReadCommand extends Program implements ReadExecutable { + + private static final String TAG = "ReadCommand"; //$NON-NLS-1$ + + private final String mFile; + private final AsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private boolean mEnded; + private final Object mSync = new Object(); + + /** + * Constructor of <code>ExecCommand</code>. + * + * @param console The current console + * @param file The file to read + * @param asyncResultListener The partial result listener + */ + public ReadCommand(SecureConsole console, String file, + AsyncResultListener asyncResultListener) { + super(console); + this.mFile = file; + this.mAsyncResultListener = asyncResultListener; + this.mCancelled = false; + this.mEnded = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Reading file %s", this.mFile)); //$NON-NLS-1$ + + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + TFile f = getConsole().buildRealFile(mFile); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mFile)); + } + } + if (!f.isFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException( + new ExecutionException("path exists but it's not a file")); //$NON-NLS-1$ + } + } + + // Read the file + read(f); + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * Method that read the file + * + * @param file The file to read + */ + private void read(TFile file) { + // Read the file + BufferedInputStream bis = null; + try { + bis = new BufferedInputStream(new TFileInputStream(file), getBufferSize()); + int read = 0; + byte[] data = new byte[getBufferSize()]; + while ((read = bis.read(data, 0, getBufferSize())) != -1) { + if (this.mAsyncResultListener != null) { + byte[] readData = new byte[read]; + System.arraycopy(data, 0, readData, 0, read); + this.mAsyncResultListener.onPartialResult(readData); + + // Check if the process was cancelled + try { + synchronized (this.mSync) { + if (this.mCancelled || this.mEnded) { + this.mSync.notify(); + break; + } + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + + } catch (Exception e) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new ExecutionException( + "failed to read file", e)); + } + + } finally { + try { + if (bis != null) { + bis.close(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + this.mCancelled = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + try { + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java new file mode 100644 index 00000000..530f26a2 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableCreator.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; +import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; +import com.cyanogenmod.filemanager.commands.ChecksumExecutable; +import com.cyanogenmod.filemanager.commands.CompressExecutable; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; +import com.cyanogenmod.filemanager.commands.CopyExecutable; +import com.cyanogenmod.filemanager.commands.CreateDirExecutable; +import com.cyanogenmod.filemanager.commands.CreateFileExecutable; +import com.cyanogenmod.filemanager.commands.DeleteDirExecutable; +import com.cyanogenmod.filemanager.commands.DeleteFileExecutable; +import com.cyanogenmod.filemanager.commands.DiskUsageExecutable; +import com.cyanogenmod.filemanager.commands.EchoExecutable; +import com.cyanogenmod.filemanager.commands.ExecExecutable; +import com.cyanogenmod.filemanager.commands.ExecutableCreator; +import com.cyanogenmod.filemanager.commands.FindExecutable; +import com.cyanogenmod.filemanager.commands.FolderUsageExecutable; +import com.cyanogenmod.filemanager.commands.GroupsExecutable; +import com.cyanogenmod.filemanager.commands.IdentityExecutable; +import com.cyanogenmod.filemanager.commands.LinkExecutable; +import com.cyanogenmod.filemanager.commands.ListExecutable; +import com.cyanogenmod.filemanager.commands.MountExecutable; +import com.cyanogenmod.filemanager.commands.MountPointInfoExecutable; +import com.cyanogenmod.filemanager.commands.MoveExecutable; +import com.cyanogenmod.filemanager.commands.ParentDirExecutable; +import com.cyanogenmod.filemanager.commands.ProcessIdExecutable; +import com.cyanogenmod.filemanager.commands.QuickFolderSearchExecutable; +import com.cyanogenmod.filemanager.commands.ReadExecutable; +import com.cyanogenmod.filemanager.commands.ResolveLinkExecutable; +import com.cyanogenmod.filemanager.commands.SIGNAL; +import com.cyanogenmod.filemanager.commands.SendSignalExecutable; +import com.cyanogenmod.filemanager.commands.UncompressExecutable; +import com.cyanogenmod.filemanager.commands.WriteExecutable; +import com.cyanogenmod.filemanager.commands.ListExecutable.LIST_MODE; +import com.cyanogenmod.filemanager.console.CommandNotFoundException; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.Group; +import com.cyanogenmod.filemanager.model.MountPoint; +import com.cyanogenmod.filemanager.model.Permissions; +import com.cyanogenmod.filemanager.model.Query; +import com.cyanogenmod.filemanager.model.User; +import com.cyanogenmod.filemanager.preferences.CompressionMode; + +/** + * A class for create shell {@link "Executable"} objects. + */ +public class SecureExecutableCreator implements ExecutableCreator { + + private final SecureConsole mConsole; + + /** + * Constructor of <code>SecureExecutableCreator</code>. + * + * @param console A shell console that use for create objects + */ + SecureExecutableCreator(SecureConsole console) { + super(); + this.mConsole = console; + } + + /** + * {@inheritDoc} + */ + @Override + public ChangeOwnerExecutable createChangeOwnerExecutable( + String fso, User newUser, Group newGroup) throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public ChangePermissionsExecutable createChangePermissionsExecutable( + String fso, Permissions newPermissions) throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public CopyExecutable createCopyExecutable(String src, String dst) + throws CommandNotFoundException { + return new CopyCommand(mConsole, src, dst); + } + + /** + * {@inheritDoc} + */ + @Override + public CreateDirExecutable createCreateDirectoryExecutable(String dir) + throws CommandNotFoundException { + return new CreateDirCommand(mConsole, dir); + } + + /** + * {@inheritDoc} + */ + @Override + public CreateFileExecutable createCreateFileExecutable(String file) + throws CommandNotFoundException { + return new CreateFileCommand(mConsole, file); + } + + /** + * {@inheritDoc} + */ + @Override + public DeleteDirExecutable createDeleteDirExecutable(String dir) + throws CommandNotFoundException { + return new DeleteDirCommand(mConsole, dir); + } + + /** + * {@inheritDoc} + */ + @Override + public DeleteFileExecutable createDeleteFileExecutable(String file) + throws CommandNotFoundException { + return new DeleteFileCommand(mConsole, file); + } + + /** + * {@inheritDoc} + */ + @Override + public DiskUsageExecutable createDiskUsageExecutable() throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public DiskUsageExecutable createDiskUsageExecutable(String dir) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public EchoExecutable createEchoExecutable(String msg) throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public ExecExecutable createExecExecutable( + String cmd, AsyncResultListener asyncResultListener) throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public FindExecutable createFindExecutable( + String directory, Query query, ConcurrentAsyncResultListener asyncResultListener) + throws CommandNotFoundException { + return new FindCommand(mConsole, directory, query, asyncResultListener); + } + + /** + * {@inheritDoc} + */ + @Override + public FolderUsageExecutable createFolderUsageExecutable( + String directory, AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + return new FolderUsageCommand(mConsole, directory, asyncResultListener); + } + + /** + * {@inheritDoc} + */ + @Override + public GroupsExecutable createGroupsExecutable() throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityExecutable createIdentityExecutable() throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public LinkExecutable createLinkExecutable(String src, String link) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + + /** + * {@inheritDoc} + */ + @Override + public ListExecutable createListExecutable(String src) + throws CommandNotFoundException { + return new ListCommand(mConsole, src, LIST_MODE.DIRECTORY); + } + + /** + * {@inheritDoc} + */ + @Override + public ListExecutable createFileInfoExecutable(String src, boolean followSymlinks) + throws CommandNotFoundException { + return new ListCommand(mConsole, src, LIST_MODE.FILEINFO); + } + + /** + * {@inheritDoc} + */ + @Override + public MountExecutable createMountExecutable(MountPoint mp, boolean rw) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public MountPointInfoExecutable createMountPointInfoExecutable() + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public MoveExecutable createMoveExecutable(String src, String dst) + throws CommandNotFoundException { + return new MoveCommand(mConsole, src, dst); + } + + /** + * {@inheritDoc} + */ + @Override + public ParentDirExecutable createParentDirExecutable(String fso) + throws CommandNotFoundException { + return new ParentDirCommand(mConsole, fso); + } + + /** + * {@inheritDoc} + */ + @Override + public ProcessIdExecutable createShellProcessIdExecutable() throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public ProcessIdExecutable createProcessIdExecutable(int pid) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public ProcessIdExecutable createProcessIdExecutable(int pid, String processName) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public QuickFolderSearchExecutable createQuickFolderSearchExecutable(String regexp) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public ReadExecutable createReadExecutable( + String file, AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + return new ReadCommand(mConsole, file, asyncResultListener); + } + + /** + * {@inheritDoc} + */ + @Override + public ResolveLinkExecutable createResolveLinkExecutable(String fso) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public SendSignalExecutable createSendSignalExecutable(int process, SIGNAL signal) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public SendSignalExecutable createKillExecutable(int process) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public WriteExecutable createWriteExecutable( + String file, AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + return new WriteCommand(mConsole, file, asyncResultListener); + } + + /** + * {@inheritDoc} + */ + @Override + public CompressExecutable createCompressExecutable( + CompressionMode mode, String dst, String[] src, + AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public CompressExecutable createCompressExecutable( + CompressionMode mode, String src, + AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public UncompressExecutable createUncompressExecutable( + String src, String dst, + AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public ChecksumExecutable createChecksumExecutable( + String src, AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + return new ChecksumCommand(mConsole, src, asyncResultListener); + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java new file mode 100644 index 00000000..3eaa44e9 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/SecureExecutableFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import com.cyanogenmod.filemanager.commands.ExecutableCreator; +import com.cyanogenmod.filemanager.commands.ExecutableFactory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +/** + * A class that represents a factory for creating java {@link "Executable"} objects. + */ +public class SecureExecutableFactory extends ExecutableFactory { + + private final SecureConsole mConsole; + + /** + * Constructor of <code>SecureExecutableFactory</code>. + * + * @param console A secure console that use for create objects + */ + public SecureExecutableFactory(SecureConsole console) { + super(); + this.mConsole = console; + } + + /** + * {@inheritDoc} + */ + @Override + public ExecutableCreator newCreator() { + return new SecureExecutableCreator(this.mConsole); + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java b/src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java new file mode 100644 index 00000000..7d3504b7 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/commands/secure/WriteCommand.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.commands.secure; + +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.WriteExecutable; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; + +import de.schlichtherle.truezip.file.TFile; +import de.schlichtherle.truezip.file.TFileOutputStream; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * A class for write data to disk.<br/> + * <br/> + * User MUST call the {@link #createOutputStream()} to get the output stream where + * write the data.<br/>. When no more exist then user MUST call the onEnd method + * of the asynchronous listener.<br/> + */ +public class WriteCommand extends Program implements WriteExecutable { + + private static final String TAG = "WriteCommand"; //$NON-NLS-1$ + + private final String mFile; + private BufferedOutputStream mBuffer; + private final AsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private final Object mSync = new Object(); + + private static final long TIMEOUT = 1000L; + + private final Object mWriteSync = new Object(); + private boolean mReady; + + /** + * Constructor of <code>WriteCommand</code>. + * + * @param console The current console + * @param file The file where to write the data + * @param asyncResultListener The partial result listener + */ + public WriteCommand(SecureConsole console, String file, + AsyncResultListener asyncResultListener) { + super(console); + this.mFile = file; + this.mAsyncResultListener = asyncResultListener; + this.mCancelled = false; + this.mReady = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public OutputStream createOutputStream() throws IOException { + try { + // Wait until command is ready + synchronized (this.mWriteSync) { + if (!this.mReady) { + try { + this.mWriteSync.wait(TIMEOUT); + } catch (Exception e) {/**NON BLOCK**/} + } + } + TFile f = getConsole().buildRealFile(mFile); + this.mBuffer = new BufferedOutputStream(new TFileOutputStream(f), getBufferSize()); + return this.mBuffer; + } catch (IOException ioEx) { + if (isTrace()) { + Log.e(TAG, "Result: FAILED. IOException", ioEx); //$NON-NLS-1$ + } + throw ioEx; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws NoSuchFileOrDirectory, ExecutionException { + synchronized (this.mSync) { + this.mReady = true; + this.mSync.notify(); + } + + if (isTrace()) { + Log.v(TAG, + String.format("Writing file %s", this.mFile)); //$NON-NLS-1$ + + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + // Wait the finalization + try { + synchronized (this.mSync) { + this.mSync.wait(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + closeBuffer(); + this.mCancelled = true; + try { + synchronized (this.mSync) { + this.mSync.notify(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + closeBuffer(); + try { + synchronized (this.mSync) { + this.mSync.notify(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + return true; + } + + /** + * Method that close the buffer + */ + private void closeBuffer() { + try { + if (this.mBuffer != null) { + this.mBuffer.close(); + } + } catch (Exception ex) {/**NON BLOCK**/} + try { + Thread.yield(); + } catch (Exception ex) {/**NON BLOCK**/} + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java b/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java index 908b6b13..7c0a6f78 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java +++ b/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java @@ -18,6 +18,7 @@ package com.cyanogenmod.filemanager.commands.shell; import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; import com.cyanogenmod.filemanager.commands.SIGNAL; import com.cyanogenmod.filemanager.util.FileHelper; @@ -93,6 +94,9 @@ public abstract class AsyncResultProgram throws InvalidCommandDefinitionException { super(id, prepare, args); this.mAsyncResultListener = asyncResultListener; + if (mAsyncResultListener instanceof ConcurrentAsyncResultListener) { + ((ConcurrentAsyncResultListener) mAsyncResultListener).onRegister(); + } this.mPartialData = Collections.synchronizedList(new ArrayList<String>()); this.mPartialDataType = Collections.synchronizedList(new ArrayList<Byte>()); this.mTempBuffer = new StringBuffer(); diff --git a/src/com/cyanogenmod/filemanager/commands/shell/Program.java b/src/com/cyanogenmod/filemanager/commands/shell/Program.java index b6a736df..4cb8105b 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/Program.java +++ b/src/com/cyanogenmod/filemanager/commands/shell/Program.java @@ -177,7 +177,6 @@ public abstract class Program extends Command implements Executable { * @throws ExecutionException If the another exception is detected in the standard error * @hide */ - @SuppressWarnings("unused") public void checkStdErr(int exitCode, String err) throws InsufficientPermissionsException, NoSuchFileOrDirectory, CommandNotFoundException, ExecutionException { diff --git a/src/com/cyanogenmod/filemanager/commands/shell/Shell.java b/src/com/cyanogenmod/filemanager/commands/shell/Shell.java index 43e43368..1e4f27ec 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/Shell.java +++ b/src/com/cyanogenmod/filemanager/commands/shell/Shell.java @@ -109,7 +109,7 @@ public abstract class Shell extends Command { * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem * @hide */ - @SuppressWarnings({ "static-method", "unused" }) + @SuppressWarnings("static-method") public void checkStdErr(Program program, int exitCode, String err) throws InsufficientPermissionsException, NoSuchFileOrDirectory, CommandNotFoundException, ExecutionException, ReadOnlyFilesystemException { diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java b/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java index ca304f5f..57fafd16 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java +++ b/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java @@ -21,6 +21,7 @@ import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; import com.cyanogenmod.filemanager.commands.ChecksumExecutable; import com.cyanogenmod.filemanager.commands.CompressExecutable; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; import com.cyanogenmod.filemanager.commands.CopyExecutable; import com.cyanogenmod.filemanager.commands.CreateDirExecutable; import com.cyanogenmod.filemanager.commands.CreateFileExecutable; @@ -222,7 +223,7 @@ public class ShellExecutableCreator implements ExecutableCreator { */ @Override public FindExecutable createFindExecutable( - String directory, Query query, AsyncResultListener asyncResultListener) + String directory, Query query, ConcurrentAsyncResultListener asyncResultListener) throws CommandNotFoundException { try { return new FindCommand(directory, query, asyncResultListener); diff --git a/src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java b/src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java new file mode 100644 index 00000000..795111e9 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/AuthenticationFailedException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console; + +import java.io.IOException; + +/** + * An exception that indicates that the operation failed because an authentication failure + */ +public class AuthenticationFailedException extends IOException { + private static final long serialVersionUID = -2199496556437722726L; + + /** + * Constructor of <code>AuthenticationFailedException</code>. + * + * @param msg The associated message + */ + public AuthenticationFailedException(String msg) { + super(msg); + } + +} diff --git a/src/com/cyanogenmod/filemanager/console/CancelledOperationException.java b/src/com/cyanogenmod/filemanager/console/CancelledOperationException.java new file mode 100644 index 00000000..e19d0dc3 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/CancelledOperationException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console; + +import java.io.IOException; + +/** + * An exception that indicates that the operation was cancelled + */ +public class CancelledOperationException extends IOException { + private static final long serialVersionUID = 2999554355110192173L; + + /** + * Constructor of <code>CancelledOperationException</code>. + */ + public CancelledOperationException() { + super(); + } + +} diff --git a/src/com/cyanogenmod/filemanager/console/Console.java b/src/com/cyanogenmod/filemanager/console/Console.java index ba28db55..6404431f 100644 --- a/src/com/cyanogenmod/filemanager/console/Console.java +++ b/src/com/cyanogenmod/filemanager/console/Console.java @@ -15,6 +15,8 @@ */ package com.cyanogenmod.filemanager.console; +import android.content.Context; + import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; import com.cyanogenmod.filemanager.commands.Executable; import com.cyanogenmod.filemanager.commands.ExecutableFactory; @@ -46,7 +48,7 @@ public abstract class Console * * @return boolean If the console has to trace */ - public boolean isTrace() { + public final boolean isTrace() { return this.mTrace; } @@ -111,6 +113,7 @@ public abstract class Console * Method for execute a command in the operating system layer. * * @param executable The executable command to be executed + * @param ctx The current context * @throws ConsoleAllocException If the console is not allocated * @throws InsufficientPermissionsException If an operation requires elevated permissions * @throws NoSuchFileOrDirectory If the file or directory was not found @@ -118,10 +121,14 @@ public abstract class Console * @throws CommandNotFoundException If the executable program was not found * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled + * @throws AuthenticationFailedException If the operation failed because an + * authentication failure + * @throws AuthenticationFailedException */ - public abstract void execute(final Executable executable) + public abstract void execute(final Executable executable, final Context ctx) throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, OperationTimeoutException, ExecutionException, CommandNotFoundException, - ReadOnlyFilesystemException; + ReadOnlyFilesystemException, CancelledOperationException, AuthenticationFailedException; } diff --git a/src/com/cyanogenmod/filemanager/console/VirtualConsole.java b/src/com/cyanogenmod/filemanager/console/VirtualConsole.java new file mode 100644 index 00000000..8512bc77 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/VirtualConsole.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 20124 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console; + +import android.content.Context; +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.SIGNAL; +import com.cyanogenmod.filemanager.console.Console; +import com.cyanogenmod.filemanager.console.ConsoleAllocException; +import com.cyanogenmod.filemanager.model.Identity; +import com.cyanogenmod.filemanager.util.AIDHelper; + +/** + * An abstract base class for all the virtual {@link Console}. + */ +public abstract class VirtualConsole extends Console { + + public static final String TAG = "VirtualConsole"; + + private boolean mActive; + private final Context mCtx; + private final Identity mIdentity; + + /** + * Constructor of <code>VirtualConsole</code> + * + * @param ctx The current context + */ + public VirtualConsole(Context ctx) { + super(); + mCtx = ctx; + mIdentity = AIDHelper.createVirtualIdentity(); + } + + public abstract String getName(); + + /** + * {@inheritDoc} + */ + @Override + public void alloc() throws ConsoleAllocException { + try { + if (isTrace()) { + Log.v(TAG, "Allocating " + getName() + " console"); + } + mActive = true; + } catch (Exception e) { + Log.e(TAG, "Failed to allocate " + getName() + " console", e); + throw new ConsoleAllocException("failed to build console", e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dealloc() { + if (isTrace()) { + Log.v(TAG, "Deallocating Java console"); + } + mActive = true; + } + + /** + * {@inheritDoc} + */ + @Override + public void realloc() throws ConsoleAllocException { + dealloc(); + alloc(); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity getIdentity() { + return mIdentity; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrivileged() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isActive() { + return mActive; + } + + /** + * Method that returns the current context + * + * @return Context The current context + */ + public Context getCtx() { + return mCtx; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCancel() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onSendSignal(SIGNAL signal) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onEnd() { + return false; + } +} diff --git a/src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java b/src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java new file mode 100644 index 00000000..ba730606 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/VirtualMountPointConsole.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 20124 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console; + +import android.content.Context; +import android.os.Environment; +import android.os.SystemClock; + +import com.cyanogenmod.filemanager.FileManagerApplication; +import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; +import com.cyanogenmod.filemanager.model.Directory; +import com.cyanogenmod.filemanager.model.DiskUsage; +import com.cyanogenmod.filemanager.model.Identity; +import com.cyanogenmod.filemanager.model.MountPoint; +import com.cyanogenmod.filemanager.model.Permissions; +import com.cyanogenmod.filemanager.preferences.AccessMode; +import com.cyanogenmod.filemanager.util.AIDHelper; +import com.cyanogenmod.filemanager.util.FileHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * An abstract base class for of a {@link VirtualConsole} that has a virtual mount point + * in the filesystem. + */ +public abstract class VirtualMountPointConsole extends VirtualConsole { + + private static final String DEFAULT_STORAGE_NAME = "storage"; + +// private static File sVirtualStorageDir; + + private static List<VirtualMountPointConsole> sVirtualConsoles; + private static Identity sVirtualIdentity; + private static Permissions sVirtualFolderPermissions; + + public VirtualMountPointConsole(Context ctx) { + super(ctx); + } + + /** + * Should return the name of the mount point name + * + * @return String The name of the mount point name of this console. + */ + public abstract String getMountPointName(); + + /** + * Method that returns if the console is secure + * + * @return boolean If the console is secure + */ + public abstract boolean isSecure(); + + /** + * Method that returns if the console is remote + * + * @return boolean If the console is remote + */ + public abstract boolean isRemote(); + + /** + * Method that returns if the console is mounted + * + * @return boolean If the console is mounted + */ + public abstract boolean isMounted(); + + /** + * Method that unmounts the filesystem + * + * @return boolean If the filesystem was unmounted + */ + public abstract boolean unmount(); + + /** + * Returns the mountpoints for the console + * + * @return List<MountPoint> The list of mountpoints handled by the console + */ + public abstract List<MountPoint> getMountPoints(); + + /** + * Returns the disk usage of every mountpoint for the console + * + * @return List<DiskUsage> The list of disk usage of the mountpoints handled by the console + */ + public abstract List<DiskUsage> getDiskUsage(); + + /** + * Returns the disk usage of the path + * + * @param path The path to check + * @return DiskUsage The disk usage for the passed path + */ + public abstract DiskUsage getDiskUsage(String path); + + /** + * Method that register all the implemented virtual consoles. This method should + * be called only once on the application instantiation. + * + * @param context The current context + */ + public static void registerVirtualConsoles(Context context) { + if (sVirtualConsoles != null) return; + sVirtualConsoles = new ArrayList<VirtualMountPointConsole>(); + sVirtualIdentity = AIDHelper.createVirtualIdentity(); + sVirtualFolderPermissions = Permissions.createDefaultFolderPermissions(); + + int bufferSize = context.getResources().getInteger(R.integer.buffer_size); + + // Register every known virtual mountable console + sVirtualConsoles.add(SecureConsole.getInstance(context, bufferSize)); + // TODO Add remote consoles. Not ready for now. + // sVirtualConsoles.add(new RemoteConsole(context)); + } + + /** + * Method that returns the virtual storage directory + * @return + */ + private static File getVirtualStorageDir() { + final Context context = FileManagerApplication.getInstance().getApplicationContext(); + File dir = new File(context.getString(R.string.virtual_storage_dir)); + AccessMode mode = FileManagerApplication.getAccessMode(); + if (mode.equals(AccessMode.SAFE) || !dir.isDirectory()) { + // Chroot environment (create a folder inside the external storage) + return getChrootedVirtualStorageDir(); + } + return dir; + } + + /** + * Method that returns the chrooted virtual storage directory + * + * @return File The Virtual storage directory + */ + private static File getChrootedVirtualStorageDir() { + File root = new File(Environment.getExternalStorageDirectory(), DEFAULT_STORAGE_NAME); + root.mkdir(); + return root; + } + + /** + * Method that list all the virtual directories + * + * @return List<Directory> The list of virtual directories + */ + public static List<Directory> getVirtualMountableDirectories() { + final Date date = new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime()); + List<Directory> directories = new ArrayList<Directory>(); + for (VirtualMountPointConsole console : sVirtualConsoles) { + File dir = null; + do { + dir = console.getVirtualMountPoint(); + } while (dir.getParentFile() != null && !isVirtualStorageDir(dir.getParent())); + + if (dir != null) { + Directory directory = new Directory( + dir.getName(), + getVirtualStorageDir().getAbsolutePath(), + sVirtualIdentity.getUser(), + sVirtualIdentity.getGroup(), + sVirtualFolderPermissions, + date, date, date); + directory.setSecure(console.isSecure()); + directory.setRemote(console.isRemote()); + + if (!directories.contains(directory)) { + directories.add(directory); + } + } + } + return directories; + } + + /** + * Method that returns the virtual mountpoints of every register console + * @return + */ + public static List<MountPoint> getVirtualMountPoints() { + List<MountPoint> mountPoints = new ArrayList<MountPoint>(); + for (VirtualMountPointConsole console : sVirtualConsoles) { + mountPoints.addAll(console.getMountPoints()); + } + return mountPoints; + } + + /** + * Method that returns the virtual disk usage of the mountpoints of every register console + * @return + */ + public static List<DiskUsage> getVirtualDiskUsage() { + List<DiskUsage> diskUsage = new ArrayList<DiskUsage>(); + for (VirtualMountPointConsole console : sVirtualConsoles) { + diskUsage.addAll(console.getDiskUsage()); + } + return diskUsage; + } + + /** + * Returns if the passed directory is the current virtual storage directory + * + * @param directory The directory to check + * @return boolean If is the current virtual storage directory + */ + public static boolean isVirtualStorageDir(String directory) { + return getVirtualStorageDir().equals(new File(directory)); + } + + /** + * Returns if the passed resource belongs to a virtual filesystem + * + * @param path The path to check + * @return boolean If is the resource belongs to a virtual filesystem + */ + public static boolean isVirtualStorageResource(String path) { + for (VirtualMountPointConsole console : sVirtualConsoles) { + if (FileHelper.belongsToDirectory(new File(path), console.getVirtualMountPoint())) { + return true; + } + } + return false; + } + + /** + * Method that returns the virtual console for the path or null if the path + * is not a virtual filesystem + * + * @param path the path to check + * @return VirtualMountPointConsole The found console + */ + public static VirtualMountPointConsole getVirtualConsoleForPath(String path) { + File file = new File(path); + for (VirtualMountPointConsole console : sVirtualConsoles) { + if (FileHelper.belongsToDirectory(file, console.getVirtualMountPoint())) { + return console; + } + } + return null; + } + + public static List<Console> getVirtualConsoleForSearchPath(String path) { + List<Console> consoles = new ArrayList<Console>(); + File dir = new File(path); + for (VirtualMountPointConsole console : sVirtualConsoles) { + if (FileHelper.belongsToDirectory(console.getVirtualMountPoint(), dir)) { + // Only mount consoles can participate in the search + if (console.isMounted()) { + consoles.add(console); + } + } + } + return consoles; + } + + /** + * Returns if the passed directory is the virtual mountpoint directory of the virtual console + * + * @param directory The directory to check + * @return boolean If is the virtual mountpoint directory of the virtual console + */ + public boolean isVirtualMountPointDir(String directory) { + return getVirtualMountPoint().equals(new File(directory)); + } + + /** + * Method that returns the virtual mount point for this console + * + * @return String The virtual mount point + */ + public final File getVirtualMountPoint() { + return new File(getVirtualStorageDir(), getMountPointName()); + } +} diff --git a/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java index daf30525..f59f7461 100644 --- a/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java +++ b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java @@ -17,43 +17,31 @@ package com.cyanogenmod.filemanager.console.java; import android.content.Context; -import android.os.Process; import android.util.Log; import com.cyanogenmod.filemanager.commands.Executable; import com.cyanogenmod.filemanager.commands.ExecutableFactory; -import com.cyanogenmod.filemanager.commands.SIGNAL; import com.cyanogenmod.filemanager.commands.java.JavaExecutableFactory; import com.cyanogenmod.filemanager.commands.java.Program; import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.Console; import com.cyanogenmod.filemanager.console.ConsoleAllocException; import com.cyanogenmod.filemanager.console.ExecutionException; import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; import com.cyanogenmod.filemanager.console.OperationTimeoutException; import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; -import com.cyanogenmod.filemanager.model.AID; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.Identity; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.util.AIDHelper; - -import java.util.ArrayList; +import com.cyanogenmod.filemanager.console.VirtualConsole; /** - * An implementation of a {@link Console} based on a java implementation.<br/> + * An implementation of a {@link VirtualConsole} based on a java implementation.<br/> * <br/> * This console is a non-privileged console an many of the functionality is not implemented * because can't be obtain from java api. */ -public final class JavaConsole extends Console { +public final class JavaConsole extends VirtualConsole { private static final String TAG = "JavaConsole"; //$NON-NLS-1$ - private boolean mActive; - - private final Context mCtx; private final int mBufferSize; /** @@ -63,45 +51,17 @@ public final class JavaConsole extends Console { * @param bufferSize The buffer size */ public JavaConsole(Context ctx, int bufferSize) { - super(); - this.mCtx = ctx; + super(ctx); this.mBufferSize = bufferSize; } - /** - * {@inheritDoc} - */ - @Override - public void alloc() throws ConsoleAllocException { - try { - if (isTrace()) { - Log.v(TAG, "Allocating Java console"); //$NON-NLS-1$ - } - this.mActive = true; - } catch (Exception e) { - Log.e(TAG, "Failed to allocate Java console", e); //$NON-NLS-1$ - throw new ConsoleAllocException("failed to build console", e); //$NON-NLS-1$ - } - } /** * {@inheritDoc} */ @Override - public void dealloc() { - if (isTrace()) { - Log.v(TAG, "Deallocating Java console"); //$NON-NLS-1$ - } - this.mActive = true; - } - - /** - * {@inheritDoc} - */ - @Override - public void realloc() throws ConsoleAllocException { - dealloc(); - alloc(); + public String getName() { + return "Java"; } /** @@ -116,48 +76,10 @@ public final class JavaConsole extends Console { * {@inheritDoc} */ @Override - public Identity getIdentity() { - AID aid = AIDHelper.getAID(Process.myUid()); - if (aid == null) return null; - return new Identity( - new User(aid.getId(), aid.getName()), - new Group(aid.getId(), aid.getName()), - new ArrayList<Group>()); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isPrivileged() { - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isActive() { - return this.mActive; - } - - /** - * Method that returns the current context - * - * @return Context The current context - */ - public Context getCtx() { - return this.mCtx; - } - - /** - * {@inheritDoc} - */ - @Override - public synchronized void execute(Executable executable) throws ConsoleAllocException, - InsufficientPermissionsException, NoSuchFileOrDirectory, - OperationTimeoutException, ExecutionException, - CommandNotFoundException, ReadOnlyFilesystemException { + public synchronized void execute(Executable executable, Context ctx) + throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, CommandNotFoundException, + ReadOnlyFilesystemException { // Check that the program is a java program try { Program p = (Program)executable; @@ -201,28 +123,4 @@ public final class JavaConsole extends Console { } } - /** - * {@inheritDoc} - */ - @Override - public boolean onCancel() { - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onSendSignal(SIGNAL signal) { - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onEnd() { - return false; - } - }
\ No newline at end of file diff --git a/src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java b/src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java new file mode 100644 index 00000000..a57b5d51 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/remote/RemoteConsole.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.cyanogenmod.filemanager.console.remote; + +import android.content.Context; + +import com.cyanogenmod.filemanager.commands.Executable; +import com.cyanogenmod.filemanager.commands.ExecutableFactory; +import com.cyanogenmod.filemanager.console.CommandNotFoundException; +import com.cyanogenmod.filemanager.console.ConsoleAllocException; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.OperationTimeoutException; +import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; +import com.cyanogenmod.filemanager.model.DiskUsage; +import com.cyanogenmod.filemanager.model.MountPoint; + +import java.util.ArrayList; +import java.util.List; + +/** + * An implementation of a {@link VirtualMountPointConsole} for remote filesystems + */ +public class RemoteConsole extends VirtualMountPointConsole { + + /** + * Constructor of <code>RemoteConsole</code> + * + * @param ctx The current context + */ + public RemoteConsole(Context ctx) { + super(ctx); + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return "Remote"; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSecure() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRemote() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isMounted() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean unmount() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public List<MountPoint> getMountPoints() { + List<MountPoint> mountPoints = new ArrayList<MountPoint>(); + return mountPoints; + } + + /** + * {@inheritDoc} + */ + @Override + public List<DiskUsage> getDiskUsage() { + List<DiskUsage> diskUsage = new ArrayList<DiskUsage>(); + return diskUsage; + } + + /** + * {@inheritDoc} + */ + @Override + public DiskUsage getDiskUsage(String path) { + // TODO Fix when remote console will be implemented + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getMountPointName() { + return "remote"; + } + + /** + * {@inheritDoc} + */ + @Override + public ExecutableFactory getExecutableFactory() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void execute(Executable executable, Context ctx) + throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, CommandNotFoundException, + ReadOnlyFilesystemException { + + } +} diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java b/src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java new file mode 100644 index 00000000..be019a66 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/secure/SecureConsole.java @@ -0,0 +1,640 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.cyanogenmod.filemanager.console.secure; + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Message; +import android.os.UserHandle; +import android.os.Handler.Callback; +import android.util.Log; +import android.widget.Toast; + +import com.cyanogenmod.filemanager.FileManagerApplication; +import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.commands.Executable; +import com.cyanogenmod.filemanager.commands.ExecutableFactory; +import com.cyanogenmod.filemanager.commands.MountExecutable; +import com.cyanogenmod.filemanager.commands.secure.Program; +import com.cyanogenmod.filemanager.commands.secure.SecureExecutableFactory; +import com.cyanogenmod.filemanager.console.AuthenticationFailedException; +import com.cyanogenmod.filemanager.console.CancelledOperationException; +import com.cyanogenmod.filemanager.console.CommandNotFoundException; +import com.cyanogenmod.filemanager.console.Console; +import com.cyanogenmod.filemanager.console.ConsoleAllocException; +import com.cyanogenmod.filemanager.console.ExecutionException; +import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; +import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; +import com.cyanogenmod.filemanager.console.OperationTimeoutException; +import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; +import com.cyanogenmod.filemanager.model.DiskUsage; +import com.cyanogenmod.filemanager.model.MountPoint; +import com.cyanogenmod.filemanager.preferences.FileManagerSettings; +import com.cyanogenmod.filemanager.preferences.Preferences; +import com.cyanogenmod.filemanager.util.DialogHelper; +import com.cyanogenmod.filemanager.util.ExceptionUtil; +import com.cyanogenmod.filemanager.util.FileHelper; + +import org.apache.http.auth.AuthenticationException; + +import de.schlichtherle.truezip.crypto.raes.RaesAuthenticationException; +import de.schlichtherle.truezip.file.TArchiveDetector; +import de.schlichtherle.truezip.file.TFile; +import de.schlichtherle.truezip.file.TVFS; +import de.schlichtherle.truezip.key.CancelledOperation; +import static de.schlichtherle.truezip.fs.FsSyncOption.CLEAR_CACHE; +import static de.schlichtherle.truezip.fs.FsSyncOption.FORCE_CLOSE_INPUT; +import static de.schlichtherle.truezip.fs.FsSyncOption.FORCE_CLOSE_OUTPUT; +import de.schlichtherle.truezip.util.BitField; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * A secure implementation of a {@link VirtualMountPointConsole} that uses a + * secure filesystem backend + */ +public class SecureConsole extends VirtualMountPointConsole { + + public static final String TAG = "SecureConsole"; + + /** The singleton TArchiveDetector which enclosure this driver **/ + public static final TArchiveDetector DETECTOR = new TArchiveDetector( + SecureStorageDriverProvider.SINGLETON, SecureStorageDriverProvider.SINGLETON.get()); + + public static String getSecureStorageName() { + return String.format("storage.%s.%s", + String.valueOf(UserHandle.myUserId()), + SecureStorageDriverProvider.SECURE_STORAGE_SCHEME); + } + + public static TFile getSecureStorageRoot() { + return new TFile(FileManagerApplication.getInstance().getExternalFilesDir(null), + getSecureStorageName(), DETECTOR); + } + + public static URI getSecureStorageRootUri() { + return new File(FileManagerApplication.getInstance().getExternalFilesDir(null), + getSecureStorageName()).toURI(); + } + + private static SecureConsole sConsole = null; + + public final Handler mSyncHandler; + + private boolean mIsMounted; + private boolean mRequiresSync; + + private final int mBufferSize; + + private static final long SYNC_WAIT = 10000L; + + private static final int MSG_SYNC_FS = 0; + + private final ExecutorService mExecutorService = Executors.newFixedThreadPool(1); + + private final Callback mSyncCallback = new Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_SYNC_FS: + mExecutorService.execute(new Runnable() { + @Override + public void run() { + sync(); + } + }); + break; + + default: + break; + } + return true; + } + }; + + /** + * Return an instance of the current console + * @return + */ + public static synchronized SecureConsole getInstance(Context ctx, int bufferSize) { + if (sConsole == null) { + sConsole = new SecureConsole(ctx, bufferSize); + } + return sConsole; + } + + private final TFile mStorageRoot; + private final String mStorageName; + + /** + * Constructor of <code>SecureConsole</code> + * + * @param ctx The current context + */ + private SecureConsole(Context ctx, int bufferSize) { + super(ctx); + mIsMounted = false; + mBufferSize = bufferSize; + mSyncHandler = new Handler(mSyncCallback); + mStorageRoot = getSecureStorageRoot(); + mStorageName = getSecureStorageName(); + + // Save a copy of the console. This has a unique instance for all the app + if (sConsole != null) { + sConsole = this; + } + } + + @Override + public void dealloc() { + super.dealloc(); + + // Synchronize the underlaying storage + mSyncHandler.removeMessages(MSG_SYNC_FS); + sync(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return "Secure"; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSecure() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isMounted() { + return mIsMounted; + } + + /** + * {@inheritDoc} + */ + @Override + public List<MountPoint> getMountPoints() { + // This console only has one mountpoint + List<MountPoint> mountPoints = new ArrayList<MountPoint>(); + String status = mIsMounted ? MountExecutable.READWRITE : MountExecutable.READONLY; + mountPoints.add(new MountPoint(getVirtualMountPoint().getAbsolutePath(), + "securestorage", "securestoragefs", status, 0, 0, true, false)); + return mountPoints; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("deprecation") + public List<DiskUsage> getDiskUsage() { + // This console only has one mountpoint, and is fully usage + List<DiskUsage> diskUsage = new ArrayList<DiskUsage>(); + File mp = mStorageRoot.getFile(); + diskUsage.add(new DiskUsage(mp.getAbsolutePath(), + mp.getTotalSpace(), + mp.length(), + mp.getTotalSpace() - mp.length())); + return diskUsage; + } + + /** + * Method that returns if the path belongs to the secure storage + * + * @param path The path to check + * @return + */ + public boolean isSecureStorageResource(String path) { + return FileHelper.belongsToDirectory(new File(path), getVirtualMountPoint()); + } + + /** + * {@inheritDoc} + */ + @Override + public DiskUsage getDiskUsage(String path) { + if (isSecureStorageResource(path)) { + return getDiskUsage().get(0); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getMountPointName() { + return "secure"; + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean isRemote() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public ExecutableFactory getExecutableFactory() { + return new SecureExecutableFactory(this); + } + + /** + * Method that request a reset of the current password + */ + public void requestReset(final Context ctx) { + AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { + @Override + protected Boolean doInBackground(Void... params) { + boolean result = false; + + // Unmount the filesystem + if (mIsMounted) { + unmount(); + } + try { + SecureStorageKeyManagerProvider.SINGLETON.reset(); + + // Mount with the new key + mount(ctx); + + // In order to claim a write, we need to be sure that an operation is + // done to disk before unmount the device. + try { + String testName = UUID.randomUUID().toString(); + TFile test = new TFile(getSecureStorageRoot(), testName); + test.createNewFile(); + test.rm(); + result = true; + } catch (IOException ex) { + ExceptionUtil.translateException(ctx, ex); + } + + } catch (Exception ex) { + ExceptionUtil.translateException(ctx, ex); + } finally { + unmount(); + } + + return result; + } + + @Override + protected void onPostExecute(Boolean result) { + if (result) { + // Success + DialogHelper.showToast(ctx, R.string.msgs_success, Toast.LENGTH_SHORT); + } + } + + }; + task.execute(); + } + + /** + * Method that request a delete of the current password + */ + @SuppressWarnings("deprecation") + public void requestDelete(final Context ctx) { + AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { + @Override + protected Boolean doInBackground(Void... params) { + boolean result = false; + + // Unmount the filesystem + if (mIsMounted) { + unmount(); + } + try { + SecureStorageKeyManagerProvider.SINGLETON.delete(); + + // Test mount/unmount + mount(ctx); + unmount(); + + // Password is valid. Delete the storage + mStorageRoot.getFile().delete(); + + // Send an broadcast to notify that the mount state of this filesystem changed + Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED); + intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT, + getVirtualMountPoint().toString()); + intent.putExtra(FileManagerSettings.EXTRA_STATUS, MountExecutable.READONLY); + getCtx().sendBroadcast(intent); + + result = true; + + } catch (Exception ex) { + ExceptionUtil.translateException(ctx, ex); + } + + return result; + } + + @Override + protected void onPostExecute(Boolean result) { + if (result) { + // Success + DialogHelper.showToast(ctx, R.string.msgs_success, Toast.LENGTH_SHORT); + } + } + + }; + task.execute(); + } + + /** + * {@inheritDoc} + */ + public boolean unmount() { + // Unmount the filesystem and cancel the cached key + mRequiresSync = true; + boolean ret = sync(); + if (ret) { + SecureStorageKeyManagerProvider.SINGLETON.unmount(); + } + mIsMounted = false; + + // Send an broadcast to notify that the mount state of this filesystem changed + Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED); + intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT, + getVirtualMountPoint().toString()); + intent.putExtra(FileManagerSettings.EXTRA_STATUS, MountExecutable.READONLY); + getCtx().sendBroadcast(intent); + + return mIsMounted; + } + + /** + * Method that verifies if the current storage is open and mount it + * + * @param ctx The current context + * @throws CancelledOperationException If the operation was cancelled (by the user) + * @throws AuthenticationException If the secure storage isn't unlocked + * @throws NoSuchFileOrDirectory If the secure storage isn't accessible + */ + @SuppressWarnings("deprecation") + public synchronized void mount(Context ctx) + throws CancelledOperationException, AuthenticationFailedException, + NoSuchFileOrDirectory { + if (!mIsMounted) { + File root = mStorageRoot.getFile(); + try { + boolean newStorage = !root.exists(); + mStorageRoot.mount(); + if (newStorage) { + // Force a synchronization + mRequiresSync = true; + sync(); + } else { + // Remove any previous cache files (if not sync invoked) + clearCache(ctx); + } + + // The device is mounted + mIsMounted = true; + + // Send an broadcast to notify that the mount state of this filesystem changed + Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED); + intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT, + getVirtualMountPoint().toString()); + intent.putExtra(FileManagerSettings.EXTRA_STATUS, MountExecutable.READWRITE); + getCtx().sendBroadcast(intent); + + } catch (IOException ex) { + if (ex.getCause() != null && ex.getCause() instanceof CancelledOperation) { + throw new CancelledOperationException(); + } + if (ex.getCause() != null && ex.getCause() instanceof RaesAuthenticationException) { + throw new AuthenticationFailedException(ctx.getString( + R.string.secure_storage_unlock_failed)); + } + Log.e(TAG, String.format("Failed to open secure storage: %s", root, ex)); + throw new NoSuchFileOrDirectory(); + } + } + } + + /** + * Method that returns if the path is the real secure storage file + * + * @param path The path to check + * @return boolean If the path is the secure storage + */ + public static boolean isSecureStorageDir(String path) { + Console vc = getVirtualConsoleForPath(path); + if (vc != null && vc instanceof SecureConsole) { + return isSecureStorageDir(((SecureConsole) vc).buildRealFile(path)); + } + return false; + } + + /** + * Method that returns if the path is the real secure storage file + * + * @param path The path to check + * @return boolean If the path is the secure storage + */ + public static boolean isSecureStorageDir(TFile path) { + return getSecureStorageRoot().equals(path); + } + + /** + * Method that build a real file from a virtual path + * + * @param path The path from build the real file + * @return TFile The real file + */ + public TFile buildRealFile(String path) { + String real = mStorageRoot.toString(); + String virtual = getVirtualMountPoint().toString(); + String src = path.replace(virtual, real); + return new TFile(src, DETECTOR); + } + + /** + * Method that build a virtual file from a real path + * + * @param path The path from build the virtual file + * @return TFile The virtual file + */ + public String buildVirtualPath(TFile path) { + String real = mStorageRoot.toString(); + String virtual = getVirtualMountPoint().toString(); + String dst = path.toString().replace(real, virtual); + return dst; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void execute(Executable executable, Context ctx) + throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, CommandNotFoundException, + ReadOnlyFilesystemException, CancelledOperationException, + AuthenticationFailedException { + // Check that the program is a secure program + try { + Program p = (Program) executable; + p.setBufferSize(mBufferSize); + } catch (Throwable e) { + Log.e(TAG, String.format("Failed to resolve program: %s", //$NON-NLS-1$ + executable.getClass().toString()), e); + throw new CommandNotFoundException("executable is not a program", e); //$NON-NLS-1$ + } + + //Auditing program execution + if (isTrace()) { + Log.v(TAG, String.format("Executing program: %s", //$NON-NLS-1$ + executable.getClass().toString())); + } + + + final Program program = (Program) executable; + + // Open storage encryption (if required) + if (program.requiresOpen()) { + mount(ctx); + } + + // Execute the program + program.setTrace(isTrace()); + if (program.isAsynchronous()) { + // Execute in a thread + Thread t = new Thread() { + @Override + public void run() { + try { + program.execute(); + requestSync(program); + } catch (Exception e) { + // Program must use onException to communicate exceptions + Log.v(TAG, + String.format("Async execute failed program: %s", //$NON-NLS-1$ + program.getClass().toString())); + } + } + }; + t.start(); + + } else { + // Synchronous execution + program.execute(); + requestSync(program); + } + } + + /** + * Request a synchronization of the underlying filesystem + * + * @param program The last called program + */ + private void requestSync(Program program) { + if (program.requiresSync()) { + mRequiresSync = true; + } + + // There is some changes to synchronize? + if (mRequiresSync) { + Boolean defaultValue = ((Boolean)FileManagerSettings. + SETTINGS_SECURE_STORAGE_DELAYED_SYNC.getDefaultValue()); + Boolean delayedSync = + Boolean.valueOf( + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_SECURE_STORAGE_DELAYED_SYNC.getId(), + defaultValue.booleanValue())); + mSyncHandler.removeMessages(MSG_SYNC_FS); + if (delayedSync) { + // Request a sync in 30 seconds, if users is not doing any operation + mSyncHandler.sendEmptyMessageDelayed(MSG_SYNC_FS, SYNC_WAIT); + } else { + // Do the synchronization now + mSyncHandler.sendEmptyMessage(MSG_SYNC_FS); + } + } + } + + /** + * Synchronize the underlying filesystem + * + * @retun boolean If the unmount success + */ + public synchronized boolean sync() { + if (mRequiresSync) { + Log.i(TAG, "Syncing underlaying storage"); + mRequiresSync = false; + // Sync the underlying storage + try { + TVFS.sync(mStorageRoot, + BitField.of(CLEAR_CACHE) + .set(FORCE_CLOSE_INPUT, true) + .set(FORCE_CLOSE_OUTPUT, true)); + return true; + } catch (IOException e) { + Log.e(TAG, String.format("Failed to sync secure storage: %s", mStorageRoot, e)); + return false; + } + } + return true; + } + + /** + * Method that clear the cache + * + * @param ctx The current context + */ + private void clearCache(Context ctx) { + File filesDir = ctx.getExternalFilesDir(null); + File[] cacheFiles = filesDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String filename) { + return filename.startsWith(mStorageName) + && filename.endsWith(".tmp"); + } + }); + for (File cacheFile : cacheFiles) { + cacheFile.delete(); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java new file mode 100644 index 00000000..df2e4822 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriver.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console.secure; + +import de.schlichtherle.truezip.fs.archive.zip.raes.SafeZipRaesDriver; +import de.schlichtherle.truezip.socket.sl.IOPoolLocator; + +/** + * Custom implementation of {@code SafeZipRaesDriver} + */ +public class SecureStorageDriver extends SafeZipRaesDriver { + + // The singleton FsDriver reference + static final SecureStorageDriver SINGLETON = new SecureStorageDriver(); + + /** + * Constructor of {@code SecureStorageDriver} + */ + private SecureStorageDriver() { + super(IOPoolLocator.SINGLETON, SecureStorageKeyManagerProvider.SINGLETON); + } + +} diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java new file mode 100644 index 00000000..17555286 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageDriverProvider.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console.secure; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import de.schlichtherle.truezip.fs.FsDriver; +import de.schlichtherle.truezip.fs.FsDriverProvider; +import de.schlichtherle.truezip.fs.FsScheme; +import de.schlichtherle.truezip.fs.file.FileDriver; + +/** + * The SecureStorage driver provider which handles {@code "secure"} data schemes + */ +public class SecureStorageDriverProvider implements FsDriverProvider { + + /** File scheme **/ + public static final String FILE_SCHEME = "file"; + + /** SecureStorage scheme **/ + public static final String SECURE_STORAGE_SCHEME = "secure"; + + /** The singleton instance of this class. */ + static final SecureStorageDriverProvider SINGLETON = new SecureStorageDriverProvider(); + + /** You cannot instantiate this class. */ + private SecureStorageDriverProvider() { + } + + /** + * {@inheritDoc} + */ + @Override + public Map<FsScheme, FsDriver> get() { + return Boot.DRIVERS; + } + + /** A static data utility class used for lazy initialization. */ + private static final class Boot { + static final Map<FsScheme, FsDriver> DRIVERS; + static { + final Map<FsScheme, FsDriver> fast = new LinkedHashMap<FsScheme, FsDriver>(); + fast.put(FsScheme.create(FILE_SCHEME), new FileDriver()); + fast.put(FsScheme.create(SECURE_STORAGE_SCHEME), SecureStorageDriver.SINGLETON); + DRIVERS = Collections.unmodifiableMap(fast); + } + } // Boot +} diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java new file mode 100644 index 00000000..810b94dd --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyManagerProvider.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console.secure; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import de.schlichtherle.truezip.crypto.raes.param.AesCipherParameters; +import de.schlichtherle.truezip.key.AbstractKeyManagerProvider; +import de.schlichtherle.truezip.key.KeyManager; +import de.schlichtherle.truezip.key.PromptingKeyManager; +import de.schlichtherle.truezip.key.PromptingKeyProvider; + +/** + * The SecureStorage KeyManager provider + */ +public class SecureStorageKeyManagerProvider extends AbstractKeyManagerProvider { + + /** The singleton instance of this class. */ + static final SecureStorageKeyManagerProvider SINGLETON = + new SecureStorageKeyManagerProvider(); + + private final static SecureStorageKeyPromptDialog PROMPT_DIALOG = + new SecureStorageKeyPromptDialog(); + + /** You cannot instantiate this class. */ + private SecureStorageKeyManagerProvider() { + } + + /** + * {@inheritDoc} + */ + @Override + public Map<Class<?>, KeyManager<?>> get() { + return Boot.MANAGERS; + } + + /** + * @hide + */ + void unmount() { + PROMPT_DIALOG.umount(); + getKeyProvider().setKey(null); + } + + /** + * @hide + */ + void reset() { + PROMPT_DIALOG.reset(); + getKeyProvider().setKey(null); + } + + /** + * @hide + */ + void delete() { + PROMPT_DIALOG.delete(); + getKeyProvider().setKey(null); + } + + @SuppressWarnings("unchecked") + private static PromptingKeyProvider<AesCipherParameters> getKeyProvider() { + PromptingKeyManager<AesCipherParameters> keyManager = + (PromptingKeyManager<AesCipherParameters>) Boot.MANAGERS.get( + AesCipherParameters.class); + return (PromptingKeyProvider<AesCipherParameters>) keyManager.getKeyProvider( + SecureConsole.getSecureStorageRootUri()); + } + + /** A static data utility class used for lazy initialization. */ + private static final class Boot { + static final Map<Class<?>, KeyManager<?>> MANAGERS; + static { + final PromptingKeyManager<AesCipherParameters> promptKeyManager = + new PromptingKeyManager<AesCipherParameters>(PROMPT_DIALOG); + final Map<Class<?>, KeyManager<?>> fast = new LinkedHashMap<Class<?>, KeyManager<?>>(); + fast.put(AesCipherParameters.class, promptKeyManager); + MANAGERS = Collections.unmodifiableMap(fast); + + // We need that the provider ask always for a password + getKeyProvider().setAskAlwaysForWriteKey(true); + } + } // class Boot + +} diff --git a/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java new file mode 100644 index 00000000..2db530c4 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/console/secure/SecureStorageKeyPromptDialog.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.console.secure; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.cyanogenmod.filemanager.FileManagerApplication; +import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.ui.ThemeManager; +import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import com.cyanogenmod.filemanager.util.DialogHelper; + +import de.schlichtherle.truezip.crypto.raes.param.AesCipherParameters; +import de.schlichtherle.truezip.crypto.raes.Type0RaesParameters.KeyStrength; +import de.schlichtherle.truezip.key.KeyPromptingCancelledException; +import de.schlichtherle.truezip.key.KeyPromptingInterruptedException; +import de.schlichtherle.truezip.key.PromptingKeyProvider.Controller; +import de.schlichtherle.truezip.key.UnknownKeyException; + +/** + * A class that remembers all the secure storage + */ +public class SecureStorageKeyPromptDialog + implements de.schlichtherle.truezip.key.PromptingKeyProvider.View<AesCipherParameters> { + + private static final int MIN_PASSWORD_LENGTH = 8; + + private static final int MSG_REQUEST_UNLOCK_DIALOG = 1; + + private static boolean sResetInProgress; + private static boolean sDeleteInProgress; + private static transient AesCipherParameters sOldUnlockKey = null; + private static transient AesCipherParameters sUnlockKey = null; + private static transient AesCipherParameters sOldUnlockKeyTemp = null; + private static transient AesCipherParameters sUnlockKeyTemp = null; + private static final Object WAIT_SYNC = new Object(); + + /** + * An activity that simulates a dialog over the activity that requested the key prompt. + */ + public static class SecureStorageKeyPromptActivity extends Activity + implements OnClickListener, TextWatcher { + + private AlertDialog mDialog; + + private TextView mMessage; + private EditText mOldKey; + private EditText mKey; + private EditText mRepeatKey; + private TextView mValidationMsg; + private Button mUnlock; + + private boolean mNewStorage; + private boolean mResetPassword; + private boolean mDeleteStorage; + + AesCipherParameters mOldKeyParams; + AesCipherParameters mKeyParams; + + @Override + @SuppressWarnings("deprecation") + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Check with java.io.File instead of TFile because TFile#exists() will + // check for password key, which is currently locked + mNewStorage = !SecureConsole.getSecureStorageRoot().getFile().exists(); + mResetPassword = sResetInProgress; + mDeleteStorage = sDeleteInProgress; + + // Set the theme before setContentView + Theme theme = ThemeManager.getCurrentTheme(this); + theme.setBaseTheme(this, true); + + // Load the dialog's custom layout + ViewGroup v = (ViewGroup) LayoutInflater.from(this).inflate( + R.layout.unlock_dialog_message, null); + mMessage = (TextView) v.findViewById(R.id.unlock_dialog_message); + mOldKey = (EditText) v.findViewById(R.id.unlock_old_password); + mOldKey.addTextChangedListener(this); + mKey = (EditText) v.findViewById(R.id.unlock_password); + mKey.addTextChangedListener(this); + mRepeatKey = (EditText) v.findViewById(R.id.unlock_repeat); + mRepeatKey.addTextChangedListener(this); + View oldPasswordLayout = v.findViewById(R.id.unlock_old_password_layout); + View repeatLayout = v.findViewById(R.id.unlock_repeat_layout); + mValidationMsg = (TextView) v.findViewById(R.id.unlock_validation_msg); + + // Load resources + int messageResourceId = R.string.secure_storage_unlock_key_prompt_msg; + int positiveButtonLabelResourceId = R.string.secure_storage_unlock_button; + String title = getString(R.string.secure_storage_unlock_title); + if (mNewStorage) { + positiveButtonLabelResourceId = R.string.secure_storage_create_button; + title = getString(R.string.secure_storage_create_title); + messageResourceId = R.string.secure_storage_unlock_key_new_msg; + } else if (mResetPassword) { + positiveButtonLabelResourceId = R.string.secure_storage_reset_button; + title = getString(R.string.secure_storage_reset_title); + messageResourceId = R.string.secure_storage_unlock_key_reset_msg; + TextView passwordLabel = (TextView) v.findViewById(R.id.unlock_password_title); + passwordLabel.setText(R.string.secure_storage_unlock_new_key_title); + } else if (mDeleteStorage) { + positiveButtonLabelResourceId = R.string.secure_storage_delete_button; + title = getString(R.string.secure_storage_delete_title); + messageResourceId = R.string.secure_storage_unlock_key_delete_msg; + } + + // Set the message according to the storage creation status + mMessage.setText(messageResourceId); + repeatLayout.setVisibility(mNewStorage || mResetPassword ? View.VISIBLE : View.GONE); + oldPasswordLayout.setVisibility(mResetPassword ? View.VISIBLE : View.GONE); + + // Set validation msg + mValidationMsg.setText(getString(R.string.secure_storage_unlock_validation_length, + MIN_PASSWORD_LENGTH)); + mValidationMsg.setVisibility(View.VISIBLE); + + // Create the dialog + mDialog = DialogHelper.createTwoButtonsDialog(this, + positiveButtonLabelResourceId, R.string.cancel, + theme.getResourceId(this,"ic_secure_drawable"), title, v, this); + mDialog.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + mDialog.dismiss(); + finish(); + } + }); + mDialog.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + sUnlockKeyTemp = null; + mDialog.cancel(); + finish(); + } + }); + mDialog.setCanceledOnTouchOutside(false); + + // Apply the theme to the custom view of the dialog + applyTheme(this, v); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + DialogHelper.delegateDialogShow(this, mDialog); + mUnlock = mDialog.getButton(DialogInterface.BUTTON_POSITIVE); + mUnlock.setEnabled(false); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + // Unlock the wait + synchronized (WAIT_SYNC) { + WAIT_SYNC.notify(); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + // Create the AES parameter and set to the prompting view + if (mResetPassword) { + AesCipherParameters params = new AesCipherParameters(); + params.setPassword(mOldKey.getText().toString().toCharArray()); + params.setKeyStrength(KeyStrength.BITS_128); + sOldUnlockKeyTemp = params; + } + AesCipherParameters params = new AesCipherParameters(); + params.setPassword(mKey.getText().toString().toCharArray()); + params.setKeyStrength(KeyStrength.BITS_128); + sUnlockKeyTemp = params; + + // We ended with this dialog + dialog.dismiss(); + break; + + case DialogInterface.BUTTON_NEGATIVE: + // User had cancelled the dialog + dialog.cancel(); + + break; + + default: + break; + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Ignore + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Ignore + } + + @Override + public void afterTextChanged(Editable s) { + // Validations: + // * Key must be MIN_PASSWORD_LENGTH characters or more + // * Repeat == Key + String oldkey = mOldKey.getText().toString(); + String key = mKey.getText().toString(); + String repeatKey = mRepeatKey.getText().toString(); + boolean validLength = key.length() >= MIN_PASSWORD_LENGTH && + (!mResetPassword || (mResetPassword && oldkey.length() >= MIN_PASSWORD_LENGTH)); + boolean validEquals = key.equals(repeatKey); + boolean valid = validLength && ((mNewStorage && validEquals) || !mNewStorage); + mUnlock.setEnabled(valid); + + if (!validLength) { + mValidationMsg.setText(getString(R.string.secure_storage_unlock_validation_length, + MIN_PASSWORD_LENGTH)); + mValidationMsg.setVisibility(View.VISIBLE); + } else if (mNewStorage && !validEquals) { + mValidationMsg.setText(R.string.secure_storage_unlock_validation_equals); + mValidationMsg.setVisibility(View.VISIBLE); + } else { + mValidationMsg.setVisibility(View.INVISIBLE); + } + } + + private void applyTheme(Context ctx, ViewGroup root) { + // Apply the current theme + Theme theme = ThemeManager.getCurrentTheme(ctx); + theme.setBackgroundDrawable(ctx, root, "background_drawable"); + theme.setTextColor(ctx, mMessage, "text_color"); + theme.setTextColor(ctx, mOldKey, "text_color"); + theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_old_password_title), + "text_color"); + theme.setTextColor(ctx, mKey, "text_color"); + theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_password_title), + "text_color"); + theme.setTextColor(ctx, mRepeatKey, "text_color"); + theme.setTextColor(ctx, (TextView) root.findViewById(R.id.unlock_repeat_title), + "text_color"); + theme.setTextColor(ctx, mValidationMsg, "text_color"); + mValidationMsg.setCompoundDrawablesWithIntrinsicBounds( + theme.getDrawable(ctx, "filesystem_warning_drawable"), //$NON-NLS-1$ + null, null, null); + } + } + + SecureStorageKeyPromptDialog() { + super(); + sResetInProgress = false; + sDeleteInProgress = false; + sOldUnlockKey = null; + sUnlockKey = null; + } + + @Override + public void promptWriteKey(Controller<AesCipherParameters> controller) + throws UnknownKeyException { + controller.setKey(getOrPromptForKey(false)); + if (sResetInProgress) { + // Not needed any more. Reads are now done with new key + sOldUnlockKey = null; + sResetInProgress = false; + } + } + + @Override + public void promptReadKey(Controller<AesCipherParameters> controller, boolean invalid) + throws UnknownKeyException { + if (!sResetInProgress && invalid) { + sUnlockKey = null; + } + controller.setKey(getOrPromptForKey(true)); + } + + /** + * {@hide} + */ + void umount() { + // Discard current keys + sResetInProgress = false; + sDeleteInProgress = false; + sOldUnlockKey = null; + sUnlockKey = null; + } + + /** + * {@hide} + */ + void reset() { + // Discard current keys + sResetInProgress = true; + sDeleteInProgress = false; + sOldUnlockKey = null; + sUnlockKey = null; + } + + /** + * {@hide} + */ + void delete() { + sDeleteInProgress = true; + sResetInProgress = false; + sOldUnlockKey = null; + } + + /** + * Method that return or prompt the user for the secure storage key + * + * @param read If should return the read or write key + * @return AesCipherParameters The AES cipher parameters + */ + private static synchronized AesCipherParameters getOrPromptForKey(boolean read) + throws UnknownKeyException { + // Check if we have a cached key + if (read && sResetInProgress && sOldUnlockKey != null) { + return sOldUnlockKey; + } + if (sUnlockKey != null) { + return sUnlockKey; + } + + // Need to prompt the user for the secure storage key, so we open a overlay activity + // to show the prompt dialog + Handler handler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message inputMessage) { + Context ctx = FileManagerApplication.getInstance(); + Intent intent = new Intent(ctx, SecureStorageKeyPromptActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + ctx.startActivity(intent); + } + }; + handler.sendEmptyMessage(MSG_REQUEST_UNLOCK_DIALOG); + + // Wait for the response + synchronized (WAIT_SYNC) { + try { + WAIT_SYNC.wait(); + } catch (InterruptedException ex) { + throw new KeyPromptingInterruptedException(ex); + } + } + + // Request for authentication is done. We need to exit from delete status + sDeleteInProgress = false; + + // Check if the user cancelled the dialog + if (sUnlockKeyTemp == null) { + throw new KeyPromptingCancelledException(); + } + + // Move temporary params to real params + sUnlockKey = sUnlockKeyTemp; + sOldUnlockKey = sOldUnlockKeyTemp; + + AesCipherParameters key = sUnlockKey; + if (sResetInProgress && read) { + key = sOldUnlockKey; + } + return key; + } +} diff --git a/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java index bc97b25a..7a1f5a9e 100644 --- a/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java +++ b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java @@ -16,6 +16,7 @@ package com.cyanogenmod.filemanager.console.shell; +import android.content.Context; import android.util.Log; import com.cyanogenmod.filemanager.FileManagerApplication; @@ -286,7 +287,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis //Retrieve identity IdentityExecutable identityCmd = getExecutableFactory().newCreator().createIdentityExecutable(); - execute(identityCmd); + execute(identityCmd, null); this.mIdentity = identityCmd.getResult(); // Identity command is required for root console detection, // but Groups command is not used for now. Also, this command is causing @@ -297,7 +298,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis //Try with groups GroupsExecutable groupsCmd = getExecutableFactory().newCreator().createGroupsExecutable(); - execute(groupsCmd); + execute(groupsCmd, null); this.mIdentity.setGroups(groupsCmd.getResult()); } } catch (Exception ex) { @@ -372,10 +373,10 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis * {@inheritDoc} */ @Override - public final synchronized void execute(final Executable executable) - throws ConsoleAllocException, InsufficientPermissionsException, - CommandNotFoundException, NoSuchFileOrDirectory, - OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException { + public synchronized void execute(Executable executable, Context ctx) + throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, CommandNotFoundException, + ReadOnlyFilesystemException { execute(executable, false); } @@ -1192,7 +1193,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis return false; } - if (this.mActiveCommand.getCommand() != null) { + if (this.mActiveCommand != null && this.mActiveCommand.getCommand() != null) { try { boolean isCancellable = true; if (this.mActiveCommand instanceof AsyncResultProgram) { diff --git a/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java b/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java index 0de97198..817bdffd 100644 --- a/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java +++ b/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java @@ -30,6 +30,11 @@ public interface OnRequestRefreshListener { void onRequestRefresh(Object o, boolean clearSelection); /** + * Invoked when bookmarks need a refresh + */ + void onRequestBookmarksRefresh(); + + /** * Invoked when the object was removed. * * @param o The object that was removed diff --git a/src/com/cyanogenmod/filemanager/model/Bookmark.java b/src/com/cyanogenmod/filemanager/model/Bookmark.java index 192f2d9c..f922a602 100644 --- a/src/com/cyanogenmod/filemanager/model/Bookmark.java +++ b/src/com/cyanogenmod/filemanager/model/Bookmark.java @@ -32,6 +32,8 @@ import java.io.Serializable; */ public class Bookmark implements Serializable, Comparable<Bookmark>, Parcelable { + private static final long serialVersionUID = -2271268193370651368L; + /** * Enumeration for types of bookmarks. */ @@ -53,13 +55,19 @@ public class Bookmark implements Serializable, Comparable<Bookmark>, Parcelable */ USB, /** + * An SECURE mount point. + */ + SECURE, + /** + * An REMOTE mount point. + */ + REMOTE, + /** * A bookmark added by the user. */ USER_DEFINED } - private static final long serialVersionUID = -7524744999056506867L; - /** * Columns of the database */ diff --git a/src/com/cyanogenmod/filemanager/model/FileSystemObject.java b/src/com/cyanogenmod/filemanager/model/FileSystemObject.java index 99110a7d..c87c07cc 100644 --- a/src/com/cyanogenmod/filemanager/model/FileSystemObject.java +++ b/src/com/cyanogenmod/filemanager/model/FileSystemObject.java @@ -33,7 +33,7 @@ import java.util.Date; */ public abstract class FileSystemObject implements Serializable, Comparable<FileSystemObject> { - private static final long serialVersionUID = 5877049750925761305L; + private static final long serialVersionUID = -571144166609728391L; //Resource identifier for default icon private static final int RESOURCE_ICON_DEFAULT = R.drawable.ic_fso_default; @@ -48,7 +48,8 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS private Date mLastAccessedTime; private Date mLastModifiedTime; private Date mLastChangedTime; - + private boolean mIsSecure; + private boolean mIsRemote; /** * Constructor of <code>FileSystemObject</code>. @@ -77,6 +78,8 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS this.mLastModifiedTime = lastModifiedTime; this.mLastChangedTime = lastChangedTime; this.mResourceIconId = RESOURCE_ICON_DEFAULT; + this.mIsSecure = false; + this.mIsRemote = false; } /** @@ -249,7 +252,7 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS } /** - * Method that returns of the object is hidden object. + * Method that returns if the object is hidden object. * * @return boolean If the object is hidden object */ @@ -258,6 +261,42 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS } /** + * Method that returns if the object is secure + * + * @return boolean If the object is secure + */ + public boolean isSecure() { + return mIsSecure; + } + + /** + * Mehtod that sets if the object is secure + * + * @param secure if the object is secure + */ + public void setSecure(boolean secure) { + mIsSecure = secure; + } + + /** + * Method that returns if the object is remote + * + * @return boolean If the object is remote + */ + public boolean isRemote() { + return mIsRemote; + } + + /** + * Mehtod that sets if the object is remote + * + * @param remote if the object is remote + */ + public void setRemote(boolean remote) { + mIsRemote = remote; + } + + /** * Method that returns the identifier of the drawable icon associated * to the object. * @@ -365,6 +404,8 @@ public abstract class FileSystemObject implements Serializable, Comparable<FileS + ", mLastAccessedTime=" + this.mLastAccessedTime //$NON-NLS-1$ + ", mLastModifiedTime=" + this.mLastModifiedTime //$NON-NLS-1$ + ", mLastChangedTime=" + this.mLastChangedTime //$NON-NLS-1$ + + ", mIsSecure=" + mIsSecure //$NON-NLS-1$ + + ", mIsRemote=" + mIsRemote //$NON-NLS-1$ + "]"; //$NON-NLS-1$ } diff --git a/src/com/cyanogenmod/filemanager/model/History.java b/src/com/cyanogenmod/filemanager/model/History.java index 32787836..2c07b450 100644 --- a/src/com/cyanogenmod/filemanager/model/History.java +++ b/src/com/cyanogenmod/filemanager/model/History.java @@ -27,7 +27,7 @@ public class History implements Serializable, Comparable<History> { private static final long serialVersionUID = -8891185225878742265L; - private final int mPosition; + private int mPosition; private final HistoryNavigable mItem; /** @@ -52,6 +52,15 @@ public class History implements Serializable, Comparable<History> { } /** + * Method that sets the current position of the history + * + * @param position The current position + */ + public void setPosition(int position) { + this.mPosition = position; + } + + /** * Method that returns the item that holds the history information. * * @return HistoryNavigable The item that holds the history information diff --git a/src/com/cyanogenmod/filemanager/model/MountPoint.java b/src/com/cyanogenmod/filemanager/model/MountPoint.java index 92576a87..a2abebc6 100644 --- a/src/com/cyanogenmod/filemanager/model/MountPoint.java +++ b/src/com/cyanogenmod/filemanager/model/MountPoint.java @@ -23,7 +23,7 @@ import java.io.Serializable; */ public class MountPoint implements Serializable, Comparable<MountPoint> { - private static final long serialVersionUID = 6283618345819358175L; + private static final long serialVersionUID = -2598921174356702897L; private final String mMountPoint; private final String mDevice; @@ -31,6 +31,8 @@ public class MountPoint implements Serializable, Comparable<MountPoint> { private final String mOptions; private final int mDump; private final int mPass; + private boolean mSecure; + private boolean mRemote; /** * Constructor of <code>MountPoint</code>. @@ -41,9 +43,12 @@ public class MountPoint implements Serializable, Comparable<MountPoint> { * @param options The mount options * @param dump The frequency to determine if the filesystem need to be dumped * @param pass The order in which filesystem checks are done at reboot time + * @param secure If the device is a secure virtual filesystem + * @param remote If the device is a remote virtual filesystem */ public MountPoint( - String mountPoint, String device, String type, String options, int dump, int pass) { + String mountPoint, String device, String type, String options, int dump, + int pass, boolean secure, boolean remote) { super(); this.mMountPoint = mountPoint; this.mDevice = device; @@ -51,6 +56,8 @@ public class MountPoint implements Serializable, Comparable<MountPoint> { this.mOptions = options; this.mDump = dump; this.mPass = pass; + this.mSecure = secure; + this.mRemote = remote; } /** @@ -109,18 +116,49 @@ public class MountPoint implements Serializable, Comparable<MountPoint> { } /** + * Method that returns if the mountpoint belongs to a virtual filesystem. + * + * @return boolean If the mountpoint belongs to a virtual filesystem. + */ + public boolean isVirtual() { + return mSecure || mRemote; + } + + /** + * Method that returns if the mountpoint belongs to a secure virtual filesystem. + * + * @return boolean If the mountpoint belongs to a secure virtual filesystem. + */ + public boolean isSecure() { + return mSecure; + } + + /** + * Method that returns if the mountpoint belongs to a remote virtual filesystem. + * + * @return boolean If the mountpoint belongs to a remote virtual filesystem. + */ + public boolean isRemote() { + return mRemote; + } + + /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + this.mDump; - result = prime * result + ((this.mDevice == null) ? 0 : this.mDevice.hashCode()); - result = prime * result + ((this.mMountPoint == null) ? 0 : this.mMountPoint.hashCode()); - result = prime * result + ((this.mOptions == null) ? 0 : this.mOptions.hashCode()); - result = prime * result + this.mPass; - result = prime * result + ((this.mType == null) ? 0 : this.mType.hashCode()); + result = prime * result + ((mDevice == null) ? 0 : mDevice.hashCode()); + result = prime * result + mDump; + result = prime * result + + ((mMountPoint == null) ? 0 : mMountPoint.hashCode()); + result = prime * result + + ((mOptions == null) ? 0 : mOptions.hashCode()); + result = prime * result + mPass; + result = prime * result + (mRemote ? 1231 : 1237); + result = prime * result + (mSecure ? 1231 : 1237); + result = prime * result + ((mType == null) ? 0 : mType.hashCode()); return result; } @@ -129,50 +167,41 @@ public class MountPoint implements Serializable, Comparable<MountPoint> { */ @Override public boolean equals(Object obj) { - if (this == obj) { + if (this == obj) return true; - } - if (obj == null) { + if (obj == null) return false; - } - if (getClass() != obj.getClass()) { + if (getClass() != obj.getClass()) return false; - } MountPoint other = (MountPoint) obj; - if (this.mDump != other.mDump) { - return false; - } - if (this.mDevice == null) { - if (other.mDevice != null) { + if (mDevice == null) { + if (other.mDevice != null) return false; - } - } else if (!this.mDevice.equals(other.mDevice)) { + } else if (!mDevice.equals(other.mDevice)) + return false; + if (mDump != other.mDump) return false; - } - if (this.mMountPoint == null) { - if (other.mMountPoint != null) { + if (mMountPoint == null) { + if (other.mMountPoint != null) return false; - } - } else if (!this.mMountPoint.equals(other.mMountPoint)) { + } else if (!mMountPoint.equals(other.mMountPoint)) return false; - } - if (this.mOptions == null) { - if (other.mOptions != null) { + if (mOptions == null) { + if (other.mOptions != null) return false; - } - } else if (!this.mOptions.equals(other.mOptions)) { + } else if (!mOptions.equals(other.mOptions)) + return false; + if (mPass != other.mPass) + return false; + if (mRemote != other.mRemote) return false; - } - if (this.mPass != other.mPass) { + if (mSecure != other.mSecure) return false; - } - if (this.mType == null) { - if (other.mType != null) { + if (mType == null) { + if (other.mType != null) return false; - } - } else if (!this.mType.equals(other.mType)) { + } else if (!mType.equals(other.mType)) return false; - } return true; } @@ -181,11 +210,10 @@ public class MountPoint implements Serializable, Comparable<MountPoint> { */ @Override public String toString() { - return "MountPoint [mountPoint=" + this.mMountPoint + ", device=" //$NON-NLS-1$//$NON-NLS-2$ - + this.mDevice + ", type=" //$NON-NLS-1$ - + this.mType + ", options=" + this.mOptions //$NON-NLS-1$ - + ", dump=" + this.mDump + ", pass=" //$NON-NLS-1$//$NON-NLS-2$ - + this.mPass + "]"; //$NON-NLS-1$ + return "MountPoint [mMountPoint=" + mMountPoint + ", mDevice=" + + mDevice + ", mType=" + mType + ", mOptions=" + mOptions + + ", mDump=" + mDump + ", mPass=" + mPass + ", mSecure=" + + mSecure + ", mRemote=" + mRemote + "]"; } /** diff --git a/src/com/cyanogenmod/filemanager/model/Permissions.java b/src/com/cyanogenmod/filemanager/model/Permissions.java index 95000865..9fdab8d9 100644 --- a/src/com/cyanogenmod/filemanager/model/Permissions.java +++ b/src/com/cyanogenmod/filemanager/model/Permissions.java @@ -29,7 +29,7 @@ import java.text.ParseException; */ public class Permissions implements Serializable, Comparable<Permissions> { - private static final long serialVersionUID = -8268598363293965341L; + private static final long serialVersionUID = -3995246732859872806L; private UserPermission mUser; private GroupPermission mGroup; @@ -245,6 +245,30 @@ public class Permissions implements Serializable, Comparable<Permissions> { } /** + * Method that returns the default permissions for folder + * + * @return Permissions The default permissions for folder + */ + public static Permissions createDefaultFolderPermissions() { + return new Permissions( + new UserPermission(true, true, true, false), + new GroupPermission(true, false, true, false), + new OthersPermission(false, false, false, false)); + } + + /** + * Method that returns the default permissions for folder + * + * @return Permissions The default permissions for folder + */ + public static Permissions createDefaultFilePermissions() { + return new Permissions( + new UserPermission(true, true, false, false), + new GroupPermission(true, true, false, false), + new OthersPermission(false, false, false, false)); + } + + /** * Method that parses and extracts the permissions from a unix string format. * * @param rawPermissions The raw permissions diff --git a/src/com/cyanogenmod/filemanager/model/Query.java b/src/com/cyanogenmod/filemanager/model/Query.java index 612bd68e..ac1f5e67 100644 --- a/src/com/cyanogenmod/filemanager/model/Query.java +++ b/src/com/cyanogenmod/filemanager/model/Query.java @@ -145,7 +145,11 @@ public class Query implements Serializable, Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeStringArray(this.mQUERIES); + int cc = this.mQUERIES.length; + dest.writeInt(cc); + for (int i = 0; i < cc; i++) { + dest.writeString(mQUERIES[i] != null ? mQUERIES[i] : ""); + } } /** @@ -154,11 +158,15 @@ public class Query implements Serializable, Parcelable { * @param in The parcel information to recreate the object */ private void readFromParcel(Parcel in) { - String[] queries = in.readStringArray(); - if (queries != null) { - int count = Math.min(SLOTS_COUNT, queries.length); - for (int i = 0; i < count; i++) { - mQUERIES[i] = queries[i]; + int len = mQUERIES.length; + int cc = in.readInt(); + for (int i = 0; i < cc; i++) { + String query = in.readString(); + if (i >= len) { + continue; + } + if (!TextUtils.isEmpty(query)) { + mQUERIES[i] = query; } } } diff --git a/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java b/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java index a6da308e..dcfa7af6 100644 --- a/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java +++ b/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java @@ -32,7 +32,7 @@ import java.util.List; */ public class SearchInfoParcelable extends HistoryNavigable { - private static final long serialVersionUID = 3051428434374087971L; + private static final long serialVersionUID = -124315348462060329L; private String mSearchDirectory; private List<SearchResult> mSearchResultList; @@ -43,8 +43,11 @@ public class SearchInfoParcelable extends HistoryNavigable { /** * Constructor of <code>SearchInfoParcelable</code>. */ - public SearchInfoParcelable() { + public SearchInfoParcelable(String searchDirectory, List<SearchResult> searchResultList, Query searchQuery) { super(); + mSearchDirectory = searchDirectory; + mSearchResultList = searchResultList; + mSearchQuery = searchQuery; setTitle(); } @@ -92,15 +95,6 @@ public class SearchInfoParcelable extends HistoryNavigable { } /** - * Method that sets the directory where to search. - * - * @param searchDirectory The directory where to search - */ - public void setSearchDirectory(String searchDirectory) { - this.mSearchDirectory = searchDirectory; - } - - /** * Method that returns the search result list. * * @return List<SearchResult> The search result list @@ -110,15 +104,6 @@ public class SearchInfoParcelable extends HistoryNavigable { } /** - * Method that sets the search result list. - * - * @param searchResultList The search result list - */ - public void setSearchResultList(List<SearchResult> searchResultList) { - this.mSearchResultList = searchResultList; - } - - /** * Method that returns the query terms of the search. * * @return Query The query terms of the search @@ -128,16 +113,6 @@ public class SearchInfoParcelable extends HistoryNavigable { } /** - * Method that sets the query terms of the search. - * - * @param searchQuery The query terms of the search - */ - public void setSearchQuery(Query searchQuery) { - this.mSearchQuery = searchQuery; - setTitle(); - } - - /** * Method that returns if the search navigation was success. * * @return boolean If the search navigation was success @@ -208,7 +183,8 @@ public class SearchInfoParcelable extends HistoryNavigable { //- 2 int hasSearchQuery = in.readInt(); if (hasSearchQuery == 1) { - this.mSearchQuery = (Query)in.readParcelable(getClass().getClassLoader()); + this.mSearchQuery = (Query)in.readParcelable( + SearchInfoParcelable.class.getClassLoader()); } setTitle(); //- 3 diff --git a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java index 93309dae..caf64f3f 100644 --- a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java +++ b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java @@ -143,6 +143,13 @@ public enum FileManagerSettings { SETTINGS_SAVE_SEARCH_TERMS("cm_filemanager_save_search_terms", Boolean.TRUE), //$NON-NLS-1$ /** + * When to delayed filesystem synchronization in secure storages + * @hide + */ + SETTINGS_SECURE_STORAGE_DELAYED_SYNC("cm_filemanager_secure_storage_delayed_sync", + Boolean.TRUE), //$NON-NLS-1$ + + /** * When to show debug traces * @hide */ @@ -193,7 +200,16 @@ public enum FileManagerSettings { * @hide */ SETTINGS_THEME("cm_filemanager_theme", //$NON-NLS-1$ - "com.cyanogenmod.filemanager:light"); //$NON-NLS-1$ + "com.cyanogenmod.filemanager:light"), + + /** + * The current theme to use in the app + * @hide + */ + USER_PREF_LAST_DRAWER_TAB("last_drawer_tab", //$NON-NLS-1$ + Integer.valueOf(0)); + + /** * A broadcast intent that is sent when a setting was changed @@ -208,6 +224,12 @@ public enum FileManagerSettings { "com.cyanogenmod.filemanager.INTENT_THEME_CHANGED"; //$NON-NLS-1$ /** + * A broadcast intent that is sent when a setting was changed + */ + public final static String INTENT_MOUNT_STATUS_CHANGED = + "com.cyanogenmod.filemanager.INTENT_MOUNT_STATUS_CHANGED"; //$NON-NLS-1$ + + /** * A broadcast intent that is sent when a file was changed */ public final static String INTENT_FILE_CHANGED = @@ -233,6 +255,16 @@ public enum FileManagerSettings { */ public final static String EXTRA_THEME_ID = "id"; //$NON-NLS-1$ + /** + * The extra key with the identifier a mountpoint event + */ + public final static String EXTRA_MOUNTPOINT = "mount_point"; //$NON-NLS-1$ + + /** + * The extra key with the notify the status of an object + */ + public final static String EXTRA_STATUS = "status"; //$NON-NLS-1$ + diff --git a/src/com/cyanogenmod/filemanager/preferences/Preferences.java b/src/com/cyanogenmod/filemanager/preferences/Preferences.java index 3cdc595e..b07ec394 100644 --- a/src/com/cyanogenmod/filemanager/preferences/Preferences.java +++ b/src/com/cyanogenmod/filemanager/preferences/Preferences.java @@ -282,6 +282,8 @@ public final class Preferences { editor.putBoolean(pref.getId(), ((Boolean)value).booleanValue()); } else if (value instanceof String && pref.getDefaultValue() instanceof String) { editor.putString(pref.getId(), (String)value); + } else if (value instanceof Integer && pref.getDefaultValue() instanceof Integer) { + editor.putInt(pref.getId(), (Integer)value); } else if (value instanceof Set && pref.getDefaultValue() instanceof Set) { editor.putStringSet(pref.getId(), (Set<String>)value); } else if (value instanceof ObjectIdentifier diff --git a/src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java b/src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java new file mode 100644 index 00000000..b06fc738 --- /dev/null +++ b/src/com/cyanogenmod/filemanager/providers/SecureResourceProvider.java @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.providers; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Message; +import android.os.Handler.Callback; +import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; +import android.util.Log; + +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.model.RegularFile; +import com.cyanogenmod.filemanager.util.CommandHelper; +import com.cyanogenmod.filemanager.util.MimeTypeHelper; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * A {@link ContentProvider} to allow access secure filesystems. + */ +public final class SecureResourceProvider extends ContentProvider { + + private static final String TAG = "SecureResourceProvider"; + + public static final String AUTHORITY = + "com.cyanogenmod.filemanager.providers.resources"; + + private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY; + + private static final String COLUMS_ID = "auth_id"; + private static final String COLUMS_NAME = OpenableColumns.DISPLAY_NAME; + private static final String COLUMS_SIZE = OpenableColumns.SIZE; + + private static final String[] COLUMN_PROJECTION = { + COLUMS_ID, COLUMS_NAME, COLUMS_SIZE + }; + + public static class AuthorizationResource { + public final RegularFile mFile; + private String mPackage; + + private AuthorizationResource(RegularFile file) { + mFile = file; + mPackage = null; + } + } + + /** + * An implementation of an {@code AsyncResultListener} + */ + private static class AsyncReader implements AsyncResultListener { + + private final CancellationSignal mSignal; + private final ParcelFileDescriptor mFdIn; + private final ParcelFileDescriptor mFdOut; + private final OutputStream mOut; + + public AsyncReader(ParcelFileDescriptor fdIn, ParcelFileDescriptor fdOut, + CancellationSignal signal) throws IOException { + super(); + mFdIn = fdIn; + mFdOut = fdOut; + mOut = new ParcelFileDescriptor.AutoCloseOutputStream(fdOut); + mSignal = signal; + } + + @Override + public void onAsyncStart() { + // Ignore + } + + @Override + public void onAsyncEnd(boolean cancelled) { + // Ignore + } + + @Override + public void onAsyncExitCode(int exitCode) { + close(); + } + + @Override + public void onPartialResult(Object result) { + try { + if (result == null) return; + byte[] partial = (byte[])result; + mOut.write(partial); + mOut.flush(); + } catch (Exception ex) { + Log.w(TAG, "Failed to parse partial result data", ex); + closeWithError("Failed to parse partial result data: " + ex.getMessage()); + if (mSignal != null) { + mSignal.cancel(); + } + } + } + + @Override + public void onException(Exception cause) { + Log.w(TAG, "Got exception while reading data", cause); + closeWithError("Got exception while reading data: " + cause.getMessage()); + if (mSignal != null) { + mSignal.cancel(); + } + } + + private void close() { + try { + mOut.close(); + } catch (IOException ex) { + // Ignore + } + try { + mFdOut.close(); + } catch (IOException ex) { + // Ignore + } + } + + private void closeWithError(String msg) { + try { + mOut.close(); + } catch (IOException ex) { + // Ignore + } + try { + mFdOut.closeWithError(msg); + } catch (IOException ex) { + // Ignore + } + try { + mFdIn.close(); + } catch (IOException ex) { + // Ignore + } + } + } + + private static final Callback CLEAR_AUTH_CALLBACK = new Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_CLEAR_AUTHORIZATIONS: + // Remove authorization + UUID uuid = UUID.fromString(msg.getData().getString(EXTRA_AUTH_ID)); + AUTHORIZATIONS.remove(uuid); + break; + + default: + break; + } + return true; + } + }; + + private static final long MAX_AUTH_LIVE_TIME = 20000L; + private static final int MSG_CLEAR_AUTHORIZATIONS = 1; + private static final String EXTRA_AUTH_ID = "auth_id"; + private static final Handler CLEAR_AUTH_HANDLER = new Handler(CLEAR_AUTH_CALLBACK); + + private static Map<UUID, AuthorizationResource> AUTHORIZATIONS = + (Map<UUID, AuthorizationResource>) Collections.synchronizedMap( + new HashMap<UUID, AuthorizationResource>()); + + private final ExecutorService mExecutorService = Executors.newFixedThreadPool(1); + + /** + * This method creates an authorization uri for a file, but this not grants + * access to this file. Callers must explicitly call to grantAuthorization in + * order to set the package associated with this grant + * + * @param fso The file to authorize + * @return Uri The uri of this authorized resource + */ + public static Uri createAuthorizationUri(RegularFile file) { + // Generate a new authorization for the filesystem + UUID uuid = null; + do { + uuid = UUID.randomUUID(); + if (!AUTHORIZATIONS.containsKey(uuid)) { + AuthorizationResource resource = new AuthorizationResource(file); + AUTHORIZATIONS.put(uuid, resource); + break; + } + } while(true); + + // Post a message to clear authorization after an interval of time + Message msg = Message.obtain(CLEAR_AUTH_HANDLER, MSG_CLEAR_AUTHORIZATIONS); + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_AUTH_ID, uuid.toString()); + msg.setData(bundle); + CLEAR_AUTH_HANDLER.sendMessageDelayed(msg, MAX_AUTH_LIVE_TIME); + return createAuthorizationUri(uuid); + } + + /** + * Method that register the {@link FileSystemObject} that allow external apps to access + * private files. An authorization MUST be explicit done by this app. Third party apps + * can register + * + * @param uri The authorized uri + * @param pkg The package to authorize + */ + public static void grantAuthorizationUri(Uri uri, String pkg) { + // Check that exists that authorization + AuthorizationResource authResource = getAuthorizacionResourceForUri(uri); + if (authResource == null) { + throw new SecurityException("Authorization not exists"); + } + + // Check that the authorization doesn't was granted before + if (authResource.mPackage != null) { + throw new SecurityException("The authorization was granted before"); + } + + // And now grant the access + Log.i(TAG, "grant authorization of uri " + uri.toString() + " to package " + pkg); + authResource.mPackage = pkg; + } + + /** + * Method that unregister un-granted authorizations. + * + * @param uri The authorized uri + */ + public static AuthorizationResource revertAuthorization(Uri uri) { + // Check that exists that authorization + AuthorizationResource authResource = getAuthorizacionResourceForUri(uri); + if (authResource == null) { + throw new SecurityException("Authorization not exists"); + } + + // Check that the authorization was granted before + if (authResource.mPackage != null) { + throw new SecurityException("The authorization was granted before"); + } + + // And now remove the un-granted authorization + UUID uuid = UUID.fromString(uri.getLastPathSegment()); + return AUTHORIZATIONS.remove(uuid); + } + + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + // Retrieve the authorization + AuthorizationResource authResource = getAuthorizacionResourceForUri(uri); + if (authResource == null) { + throw new SecurityException("Authorization not exists"); + } + + // Create an in-memory cursor + String[] cols = new String[COLUMN_PROJECTION.length]; + Object[] values = new Object[COLUMN_PROJECTION.length]; + for (int i = 0; i < COLUMN_PROJECTION.length; i++) { + cols[i] = COLUMN_PROJECTION[i]; + switch (i) { + case 0: + values[i] = uri.getLastPathSegment(); + break; + case 1: + values[i] = authResource.mFile.getName(); + break; + case 2: + values[i] = authResource.mFile.getSize(); + break; + + default: + break; + } + } + + final MatrixCursor cursor = new MatrixCursor(cols, 1); + cursor.addRow(values); + return cursor; + } + + @Override + public String getType(Uri uri) { + // Retrieve the authorization + AuthorizationResource authResource = getAuthorizacionResourceForUri(uri); + if (authResource == null) { + throw new SecurityException("Authorization not exists"); + } + + return MimeTypeHelper.getMimeType(getContext(), authResource.mFile); + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + throw new SecurityException("Insert is not allowed"); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new SecurityException("Delete is not allowed"); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new SecurityException("Update is not allowed"); + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + return this.openFile(uri, mode, null); + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode, final CancellationSignal signal) + throws FileNotFoundException { + // Retrieve the authorization + final AuthorizationResource authResource = getAuthorizacionResourceForUri(uri); + if (authResource == null) { + throw new SecurityException("Authorization not exists"); + } + + // Check that the request comes from the authorized package + String[] pkgs = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid()); + if (pkgs == null) { + throw new SecurityException("Authorization denied. No packages"); + } + boolean isPackageAuthorized = false; + for (String pkg : pkgs) { + if (pkg.equals(authResource.mPackage)) { + isPackageAuthorized = true; + break; + } + } + if (!isPackageAuthorized) { + throw new SecurityException("Authorization denied. Package mismatch"); + } + + // Open a pipe between the package and this provider + try { + final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe(); + mExecutorService.execute(new Runnable() { + @Override + public void run() { + try { + AsyncReader reader = new AsyncReader(fds[0], fds[1], signal); + CommandHelper.read(getContext(), authResource.mFile.getFullPath(), + reader, null); + } catch (Exception e) { + Log.w(TAG, "Failure writing pipe. ", e); + } + } + }); + return fds[0]; + + } catch (IOException ex) { + Log.w(TAG, "Failed to create pipe descriptors. ", ex); + } + return null; + } + + /** + * Method that returns an authorization for the passed Uri. + * + * @param uri The uri to check + * @param revoke Whether revoke the grant + * @return AuthorizationResource The authorization resource or null if not there is not + * authorization + */ + private static AuthorizationResource getAuthorizacionResourceForUri(Uri uri) { + UUID uuid = UUID.fromString(uri.getLastPathSegment()); + if (uuid == null || !AUTHORIZATIONS.containsKey(uuid)) { + return null; + } + return AUTHORIZATIONS.get(uuid); + } + + /** + * Method that returns an authorization URI from the authorization UUID + * + * @param uuid The UUID of the authorization + * @return Uri The authorization Uri + */ + private static Uri createAuthorizationUri(UUID uuid) { + return Uri.withAppendedPath(Uri.parse(CONTENT_AUTHORITY), + uuid.toString()); + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java index c789fca5..5f076548 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java +++ b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java @@ -36,13 +36,17 @@ import com.cyanogenmod.filemanager.FileManagerApplication; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.activities.NavigationActivity; import com.cyanogenmod.filemanager.adapters.TwoColumnsMenuListAdapter; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; import com.cyanogenmod.filemanager.listeners.OnSelectionListener; import com.cyanogenmod.filemanager.model.Bookmark; +import com.cyanogenmod.filemanager.model.Directory; import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.Symlink; import com.cyanogenmod.filemanager.model.SystemFile; import com.cyanogenmod.filemanager.preferences.AccessMode; +import com.cyanogenmod.filemanager.preferences.FileManagerSettings; +import com.cyanogenmod.filemanager.preferences.Preferences; import com.cyanogenmod.filemanager.ui.ThemeManager; import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; import com.cyanogenmod.filemanager.ui.policy.BookmarksActionPolicy; @@ -57,6 +61,7 @@ import com.cyanogenmod.filemanager.ui.policy.NavigationActionPolicy; import com.cyanogenmod.filemanager.ui.policy.NewActionPolicy; import com.cyanogenmod.filemanager.ui.policy.PrintActionPolicy; import com.cyanogenmod.filemanager.util.DialogHelper; +import com.cyanogenmod.filemanager.util.ExceptionUtil; import com.cyanogenmod.filemanager.util.FileHelper; import com.cyanogenmod.filemanager.util.MimeTypeHelper; import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory; @@ -64,6 +69,7 @@ import com.cyanogenmod.filemanager.util.SelectionHelper; import com.cyanogenmod.filemanager.util.StorageHelper; import java.io.File; +import java.io.InvalidClassException; import java.util.ArrayList; import java.util.List; @@ -437,6 +443,19 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen this.mContext, this.mFso, this.mOnRequestRefreshListener); break; + // Set as home + case R.id.mnu_actions_set_as_home: + case R.id.mnu_actions_global_set_as_home: + try { + Preferences.savePreference( + FileManagerSettings.SETTINGS_INITIAL_DIR, mFso.getFullPath(), true); + mOnRequestRefreshListener.onRequestBookmarksRefresh(); + DialogHelper.showToast(mContext, R.string.msgs_success, Toast.LENGTH_SHORT); + } catch (InvalidClassException e) { + ExceptionUtil.translateException(mContext, e); + } + break; + default: break; } @@ -651,8 +670,7 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen } //- Print (only for text and image categories) - if (category.compareTo(MimeTypeCategory.TEXT) != 0 && - category.compareTo(MimeTypeCategory.IMAGE) != 0) { + if (!PrintActionPolicy.isPrintedAllowed(mContext, mFso)) { menu.removeItem(R.id.mnu_actions_print); } } @@ -680,18 +698,21 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen } } //- Create link - if (this.mGlobal && (selection == null || selection.size() == 0 || selection.size() > 1)) { + if (this.mGlobal && (selection == null || selection.size() == 0 + || selection.size() > 1)) { // Only when one item is selected menu.removeItem(R.id.mnu_actions_create_link_global); } else if (this.mGlobal && selection != null) { - // Create link (not allow in storage volume) + // Create link (not allow in sdcard, secure or remote storage volumes) FileSystemObject fso = selection.get(0); - if (StorageHelper.isPathInStorageVolume(fso.getFullPath())) { - menu.removeItem(R.id.mnu_actions_create_link); + if (StorageHelper.isPathInStorageVolume(fso.getFullPath()) + || fso.isSecure() || fso.isRemote()) { + menu.removeItem(R.id.mnu_actions_create_link_global); } } else if (!this.mGlobal) { - // Create link (not allow in storage volume) - if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) { + // Create link (not allow in sdcard, secure or remote storage volumes) + if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath()) + || mFso.isSecure() || mFso.isRemote()) { menu.removeItem(R.id.mnu_actions_create_link); } } @@ -702,10 +723,18 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen if (this.mGlobal) { if (selection == null || selection.size() == 0) { menu.removeItem(R.id.mnu_actions_compress_selection); + } else { + for (FileSystemObject fso : selection) { + // Ignore for system, secure or remote files + if (fso instanceof SystemFile || fso.isSecure() || fso.isRemote()) { + menu.removeItem(R.id.mnu_actions_compress_selection); + break; + } + } } } else { - // Ignore for system files - if (this.mFso instanceof SystemFile) { + // Ignore for system, secure or remote files + if (this.mFso instanceof SystemFile || mFso.isSecure() || mFso.isRemote()) { menu.removeItem(R.id.mnu_actions_compress); } } @@ -735,16 +764,68 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen } } + // Shotcuts and Bookmarks (not available in virtual filesystems) + if (!mGlobal && (mFso.isSecure() || mFso.isRemote())) { + menu.removeItem(R.id.mnu_actions_add_shortcut); + menu.removeItem(R.id.mnu_actions_add_to_bookmarks); + } else if (mGlobal) { + if (selection != null && selection.size() > 0) { + for (FileSystemObject fso : selection) { + if (fso.isSecure() || fso.isRemote()) { + menu.removeItem(R.id.mnu_actions_add_shortcut_current_folder); + menu.removeItem(R.id.mnu_actions_add_to_bookmarks_current_folder); + break; + } + } + } + } + + // Set as home + if (!mGlobal && !FileHelper.isDirectory(mFso)) { + menu.removeItem(R.id.mnu_actions_set_as_home); + } else if (mGlobal && (selection != null && selection.size() > 0)) { + menu.removeItem(R.id.mnu_actions_global_set_as_home); + } + // Not allowed in search if (this.mSearch) { menu.removeItem(R.id.mnu_actions_extract); menu.removeItem(R.id.mnu_actions_compress); menu.removeItem(R.id.mnu_actions_create_link); + } else { + // Not allowed if not in search + menu.removeItem(R.id.mnu_actions_open_parent_folder); } - // Not allowed if not in search - if (!this.mSearch) { - menu.removeItem(R.id.mnu_actions_open_parent_folder); + // Remove unsafe operations over virtual mountpoint directories + List<Directory> virtualDirs = VirtualMountPointConsole.getVirtualMountableDirectories(); + if (!mGlobal && FileHelper.isDirectory(mFso) && virtualDirs.contains(mFso)) { + menu.removeItem(R.id.mnu_actions_delete); + menu.removeItem(R.id.mnu_actions_rename); + menu.removeItem(R.id.mnu_actions_compress); + menu.removeItem(R.id.mnu_actions_create_copy); + menu.removeItem(R.id.mnu_actions_create_link); + menu.removeItem(R.id.mnu_actions_add_shortcut); + menu.removeItem(R.id.mnu_actions_add_to_bookmarks); + } else if (mGlobal) { + if (selection != null && selection.size() > 0) { + for (FileSystemObject fso : selection) { + if (FileHelper.isDirectory(fso) && virtualDirs.contains(fso)) { + menu.removeItem(R.id.mnu_actions_paste_selection); + menu.removeItem(R.id.mnu_actions_move_selection); + menu.removeItem(R.id.mnu_actions_delete_selection); + menu.removeItem(R.id.mnu_actions_compress_selection); + menu.removeItem(R.id.mnu_actions_create_link_global); + menu.removeItem(R.id.mnu_actions_send_selection); + menu.removeItem(R.id.mnu_actions_create_link_global); + menu.removeItem(R.id.mnu_actions_create_link_global); + menu.removeItem(R.id.mnu_actions_create_link_global); + menu.removeItem(R.id.mnu_actions_add_shortcut_current_folder); + menu.removeItem(R.id.mnu_actions_add_to_bookmarks_current_folder); + break; + } + } + } } // Remove not-ChRooted actions (actions that can't be present when running in diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java index cd689330..80d05c13 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java +++ b/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java @@ -480,7 +480,7 @@ public class AssociationsDialog implements OnItemClickListener { if (intent != null) { // Capture security exceptions try { - this.mContext.startActivity(intent); + mContext.startActivity(intent); } catch (Exception e) { ExceptionUtil.translateException(this.mContext, e); } diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java index f7b34bb7..b1e0219c 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java +++ b/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java @@ -123,7 +123,7 @@ public class ComputeChecksumDialog implements title, layout); this.mDialog.setButton( - DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this); + DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.ok), this); // Start checksum compute try { diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java index 7c63d0a0..392f36fd 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java +++ b/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java @@ -213,13 +213,14 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis } //Configure status switch + boolean isVirtual = this.mMountPoint.isVirtual(); boolean hasPrivileged = false; try { hasPrivileged = ConsoleBuilder.isPrivileged(); } catch (Throwable ex) {/**NON BLOCK**/} boolean mountAllowed = MountPointHelper.isMountAllowed(this.mMountPoint); - if (this.mIsAdvancedMode) { + if (!isVirtual || this.mIsAdvancedMode) { if (hasPrivileged) { if (!mountAllowed) { this.mInfoMsgView.setText( @@ -236,7 +237,7 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis this.mInfoMsgView.setVisibility(View.GONE); this.mInfoMsgView.setOnClickListener(null); } - this.mIsMountAllowed = hasPrivileged && mountAllowed && this.mIsAdvancedMode; + this.mIsMountAllowed = isVirtual || (hasPrivileged && mountAllowed && this.mIsAdvancedMode); this.mSwStatus.setEnabled(this.mIsMountAllowed); this.mSwStatus.setChecked(MountPointHelper.isReadWrite(this.mMountPoint)); @@ -339,8 +340,7 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis ret = CommandHelper.remount( this.mContext, this.mMountPoint, isChecked, null); - - if (ret) { + if (ret && !mMountPoint.isSecure()) { Console bgConsole = FileManagerApplication.getBackgroundConsole(); if (bgConsole != null) { ret = CommandHelper.remount( @@ -367,6 +367,8 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis this.mInfoMsgView.setText(R.string.filesystem_info_mount_failed_msg); this.mInfoMsgView.setVisibility(View.VISIBLE); sw.setChecked(!isChecked); + } else if (mMountPoint.isSecure()) { + mDialog.dismiss(); } break; diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java index 975d335d..1ac6d001 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java +++ b/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java @@ -43,6 +43,7 @@ import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.commands.AsyncResultListener; import com.cyanogenmod.filemanager.commands.FolderUsageExecutable; import com.cyanogenmod.filemanager.console.ConsoleBuilder; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; import com.cyanogenmod.filemanager.model.AID; import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.FolderUsage; @@ -139,6 +140,7 @@ public class FsoPropertiesDialog * @hide */ boolean mIgnoreCheckEvents; + private boolean mIsVirtual; private boolean mHasPrivileged; private final boolean mIsAdvancedMode; @@ -335,13 +337,14 @@ public class FsoPropertiesDialog } // Check if permissions operations are allowed + mIsVirtual = VirtualMountPointConsole.isVirtualStorageResource(mFso.getFullPath()); try { this.mHasPrivileged = ConsoleBuilder.getConsole(this.mContext).isPrivileged(); } catch (Throwable ex) {/**NON BLOCK**/} this.mSpnOwner.setEnabled(this.mHasPrivileged); this.mSpnGroup.setEnabled(this.mHasPrivileged); // Not allowed for symlinks - if (!(this.mFso instanceof Symlink)) { + if (!mIsVirtual && !(this.mFso instanceof Symlink)) { setCheckBoxesPermissionsEnable(this.mChkUserPermission, this.mHasPrivileged); setCheckBoxesPermissionsEnable(this.mChkGroupPermission, this.mHasPrivileged); setCheckBoxesPermissionsEnable(this.mChkOthersPermission, this.mHasPrivileged); @@ -350,7 +353,7 @@ public class FsoPropertiesDialog setCheckBoxesPermissionsEnable(this.mChkGroupPermission, false); setCheckBoxesPermissionsEnable(this.mChkOthersPermission, false); } - if (!this.mHasPrivileged && this.mIsAdvancedMode) { + if (!mIsVirtual && !this.mHasPrivileged && this.mIsAdvancedMode) { this.mInfoMsgView.setVisibility(View.VISIBLE); this.mInfoMsgView.setOnClickListener(this); } @@ -523,7 +526,8 @@ public class FsoPropertiesDialog adjustSpinnerSize(this.mSpnGroup); } this.mInfoMsgView.setVisibility( - this.mHasPrivileged || !this.mIsAdvancedMode ? View.GONE : View.VISIBLE); + mIsVirtual || this.mHasPrivileged || !this.mIsAdvancedMode + ? View.GONE : View.VISIBLE); break; case R.id.fso_info_msg: @@ -1028,7 +1032,7 @@ public class FsoPropertiesDialog void setMsg(String msg) { this.mInfoMsgView.setText(msg); this.mInfoMsgView.setVisibility( - !this.mIsAdvancedMode || (this.mHasPrivileged && msg == null) ? + mIsVirtual || !this.mIsAdvancedMode || (this.mHasPrivileged && msg == null) ? View.GONE : View.VISIBLE); } diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java index ab4f8ec9..9ddc3002 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java +++ b/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.DialogInterface; import android.text.Spanned; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; diff --git a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java index 4d687dfe..01020622 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java @@ -32,8 +32,11 @@ import android.widget.Toast; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.activities.ShortcutActivity; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.RegularFile; +import com.cyanogenmod.filemanager.providers.SecureResourceProvider; +import com.cyanogenmod.filemanager.providers.SecureResourceProvider.AuthorizationResource; import com.cyanogenmod.filemanager.ui.dialogs.AssociationsDialog; import com.cyanogenmod.filemanager.util.DialogHelper; import com.cyanogenmod.filemanager.util.ExceptionUtil; @@ -99,11 +102,10 @@ public final class IntentsActionPolicy extends ActionsPolicy { // Obtain the mime/type and passed it to intent String mime = MimeTypeHelper.getMimeType(ctx, fso); - File file = new File(fso.getFullPath()); if (mime != null) { - intent.setDataAndType(getUriFromFile(ctx, file), mime); + intent.setDataAndType(getUriFromFile(ctx, fso), mime); } else { - intent.setData(getUriFromFile(ctx, file)); + intent.setData(getUriFromFile(ctx, fso)); } // Resolve the intent @@ -140,7 +142,7 @@ public final class IntentsActionPolicy extends ActionsPolicy { intent.setAction(android.content.Intent.ACTION_SEND); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setType(MimeTypeHelper.getMimeType(ctx, fso)); - Uri uri = getUriFromFile(ctx, new File(fso.getFullPath())); + Uri uri = getUriFromFile(ctx, fso); intent.putExtra(Intent.EXTRA_STREAM, uri); // Resolve the intent @@ -201,7 +203,7 @@ public final class IntentsActionPolicy extends ActionsPolicy { lastMimeType = mimeType; // Add the uri - uris.add(getUriFromFile(ctx, new File(fso.getFullPath()))); + uris.add(getUriFromFile(ctx, fso)); } if (sameMimeType) { intent.setType(lastMimeType); @@ -291,6 +293,15 @@ public final class IntentsActionPolicy extends ActionsPolicy { rie.activityInfo.packageName) == 0 && ri.activityInfo.name.compareTo( rie.activityInfo.name) == 0) { + + // Mark as internal + if (ri.activityInfo.metaData == null) { + ri.activityInfo.metaData = new Bundle(); + ri.activityInfo.metaData.putString( + EXTRA_INTERNAL_ACTION, ii.getAction()); + ri.activityInfo.metaData.putBoolean( + CATEGORY_INTERNAL_VIEWER, true); + } exists = true; break; } @@ -463,7 +474,8 @@ public final class IntentsActionPolicy extends ActionsPolicy { ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name), request); - if (isInternalEditor(ri)) { + boolean isInternalEditor = isInternalEditor(ri); + if (isInternalEditor) { String a = Intent.ACTION_VIEW; if (ri.activityInfo.metaData != null) { a = ri.activityInfo.metaData.getString( @@ -476,10 +488,47 @@ public final class IntentsActionPolicy extends ActionsPolicy { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); } + + // Grant access to resources if needed + grantSecureAccessIfNeeded(intent, ri); + return intent; } /** + * Method that add grant access to secure resources if needed + * + * @param intent The intent to grant access + * @param ri The resolved info associated with the intent + */ + public static final void grantSecureAccessIfNeeded(Intent intent, ResolveInfo ri) { + // If this intent will be serve by the SecureResourceProvider then this uri must + // be granted before we start it, only for external apps. The internal editor + // must receive an file scheme uri + Uri uri = intent.getData(); + String authority = null; + if (uri != null) { + authority = uri.getAuthority(); + } else if (intent.getExtras() != null) { + uri = (Uri) intent.getExtras().get(Intent.EXTRA_STREAM); + authority = uri.getAuthority(); + } + if (authority != null && authority.equals(SecureResourceProvider.AUTHORITY)) { + boolean isInternalEditor = isInternalEditor(ri); + if (isInternalEditor) { + // remove the authorization and change request to file scheme + AuthorizationResource auth = SecureResourceProvider.revertAuthorization(uri); + intent.setData(Uri.fromFile(new File(auth.mFile.getFullPath()))); + + } else { + // Grant access to the package + SecureResourceProvider.grantAuthorizationUri(uri, + ri.activityInfo.applicationInfo.packageName); + } + } + } + + /** * Method that returns an {@link Intent} from his {@link ComponentName} * * @param cn The ComponentName @@ -593,7 +642,17 @@ public final class IntentsActionPolicy extends ActionsPolicy { * @param ctx The current context * @param file The file to resolve */ - private static Uri getUriFromFile(Context ctx, File file) { + private static Uri getUriFromFile(Context ctx, FileSystemObject fso) { + // If the passed object is secure file then we have to provide access with + // the internal resource provider + if (fso.isSecure() && SecureConsole.isVirtualStorageResource(fso.getFullPath()) + && fso instanceof RegularFile) { + RegularFile file = (RegularFile) fso; + return SecureResourceProvider.createAuthorizationUri(file); + } + + // Try to resolve media data or return a file uri + final File file = new File(fso.getFullPath()); ContentResolver cr = ctx.getContentResolver(); Uri uri = MediaHelper.fileToContentUri(cr, file); if (uri == null) { diff --git a/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java index 0f998739..6f7087c8 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/PrintActionPolicy.java @@ -41,9 +41,13 @@ import android.util.Log; import android.widget.Toast; import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ReadExecutable; import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.util.CommandHelper; import com.cyanogenmod.filemanager.util.DialogHelper; import com.cyanogenmod.filemanager.util.ExceptionUtil; +import com.cyanogenmod.filemanager.util.FileHelper; import com.cyanogenmod.filemanager.util.MimeTypeHelper; import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory; import com.cyanogenmod.filemanager.util.StringHelper; @@ -51,11 +55,12 @@ import com.cyanogenmod.filemanager.util.StringHelper; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; @@ -67,6 +72,23 @@ public final class PrintActionPolicy extends ActionsPolicy { private static final String TAG = "PrintActionPolicy"; //$NON-NLS-1$ + private static final String PDF_FILE_EXT = "pdf"; + + /** + * Method that returns if the {@code FileSystemObject} can be printed + * + * @param ctx The current context + * @param fso The fso to check + * @return boolean If the fso can be printed + */ + public static boolean isPrintedAllowed(Context ctx, FileSystemObject fso) { + MimeTypeCategory category = MimeTypeHelper.getCategory(ctx, fso); + String extension = FileHelper.getExtension(fso); + return category.compareTo(MimeTypeCategory.TEXT) == 0 + || category.compareTo(MimeTypeCategory.IMAGE) == 0 + || (extension != null && extension.toLowerCase().equals(PDF_FILE_EXT)); + } + /** * Method that prints the passed document * @@ -83,335 +105,446 @@ public final class PrintActionPolicy extends ActionsPolicy { printImage(ctx, fso); return; } + String ext = FileHelper.getExtension(fso); + if (ext != null && ext.toLowerCase().equals(PDF_FILE_EXT)) { + printPdfDocument(ctx, fso); + return; + } DialogHelper.showToast(ctx, R.string.print_unsupported_document, Toast.LENGTH_SHORT); } + public static abstract class DocumentAdapterReader { + /** + * Read the document to an string array + * + * @param lines The array where to put the document + * @param adjustedLines The array where to put the document + */ + public abstract void read(List<String> lines, List<String> adjustedLines); + + /** + * Read the document mode [0-Invalid; 1-Text; 2-Binary] + * + * @return int The document mode + */ + public abstract int getDocumentMode(); + } + /** - * Method that prints the document as a text document - * - * @param ctx The current context - * @param fso The document to print + * A document adapter */ - private static void printTextDocument(final Context ctx, final FileSystemObject document) { - final int printPageMargins = ctx.getResources().getDimensionPixelSize( - R.dimen.print_page_margins); + private static class DocumentAdapter extends PrintDocumentAdapter { + private PrintAttributes mAttributes; + private Paint mPaint; + private RectF mTextBounds; + private List<String> mLines; + private List<String> mAdjustedLines; - PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE); - PrintAttributes attr = new PrintAttributes.Builder() - .setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT) - .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME) - .build(); - printManager.print(document.getName(), new PrintDocumentAdapter() { - private PrintAttributes mAttributes; - private Paint mPaint; - private RectF mTextBounds; - private boolean mIsBinaryDocument; - private List<String> mLines; - private List<String> mAdjustedLines; + private static final int MILS_PER_INCH = 1000; + private static final int POINTS_IN_INCH = 72; - private static final int MILS_PER_INCH = 1000; - private static final int POINTS_IN_INCH = 72; + private final Context mCtx; + private final FileSystemObject mDocument; + private final int mPrintPageMargin; + private final DocumentAdapterReader mReader; - @Override - public void onStart() { - super.onStart(); + public DocumentAdapter(Context ctx, FileSystemObject document, + DocumentAdapterReader reader) { + super(); + mCtx = ctx; + mDocument = document; + mPrintPageMargin = ctx.getResources().getDimensionPixelSize( + R.dimen.print_page_margins); + mReader = reader; + } - // Create the paint used for draw text - Typeface courier = Typeface.createFromAsset(ctx.getAssets(), - "fonts/Courier-Prime.ttf"); - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setTypeface(courier); - mPaint.setTextSize(ctx.getResources().getDimensionPixelSize( - R.dimen.print_text_size)); - mPaint.setColor(Color.BLACK); + @Override + public void onStart() { + super.onStart(); - // Get the text width and height - mTextBounds = new RectF(); - mTextBounds.right = mPaint.measureText(new char[]{'A'}, 0, 1); - mTextBounds.bottom = mPaint.getFontMetrics().descent - - mPaint.getFontMetrics().ascent + mPaint.getFontMetrics().leading; + // Create the paint used for draw text + Typeface courier = Typeface.createFromAsset(mCtx.getAssets(), + "fonts/Courier-Prime.ttf"); + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setTypeface(courier); + mPaint.setTextSize(mCtx.getResources().getDimensionPixelSize( + R.dimen.print_text_size)); + mPaint.setColor(Color.BLACK); - mLines = new ArrayList<String>(); - readFile(); - } + // Get the text width and height + mTextBounds = new RectF(); + mTextBounds.right = mPaint.measureText(new char[]{'A'}, 0, 1); + mTextBounds.bottom = mPaint.getFontMetrics().descent + - mPaint.getFontMetrics().ascent + mPaint.getFontMetrics().leading; - @Override - public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, - CancellationSignal cancellationSignal, WriteResultCallback callback) { - PrintedPdfDocument pdfDocument = new PrintedPdfDocument(ctx, - mAttributes); - try { - Rect pageContentRect = getContentRect(mAttributes); - int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width()); - int rowsPerPage = rowsPerPage(pageContentRect); - - int currentPage = 0; - int currentLine = 0; - Page page = null; - if (mAdjustedLines.size() > 0) { - page = pdfDocument.startPage(currentPage++); - printHeader(ctx, page, pageContentRect, charsPerRow); - } - // Top (with margin) + header - float top = pageContentRect.top + (mTextBounds.height() * 2); - for (String line : mAdjustedLines) { - currentLine++; - page.getCanvas().drawText(line, pageContentRect.left, - top + (currentLine * mTextBounds.height()), mPaint); - - if (currentLine >= rowsPerPage) { - if (page != null) { - printFooter(ctx, page, pageContentRect, currentPage); - pdfDocument.finishPage(page); - } - currentLine = 0; - page = pdfDocument.startPage(currentPage++); - printHeader(ctx, page, pageContentRect, charsPerRow); + mLines = new ArrayList<String>(); + mAdjustedLines = new ArrayList<String>(); + mReader.read(mLines, mAdjustedLines); + } + + @Override + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { + PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mCtx, + mAttributes); + try { + Rect pageContentRect = getContentRect(mAttributes); + int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width()); + int rowsPerPage = rowsPerPage(pageContentRect); + + int currentPage = 0; + int currentLine = 0; + Page page = null; + if (mAdjustedLines.size() > 0) { + page = pdfDocument.startPage(currentPage++); + printHeader(mCtx, page, pageContentRect, charsPerRow); + } + // Top (with margin) + header + float top = pageContentRect.top + (mTextBounds.height() * 2); + for (String line : mAdjustedLines) { + currentLine++; + page.getCanvas().drawText(line, pageContentRect.left, + top + (currentLine * mTextBounds.height()), mPaint); + + if (currentLine >= rowsPerPage) { + if (page != null) { + printFooter(mCtx, page, pageContentRect, currentPage); + pdfDocument.finishPage(page); } + currentLine = 0; + page = pdfDocument.startPage(currentPage++); + printHeader(mCtx, page, pageContentRect, charsPerRow); } + } - // Finish the last page - printFooter(ctx, page, pageContentRect, currentPage); + // Finish the last page + if (page != null) { + printFooter(mCtx, page, pageContentRect, currentPage); pdfDocument.finishPage(page); + } else { + page = pdfDocument.startPage(1); + printHeader(mCtx, page, pageContentRect, charsPerRow); + printFooter(mCtx, page, pageContentRect, currentPage); + pdfDocument.finishPage(page); + } - try { - // Write the document - pdfDocument.writeTo(new FileOutputStream(destination.getFileDescriptor())); + try { + // Write the document + pdfDocument.writeTo(new FileOutputStream(destination.getFileDescriptor())); - // Done - callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES}); + // Done + callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES}); + } catch (IOException ioe) { + // Failed. + ExceptionUtil.translateException(mCtx, ioe); + callback.onWriteFailed("Failed to print image"); + } + } finally { + if (destination != null) { + try { + destination.close(); } catch (IOException ioe) { - // Failed. - ExceptionUtil.translateException(ctx, ioe); - callback.onWriteFailed(null); - } - } finally { - if (destination != null) { - try { - destination.close(); - } catch (IOException ioe) { - /* ignore */ - } + /* ignore */ } } } + } - @Override - public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, - CancellationSignal cancellationSignal, LayoutResultCallback callback, - Bundle extras) { - - mAttributes = newAttributes; - Rect pageContentRect = getContentRect(newAttributes); - int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width()); - int rowsPerPage = rowsPerPage(pageContentRect); - adjustLines(pageContentRect, charsPerRow); + @Override + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + CancellationSignal cancellationSignal, LayoutResultCallback callback, + Bundle extras) { - PrintDocumentInfo info = new PrintDocumentInfo.Builder(document.getName()) - .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) - .setPageCount(calculatePageCount(rowsPerPage)) - .build(); - info.setDataSize(document.getSize()); - boolean changed = !newAttributes.equals(oldAttributes); - callback.onLayoutFinished(info, changed); + // Check if document is valid + if (mReader.getDocumentMode() == 0) { + callback.onLayoutFailed("Failed to read document"); + return; } - private Rect getContentRect(PrintAttributes attributes) { - MediaSize mediaSize = attributes.getMediaSize(); - - // Compute the size of the target canvas from the attributes. - int pageWidth = (int) (((float) mediaSize.getWidthMils() / MILS_PER_INCH) - * POINTS_IN_INCH); - int pageHeight = (int) (((float) mediaSize.getHeightMils() / MILS_PER_INCH) - * POINTS_IN_INCH); - - // Compute the content size from the attributes. - Margins minMargins = attributes.getMinMargins(); - final int marginLeft = (int) (((float) minMargins.getLeftMils() / MILS_PER_INCH) - * POINTS_IN_INCH); - final int marginTop = (int) (((float) minMargins.getTopMils() / MILS_PER_INCH) - * POINTS_IN_INCH); - final int marginRight = (int) (((float) minMargins.getRightMils() / MILS_PER_INCH) - * POINTS_IN_INCH); - final int marginBottom = (int) (((float) minMargins.getBottomMils() / MILS_PER_INCH) - * POINTS_IN_INCH); - return new Rect( - Math.max(marginLeft, printPageMargins), - Math.max(marginTop, printPageMargins), - pageWidth - Math.max(marginRight, printPageMargins), - pageHeight - Math.max(marginBottom, printPageMargins)); - } - - private void printHeader(Context ctx, Page page, Rect pageContentRect, - int charsPerRow) { - String header = ctx.getString(R.string.print_document_header, document.getName()); - if (header.length() >= charsPerRow) { - header = header.substring(header.length() - 3) + "..."; - } - page.getCanvas().drawText(header, - (int) (pageContentRect.width() / 2) - (mPaint.measureText(header) / 2), - pageContentRect.top + mTextBounds.height(), mPaint); + if (cancellationSignal.isCanceled()) { + callback.onLayoutCancelled(); + return; } + mAttributes = newAttributes; + Rect pageContentRect = getContentRect(newAttributes); + int charsPerRow = (int) (pageContentRect.width() / mTextBounds.width()); + int rowsPerPage = rowsPerPage(pageContentRect); + adjustLines(pageContentRect, charsPerRow); - private void printFooter(Context ctx, Page page, Rect pageContentRect, int pageNumber) { - String footer = ctx.getString(R.string.print_document_footer, pageNumber); - page.getCanvas().drawText(footer, - (int) (pageContentRect.width() / 2) - (mPaint.measureText(footer) / 2), - pageContentRect.bottom - mTextBounds.height(), mPaint); - } + PrintDocumentInfo info = new PrintDocumentInfo.Builder(mDocument.getName()) + .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) + .setPageCount(calculatePageCount(rowsPerPage)) + .build(); + info.setDataSize(mDocument.getSize()); + boolean changed = !newAttributes.equals(oldAttributes); + callback.onLayoutFinished(info, changed); + } - private void adjustLines(Rect pageRect, int charsPerRow) { - if (mIsBinaryDocument) { - return; - } - mAdjustedLines = new ArrayList<String>(mLines); - for (int i = 0; i < mAdjustedLines.size(); i++) { - String line = mAdjustedLines.get(i); - if (line.length() > charsPerRow) { - int prevSpace = line.lastIndexOf(" ", charsPerRow); - if (prevSpace != -1) { - // Split in the previous word - String currentLine = line.substring(0, prevSpace + 1); - String nextLine = line.substring(prevSpace + 1); - mAdjustedLines.set(i, currentLine); - mAdjustedLines.add(i + 1, nextLine); - } else { - // Just split at margin - String currentLine = line.substring(0, charsPerRow); - String nextLine = line.substring(charsPerRow); - mAdjustedLines.set(i, currentLine); - mAdjustedLines.add(i + 1, nextLine); - } - } - } - } + private Rect getContentRect(PrintAttributes attributes) { + MediaSize mediaSize = attributes.getMediaSize(); - private int calculatePageCount(int rowsPerPage) { - int pages = mAdjustedLines.size() / rowsPerPage; - return pages <= 0 ? PrintDocumentInfo.PAGE_COUNT_UNKNOWN : pages; - } + // Compute the size of the target canvas from the attributes. + int pageWidth = (int) (((float) mediaSize.getWidthMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + int pageHeight = (int) (((float) mediaSize.getHeightMils() / MILS_PER_INCH) + * POINTS_IN_INCH); - private int rowsPerPage(Rect pageContentRect) { - // Text height - header - footer - return (int) ((pageContentRect.height() / mTextBounds.height()) - 4); - } + // Compute the content size from the attributes. + Margins minMargins = attributes.getMinMargins(); + final int marginLeft = (int) (((float) minMargins.getLeftMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + final int marginTop = (int) (((float) minMargins.getTopMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + final int marginRight = (int) (((float) minMargins.getRightMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + final int marginBottom = (int) (((float) minMargins.getBottomMils() / MILS_PER_INCH) + * POINTS_IN_INCH); + return new Rect( + Math.max(marginLeft, mPrintPageMargin), + Math.max(marginTop, mPrintPageMargin), + pageWidth - Math.max(marginRight, mPrintPageMargin), + pageHeight - Math.max(marginBottom, mPrintPageMargin)); + } - private void readFile() { - mIsBinaryDocument = isBinaryDocument(); - if (mIsBinaryDocument) { - readHexDumpDocumentFile(); - } else { - readDocumentFile(); - } + private void printHeader(Context ctx, Page page, Rect pageContentRect, + int charsPerRow) { + String header = ctx.getString(R.string.print_document_header, mDocument.getName()); + if (header.length() >= charsPerRow) { + header = header.substring(header.length() - 3) + "..."; } + page.getCanvas().drawText(header, + (int) (pageContentRect.width() / 2) - (mPaint.measureText(header) / 2), + pageContentRect.top + mTextBounds.height(), mPaint); + } - private boolean isBinaryDocument() { - BufferedReader br = null; - try { - br = new BufferedReader(new FileReader(document.getFullPath())); - char[] data = new char[50]; - int read = br.read(data); - for (int i = 0; i < read; i++) { - if (!StringHelper.isPrintableCharacter(data[i])) { - return true; - } - } - } catch (IOException ex) { - //Ignore - } finally { - if (br != null) { - try { - br.close(); - } catch (IOException ex) { - //Ignore - } + private void printFooter(Context ctx, Page page, Rect pageContentRect, int pageNumber) { + String footer = ctx.getString(R.string.print_document_footer, pageNumber); + page.getCanvas().drawText(footer, + (int) (pageContentRect.width() / 2) - (mPaint.measureText(footer) / 2), + pageContentRect.bottom - mTextBounds.height(), mPaint); + } + + private void adjustLines(Rect pageRect, int charsPerRow) { + if (mReader.getDocumentMode() == 2) { + return; + } + mAdjustedLines = new ArrayList<String>(mLines); + for (int i = 0; i < mAdjustedLines.size(); i++) { + String line = mAdjustedLines.get(i); + if (line.length() > charsPerRow) { + int prevSpace = line.lastIndexOf(" ", charsPerRow); + if (prevSpace != -1) { + // Split in the previous word + String currentLine = line.substring(0, prevSpace + 1); + String nextLine = line.substring(prevSpace + 1); + mAdjustedLines.set(i, currentLine); + mAdjustedLines.add(i + 1, nextLine); + } else { + // Just split at margin + String currentLine = line.substring(0, charsPerRow); + String nextLine = line.substring(charsPerRow); + mAdjustedLines.set(i, currentLine); + mAdjustedLines.add(i + 1, nextLine); } } - return false; } + } + + private int calculatePageCount(int rowsPerPage) { + int pages = mAdjustedLines.size() / rowsPerPage; + return pages <= 0 ? PrintDocumentInfo.PAGE_COUNT_UNKNOWN : pages; + } + + private int rowsPerPage(Rect pageContentRect) { + // Text height - header - footer + return (int) ((pageContentRect.height() / mTextBounds.height()) - 4); + } + } - private void readDocumentFile() { + /** + * Method that prints the document from a string buffer + * + * @param ctx The current context + * @param fso The document to print + * @param sb The buffer to print + * @param adjustLines If document must be adjusted + */ + public static void printStringDocument(final Context ctx, final FileSystemObject document, + final StringBuilder sb) { + PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE); + PrintAttributes attr = new PrintAttributes.Builder() + .setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT) + .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME) + .build(); + final DocumentAdapterReader reader = new DocumentAdapterReader() { + @Override + public void read(List<String> lines, List<String> adjustedLines) { BufferedReader br = null; try { - br = new BufferedReader(new FileReader(document.getFullPath())); + int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size); + br = new BufferedReader(new StringReader(sb.toString()), bufferSize); String line = null; - while((line = br.readLine()) != null) { - mLines.add(line); + while ((line = br.readLine()) != null) { + lines.add(line); } + } catch (IOException ex) { - mLines.clear(); Log.e(TAG, "Failed to read file " + document.getFullPath(), ex); + lines.clear(); } finally { if (br != null) { try { br.close(); } catch (IOException ex) { - //Ignore + // Ignore } } } } - private void readHexDumpDocumentFile() { - InputStream is = null; - ByteArrayOutputStream baos; + @Override + public int getDocumentMode() { + // Always is text + return 1; + } + }; + printManager.print(document.getName(), new DocumentAdapter(ctx, document, reader), attr); + } + + /** + * Method that prints the document as a text document + * + * @param ctx The current context + * @param fso The document to print + */ + private static void printTextDocument(final Context ctx, final FileSystemObject document) { + PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE); + PrintAttributes attr = new PrintAttributes.Builder() + .setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT) + .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME) + .build(); + final DocumentAdapterReader reader = new DocumentAdapterReader() { + private int mDocumentMode = -1; + + @Override + public void read(List<String> lines, List<String> adjustedLines) { + mDocumentMode = getDocumentMode(); + if (mDocumentMode <= 0) { + lines.clear(); + } else if (mDocumentMode == 2) { + adjustedLines.addAll(readHexDumpDocumentFile(ctx, document, lines)); + } else { + readDocumentFile(ctx, document, lines); + } + } + + @Override + public int getDocumentMode() { + if (mDocumentMode == -1) { + String mimeType = MimeTypeHelper.getMimeType(ctx, document); + if (mimeType == null) { + mDocumentMode = 0; // Invalid + } else { + mDocumentMode = isBinaryDocument(ctx, document) ? 2 : 1; // binary / text + } + } + return mDocumentMode; + } + }; + printManager.print(document.getName(), new DocumentAdapter(ctx, document, reader), attr); + } + + /** + * Method that prints the document as a Pdf + * + * @param ctx The current context + * @param fso The pdf to print + */ + private static void printPdfDocument(final Context ctx, final FileSystemObject document) { + PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE); + PrintAttributes.MediaSize mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT; + PrintAttributes attr = new PrintAttributes.Builder() + .setMediaSize(mediaSize) + .setColorMode(PrintAttributes.COLOR_MODE_COLOR) + .build(); + printManager.print(document.getName(), new PrintDocumentAdapter() { + @Override + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { + FileInputStream fis = null; + FileOutputStream fos = null; + AsyncDocumentReader reader = null; + try { - int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size); + // Try first with java.io before using pipes - baos = new ByteArrayOutputStream(); - is = new BufferedInputStream(new FileInputStream(document.getFullPath())); + File file = new File(document.getFullPath()); + if (file.isFile() && file.canRead()) { + fis = new FileInputStream(file); + } else { + reader = new AsyncDocumentReader(ctx); + CommandHelper.read(ctx, document.getFullPath(), reader, null); + fis = reader.mIn; + } + fos = new FileOutputStream(destination.getFileDescriptor()); + + // Write the document + int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size); byte[] data = new byte[bufferSize]; int read = 0; - while((read = is.read(data, 0, bufferSize)) != -1) { - baos.write(data, 0, read); + while ((read = fis.read(data)) > 0) { + fos.write(data, 0, read); } - } catch (IOException ex) { - mLines.clear(); - Log.e(TAG, "Failed to read file " + document.getFullPath(), ex); - return; + + // All was ok + callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES}); + + } catch (Exception ex) { + // Failed. + ExceptionUtil.translateException(ctx, ex); + callback.onWriteFailed("Failed to print image"); + } finally { - if (is != null) { - try { - is.close(); - } catch (IOException ex) { - //Ignore + try { + if (fis != null) { + fis.close(); } + } catch (IOException e) { + // Ignore } - } - - // Convert the bytes to a hex printable string and free resources - String documentBuffer = StringHelper.toHexPrintableString(baos.toByteArray()); - try { - baos.close(); - } catch (IOException ex) { - //Ignore - } - - BufferedReader br = null; - try { - br = new BufferedReader(new StringReader(documentBuffer)); - String line = null; - while((line = br.readLine()) != null) { - mLines.add(line); + try { + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + // Ignore } - } catch (IOException ex) { - mLines.clear(); - Log.e(TAG, "Failed to read file " + document.getFullPath(), ex); - } finally { - if (br != null) { + if (reader != null && reader.mIn != null) { try { - br.close(); + reader.mIn.close(); } catch (IOException ex) { //Ignore } } } - - // Use the final array and clear the original (we don't use it anymore) - mAdjustedLines = new ArrayList<String>(mLines); - mLines.clear(); } + @Override + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + CancellationSignal cancellationSignal, LayoutResultCallback callback, + Bundle extras) { + + if (cancellationSignal.isCanceled()) { + callback.onLayoutCancelled(); + return; + } + + PrintDocumentInfo info = new PrintDocumentInfo.Builder(document.getName()) + .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) + .build(); + boolean changed = !newAttributes.equals(oldAttributes); + callback.onLayoutFinished(info, changed); + } }, attr); } @@ -428,13 +561,52 @@ public final class PrintActionPolicy extends ActionsPolicy { return; } - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Bitmap.Config.RGB_565; - final Bitmap bitmap = BitmapFactory.decodeFile(image.getFullPath(), options); + Bitmap bitmap = null; + AsyncDocumentReader reader = null; + try { + // Try first with java.io before using pipes + File file = new File(image.getFullPath()); + if (file.isFile() && file.canRead()) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + bitmap = BitmapFactory.decodeFile(image.getFullPath(), options); + } else { + reader = new AsyncDocumentReader(ctx); + CommandHelper.read(ctx, image.getFullPath(), reader, null); + + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + bitmap = BitmapFactory.decodeStream(reader.mIn); + } + if (bitmap == null) { + throw new IOException("Failed to load image"); + } + + } catch (Exception ex) { + ExceptionUtil.translateException(ctx, ex); + return; + } finally { + if (reader != null && reader.mIn != null) { + try { + reader.mIn.close(); + } catch (IOException ex) { + //Ignore + } + } + if (reader != null && reader.mFdIn != null) { + try { + reader.mFdIn.close(); + } catch (IOException ex) { + //Ignore + } + } + } + + final Bitmap fBitmap = bitmap; PrintManager printManager = (PrintManager) ctx.getSystemService(Context.PRINT_SERVICE); PrintAttributes.MediaSize mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT; - if (bitmap.getWidth() > bitmap.getHeight()) { + if (fBitmap.getWidth() > fBitmap.getHeight()) { mediaSize = PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE; } PrintAttributes attr = new PrintAttributes.Builder() @@ -451,13 +623,11 @@ public final class PrintActionPolicy extends ActionsPolicy { mAttributes); try { Page page = pdfDocument.startPage(1); - RectF content = new RectF(page.getInfo().getContentRect()); - - Matrix matrix = getMatrix(bitmap.getWidth(), bitmap.getHeight(), content); + Matrix matrix = getMatrix(fBitmap.getWidth(), fBitmap.getHeight(), content); // Draw the bitmap. - page.getCanvas().drawBitmap(bitmap, matrix, null); + page.getCanvas().drawBitmap(fBitmap, matrix, null); // Finish the page. pdfDocument.finishPage(page); @@ -471,7 +641,7 @@ public final class PrintActionPolicy extends ActionsPolicy { } catch (IOException ioe) { // Failed. ExceptionUtil.translateException(ctx, ioe); - callback.onWriteFailed(null); + callback.onWriteFailed("Failed to print image"); } } finally { if (pdfDocument != null) { @@ -492,6 +662,10 @@ public final class PrintActionPolicy extends ActionsPolicy { CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { + if (cancellationSignal.isCanceled()) { + callback.onLayoutCancelled(); + return; + } mAttributes = newAttributes; PrintDocumentInfo info = new PrintDocumentInfo.Builder(image.getName()) @@ -505,8 +679,8 @@ public final class PrintActionPolicy extends ActionsPolicy { @Override public void onFinish() { super.onFinish(); - if (bitmap != null) { - bitmap.recycle(); + if (fBitmap != null) { + fBitmap.recycle(); } } @@ -546,4 +720,305 @@ public final class PrintActionPolicy extends ActionsPolicy { } return bitmap != null; } -}
\ No newline at end of file + + /** + * Method that checks if the file has a binary format + * + * @param ctx The current context + * @param document The document to read + * @return boolean If the document has a binary format + */ + private static boolean isBinaryDocument(Context ctx, FileSystemObject document) { + BufferedReader br = null; + boolean binary = false; + AsyncDocumentReader reader = null; + try { + reader = new AsyncDocumentReader(ctx); + ReadExecutable command = CommandHelper.read(ctx, document.getFullPath(), reader, null); + br = new BufferedReader(new InputStreamReader(reader.mIn)); + + char[] data = new char[50]; + int read = br.read(data); + for (int i = 0; i < read; i++) { + if (!StringHelper.isPrintableCharacter(data[i])) { + binary = true; + break; + } + } + command.cancel(); + + } catch (Exception ex) { + //Ignore + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException ex) { + //Ignore + } + } + if (reader != null && reader.mIn != null) { + try { + reader.mIn.close(); + } catch (IOException ex) { + //Ignore + } + } + if (reader != null && reader.mFdIn != null) { + try { + reader.mFdIn.close(); + } catch (IOException ex) { + //Ignore + } + } + } + return binary; + } + + /** + * Read a file as document + * + * @param ctx The current context + * @param document The document to read + * @param lines The output + */ + private static void readDocumentFile(Context ctx, FileSystemObject document, + List<String> lines) { + BufferedReader br = null; + AsyncDocumentReader reader = null; + try { + // Async read the document while blocking with a buffered reader + int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size); + reader = new AsyncDocumentReader(ctx); + CommandHelper.read(ctx, document.getFullPath(), reader, null); + br = new BufferedReader(new InputStreamReader(reader.mIn), bufferSize); + + String line = null; + while((line = br.readLine()) != null) { + lines.add(line); + } + + // Got an exception? + if (reader.mCause != null) { + lines.clear(); + Log.e(TAG, "Failed to read file " + document.getFullPath(), reader.mCause); + } + + } catch (Exception ex) { + lines.clear(); + Log.e(TAG, "Failed to read file " + document.getFullPath(), ex); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException ex) { + //Ignore + } + } + if (reader != null && reader.mIn != null) { + try { + reader.mIn.close(); + } catch (IOException ex) { + //Ignore + } + } + if (reader != null && reader.mFdIn != null) { + try { + reader.mFdIn.close(); + } catch (IOException ex) { + //Ignore + } + } + } + } + + /** + * Read a file as hex document + * + * @param ctx The current context + * @param document The document to read + * @param lines The internal output + * @return output The output + */ + private static List<String> readHexDumpDocumentFile(Context ctx, FileSystemObject document, + List<String> lines) { + InputStream is = null; + ByteArrayOutputStream baos; + AsyncDocumentReader reader = null; + try { + // Async read the document while blocking with a buffered stream + reader = new AsyncDocumentReader(ctx); + CommandHelper.read(ctx, document.getFullPath(), reader, null); + + int bufferSize = ctx.getResources().getInteger(R.integer.buffer_size); + baos = new ByteArrayOutputStream(); + is = new BufferedInputStream(reader.mIn); + + byte[] data = new byte[bufferSize]; + int read = 0; + while((read = is.read(data, 0, bufferSize)) != -1) { + baos.write(data, 0, read); + } + + // Got an exception? + if (reader.mCause != null) { + lines.clear(); + Log.e(TAG, "Failed to read file " + document.getFullPath(), reader.mCause); + } + } catch (Exception ex) { + Log.e(TAG, "Failed to read file " + document.getFullPath(), ex); + lines.clear(); + return new ArrayList<String>(); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + //Ignore + } + } + if (reader != null && reader.mIn != null) { + try { + reader.mIn.close(); + } catch (IOException ex) { + //Ignore + } + } + if (reader != null && reader.mFdIn != null) { + try { + reader.mFdIn.close(); + } catch (IOException ex) { + //Ignore + } + } + } + + // Convert the bytes to a hex printable string and free resources + String documentBuffer = StringHelper.toHexPrintableString(baos.toByteArray()); + try { + baos.close(); + } catch (IOException ex) { + //Ignore + } + + BufferedReader br = null; + try { + br = new BufferedReader(new StringReader(documentBuffer)); + String line = null; + while((line = br.readLine()) != null) { + lines.add(line); + } + } catch (IOException ex) { + lines.clear(); + Log.e(TAG, "Failed to read file " + document.getFullPath(), ex); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException ex) { + //Ignore + } + } + } + + // Use the final array and clear the original (we don't use it anymore) + List<String> output = new ArrayList<String>(lines); + lines.clear(); + return output; + } + + /** + * An implementation of an {@code AsyncResultListener} based on pipes for readers + */ + private static class AsyncDocumentReader implements AsyncResultListener { + + final FileInputStream mIn; + private final FileOutputStream mOut; + final ParcelFileDescriptor mFdIn; + private final ParcelFileDescriptor mFdOut; + Exception mCause; + + public AsyncDocumentReader(Context ctx) throws IOException { + super(); + + ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe(); + mFdIn = fds[0]; + mFdOut = fds[1]; + mIn = new ParcelFileDescriptor.AutoCloseInputStream(mFdIn); + mOut = new ParcelFileDescriptor.AutoCloseOutputStream(mFdOut); + mCause = null; + } + + @Override + public void onAsyncStart() { + // Ignore + } + + @Override + public void onAsyncEnd(boolean cancelled) { + // Ignore + } + + @Override + public void onAsyncExitCode(int exitCode) { + close(); + } + + @Override + public void onPartialResult(Object result) { + try { + if (result == null) return; + byte[] partial = (byte[])result; + mOut.write(partial); + mOut.flush(); + } catch (Exception ex) { + Log.w(TAG, "Failed to parse partial result data", ex); + closeWithError("Failed to parse partial result data: " + ex.getMessage()); + mCause = ex; + } + } + + @Override + public void onException(Exception cause) { + Log.w(TAG, "Got exception while reading data", cause); + closeWithError("Got exception while reading data: " + cause.getMessage()); + mCause = cause; + } + + private void close() { + try { + mOut.close(); + } catch (IOException ex) { + // Ignore + } + try { + mFdOut.close(); + } catch (IOException ex) { + // Ignore + } + } + + private void closeWithError(String msg) { + try { + mOut.close(); + } catch (IOException ex) { + // Ignore + } + try { + mIn.close(); + } catch (IOException ex) { + // Ignore + } + try { + mFdOut.closeWithError(msg); + } catch (IOException ex) { + // Ignore + } + try { + mFdIn.close(); + } catch (IOException ex) { + // Ignore + } + } + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java b/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java index 22b5b261..38f4a951 100644 --- a/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java +++ b/src/com/cyanogenmod/filemanager/ui/preferences/ColorPickerPreference.java @@ -229,7 +229,6 @@ public class ColorPickerPreference extends DialogPreference { /** * A class that generates instances of the <code>SavedState</code> class from a Parcel. */ - @SuppressWarnings("hiding") public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { diff --git a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java b/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java index bb70bfbd..164ec6ec 100644 --- a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java +++ b/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java @@ -306,7 +306,6 @@ public class ThemeSelectorPreference extends Preference implements OnClickListen /** * A class for create the saved state */ - @SuppressWarnings("hiding") public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java b/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java index 4ef9e483..2397ac32 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/ActionBarDrawerToggle.java @@ -31,12 +31,10 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LevelListDrawable; import android.os.Build; -import android.util.Log; import android.view.Gravity; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; /** * This class provides a handy way to tie together the functionality of @@ -71,7 +69,6 @@ import android.widget.ImageView; * </p> */ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { - private static final String TAG = "ActionBarDrawerToggle"; /** * Allows an implementing Activity to return an @@ -148,16 +145,13 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { } } + @SuppressWarnings("unused") private static class SetIndicatorInfo { - public Method setHomeAsUpIndicator; - public Method setHomeActionContentDescription; - public ImageView upIndicatorView; - SetIndicatorInfo(Activity activity) { try { - setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod( + Method setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod( "setHomeAsUpIndicator", Drawable.class); - setHomeActionContentDescription = ActionBar.class + Method setHomeActionContentDescription = ActionBar.class .getDeclaredMethod("setHomeActionContentDescription", Integer.TYPE); @@ -185,16 +179,9 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { final View first = parent.getChildAt(0); final View second = parent.getChildAt(1); final View up = first.getId() == android.R.id.home ? second : first; - - if (up instanceof ImageView) { - // Jackpot! (Probably...) - upIndicatorView = (ImageView) up; - } } } - private static final ActionBarDrawerToggleImpl IMPL = new ActionBarDrawerToggleImpl(); - /** Fraction of its total width by which to offset the toggle drawable. */ private static final float TOGGLE_DRAWABLE_OFFSET = 1 / 3f; @@ -462,7 +449,7 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { if (mActivityImpl != null) { return mActivityImpl.getThemeUpIndicator(); } - return IMPL.getThemeUpIndicator(mActivity); + return ActionBarDrawerToggleImpl.getThemeUpIndicator(mActivity); } void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { @@ -470,7 +457,7 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes); return; } - mSetIndicatorInfo = IMPL.setActionBarUpIndicator(mSetIndicatorInfo, + mSetIndicatorInfo = ActionBarDrawerToggleImpl.setActionBarUpIndicator(mSetIndicatorInfo, mActivity, upDrawable, contentDescRes); } @@ -479,7 +466,7 @@ public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { mActivityImpl.setActionBarDescription(contentDescRes); return; } - mSetIndicatorInfo = IMPL.setActionBarDescription(mSetIndicatorInfo, + mSetIndicatorInfo = ActionBarDrawerToggleImpl.setActionBarDescription(mSetIndicatorInfo, mActivity, contentDescRes); } diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java b/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java index 41071056..8f06c4ef 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbView.java @@ -35,6 +35,7 @@ import com.cyanogenmod.filemanager.tasks.FilesystemAsyncTask; import com.cyanogenmod.filemanager.ui.ThemeManager; import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; import com.cyanogenmod.filemanager.util.FileHelper; +import com.cyanogenmod.filemanager.util.MountPointHelper; import com.cyanogenmod.filemanager.util.StorageHelper; import java.io.File; @@ -370,5 +371,18 @@ public class BreadcrumbView extends RelativeLayout implements Breadcrumb, OnClic Drawable dw = theme.getDrawable(getContext(), "horizontal_progress_bar"); //$NON-NLS-1$ this.mDiskUsageInfo.setProgressDrawable(dw); } + final ImageView fsInfo = (ImageView)findViewById(R.id.ab_filesystem_info); + if (fsInfo != null) { + MountPoint mp = (MountPoint) fsInfo.getTag(); + if (mp == null) { + theme.setImageDrawable(getContext(), fsInfo, "filesystem_warning_drawable"); + } else { + String resource = + MountPointHelper.isReadOnly(mp) + ? "filesystem_locked_drawable" + : "filesystem_unlocked_drawable"; + theme.setImageDrawable(getContext(), fsInfo, resource); + } + } } } diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java b/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java index 0787e64d..ecd0cfa0 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java @@ -162,7 +162,6 @@ public class DiskUsageGraph extends View { * {@inheritDoc} */ @Override - @SuppressWarnings("null") public void run() { //Get information about the drawing zone, and adjust the size Rect rect = new Rect(); diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java b/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java index 04a9130b..641d8f18 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java @@ -463,7 +463,7 @@ public class FlingerListView extends ListView { // What is the motion if (!this.mScrolling && this.mFlingingView != null) { - if(!this.mMoveStarted && !this.mLongPress) { + if (!this.mMoveStarted && !this.mLongPress) { this.mFlingingView.removeCallbacks(this.mLongPressDetection); this.mFlingingView.setPressed(true); this.mFlingingViewPressed = true; diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java index 1f1ce3ec..9573f682 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java @@ -38,7 +38,9 @@ import com.cyanogenmod.filemanager.FileManagerApplication; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter; import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter.OnSelectionChangedListener; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.ConsoleAllocException; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; import com.cyanogenmod.filemanager.listeners.OnHistoryListener; import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; import com.cyanogenmod.filemanager.listeners.OnSelectionListener; @@ -299,6 +301,9 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe /**NON BLOCK**/ } } + if (ex instanceof CancelledOperationException) { + return null; + } //Capture exception (attach task, and use listener to do the anim) ExceptionUtil.attachAsyncTask( @@ -973,6 +978,16 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe * Method that changes the current directory of the view. * * @param newDir The new directory location + * @param addToHistory Add the directory to history + */ + public void changeCurrentDir(final String newDir, boolean addToHistory) { + changeCurrentDir(newDir, addToHistory, false, false, null, null); + } + + /** + * Method that changes the current directory of the view. + * + * @param newDir The new directory location * @param searchInfo The search information (if calling activity is {@link "SearchActivity"}) */ public void changeCurrentDir(final String newDir, SearchInfoParcelable searchInfo) { @@ -998,6 +1013,27 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, newDir); } + /** + * Remove all unmounted files in the current selection + */ + public void removeUnmountedSelection() { + List<FileSystemObject> selection = mAdapter.getSelectedItems(); + int cc = selection.size() - 1; + for (int i = cc; i >= 0; i--) { + FileSystemObject item = selection.get(i); + VirtualMountPointConsole vc = + VirtualMountPointConsole.getVirtualConsoleForPath(item.getFullPath()); + if (vc != null && !vc.isMounted()) { + selection.remove(i); + } + } + mAdapter.setSelectedItems(selection); + mAdapter.notifyDataSetChanged(); + + // Do not call the selection listener. This method is supposed to be called by the + // listener itself + } + /** * Method invoked when a execution ends. @@ -1203,6 +1239,14 @@ BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRe * {@inheritDoc} */ @Override + public void onRequestBookmarksRefresh() { + // Ignore + } + + /** + * {@inheritDoc} + */ + @Override public void onRequestRemove(Object o, boolean clearSelection) { if (o != null && o instanceof FileSystemObject) { removeItem((FileSystemObject)o); diff --git a/src/com/cyanogenmod/filemanager/util/AIDHelper.java b/src/com/cyanogenmod/filemanager/util/AIDHelper.java index d49f1687..c117cfdb 100644 --- a/src/com/cyanogenmod/filemanager/util/AIDHelper.java +++ b/src/com/cyanogenmod/filemanager/util/AIDHelper.java @@ -19,12 +19,17 @@ package com.cyanogenmod.filemanager.util; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.Process; import android.util.Log; import android.util.SparseArray; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.model.AID; +import com.cyanogenmod.filemanager.model.Group; +import com.cyanogenmod.filemanager.model.Identity; +import com.cyanogenmod.filemanager.model.User; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; @@ -137,4 +142,18 @@ public final class AIDHelper { return null; } + /** + * Method that return a virtual identity composed by the name of the current process + * + * @return Identity The virtual identity + */ + public static Identity createVirtualIdentity() { + AID aid = AIDHelper.getAID(Process.myUid()); + if (aid == null) return null; + return new Identity( + new User(aid.getId(), aid.getName()), + new Group(aid.getId(), aid.getName()), + new ArrayList<Group>()); + } + } diff --git a/src/com/cyanogenmod/filemanager/util/AndroidHelper.java b/src/com/cyanogenmod/filemanager/util/AndroidHelper.java index 34194923..891e6e3a 100644 --- a/src/com/cyanogenmod/filemanager/util/AndroidHelper.java +++ b/src/com/cyanogenmod/filemanager/util/AndroidHelper.java @@ -19,7 +19,11 @@ package com.cyanogenmod.filemanager.util; import android.app.ActivityManager; import android.app.ActivityManager.MemoryInfo; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; import android.content.res.Configuration; import android.content.res.Resources; import android.os.UserHandle; @@ -27,13 +31,21 @@ import android.os.UserManager; import android.util.DisplayMetrics; import android.view.ViewConfiguration; -import com.cyanogenmod.filemanager.R; +import com.android.internal.util.HexDump; + +import java.io.ByteArrayInputStream; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; /** * A helper class with useful methods for deal with android. */ public final class AndroidHelper { + private static Boolean sIsAppPlatformSigned; + /** * Method that returns if the device is a tablet * @@ -91,18 +103,47 @@ public final class AndroidHelper { * @return boolean If the app is signed with the platform signature */ public static boolean isAppPlatformSignature(Context ctx) { - // TODO This need to be improved, checking if the app is really with the platform signature - try { - // For now only check that the app is installed in system directory - PackageManager pm = ctx.getPackageManager(); - String appDir = pm.getApplicationInfo(ctx.getPackageName(), 0).sourceDir; - String systemDir = ctx.getString(R.string.system_dir); - return appDir.startsWith(systemDir); - - } catch (Exception e) { - ExceptionUtil.translateException(ctx, e, true, false); + if (sIsAppPlatformSigned == null) { + try { + // First check that the app is installed as a system app + PackageManager pm = ctx.getPackageManager(); + ApplicationInfo ai = pm.getApplicationInfo(ctx.getPackageName(), + PackageManager.GET_SIGNATURES); + if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + sIsAppPlatformSigned = Boolean.FALSE; + } else { + final MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + final CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + // Get signature of the current package + PackageInfo info = pm.getPackageInfo(ctx.getPackageName(), + PackageManager.GET_SIGNATURES); + Signature[] signatures = info.signatures; + X509Certificate cert = (X509Certificate) cf.generateCertificate( + new ByteArrayInputStream(signatures[0].toByteArray())); + sha1.update(cert.getEncoded()); + String appHash = HexDump.toHexString(sha1.digest()); + + // Get the signature of the system package + info = pm.getPackageInfo("android", + PackageManager.GET_SIGNATURES); + signatures = info.signatures; + cert = (X509Certificate) cf.generateCertificate( + new ByteArrayInputStream(signatures[0].toByteArray())); + sha1.update(cert.getEncoded()); + String systemHash = HexDump.toHexString(sha1.digest()); + + // Is platform signed? + sIsAppPlatformSigned = appHash.equals(systemHash); + } + + } catch (NameNotFoundException e) { + sIsAppPlatformSigned = Boolean.FALSE; + } catch (GeneralSecurityException e) { + sIsAppPlatformSigned = Boolean.FALSE; + } } - return false; + return sIsAppPlatformSigned.booleanValue(); } public static boolean hasSupportForMultipleUsers(Context context) { diff --git a/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java b/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java index 0bc5dfee..8f35895d 100644 --- a/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java +++ b/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java @@ -49,6 +49,12 @@ public final class BookmarksHelper { if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.USB) == 0) { return "ic_usb_drawable"; //$NON-NLS-1$ } + if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.SECURE) == 0) { + return "ic_secure_drawable"; //$NON-NLS-1$ + } + if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.REMOTE) == 0) { + return "ic_remote_drawable"; //$NON-NLS-1$ + } //Bookmark add by the user return "ic_user_defined_bookmark_drawable"; //$NON-NLS-1$ } diff --git a/src/com/cyanogenmod/filemanager/util/CommandHelper.java b/src/com/cyanogenmod/filemanager/util/CommandHelper.java index 7d1b236d..4c98e95d 100644 --- a/src/com/cyanogenmod/filemanager/util/CommandHelper.java +++ b/src/com/cyanogenmod/filemanager/util/CommandHelper.java @@ -17,6 +17,7 @@ package com.cyanogenmod.filemanager.util; import android.content.Context; +import android.content.Intent; import android.media.MediaScannerConnection; import com.cyanogenmod.filemanager.commands.AsyncResultListener; @@ -24,6 +25,7 @@ import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; import com.cyanogenmod.filemanager.commands.ChecksumExecutable; import com.cyanogenmod.filemanager.commands.CompressExecutable; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; import com.cyanogenmod.filemanager.commands.CopyExecutable; import com.cyanogenmod.filemanager.commands.CreateDirExecutable; import com.cyanogenmod.filemanager.commands.CreateFileExecutable; @@ -54,6 +56,8 @@ import com.cyanogenmod.filemanager.commands.UncompressExecutable; import com.cyanogenmod.filemanager.commands.WritableExecutable; import com.cyanogenmod.filemanager.commands.WriteExecutable; import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException; +import com.cyanogenmod.filemanager.console.AuthenticationFailedException; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.CommandNotFoundException; import com.cyanogenmod.filemanager.console.Console; import com.cyanogenmod.filemanager.console.ConsoleAllocException; @@ -63,6 +67,8 @@ import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; import com.cyanogenmod.filemanager.console.OperationTimeoutException; import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; +import com.cyanogenmod.filemanager.console.VirtualMountPointConsole; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; import com.cyanogenmod.filemanager.model.DiskUsage; import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.FolderUsage; @@ -74,10 +80,12 @@ import com.cyanogenmod.filemanager.model.Query; import com.cyanogenmod.filemanager.model.SearchResult; import com.cyanogenmod.filemanager.model.User; import com.cyanogenmod.filemanager.preferences.CompressionMode; +import com.cyanogenmod.filemanager.preferences.FileManagerSettings; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; import java.util.List; @@ -150,7 +158,8 @@ public final class CommandHelper { createMountExecutable( UnmountAsyncResultListener.this.mMountPoint, false); - UnmountAsyncResultListener.this.mConsole.execute(unmountExecutable); + UnmountAsyncResultListener.this.mConsole.execute( + unmountExecutable, mCtx); } catch (Exception e) { // Capture the exception but not show to the user ExceptionUtil.translateException( @@ -210,14 +219,16 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see ChangeOwnerExecutable */ public static boolean changeOwner( Context context, String src, User user, Group group, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, - CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { + CommandNotFoundException, OperationTimeoutException, ExecutionException, + InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { Console c = ensureConsole(context, console); ChangeOwnerExecutable executable = c.getExecutableFactory(). @@ -245,6 +256,7 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see ChangePermissionsExecutable */ public static boolean changePermissions( @@ -252,7 +264,8 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { Console c = ensureConsole(context, console); ChangePermissionsExecutable executable = c.getExecutableFactory().newCreator(). @@ -279,14 +292,16 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see CreateDirExecutable */ public static boolean createDirectory(Context context, String directory, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { + Console c = ensureConsoleForFile(context, console, directory); CreateDirExecutable executable = c.getExecutableFactory().newCreator().createCreateDirectoryExecutable(directory); writableExecute(context, executable, c); @@ -311,14 +326,16 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see CreateFileExecutable */ public static boolean createFile(Context context, String file, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { + Console c = ensureConsoleForFile(context, console, file); CreateFileExecutable executable = c.getExecutableFactory().newCreator().createCreateFileExecutable(file); writableExecute(context, executable, c); @@ -348,14 +365,16 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see DeleteDirExecutable */ public static boolean deleteDirectory(Context context, String directory, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { + Console c = ensureConsoleForFile(context, console, directory); DeleteDirExecutable executable = c.getExecutableFactory().newCreator().createDeleteDirExecutable(directory); writableExecute(context, executable, c); @@ -388,14 +407,16 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see DeleteFileExecutable */ public static boolean deleteFile(Context context, String file, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { + Console c = ensureConsoleForFile(context, console, file); DeleteFileExecutable executable = c.getExecutableFactory().newCreator().createDeleteFileExecutable(file); writableExecute(context, executable, c); @@ -427,13 +448,14 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ResolveLinkExecutable */ public static FileSystemObject resolveSymlink(Context context, String symlink, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); ResolveLinkExecutable executable = c.getExecutableFactory().newCreator().createResolveLinkExecutable(symlink); @@ -458,13 +480,14 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ListExecutable */ public static FileSystemObject getFileInfo(Context context, String src, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { return getFileInfo(context, src, true, console); } @@ -486,6 +509,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ListExecutable */ public static FileSystemObject getFileInfo( @@ -493,8 +517,8 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + Console c = ensureConsoleForFile(context, console, src); ListExecutable executable = c.getExecutableFactory(). newCreator().createFileInfoExecutable(src, followSymlinks); @@ -526,13 +550,14 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see GroupsExecutable */ public static List<Group> getGroups(Context context, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); GroupsExecutable executable = c.getExecutableFactory().newCreator().createGroupsExecutable(); @@ -541,28 +566,29 @@ public final class CommandHelper { } /** - * Method that retrieves the identity of the current user. - * - * @param context The current context (needed if console == null) - * @param console The console in which execute the program. <code>null</code> - * to attach to the default console - * @return Identity The identity of the current user - * @throws FileNotFoundException If the initial directory not exists - * @throws IOException If initial directory couldn't be checked - * @throws InvalidCommandDefinitionException If the command has an invalid definition - * @throws NoSuchFileOrDirectory If the file or directory was not found - * @throws ConsoleAllocException If the console can't be allocated - * @throws InsufficientPermissionsException If an operation requires elevated permissions - * @throws CommandNotFoundException If the command was not found - * @throws OperationTimeoutException If the operation exceeded the maximum time of wait - * @throws ExecutionException If the operation returns a invalid exit code - * @see IdentityExecutable - */ + * Method that retrieves the identity of the current user. + * + * @param context The current context (needed if console == null) + * @param console The console in which execute the program. <code>null</code> + * to attach to the default console + * @return Identity The identity of the current user + * @throws FileNotFoundException If the initial directory not exists + * @throws IOException If initial directory couldn't be checked + * @throws InvalidCommandDefinitionException If the command has an invalid definition + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws ConsoleAllocException If the console can't be allocated + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws CommandNotFoundException If the command was not found + * @throws OperationTimeoutException If the operation exceeded the maximum time of wait + * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled + * @see IdentityExecutable + */ public static Identity getIdentity(Context context, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); IdentityExecutable executable = c.getExecutableFactory().newCreator().createIdentityExecutable(); @@ -575,7 +601,7 @@ public final class CommandHelper { * * @param context The current context (needed if console == null) * @param src The absolute path to the source fso - * @param link The absolute path to the link fso + * @param link The absolute path to the link fso * @param console The console in which execute the program. <code>null</code> * to attach to the default console * @return boolean The operation result @@ -589,13 +615,15 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see LinkExecutable */ public static boolean createLink(Context context, String src, String link, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { Console c = ensureConsole(context, console); LinkExecutable executable = c.getExecutableFactory().newCreator().createLinkExecutable(src, link); @@ -620,14 +648,15 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ParentDirExecutable */ public static String getParentDir(Context context, String src, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + Console c = ensureConsoleForFile(context, console, src); ParentDirExecutable executable = c.getExecutableFactory().newCreator().createParentDirExecutable(src); execute(context, executable, c); @@ -652,13 +681,14 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see EchoExecutable */ public static String getVariable(Context context, String msg, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); EchoExecutable executable = c.getExecutableFactory().newCreator().createEchoExecutable(msg); @@ -683,6 +713,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ListExecutable */ public static List<FileSystemObject> listFiles( @@ -690,14 +721,21 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException, + CancelledOperationException { + Console c = ensureConsoleForFile(context, console, directory); ListExecutable executable = c.getExecutableFactory().newCreator(). createListExecutable(directory); execute(context, executable, c); List<FileSystemObject> result = executable.getResult(); FileHelper.resolveSymlinks(context, result); + + // And now we need to verify if the directory is the + if (VirtualMountPointConsole.isVirtualStorageDir(directory)) { + result.addAll(VirtualMountPointConsole.getVirtualMountableDirectories()); + } + return result; } @@ -720,28 +758,72 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see MoveExecutable */ public static boolean move(Context context, String src, String dst, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { - Console c = ensureConsole(context, console); - MoveExecutable executable = - c.getExecutableFactory().newCreator().createMoveExecutable(src, dst); - writableExecute(context, executable, c); + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { + Console cSrc = ensureConsoleForFile(context, console, src); + Console cDst = ensureConsoleForFile(context, console, dst); + boolean ret = true; + if (cSrc.equals(cDst)) { + // Is safe to use the same console + MoveExecutable executable = + cSrc.getExecutableFactory().newCreator().createMoveExecutable(src, dst); + writableExecute(context, executable, cSrc); + ret = executable.getResult().booleanValue(); + } else { + // We need to create a temporary file in the external filesystem to make it + // available to virtual consoles + + // 1.- Move to a temporary file with the source console (destination + // is a safe location) + File tmp = FileHelper.createTempFilename(context, true); + try { + MoveExecutable moveExecutable = + cSrc.getExecutableFactory().newCreator().createMoveExecutable( + src, tmp.getAbsolutePath()); + writableExecute(context, moveExecutable, cSrc); + if (!moveExecutable.getResult().booleanValue()) { + ret = false; + } - // Do media scan - File parent = new File(src).getParentFile(); - if (parent != null) { - MediaScannerConnection.scanFile(context, new String[]{ - MediaHelper.normalizeMediaPath(parent.getAbsolutePath())}, null, null); + // 2.- Move the temporary file to the final filesystem with the destination console + if (ret) { + moveExecutable = + cDst.getExecutableFactory().newCreator().createMoveExecutable( + tmp.getAbsolutePath(), dst); + writableExecute(context, moveExecutable, cDst); + if (!moveExecutable.getResult().booleanValue()) { + ret = false; + } + } + + } finally { + FileHelper.deleteFileOrFolder(tmp); + } } - MediaScannerConnection.scanFile(context, new String[]{ - MediaHelper.normalizeMediaPath(dst)}, null, null); - return executable.getResult().booleanValue(); + // Do media scan (don't scan the file if is virtual file) + if (ret) { + File parent = new File(src).getParentFile(); + if (parent != null) { + if (!VirtualMountPointConsole.isVirtualStorageResource(parent.getAbsolutePath())) { + MediaScannerConnection.scanFile(context, new String[]{ + MediaHelper.normalizeMediaPath(parent.getAbsolutePath())}, null, null); + } + } + if (!VirtualMountPointConsole.isVirtualStorageResource(parent.getAbsolutePath())) { + MediaScannerConnection.scanFile(context, new String[]{ + MediaHelper.normalizeMediaPath(dst)}, null, null); + } + } + + return ret; } /** @@ -763,23 +845,65 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see CopyExecutable */ public static boolean copy(Context context, String src, String dst, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { - Console c = ensureConsole(context, console); - CopyExecutable executable = - c.getExecutableFactory().newCreator().createCopyExecutable(src, dst); - writableExecute(context, executable, c); + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { + Console cSrc = ensureConsoleForFile(context, console, src); + Console cDst = ensureConsoleForFile(context, console, dst); + boolean ret = true; + if (cSrc.equals(cDst)) { + // Is safe to use the same console + CopyExecutable executable = + cSrc.getExecutableFactory().newCreator().createCopyExecutable(src, dst); + writableExecute(context, executable, cSrc); + ret = executable.getResult().booleanValue(); + } else { + // We need to create a temporary file in the external filesystem to make it + // available to virtual consoles + + // 1.- Copy to a temporary file with the source console (destination + // is a safe location) + File tmp = FileHelper.createTempFilename(context, true); + try { + CopyExecutable copyExecutable = + cSrc.getExecutableFactory().newCreator().createCopyExecutable( + src, tmp.getAbsolutePath()); + writableExecute(context, copyExecutable, cSrc); + if (!copyExecutable.getResult().booleanValue()) { + ret = false; + } - // Do media scan - MediaScannerConnection.scanFile(context, new String[]{ - MediaHelper.normalizeMediaPath(dst)}, null, null); + // 2.- Move the temporary file to the final filesystem with the destination console + if (ret) { + MoveExecutable moveExecutable = + cDst.getExecutableFactory().newCreator().createMoveExecutable( + tmp.getAbsolutePath(), dst); + writableExecute(context, moveExecutable, cDst); + if (!moveExecutable.getResult().booleanValue()) { + ret = false; + } + } - return executable.getResult().booleanValue(); + } finally { + FileHelper.deleteFileOrFolder(tmp); + } + } + + // Do media scan (don't scan the file if is virtual file) + if (ret) { + if (!VirtualMountPointConsole.isVirtualStorageResource(dst)) { + MediaScannerConnection.scanFile(context, new String[]{ + MediaHelper.normalizeMediaPath(dst)}, null, null); + } + } + + return ret; } /** @@ -800,6 +924,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ExecExecutable */ public static ExecExecutable exec( @@ -807,7 +932,7 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); ExecExecutable executable = c.getExecutableFactory().newCreator(). @@ -835,22 +960,48 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see SearchResult * @see FindExecutable */ public static FindExecutable findFiles( Context context, String directory, Query search, - AsyncResultListener asyncResultListener, Console console) + ConcurrentAsyncResultListener asyncResultListener, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); - FindExecutable executable = - c.getExecutableFactory().newCreator(). - createFindExecutable(directory, search, asyncResultListener); - execute(context, executable, c); - return executable; + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + List<Console> consoles = new ArrayList<Console>(); + List<FindExecutable> executables = new ArrayList<FindExecutable>(); + Console c = ensureConsoleForFile(context, console, directory); + consoles.add(c); + + // Obtain all the rest of console that will participate in the search, that aren't the + // current console + List<Console> vcs = VirtualMountPointConsole.getVirtualConsoleForSearchPath(directory); + for (int i = vcs.size() - 1; i >= 0; i--) { + Console vc = vcs.get(i); + if (vc.equals(c)) { + vcs.remove(i); + } + } + consoles.addAll(vcs); + + // Register all the executables + for (Console cc : consoles) { + executables.add( + cc.getExecutableFactory().newCreator(). + createFindExecutable(directory, search, asyncResultListener)); + } + + // Launch every executable + int count = executables.size(); + for (int i = 0; i < count; i++) { + execute(context, executables.get(i), consoles.get(i)); + } + + // Return the first of the executables + return executables.get(0); } /** @@ -871,6 +1022,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see FolderUsage * @see FolderUsageExecutable */ @@ -880,8 +1032,8 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + Console c = ensureConsoleForFile(context, console, directory); FolderUsageExecutable executable = c.getExecutableFactory().newCreator(). createFolderUsageExecutable(directory, asyncResultListener); @@ -905,18 +1057,21 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see DiskUsageExecutable */ public static List<DiskUsage> getDiskUsage(Context context, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); DiskUsageExecutable executable = c.getExecutableFactory().newCreator().createDiskUsageExecutable(); execute(context, executable, c); - return executable.getResult(); + List<DiskUsage> diskUsage = executable.getResult(); + diskUsage.addAll(VirtualMountPointConsole.getVirtualDiskUsage()); + return diskUsage; } /** @@ -936,20 +1091,29 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see DiskUsageExecutable */ public static DiskUsage getDiskUsage(Context context, String dir, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); - DiskUsageExecutable executable = - c.getExecutableFactory().newCreator().createDiskUsageExecutable(dir); - execute(context, executable, c); - List<DiskUsage> du = executable.getResult(); - if (du != null && du.size() > 0) { - return du.get(0); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + + // Virtual directories don't implement a disk usage command, just return the data if + // the directory belongs to a virtual filesystem + VirtualMountPointConsole vc = VirtualMountPointConsole.getVirtualConsoleForPath(dir); + if (vc != null) { + return vc.getDiskUsage(dir); + } else { + Console c = ensureConsole(context, console); + DiskUsageExecutable executable = + c.getExecutableFactory().newCreator().createDiskUsageExecutable(dir); + execute(context, executable, c); + List<DiskUsage> du = executable.getResult(); + if (du != null && du.size() > 0) { + return du.get(0); + } } return null; } @@ -970,18 +1134,21 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see MountPointInfoExecutable */ public static List<MountPoint> getMountPoints(Context context, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); MountPointInfoExecutable executable = c.getExecutableFactory().newCreator().createMountPointInfoExecutable(); execute(context, executable, c); - return executable.getResult(); + List<MountPoint> mountPoints = executable.getResult(); + mountPoints.addAll(VirtualMountPointConsole.getVirtualMountPoints()); + return mountPoints; } /** @@ -1002,18 +1169,43 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see MountExecutable */ public static boolean remount(Context context, MountPoint mp, boolean rw, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); - MountExecutable executable = - c.getExecutableFactory().newCreator().createMountExecutable(mp, rw); - execute(context, executable, c); - return executable.getResult().booleanValue(); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + boolean ret = false; + if (mp.isSecure()) { + // Unmount the secure file system + SecureConsole sc = (SecureConsole) ensureConsoleForFile( + context, console, mp.getMountPoint()); + if (rw) { + sc.mount(context); + } else { + sc.unmount(); + } + ret = true; + } else { + Console c = ensureConsole(context, console); + MountExecutable executable = + c.getExecutableFactory().newCreator().createMountExecutable(mp, rw); + execute(context, executable, c); + ret = executable.getResult().booleanValue(); + } + + if (ret) { + // Send an broadcast to notify that the mount state of this filesystem changed + Intent intent = new Intent(FileManagerSettings.INTENT_MOUNT_STATUS_CHANGED); + intent.putExtra(FileManagerSettings.EXTRA_MOUNTPOINT, mp.getMountPoint()); + intent.putExtra(FileManagerSettings.EXTRA_STATUS, rw + ? MountExecutable.READWRITE : MountExecutable.READONLY); + context.sendBroadcast(intent); + } + + return ret; } /** @@ -1033,13 +1225,14 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see QuickFolderSearchExecutable */ public static List<String> quickFolderSearch(Context context, String regexp, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); QuickFolderSearchExecutable executable = c.getExecutableFactory().newCreator().createQuickFolderSearchExecutable(regexp); @@ -1065,6 +1258,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ProcessIdExecutable */ public static List<Integer> getProcessesIds( @@ -1072,7 +1266,7 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); ProcessIdExecutable executable = c.getExecutableFactory().newCreator().createProcessIdExecutable(pid); @@ -1099,6 +1293,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ProcessIdExecutable */ public static Integer getProcessId( @@ -1106,7 +1301,7 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); ProcessIdExecutable executable = c.getExecutableFactory().newCreator().createProcessIdExecutable(pid, processName); @@ -1135,6 +1330,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ProcessIdExecutable */ public static void sendSignal( @@ -1142,7 +1338,7 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); SendSignalExecutable executable = c.getExecutableFactory().newCreator().createSendSignalExecutable(process, signal); @@ -1165,6 +1361,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see ProcessIdExecutable */ public static void sendSignal( @@ -1172,7 +1369,7 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { Console c = ensureConsole(context, console); SendSignalExecutable executable = c.getExecutableFactory().newCreator().createKillExecutable(process); @@ -1197,6 +1394,7 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled * @see "byte[]" * @see ReadExecutable */ @@ -1206,8 +1404,8 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + Console c = ensureConsoleForFile(context, console, file); ReadExecutable executable = c.getExecutableFactory().newCreator(). createReadExecutable(file, asyncResultListener); @@ -1234,6 +1432,7 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see WriteExecutable */ public static WriteExecutable write( @@ -1242,8 +1441,9 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { + Console c = ensureConsoleForFile(context, console, file); // Create a wrapper listener, for unmount the filesystem if necessary UnmountAsyncResultListener wrapperListener = new UnmountAsyncResultListener(); @@ -1293,6 +1493,7 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see CompressExecutable */ public static CompressExecutable compress( @@ -1301,7 +1502,8 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { Console c = ensureConsole(context, console); // Create a wrapper listener, for unmount the filesystem if necessary @@ -1365,6 +1567,7 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see CompressExecutable */ public static CompressExecutable compress( @@ -1373,7 +1576,8 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { Console c = ensureConsole(context, console); // Create a wrapper listener, for unmount the filesystem if necessary @@ -1429,6 +1633,7 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled * @see CompressExecutable */ public static UncompressExecutable uncompress( @@ -1437,7 +1642,8 @@ public final class CommandHelper { throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException { + ExecutionException, InvalidCommandDefinitionException, ReadOnlyFilesystemException, + CancelledOperationException { Console c = ensureConsole(context, console); // Create a wrapper listener, for unmount the filesystem if necessary @@ -1479,7 +1685,7 @@ public final class CommandHelper { // Do media scan MediaScannerConnection.scanFile(context, new String[]{ - MediaHelper.normalizeMediaPath(dst)}, null, null); + MediaHelper.normalizeMediaPath(compressOutFile)}, null, null); return executable1; } @@ -1495,7 +1701,7 @@ public final class CommandHelper { * @param asyncResultListener The partial result listener * @param console The console in which execute the program. * <code>null</code> to attach to the default console - * @return WriteExecutable The command executed in background + * @return ChecksumExecutable The command executed in background * @throws FileNotFoundException If the initial directory not exists * @throws IOException If initial directory couldn't be checked * @throws InvalidCommandDefinitionException If the command has an invalid definition @@ -1505,16 +1711,16 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code - * @see WriteExecutable + * @throws CancelledOperationException If the operation was cancelled + * @see ChecksumExecutable */ - public static ChecksumExecutable checksum( - Context context, String src, + public static ChecksumExecutable checksum(Context context, String src, AsyncResultListener asyncResultListener, Console console) throws FileNotFoundException, IOException, ConsoleAllocException, NoSuchFileOrDirectory, InsufficientPermissionsException, CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); + ExecutionException, InvalidCommandDefinitionException, CancelledOperationException { + Console c = ensureConsoleForFile(context, console, src); ChecksumExecutable executable = c.getExecutableFactory().newCreator(). createChecksumExecutable(src, asyncResultListener); @@ -1539,6 +1745,7 @@ public final class CommandHelper { * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem * @throws InvalidCommandDefinitionException If the command has an invalid definition * @throws IOException If initial directory couldn't be checked + * @throws CancelledOperationException If the operation was cancelled * @throws FileNotFoundException If the initial directory not exists */ public static Object reexecute( @@ -1546,9 +1753,10 @@ public final class CommandHelper { throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, OperationTimeoutException, ExecutionException, CommandNotFoundException, ReadOnlyFilesystemException, - FileNotFoundException, IOException, InvalidCommandDefinitionException { + FileNotFoundException, IOException, InvalidCommandDefinitionException, + CancelledOperationException { Console c = ensureConsole(context, console); - c.execute(executable); + c.execute(executable, context); return executable.getResult(); } @@ -1566,13 +1774,16 @@ public final class CommandHelper { * @throws CommandNotFoundException If the command was not found * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code + * @throws CancelledOperationException If the operation was cancelled + * @throws AuthenticationFailedException If the operation failed caused by an + * authentication failure */ private static void execute(Context context, Executable executable, Console console) throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, - OperationTimeoutException, ExecutionException, - CommandNotFoundException { + OperationTimeoutException, ExecutionException, CommandNotFoundException, + CancelledOperationException, AuthenticationFailedException { try { - console.execute(executable); + console.execute(executable, context); } catch (ReadOnlyFilesystemException rofEx) { // ReadOnlyFilesystemException don't have sense if command is not writable // WritableExecutable must be used with "writableExecute" method @@ -1595,12 +1806,15 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled + * @throws AuthenticationFailedException If the operation failed caused by an + * authentication failure */ - private static void writableExecute( - Context context, WritableExecutable executable, Console console) - throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, - OperationTimeoutException, ExecutionException, - CommandNotFoundException, ReadOnlyFilesystemException { + private static void writableExecute(Context context, WritableExecutable executable, + Console console) throws ConsoleAllocException, InsufficientPermissionsException, + NoSuchFileOrDirectory, OperationTimeoutException, ExecutionException, + CommandNotFoundException, ReadOnlyFilesystemException, CancelledOperationException, + AuthenticationFailedException{ writableExecute(context, executable, console, false); } @@ -1622,13 +1836,15 @@ public final class CommandHelper { * @throws OperationTimeoutException If the operation exceeded the maximum time of wait * @throws ExecutionException If the operation returns a invalid exit code * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @throws CancelledOperationException If the operation was cancelled + * @throws AuthenticationFailedException If the operation failed caused by an + * authentication failure */ - private static boolean writableExecute( - Context context, WritableExecutable executable, Console console, - boolean leaveDeviceMounted) - throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, - OperationTimeoutException, ExecutionException, - CommandNotFoundException, ReadOnlyFilesystemException { + private static boolean writableExecute(Context context, WritableExecutable executable, + Console console, boolean leaveDeviceMounted) throws ConsoleAllocException, + InsufficientPermissionsException, NoSuchFileOrDirectory, OperationTimeoutException, + ExecutionException, CommandNotFoundException, ReadOnlyFilesystemException, + CancelledOperationException, AuthenticationFailedException { //Retrieve the mount point information to check if a remount operation is required //There are 2 mount points: destination and source. Check both @@ -1699,17 +1915,17 @@ public final class CommandHelper { try { if (needMountDst) { //Execute the mount command - console.execute(mountDstExecutable); + console.execute(mountDstExecutable, context); mountExecutedDst = true; } if (needMountSrc) { //Execute the mount command - console.execute(mountSrcExecutable); + console.execute(mountSrcExecutable, context); mountExecutedSrc = true; } //Execute the command - console.execute(executable); + console.execute(executable, context); } catch (InsufficientPermissionsException ipEx) { //Configure the commands to execute @@ -1739,11 +1955,11 @@ public final class CommandHelper { //and unmount operation if (mountExecutedDst && !leaveDeviceMounted) { //Execute the unmount command - console.execute(unmountDstExecutable); + console.execute(unmountDstExecutable, context); } if (mountExecutedSrc && !leaveDeviceMounted) { //Execute the unmount command - console.execute(unmountSrcExecutable); + console.execute(unmountSrcExecutable, context); } } @@ -1774,4 +1990,36 @@ public final class CommandHelper { return c; } + /** + * Method that ensure the console retrieve the default console if a console + * is not passed. + * + * @param context The current context (needed if console == null) + * @param console The console passed + * @param src The source file to check + * @return Console The console passed if not is null. Otherwise, the default console + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws ConsoleAllocException If the console can't be allocated + * @throws InvalidCommandDefinitionException If the command has an invalid definition + * @throws IOException If initial directory couldn't be checked + * @throws FileNotFoundException If the initial directory not exists + */ + private static Console ensureConsoleForFile(Context context, Console console, String src) + throws FileNotFoundException, IOException, InvalidCommandDefinitionException, + ConsoleAllocException, InsufficientPermissionsException { + + // Check if the path belongs to a virtual mount point + Console c = VirtualMountPointConsole.getVirtualConsoleForPath(src); + if (c != null) { + return c; + } + + // Recover a real console + c = console; + if (c == null) { + c = ConsoleBuilder.getConsole(context); + } + return c; + } + } diff --git a/src/com/cyanogenmod/filemanager/util/DialogHelper.java b/src/com/cyanogenmod/filemanager/util/DialogHelper.java index 0dc9965c..40464452 100644 --- a/src/com/cyanogenmod/filemanager/util/DialogHelper.java +++ b/src/com/cyanogenmod/filemanager/util/DialogHelper.java @@ -416,6 +416,84 @@ public final class DialogHelper { } /** + * Method that creates a new {@link AlertDialog} with one buttons. + * + * @param context The current context + * @param button1 The resource identifier of the text of the button 1 (POSITIVE) + * @param icon The icon resource + * @param title The title of the alert dialog + * @param content The content layout + * @param onClickListener The listener where returns the button pressed + * @return AlertDialog The alert dialog reference + */ + public static AlertDialog createOneButtonsDialog(Context context, int button1, + int icon, String title, View content, OnClickListener onClickListener) { + //Create the alert dialog + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCustomTitle(createTitle(context, icon, title, false)); + builder.setView(content); + AlertDialog dialog = builder.create(); + dialog.setButton( + DialogInterface.BUTTON_POSITIVE, context.getString(button1), onClickListener); + return dialog; + } + + /** + * Method that creates a new {@link AlertDialog} with two buttons. + * + * @param context The current context + * @param button1 The resource identifier of the text of the button 1 (POSITIVE) + * @param button2 The resource identifier of the text of the button 2 (NEUTRAL) + * @param icon The icon resource + * @param title The title of the alert dialog + * @param content The content layout + * @param onClickListener The listener where returns the button pressed + * @return AlertDialog The alert dialog reference + */ + public static AlertDialog createTwoButtonsDialog(Context context, int button1, int button2, + int icon, String title, View content, OnClickListener onClickListener) { + //Create the alert dialog + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCustomTitle(createTitle(context, icon, title, false)); + builder.setView(content); + AlertDialog dialog = builder.create(); + dialog.setButton( + DialogInterface.BUTTON_POSITIVE, context.getString(button1), onClickListener); + dialog.setButton( + DialogInterface.BUTTON_NEGATIVE, context.getString(button2), onClickListener); + return dialog; + } + + /** + * Method that creates a new {@link AlertDialog} with three buttons. + * + * @param context The current context + * @param button1 The resource identifier of the text of the button 1 (POSITIVE) + * @param button2 The resource identifier of the text of the button 2 (NEUTRAL) + * @param button3 The resource identifier of the text of the button 3 (NEGATIVE) + * @param icon The icon resource + * @param title The title of the alert dialog + * @param content The content layout + * @param onClickListener The listener where returns the button pressed + * @return AlertDialog The alert dialog reference + */ + public static AlertDialog createThreeButtonsDialog(Context context, int button1, int button2, + int button3, int icon, String title, View content, OnClickListener onClickListener) { + //Create the alert dialog + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCustomTitle(createTitle(context, icon, title, false)); + builder.setView(content); + AlertDialog dialog = builder.create(); + dialog.setButton( + DialogInterface.BUTTON_POSITIVE, context.getString(button1), onClickListener); + dialog.setButton( + DialogInterface.BUTTON_NEUTRAL, context.getString(button2), onClickListener); + dialog.setButton( + DialogInterface.BUTTON_NEGATIVE, context.getString(button3), onClickListener); + return dialog; + } + + /** * Method that creates and returns the title of the dialog. * * @param context The current context diff --git a/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java b/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java index 26f084f2..7b7ce745 100644 --- a/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java +++ b/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java @@ -29,6 +29,8 @@ import com.cyanogenmod.filemanager.FileManagerApplication; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.commands.SyncResultExecutable; import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException; +import com.cyanogenmod.filemanager.console.AuthenticationFailedException; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.CommandNotFoundException; import com.cyanogenmod.filemanager.console.ConsoleAllocException; import com.cyanogenmod.filemanager.console.ConsoleBuilder; @@ -94,7 +96,8 @@ public final class ExceptionUtil { OperationTimeoutException.class, ExecutionException.class, ParseException.class, - ActivityNotFoundException.class + ActivityNotFoundException.class, + AuthenticationFailedException.class }; private static final int[] KNOWN_EXCEPTIONS_IDS = { R.string.msgs_file_not_found, @@ -108,7 +111,8 @@ public final class ExceptionUtil { R.string.msgs_operation_timeout, R.string.msgs_operation_failure, R.string.msgs_operation_failure, - R.string.msgs_not_registered_app + R.string.msgs_not_registered_app, + 0 }; private static final boolean[] KNOWN_EXCEPTIONS_TOAST = { false, @@ -122,7 +126,8 @@ public final class ExceptionUtil { true, true, true, - false + false, + true }; /** @@ -181,6 +186,11 @@ public final class ExceptionUtil { final boolean quiet, final boolean askUser, final OnRelaunchCommandResult listener) { + // Is cancellable? + if (ex instanceof CancelledOperationException) { + return; + } + //Get the appropriate message for the exception int msgResId = R.string.msgs_unknown; boolean toast = true; @@ -216,12 +226,18 @@ public final class ExceptionUtil { @Override public void run() { try { + String msg = null; + if (fMsgResId > 0) { + msg = context.getString(fMsgResId); + } else { + msg = ex.getMessage(); + } if (fToast) { - DialogHelper.showToast(context, fMsgResId, Toast.LENGTH_SHORT); + DialogHelper.showToast(context, msg, Toast.LENGTH_SHORT); } else { AlertDialog dialog = DialogHelper.createErrorDialog( - context, R.string.error_title, fMsgResId); + context, R.string.error_title, msg); DialogHelper.delegateDialogShow(context, dialog); } } catch (Exception e) { diff --git a/src/com/cyanogenmod/filemanager/util/FileHelper.java b/src/com/cyanogenmod/filemanager/util/FileHelper.java index 811225a9..3b43aa83 100644 --- a/src/com/cyanogenmod/filemanager/util/FileHelper.java +++ b/src/com/cyanogenmod/filemanager/util/FileHelper.java @@ -66,6 +66,7 @@ import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.UUID; /** * A helper class with useful methods for deal with files. @@ -877,10 +878,14 @@ public final class FileHelper { // Check that have a valid file if (fso == null) return false; - //Only regular files + // Only regular files if (isDirectory(fso) || fso instanceof Symlink) { return false; } + // No in virtual filesystems + if (fso.isSecure() || fso.isRemote()) { + return false; + } String ext = getExtension(fso); if (ext != null) { int cc = VALID.length; @@ -938,11 +943,9 @@ public final class FileHelper { */ public static FileSystemObject createFileSystemObject(File file) { try { - // The user and group name of the files. In ChRoot, aosp give restrict access to - // this user and group. - final String USER = "system"; //$NON-NLS-1$ + // The user and group name of the files. Use the defaults one for sdcards + final String USER = "root"; //$NON-NLS-1$ final String GROUP = "sdcard_r"; //$NON-NLS-1$ - final String PERMISSIONS = "----rwxr-x"; //$NON-NLS-1$ // The user and group name of the files. In ChRoot, aosp give restrict access to // this user and group. This applies for permission also. This has no really much @@ -951,7 +954,9 @@ public final class FileHelper { AID groupAID = AIDHelper.getAIDFromName(GROUP); User user = new User(userAID.getId(), userAID.getName()); Group group = new Group(groupAID.getId(), groupAID.getName()); - Permissions perm = Permissions.fromRawString(PERMISSIONS); + Permissions perm = file.isDirectory() + ? Permissions.createDefaultFolderPermissions() + : Permissions.createDefaultFilePermissions(); // Build a directory? Date lastModified = new Date(file.lastModified()); @@ -1318,4 +1323,46 @@ public final class FileHelper { return sDateFormat.format(filetime); } } + + /** + * Method that create a new temporary filename + * + * @param external If the file should be created in the external or the internal cache dir + */ + public static synchronized File createTempFilename(Context context, boolean external) { + File tempDirectory = external ? context.getExternalCacheDir() : context.getCacheDir(); + File tempFile; + do { + UUID uuid = UUID.randomUUID(); + tempFile = new File(tempDirectory, uuid.toString()); + } while (tempFile.exists()); + return tempFile; + } + + /** + * Method that delete a file or a folder + * + * @param src The file or folder to delete + * @return boolean If the operation was successfully + */ + public static boolean deleteFileOrFolder(File src) { + if (src.isDirectory()) { + return FileHelper.deleteFolder(src); + } + return src.delete(); + } + + /** + * Method that checks if the source file passed belongs to (is under) the directory passed + * + * @param src The file to check + * @param dir The parent file to check + * @return boolean If the source belongs to the directory + */ + public static boolean belongsToDirectory(File src, File dir) { + if (dir.isFile()) { + return false; + } + return src.getAbsolutePath().startsWith(dir.getAbsolutePath()); + } } diff --git a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java index 1220272c..02833052 100644 --- a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java +++ b/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java @@ -22,6 +22,7 @@ import android.text.TextUtils; import android.util.Log; import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.console.secure.SecureConsole; import com.cyanogenmod.filemanager.model.BlockDevice; import com.cyanogenmod.filemanager.model.CharacterDevice; import com.cyanogenmod.filemanager.model.Directory; @@ -241,6 +242,11 @@ public final class MimeTypeHelper { //Check if the argument is a folder if (fso instanceof Directory) { + if (fso.isSecure() && SecureConsole.isSecureStorageDir(fso.getFullPath())) { + return "fso_folder_secure"; //$NON-NLS-1$ + } else if (fso.isRemote()) { + return "fso_folder_remote"; //$NON-NLS-1$ + } return "ic_fso_folder_drawable"; //$NON-NLS-1$ } diff --git a/src/com/cyanogenmod/filemanager/util/ParseHelper.java b/src/com/cyanogenmod/filemanager/util/ParseHelper.java index b593875a..ada51aae 100644 --- a/src/com/cyanogenmod/filemanager/util/ParseHelper.java +++ b/src/com/cyanogenmod/filemanager/util/ParseHelper.java @@ -322,7 +322,7 @@ public final class ParseHelper { //Return the mount point - return new MountPoint(mountPoint, device, type, options, dump, pass); + return new MountPoint(mountPoint, device, type, options, dump, pass, false, false); } catch (Exception e) { throw new ParseException(e.getMessage(), 0); diff --git a/src/com/cyanogenmod/filemanager/util/StringHelper.java b/src/com/cyanogenmod/filemanager/util/StringHelper.java index 3702746b..89d7d2f0 100644 --- a/src/com/cyanogenmod/filemanager/util/StringHelper.java +++ b/src/com/cyanogenmod/filemanager/util/StringHelper.java @@ -47,6 +47,33 @@ public final class StringHelper { return TextUtils.isGraphic(c); } + public static boolean isBinaryData(byte[] data) { + int lastByteTranslated = 0; + final int read = Math.min(10 * 1024, data.length); + final long max = ((5 * read) / 100); // 5% percent of binary bytes + int hits = 0; + for (int i = 0; i < read; i++) { + final byte b = data[i]; + int ub = b & (0xff); // unsigned + int utf8value = lastByteTranslated + ub; + lastByteTranslated = (ub) << 8; + + if (ub == 0x09 /*(tab)*/ + || ub == 0x0A /*(line feed)*/ + || ub == 0x0C /*(form feed)*/ + || ub == 0x0D /*(carriage return)*/ + || (ub >= 0x20 && ub <= 0x7E) /* Letters, Numbers and other "normal symbols" */ + || (ub >= 0xA0 && ub <= 0xEE) /* Symbols of Latin-1 */ + || (utf8value >= 0x2E2E && utf8value <= 0xC3BF)) { /* Latin-1 in UTF-8 encoding */ + // ok + } else { + // binary + hits++; + } + } + return hits > max; + } + /** * Method that converts to a visual printable hex string * diff --git a/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java b/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java index d12b69e4..b8257cd1 100644 --- a/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java +++ b/tests/src/com/cyanogenmod/filemanager/commands/shell/FindCommandTest.java @@ -20,7 +20,7 @@ import android.os.Environment; import android.test.suitebuilder.annotation.LargeTest; import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; +import com.cyanogenmod.filemanager.commands.ConcurrentAsyncResultListener; import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.Query; import com.cyanogenmod.filemanager.util.CommandHelper; @@ -77,29 +77,31 @@ public class FindCommandTest extends AbstractConsoleTest { Query query = new Query().setSlot(FIND_TERM_PARTIAL, 0); final List<FileSystemObject> files = new ArrayList<FileSystemObject>(); AsyncResultExecutable cmd = - CommandHelper.findFiles(getContext(), FIND_PATH, query, new AsyncResultListener() { + CommandHelper.findFiles(getContext(), FIND_PATH, + query, new ConcurrentAsyncResultListener() { + @Override - public void onAsyncStart() { + public void onConcurrentAsyncStart() { /**NON BLOCK**/ } @Override - public void onAsyncEnd(boolean cancelled) { + public void onConcurrentAsyncEnd(boolean cancelled) { synchronized (FindCommandTest.this.mSync) { FindCommandTest.this.mNormalEnd = true; FindCommandTest.this.mSync.notify(); } } @Override - public void onAsyncExitCode(int exitCode) { + public void onConcurrentAsyncExitCode(int exitCode) { /**NON BLOCK**/ } @Override - public void onException(Exception cause) { + public void onConcurrentException(Exception cause) { fail(String.valueOf(cause)); } @Override @SuppressWarnings("unchecked") - public void onPartialResult(Object results) { + public void onConcurrentPartialResult(Object results) { FindCommandTest.this.mNewPartialData = true; files.addAll((List<FileSystemObject>)results); } diff --git a/themes/res/drawable-hdpi/ic_holo_dark_delete.png b/themes/res/drawable-hdpi/ic_holo_dark_delete.png Binary files differnew file mode 100644 index 00000000..32a63ee1 --- /dev/null +++ b/themes/res/drawable-hdpi/ic_holo_dark_delete.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_print.png b/themes/res/drawable-hdpi/ic_holo_dark_print.png Binary files differnew file mode 100644 index 00000000..4544c7bf --- /dev/null +++ b/themes/res/drawable-hdpi/ic_holo_dark_print.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_remote.png b/themes/res/drawable-hdpi/ic_holo_dark_remote.png Binary files differnew file mode 100644 index 00000000..17ed77a9 --- /dev/null +++ b/themes/res/drawable-hdpi/ic_holo_dark_remote.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_secure.png b/themes/res/drawable-hdpi/ic_holo_dark_secure.png Binary files differnew file mode 100755 index 00000000..f24aed37 --- /dev/null +++ b/themes/res/drawable-hdpi/ic_holo_dark_secure.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_settings.png b/themes/res/drawable-hdpi/ic_holo_dark_settings.png Binary files differnew file mode 100644 index 00000000..eee54507 --- /dev/null +++ b/themes/res/drawable-hdpi/ic_holo_dark_settings.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_delete.png b/themes/res/drawable-mdpi/ic_holo_dark_delete.png Binary files differnew file mode 100644 index 00000000..274f30ad --- /dev/null +++ b/themes/res/drawable-mdpi/ic_holo_dark_delete.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_print.png b/themes/res/drawable-mdpi/ic_holo_dark_print.png Binary files differnew file mode 100644 index 00000000..947459d1 --- /dev/null +++ b/themes/res/drawable-mdpi/ic_holo_dark_print.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_remote.png b/themes/res/drawable-mdpi/ic_holo_dark_remote.png Binary files differnew file mode 100644 index 00000000..0cf01df3 --- /dev/null +++ b/themes/res/drawable-mdpi/ic_holo_dark_remote.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_secure.png b/themes/res/drawable-mdpi/ic_holo_dark_secure.png Binary files differnew file mode 100644 index 00000000..1df65e30 --- /dev/null +++ b/themes/res/drawable-mdpi/ic_holo_dark_secure.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_settings.png b/themes/res/drawable-mdpi/ic_holo_dark_settings.png Binary files differnew file mode 100644 index 00000000..78c3f3cf --- /dev/null +++ b/themes/res/drawable-mdpi/ic_holo_dark_settings.png diff --git a/themes/res/drawable-nodpi/dark_background_disabled.9.png b/themes/res/drawable-nodpi/dark_background_disabled.9.png Binary files differnew file mode 100644 index 00000000..ac4ec10b --- /dev/null +++ b/themes/res/drawable-nodpi/dark_background_disabled.9.png diff --git a/themes/res/drawable-nodpi/dark_theme_preview.png b/themes/res/drawable-nodpi/dark_theme_preview.png Binary files differindex 0ba30cd7..0fc9e11c 100644 --- a/themes/res/drawable-nodpi/dark_theme_preview.png +++ b/themes/res/drawable-nodpi/dark_theme_preview.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_delete.png b/themes/res/drawable-xhdpi/ic_holo_dark_delete.png Binary files differnew file mode 100644 index 00000000..cff1862f --- /dev/null +++ b/themes/res/drawable-xhdpi/ic_holo_dark_delete.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_print.png b/themes/res/drawable-xhdpi/ic_holo_dark_print.png Binary files differnew file mode 100644 index 00000000..781564ae --- /dev/null +++ b/themes/res/drawable-xhdpi/ic_holo_dark_print.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_remote.png b/themes/res/drawable-xhdpi/ic_holo_dark_remote.png Binary files differnew file mode 100644 index 00000000..c8c6297d --- /dev/null +++ b/themes/res/drawable-xhdpi/ic_holo_dark_remote.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_secure.png b/themes/res/drawable-xhdpi/ic_holo_dark_secure.png Binary files differnew file mode 100644 index 00000000..40a99b75 --- /dev/null +++ b/themes/res/drawable-xhdpi/ic_holo_dark_secure.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_settings.png b/themes/res/drawable-xhdpi/ic_holo_dark_settings.png Binary files differnew file mode 100644 index 00000000..ad69b56f --- /dev/null +++ b/themes/res/drawable-xhdpi/ic_holo_dark_settings.png diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_delete.png b/themes/res/drawable-xxhdpi/ic_holo_dark_delete.png Binary files differnew file mode 100644 index 00000000..0555c6a6 --- /dev/null +++ b/themes/res/drawable-xxhdpi/ic_holo_dark_delete.png diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_print.png b/themes/res/drawable-xxhdpi/ic_holo_dark_print.png Binary files differnew file mode 100644 index 00000000..f5248b9b --- /dev/null +++ b/themes/res/drawable-xxhdpi/ic_holo_dark_print.png diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_remote.png b/themes/res/drawable-xxhdpi/ic_holo_dark_remote.png Binary files differnew file mode 100644 index 00000000..a6b57504 --- /dev/null +++ b/themes/res/drawable-xxhdpi/ic_holo_dark_remote.png diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_secure.png b/themes/res/drawable-xxhdpi/ic_holo_dark_secure.png Binary files differnew file mode 100644 index 00000000..fa79fcb3 --- /dev/null +++ b/themes/res/drawable-xxhdpi/ic_holo_dark_secure.png diff --git a/themes/res/drawable-xxhdpi/ic_holo_dark_settings.png b/themes/res/drawable-xxhdpi/ic_holo_dark_settings.png Binary files differnew file mode 100644 index 00000000..e5a1fbad --- /dev/null +++ b/themes/res/drawable-xxhdpi/ic_holo_dark_settings.png diff --git a/themes/res/drawable/dark_holo_button_selector.xml b/themes/res/drawable/dark_holo_button_selector.xml index 76d512d1..efb778c2 100644 --- a/themes/res/drawable/dark_holo_button_selector.xml +++ b/themes/res/drawable/dark_holo_button_selector.xml @@ -24,6 +24,9 @@ android:state_enabled="true" android:state_focused="true"/> <item + android:drawable="@drawable/dark_background_disabled" + android:state_enabled="false"/> + <item android:drawable="@drawable/dark_background"/> </selector> diff --git a/themes/res/values/dark_theme.xml b/themes/res/values/dark_theme.xml index c55371bb..9fb44dbe 100644 --- a/themes/res/values/dark_theme.xml +++ b/themes/res/values/dark_theme.xml @@ -60,6 +60,12 @@ <drawable name="dark_ab_save_drawable">@drawable/ic_holo_dark_save</drawable> <!-- The drawable for the tab action bar button --> <drawable name="dark_ab_tab_drawable">@drawable/ic_holo_dark_tab</drawable> + <!-- The drawable for the print action bar button --> + <drawable name="dark_ab_print_drawable">@drawable/ic_holo_dark_print</drawable> + <!-- The drawable for the settings action bar button --> + <drawable name="dark_ab_settings_drawable">@drawable/ic_holo_dark_settings</drawable> + <!-- The drawable for the delete action bar button --> + <drawable name="dark_ab_delete_drawable">@drawable/ic_holo_dark_delete</drawable> <!-- The close action drawable from the expander bar --> <drawable name="dark_expander_close_drawable">@drawable/ic_holo_dark_expander_close</drawable> @@ -85,6 +91,11 @@ <!-- FileSystem warning drawable --> <drawable name="dark_filesystem_warning_drawable">@drawable/ic_holo_dark_fs_warning</drawable> + <!-- Secure FileSystem icon --> + <drawable name="dark_secure_filesystem_drawable">@drawable/ic_holo_dark_secure</drawable> + <!-- Remote FileSystem icon --> + <drawable name="dark_remote_filesystem_drawable">@drawable/ic_holo_dark_remote</drawable> + <!-- The popup menu checkable selector drawable --> <drawable name="dark_popup_checkable_selector_drawable">@drawable/dark_checkable_selector</drawable> <!-- The menu checkable selector drawable --> @@ -116,6 +127,8 @@ <drawable name="dark_ic_usb_drawable">@drawable/ic_holo_dark_usb</drawable> <drawable name="dark_ic_user_defined_bookmark_drawable">@drawable/ic_holo_dark_user_defined_bookmark</drawable> <drawable name="dark_ic_copy_drawable">@drawable/ic_holo_dark_copy</drawable> + <drawable name="dark_ic_secure_drawable">@drawable/ic_holo_dark_secure</drawable> + <drawable name="dark_ic_remote_drawable">@drawable/ic_holo_dark_remote</drawable> <!-- Disk usage graph --> <color name="dark_disk_usage_total_color">#7ecccccc</color> |