From: Bartek Przybylski Date: Fri, 19 Aug 2011 20:37:35 +0000 (+0200) Subject: initial commit X-Git-Tag: oc-android-1.4.3~521 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/154bb85cf1cc5347333be1cea339907b13cc746a?ds=sidebyside initial commit --- 154bb85cf1cc5347333be1cea339907b13cc746a diff --git a/.classpath b/.classpath new file mode 100644 index 00000000..f6f1092a --- /dev/null +++ b/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..58c41c99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Mac .DS_Store files +.DS_Store \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 00000000..a1311141 --- /dev/null +++ b/.project @@ -0,0 +1,33 @@ + + + ownCloud + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 00000000..0c39fc1b --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/default.properties b/default.properties new file mode 100644 index 00000000..46769a72 --- /dev/null +++ b/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-7 diff --git a/lib/commons-codec-1.4.jar b/lib/commons-codec-1.4.jar new file mode 100644 index 00000000..458d432d Binary files /dev/null and b/lib/commons-codec-1.4.jar differ diff --git a/lib/commons-httpclient-3.0.1.jar b/lib/commons-httpclient-3.0.1.jar new file mode 100644 index 00000000..cfc777c7 Binary files /dev/null and b/lib/commons-httpclient-3.0.1.jar differ diff --git a/lib/commons-io-2.0.1.jar b/lib/commons-io-2.0.1.jar new file mode 100644 index 00000000..5b64b7d6 Binary files /dev/null and b/lib/commons-io-2.0.1.jar differ diff --git a/lib/commons-logging-1.1.1.jar b/lib/commons-logging-1.1.1.jar new file mode 100644 index 00000000..1deef144 Binary files /dev/null and b/lib/commons-logging-1.1.1.jar differ diff --git a/lib/httpclient-4.1.1.jar b/lib/httpclient-4.1.1.jar new file mode 100644 index 00000000..c845ef96 Binary files /dev/null and b/lib/httpclient-4.1.1.jar differ diff --git a/lib/httpclient-cache-4.1.1.jar b/lib/httpclient-cache-4.1.1.jar new file mode 100644 index 00000000..3e1da089 Binary files /dev/null and b/lib/httpclient-cache-4.1.1.jar differ diff --git a/lib/httpcore-4.1.jar b/lib/httpcore-4.1.jar new file mode 100644 index 00000000..a357c076 Binary files /dev/null and b/lib/httpcore-4.1.jar differ diff --git a/lib/httpmime-4.1.1.jar b/lib/httpmime-4.1.1.jar new file mode 100644 index 00000000..01af40b2 Binary files /dev/null and b/lib/httpmime-4.1.1.jar differ diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 00000000..12dd0392 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,36 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/anim/disappear.xml b/res/anim/disappear.xml new file mode 100644 index 00000000..b1fd5e3a --- /dev/null +++ b/res/anim/disappear.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/res/anim/grow_from_bottom.xml b/res/anim/grow_from_bottom.xml new file mode 100644 index 00000000..d2a371d1 --- /dev/null +++ b/res/anim/grow_from_bottom.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/grow_from_bottomleft_to_topright.xml b/res/anim/grow_from_bottomleft_to_topright.xml new file mode 100644 index 00000000..a4bf4ea2 --- /dev/null +++ b/res/anim/grow_from_bottomleft_to_topright.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/res/anim/grow_from_bottomright_to_topleft.xml b/res/anim/grow_from_bottomright_to_topleft.xml new file mode 100644 index 00000000..3446623a --- /dev/null +++ b/res/anim/grow_from_bottomright_to_topleft.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/res/anim/grow_from_top.xml b/res/anim/grow_from_top.xml new file mode 100644 index 00000000..ffd722c3 --- /dev/null +++ b/res/anim/grow_from_top.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/grow_from_topleft_to_bottomright.xml b/res/anim/grow_from_topleft_to_bottomright.xml new file mode 100644 index 00000000..b67ebe5e --- /dev/null +++ b/res/anim/grow_from_topleft_to_bottomright.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/grow_from_topright_to_bottomleft.xml b/res/anim/grow_from_topright_to_bottomleft.xml new file mode 100644 index 00000000..d4744f68 --- /dev/null +++ b/res/anim/grow_from_topright_to_bottomleft.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/res/anim/pump_bottom.xml b/res/anim/pump_bottom.xml new file mode 100644 index 00000000..f681951a --- /dev/null +++ b/res/anim/pump_bottom.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/pump_top.xml b/res/anim/pump_top.xml new file mode 100644 index 00000000..65637b56 --- /dev/null +++ b/res/anim/pump_top.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/push_left_in.xml b/res/anim/push_left_in.xml new file mode 100644 index 00000000..894d2223 --- /dev/null +++ b/res/anim/push_left_in.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/res/anim/push_left_out.xml b/res/anim/push_left_out.xml new file mode 100644 index 00000000..28802d2e --- /dev/null +++ b/res/anim/push_left_out.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/res/anim/shrink_from_bottom.xml b/res/anim/shrink_from_bottom.xml new file mode 100644 index 00000000..a98d592a --- /dev/null +++ b/res/anim/shrink_from_bottom.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/shrink_from_bottomleft_to_topright.xml b/res/anim/shrink_from_bottomleft_to_topright.xml new file mode 100644 index 00000000..6e56c1e2 --- /dev/null +++ b/res/anim/shrink_from_bottomleft_to_topright.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/res/anim/shrink_from_bottomright_to_topleft.xml b/res/anim/shrink_from_bottomright_to_topleft.xml new file mode 100644 index 00000000..d4ed5134 --- /dev/null +++ b/res/anim/shrink_from_bottomright_to_topleft.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/shrink_from_top.xml b/res/anim/shrink_from_top.xml new file mode 100644 index 00000000..89cd8f4f --- /dev/null +++ b/res/anim/shrink_from_top.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/anim/shrink_from_topleft_to_bottomright.xml b/res/anim/shrink_from_topleft_to_bottomright.xml new file mode 100644 index 00000000..13417287 --- /dev/null +++ b/res/anim/shrink_from_topleft_to_bottomright.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/res/anim/shrink_from_topright_to_bottomleft.xml b/res/anim/shrink_from_topright_to_bottomleft.xml new file mode 100644 index 00000000..d4064fcc --- /dev/null +++ b/res/anim/shrink_from_topright_to_bottomleft.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/drawable-hdpi/ic_menu_archive.png b/res/drawable-hdpi/ic_menu_archive.png new file mode 100644 index 00000000..e2d9bc1a Binary files /dev/null and b/res/drawable-hdpi/ic_menu_archive.png differ diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png new file mode 100644 index 00000000..5702d8e5 Binary files /dev/null and b/res/drawable-hdpi/icon.png differ diff --git a/res/drawable-hdpi/owncloud_logo.png b/res/drawable-hdpi/owncloud_logo.png new file mode 100644 index 00000000..ecea3b0e Binary files /dev/null and b/res/drawable-hdpi/owncloud_logo.png differ diff --git a/res/drawable-ldpi/ic_menu_archive.png b/res/drawable-ldpi/ic_menu_archive.png new file mode 100644 index 00000000..719ecd85 Binary files /dev/null and b/res/drawable-ldpi/ic_menu_archive.png differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 00000000..11cf0ab1 Binary files /dev/null and b/res/drawable-ldpi/icon.png differ diff --git a/res/drawable-ldpi/owncloud_logo.png b/res/drawable-ldpi/owncloud_logo.png new file mode 100644 index 00000000..62dff255 Binary files /dev/null and b/res/drawable-ldpi/owncloud_logo.png differ diff --git a/res/drawable-mdpi/ic_menu_archive.png b/res/drawable-mdpi/ic_menu_archive.png new file mode 100644 index 00000000..49ac569d Binary files /dev/null and b/res/drawable-mdpi/ic_menu_archive.png differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png new file mode 100644 index 00000000..6997c7e4 Binary files /dev/null and b/res/drawable-mdpi/icon.png differ diff --git a/res/drawable-mdpi/owncloud_logo.png b/res/drawable-mdpi/owncloud_logo.png new file mode 100644 index 00000000..ecea3b0e Binary files /dev/null and b/res/drawable-mdpi/owncloud_logo.png differ diff --git a/res/drawable/action_item_btn.xml b/res/drawable/action_item_btn.xml new file mode 100644 index 00000000..3a48a675 --- /dev/null +++ b/res/drawable/action_item_btn.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/action_item_selected.9.png b/res/drawable/action_item_selected.9.png new file mode 100644 index 00000000..de41f40e Binary files /dev/null and b/res/drawable/action_item_selected.9.png differ diff --git a/res/drawable/arrow_down.png b/res/drawable/arrow_down.png new file mode 100644 index 00000000..05e6a596 Binary files /dev/null and b/res/drawable/arrow_down.png differ diff --git a/res/drawable/arrow_left.png b/res/drawable/arrow_left.png new file mode 100644 index 00000000..c73932b4 Binary files /dev/null and b/res/drawable/arrow_left.png differ diff --git a/res/drawable/arrow_right.png b/res/drawable/arrow_right.png new file mode 100644 index 00000000..02e7c0ae Binary files /dev/null and b/res/drawable/arrow_right.png differ diff --git a/res/drawable/arrow_up.png b/res/drawable/arrow_up.png new file mode 100644 index 00000000..4412938c Binary files /dev/null and b/res/drawable/arrow_up.png differ diff --git a/res/drawable/btn.xml b/res/drawable/btn.xml new file mode 100644 index 00000000..8e01f676 --- /dev/null +++ b/res/drawable/btn.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/res/drawable/btn_round.xml b/res/drawable/btn_round.xml new file mode 100644 index 00000000..a7c5063f --- /dev/null +++ b/res/drawable/btn_round.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/btn_round_pressed.xml b/res/drawable/btn_round_pressed.xml new file mode 100644 index 00000000..34d860dc --- /dev/null +++ b/res/drawable/btn_round_pressed.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable/dashboard.png b/res/drawable/dashboard.png new file mode 100644 index 00000000..c919de37 Binary files /dev/null and b/res/drawable/dashboard.png differ diff --git a/res/drawable/download.png b/res/drawable/download.png new file mode 100644 index 00000000..783ad448 Binary files /dev/null and b/res/drawable/download.png differ diff --git a/res/drawable/file.png b/res/drawable/file.png new file mode 100644 index 00000000..49790448 Binary files /dev/null and b/res/drawable/file.png differ diff --git a/res/drawable/folder.png b/res/drawable/folder.png new file mode 100644 index 00000000..3edbe257 Binary files /dev/null and b/res/drawable/folder.png differ diff --git a/res/drawable/header.png b/res/drawable/header.png new file mode 100644 index 00000000..d377f651 Binary files /dev/null and b/res/drawable/header.png differ diff --git a/res/drawable/ic_menu_archive.png b/res/drawable/ic_menu_archive.png new file mode 100644 index 00000000..e2d9bc1a Binary files /dev/null and b/res/drawable/ic_menu_archive.png differ diff --git a/res/drawable/owncloud_logo_small_white.png b/res/drawable/owncloud_logo_small_white.png new file mode 100644 index 00000000..c0d8a92d Binary files /dev/null and b/res/drawable/owncloud_logo_small_white.png differ diff --git a/res/drawable/popup.9.png b/res/drawable/popup.9.png new file mode 100644 index 00000000..33422723 Binary files /dev/null and b/res/drawable/popup.9.png differ diff --git a/res/drawable/share.png b/res/drawable/share.png new file mode 100644 index 00000000..300ce575 Binary files /dev/null and b/res/drawable/share.png differ diff --git a/res/drawable/uploader_list_separator.xml b/res/drawable/uploader_list_separator.xml new file mode 100644 index 00000000..25b94491 --- /dev/null +++ b/res/drawable/uploader_list_separator.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/res/layout/account_setup.xml b/res/layout/account_setup.xml new file mode 100644 index 00000000..855c5ffb --- /dev/null +++ b/res/layout/account_setup.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/action_item.xml b/res/layout/action_item.xml new file mode 100644 index 00000000..094350c9 --- /dev/null +++ b/res/layout/action_item.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/file_display.xml b/res/layout/file_display.xml new file mode 100644 index 00000000..53564051 --- /dev/null +++ b/res/layout/file_display.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/file_display_action_list_element.xml b/res/layout/file_display_action_list_element.xml new file mode 100644 index 00000000..1d0b084a --- /dev/null +++ b/res/layout/file_display_action_list_element.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/res/layout/list_layout.xml b/res/layout/list_layout.xml new file mode 100644 index 00000000..60f6689e --- /dev/null +++ b/res/layout/list_layout.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100644 index 00000000..ca77ad9f --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/res/layout/popup.xml b/res/layout/popup.xml new file mode 100644 index 00000000..2f2d79a4 --- /dev/null +++ b/res/layout/popup.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/uploader_layout.xml b/res/layout/uploader_layout.xml new file mode 100644 index 00000000..b3e6565d --- /dev/null +++ b/res/layout/uploader_layout.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/res/layout/uploader_list_item_layout.xml b/res/layout/uploader_list_item_layout.xml new file mode 100644 index 00000000..d546814e --- /dev/null +++ b/res/layout/uploader_list_item_layout.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/res/menu/menu.xml b/res/menu/menu.xml new file mode 100644 index 00000000..b6d54d38 --- /dev/null +++ b/res/menu/menu.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/res/menu/prefs_menu.xml b/res/menu/prefs_menu.xml new file mode 100644 index 00000000..c3f64652 --- /dev/null +++ b/res/menu/prefs_menu.xml @@ -0,0 +1,5 @@ + + + + diff --git a/res/menu/session_context_menu.xml b/res/menu/session_context_menu.xml new file mode 100644 index 00000000..53063a7f --- /dev/null +++ b/res/menu/session_context_menu.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 00000000..4c12ed76 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,39 @@ + + + Hello World, OwnCloudMainScreen! + ownCloud + Password: + Username: + Login + General + Stored sessions + Add new session + Session Name + URL + Username + Password + OK + Cancel + Wrong URL given + Wrong session name + filelist and pinned files + No file selected for upload + Username + Password + Web address + Connect to your ownCloud + Connect + Upload + No account found + No correct ownCloud account found on device. Please setup account first. + Setup + Quit + Uploading + Create dir for upload + OK + Cancel + Directory name + Uploading completed successfully + Upload failed: + Choose account + diff --git a/res/values/styles.xml b/res/values/styles.xml new file mode 100644 index 00000000..f2c0ea50 --- /dev/null +++ b/res/values/styles.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + #777777 + #000000 + \ No newline at end of file diff --git a/res/xml/authenticator.xml b/res/xml/authenticator.xml new file mode 100644 index 00000000..ae32f0dc --- /dev/null +++ b/res/xml/authenticator.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/res/xml/contacts.xml b/res/xml/contacts.xml new file mode 100644 index 00000000..88709417 --- /dev/null +++ b/res/xml/contacts.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml new file mode 100644 index 00000000..2b733ae9 --- /dev/null +++ b/res/xml/preferences.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/xml/preferences_new_session.xml b/res/xml/preferences_new_session.xml new file mode 100644 index 00000000..da7d6657 --- /dev/null +++ b/res/xml/preferences_new_session.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/res/xml/syncadapter.xml b/res/xml/syncadapter.xml new file mode 100644 index 00000000..3285ea8b --- /dev/null +++ b/res/xml/syncadapter.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/src/eu/alefzero/owncloud/ActionItem.java b/src/eu/alefzero/owncloud/ActionItem.java new file mode 100644 index 00000000..5f97c559 --- /dev/null +++ b/src/eu/alefzero/owncloud/ActionItem.java @@ -0,0 +1,37 @@ +package eu.alefzero.owncloud; + +import android.graphics.drawable.Drawable; +import android.view.View.OnClickListener; + +public class ActionItem { + private Drawable mIcon; + private String mTitle; + private OnClickListener mClickListener; + + public ActionItem() { } + + public void setTitle(String title) { + mTitle = title; + } + + public String getTitle() { + return mTitle; + } + + public void setIcon(Drawable icon) { + mIcon = icon; + } + + public Drawable getIcon() { + return mIcon; + } + + public void setOnClickListener(OnClickListener listener) { + mClickListener = listener; + } + + public OnClickListener getOnClickListerner() { + return mClickListener; + } + +} diff --git a/src/eu/alefzero/owncloud/CustomPopup.java b/src/eu/alefzero/owncloud/CustomPopup.java new file mode 100644 index 00000000..7c919700 --- /dev/null +++ b/src/eu/alefzero/owncloud/CustomPopup.java @@ -0,0 +1,126 @@ +package eu.alefzero.owncloud; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.View.OnTouchListener; +import android.view.ViewGroup.LayoutParams; +import android.widget.PopupWindow; + +public class CustomPopup { + protected final View mAnchor; + protected final PopupWindow mWindow; + private View root; + private Drawable background = null; + protected final WindowManager mWManager; + + public CustomPopup(View anchor) { + mAnchor = anchor; + mWindow = new PopupWindow(anchor.getContext()); + + mWindow.setTouchInterceptor(new OnTouchListener() { + + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + CustomPopup.this.dismiss(); + return true; + } + return false; + } + }); + + mWManager = (WindowManager) anchor.getContext().getSystemService(Context.WINDOW_SERVICE); + onCreate(); + } + + + public void onCreate() {} + public void onShow() {} + + public void preShow() { + if (root == null) { + throw new IllegalStateException("setContentView called with a view to display"); + } + + onShow(); + + if (background == null) { + mWindow.setBackgroundDrawable(new BitmapDrawable()); + } else { + mWindow.setBackgroundDrawable(background); + } + + mWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); + mWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); + mWindow.setTouchable(true); + mWindow.setFocusable(true); + mWindow.setOutsideTouchable(true); + + mWindow.setContentView(root); + } + + public void setBackgroundDrawable(Drawable background) { + this.background = background; + } + + public void setContentView(View root) { + this.root = root; + mWindow.setContentView(root); + } + + public void setContentView(int layoutResId) { + LayoutInflater inflater = + (LayoutInflater) mAnchor.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + setContentView(inflater.inflate(layoutResId, null)); + } + + public void showDropDown() { + showDropDown(0, 0); + } + + public void showDropDown(int x, int y) { + preShow(); + mWindow.setAnimationStyle(android.R.style.Animation_Dialog); + mWindow.showAsDropDown(mAnchor, x, y); + } + + public void showLikeQuickAction() { + showLikeQuickAction(0, 0); + } + + public void showLikeQuickAction(int x, int y) { + preShow(); + + mWindow.setAnimationStyle(android.R.style.Animation_Dialog); + int[] location = new int[2]; + mAnchor.getLocationOnScreen(location); + + Rect anchorRect = + new Rect(location[0], location[1], location[0] + mAnchor.getWidth(), location[1] + mAnchor.getHeight()); + + root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + int rootW = root.getWidth(), rootH = root.getHeight(); + int screenW = mWManager.getDefaultDisplay().getWidth(); + + int xpos = ((screenW-rootW)/2) + x; + int ypos = anchorRect.top - rootH + y; + + if (rootH > anchorRect.top) { + ypos = anchorRect.bottom + y; + } + mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, xpos, ypos); + } + + public void dismiss() { + mWindow.dismiss(); + } + +} diff --git a/src/eu/alefzero/owncloud/DbHandler.java b/src/eu/alefzero/owncloud/DbHandler.java new file mode 100644 index 00000000..c47bd439 --- /dev/null +++ b/src/eu/alefzero/owncloud/DbHandler.java @@ -0,0 +1,79 @@ +package eu.alefzero.owncloud; + +import java.util.Vector; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +public class DbHandler { + private SQLiteDatabase mDB; + private OpenerHepler mHelper; + private final String mDatabaseName = "ownCloud"; + private final String TABLE_SESSIONS = "sessions"; + private final int mDatabaseVersion = 1; + + public DbHandler(Context context) { + mHelper = new OpenerHepler(context); + mDB = mHelper.getWritableDatabase(); + } + + public Vector getSessionList() { + Cursor c = mDB.query(TABLE_SESSIONS, null, null, null, null, null, null); + Vector v = new Vector(); + if (!c.moveToFirst()) { + return v; + } + while (!c.isAfterLast()) { + v.add(new OwnCloudSession(c.getString(c.getColumnIndex("sessionName")), + c.getString(c.getColumnIndex("sessionUrl")), + c.getInt(c.getColumnIndex("_id")))); + c.moveToNext(); + } + c.close(); + return v; + } + + public void addSession(String sessionName, String uri) { + ContentValues cv = new ContentValues(); + cv.put("sessionName", sessionName); + cv.put("sessionUrl", uri); + mDB.insert(TABLE_SESSIONS, null, cv); + } + + public void removeSessionWithId(int sessionId) { + mDB.delete(TABLE_SESSIONS, "_id = ?", new String[] {String.valueOf(sessionId)}); + } + + public void changeSessionFields(int id, String hostname, String uri) { + ContentValues cv = new ContentValues(); + cv.put("sessionName", hostname); + cv.put("sessionUrl", uri); + mDB.update(TABLE_SESSIONS, cv, "_id = ?", new String[] {String.valueOf(id)}); + } + + public void close() { + mDB.close(); + } + + private class OpenerHepler extends SQLiteOpenHelper { + public OpenerHepler(Context context) { + super(context, mDatabaseName, null, mDatabaseVersion); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_SESSIONS + " (" + + " _id INTEGER PRIMARY KEY, " + + " sessionName TEXT, " + + " sessionUrl TEXT);"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + } +} diff --git a/src/eu/alefzero/owncloud/DisplayUtils.java b/src/eu/alefzero/owncloud/DisplayUtils.java new file mode 100644 index 00000000..bb16f596 --- /dev/null +++ b/src/eu/alefzero/owncloud/DisplayUtils.java @@ -0,0 +1,60 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud; + +import java.util.HashMap; + +public class DisplayUtils { + public static String bitsToHumanReadable(long bitsLen) { + double result = bitsLen; + int attachedsuff = 0; + while (result > 1024 && attachedsuff < suffixes.length) { + result /= 1024.; + attachedsuff++; + } + result = ((int)(result * 100))/100.; + return result+suffixes[attachedsuff]; + } + + public static String convertMIMEtoPrettyPrint(String mimetype) { + if (mimeType2HUmanReadable.containsKey(mimetype)) { + return mimeType2HUmanReadable.get(mimetype); + } + return mimetype.split("/")[1].toUpperCase() + " file"; + } + + private static final String[] suffixes = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; + + private static HashMap mimeType2HUmanReadable; + static { + mimeType2HUmanReadable = new HashMap(); + // images + mimeType2HUmanReadable.put("image/jpeg", "JPEG image"); + mimeType2HUmanReadable.put("image/jpg", "JPEG image"); + mimeType2HUmanReadable.put("image/png", "PNG image"); + mimeType2HUmanReadable.put("image/bmp", "Bitmap image"); + mimeType2HUmanReadable.put("image/gif", "GIF image"); + mimeType2HUmanReadable.put("image/svg+xml", "JPEG image"); + mimeType2HUmanReadable.put("image/tiff", "TIFF image"); + // music + mimeType2HUmanReadable.put("audio/mpeg", "MP3 music file"); + mimeType2HUmanReadable.put("application/ogg", "OGG music file"); + + } +} diff --git a/src/eu/alefzero/owncloud/FileListActionListAdapter.java b/src/eu/alefzero/owncloud/FileListActionListAdapter.java new file mode 100644 index 00000000..65ed6f05 --- /dev/null +++ b/src/eu/alefzero/owncloud/FileListActionListAdapter.java @@ -0,0 +1,158 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud; + +import java.io.File; + +import eu.alefzero.owncloud.authenticator.AccountAuthenticator; +import eu.alefzero.owncloud.db.ProviderMeta; +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.DataSetObserver; +import android.net.Uri; +import android.provider.MediaStore.Images.Media; +import android.sax.StartElementListener; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +public class FileListActionListAdapter implements ListAdapter { + + private Context mContext; + private Account mAccount; + private String mFilename, mFileType, mFilePath, mFileStoragePath, mItemId; + + private final int ITEM_DOWNLOAD = 0; + private final int ITEM_SHARE = 1; + + public FileListActionListAdapter(Cursor c, Context co, Account account) { + mContext = co; + mFilename = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NAME)); + mFileType = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)); + mFilePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)); + mFileStoragePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)); + mItemId = c.getString(c.getColumnIndex(ProviderTableMeta._ID)); + mAccount = account; + } + + public boolean areAllItemsEnabled() { + // TODO Auto-generated method stub + return true; + } + + public boolean isEnabled(int position) { + // TODO Auto-generated method stub + return true; + } + + public int getCount() { + // TODO Auto-generated method stub + return 1; + } + + public Object getItem(int position) { + if (position == 0) { + AccountManager accm = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE); + String ocurl = accm.getUserData(mAccount, AccountAuthenticator.KEY_OC_URL); + ocurl += mFilePath + mFilename; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(new File(mFileStoragePath)), mFileType); + return intent; + } + return null; + } + + public long getItemId(int position) { + // TODO Auto-generated method stub + return 0; + } + + public int getItemViewType(int position) { + // TODO Auto-generated method stub + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + if (v == null) { + LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(R.layout.file_display_action_list_element, null); + } + + TextView tv; + ImageView iv; + switch (position) { + case ITEM_DOWNLOAD : + tv = (TextView) v.findViewById(R.id.textView1); + if (mFileStoragePath == null) { + tv.setText("Download"); + } else { + setActionName(tv); + } + iv = (ImageView) v.findViewById(R.id.imageView1); + iv.setImageResource(R.drawable.download); + break; + } + + return v; + } + + public int getViewTypeCount() { + // TODO Auto-generated method stub + return 2; + } + + public boolean hasStableIds() { + // TODO Auto-generated method stub + return false; + } + + public boolean isEmpty() { + // TODO Auto-generated method stub + return false; + } + + public void registerDataSetObserver(DataSetObserver observer) { + // TODO Auto-generated method stub + + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + // TODO Auto-generated method stub + + } + + private void setActionName(TextView tv) { + if (mFileType.matches("image/.*")) { + tv.setText("View"); + } else if (mFileType.matches("audio/.*") || mFileType.matches("video/.*")) { + tv.setText("Play"); + } else { + tv.setText("Open"); + } + } + +} diff --git a/src/eu/alefzero/owncloud/FileListListAdapter.java b/src/eu/alefzero/owncloud/FileListListAdapter.java new file mode 100644 index 00000000..4cf7c4df --- /dev/null +++ b/src/eu/alefzero/owncloud/FileListListAdapter.java @@ -0,0 +1,103 @@ +package eu.alefzero.owncloud; + +import java.security.Provider; + +import eu.alefzero.owncloud.db.ProviderMeta; +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; + +import android.content.Context; +import android.database.Cursor; +import android.database.DataSetObserver; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnLongClickListener; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +public class FileListListAdapter implements ListAdapter { + + private Cursor mCursor; + private Context mContext; + + public FileListListAdapter(Cursor c, Context context) { + mCursor = c; + mContext = context; + } + + public boolean areAllItemsEnabled() { + return true; + } + + public boolean isEnabled(int position) { + // TODO Auto-generated method stub + return true; + } + + public int getCount() { + // TODO Auto-generated method stub + return mCursor.getCount(); + } + + public Object getItem(int position) { + // TODO Auto-generated method stub + return null; + } + + public long getItemId(int position) { + // TODO Auto-generated method stub + return 0; + } + + public int getItemViewType(int position) { + // TODO Auto-generated method stub + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + if (v == null) { + LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(R.layout.list_layout, null); + } + if (mCursor.moveToPosition(position)) { + TextView tv = (TextView) v.findViewById(R.id.Filename); + tv.setText(mCursor.getString(mCursor.getColumnIndex(ProviderMeta.ProviderTableMeta.FILE_NAME))); + if (!mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)).equals("DIR")) { + ImageView iv = (ImageView) v.findViewById(R.id.imageView1); + iv.setImageResource(R.drawable.file); + } + } + + return v; + } + + public int getViewTypeCount() { + // TODO Auto-generated method stub + return 4; + } + + public boolean hasStableIds() { + // TODO Auto-generated method stub + return true; + } + + public boolean isEmpty() { + // TODO Auto-generated method stub + return false; + } + + public void registerDataSetObserver(DataSetObserver observer) { + // TODO Auto-generated method stub + + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + // TODO Auto-generated method stub + + } +} diff --git a/src/eu/alefzero/owncloud/OwnCloudMainScreen.java b/src/eu/alefzero/owncloud/OwnCloudMainScreen.java new file mode 100644 index 00000000..e30789a5 --- /dev/null +++ b/src/eu/alefzero/owncloud/OwnCloudMainScreen.java @@ -0,0 +1,281 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud; + +import java.util.Stack; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ListActivity; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.DialogInterface.OnCancelListener; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import eu.alefzero.owncloud.authenticator.AccountAuthenticator; +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; + +public class OwnCloudMainScreen extends ListActivity { + private DbHandler mDBHandler; + private Stack mParents; + private Account mAccount; + private Cursor mCursor; + private boolean mIsDisplayingFile; + + private static final int DIALOG_CHOOSE_ACCOUNT = 0; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mParents = new Stack(); + mIsDisplayingFile = false; + mDBHandler = new DbHandler(getBaseContext()); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.main); + + AccountManager accMan = AccountManager.get(this); + Account[] accounts = accMan.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); + + if (accounts.length == 0) { + // using string value since in API7 this constatn is not defined + // in API7 < this constatant is defined in Settings.ADD_ACCOUNT_SETTINGS + // and Settings.EXTRA_AUTHORITIES + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + intent.putExtra("authorities", new String[] {AccountAuthenticator.AUTH_TOKEN_TYPE}); + startActivity(intent); + } else if (accounts.length > 1) { + showDialog(DIALOG_CHOOSE_ACCOUNT); + } else { + mAccount = accounts[0]; + populateFileList(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.settingsItem : + Intent i = new Intent(this, Preferences.class); + startActivity(i); + break; + } + return true; + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_CHOOSE_ACCOUNT: + return createChooseAccountDialog(); + default: + throw new IllegalArgumentException("Unknown dialog id: " + id); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu, menu); + return true; + } + + @Override + protected void onDestroy() { + mDBHandler.close(); + super.onDestroy(); + } + + private Dialog createChooseAccountDialog() { + final AccountManager accMan = AccountManager.get(this); + CharSequence[] items = new CharSequence[accMan.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length]; + int i = 0; + for (Account a : accMan.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)) { + items[i++] = a.name; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.common_choose_account); + builder.setCancelable(true); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + mAccount = accMan.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[item]; + dialog.dismiss(); + populateFileList(); + } + }); + builder.setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + OwnCloudMainScreen.this.finish(); + } + }); + AlertDialog alert = builder.create(); + return alert; + } + + @Override + public void onBackPressed() { + if (mIsDisplayingFile) { + mIsDisplayingFile = false; + setContentView(R.layout.main); + Uri uri; + if (mParents.empty()) { + uri = ProviderTableMeta.CONTENT_URI; + } else { + uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, mParents.peek()); + } + mCursor = managedQuery(uri, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", + new String[]{mAccount.name}, null); + + if (mCursor.moveToFirst()) { + TextView tv = (TextView) findViewById(R.id.directory_name); + tv.setText(mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH))); + } + getListView().setAdapter(new FileListListAdapter(mCursor, this)); + getListView().invalidate(); + return; + } + if (mParents.size()==0) { + super.onBackPressed(); + return; + } else if (mParents.size() == 1) { + mParents.pop(); + mCursor = managedQuery(ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", + new String[]{mAccount.name}, + null); + } else { + mParents.pop(); + mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, mParents.peek()), + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", + new String[]{mAccount.name}, + null); + } + + setListAdapter(new FileListListAdapter(mCursor, this)); + getListView().invalidate(); + + TextView tv = (TextView) findViewById(R.id.directory_name); + String s = tv.getText().toString(); + if (s.endsWith("/")) { + s = s.substring(0, s.length() - 1); + } + s = s.substring(0, s.lastIndexOf('/') + 1); + tv.setText(s); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + if (!mCursor.moveToPosition(position)) { + throw new IndexOutOfBoundsException("Incorrect item selected"); + } + if (!mIsDisplayingFile) { + if (mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)).equals("DIR")) { + String id_ = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta._ID)); + String dirname = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)); + TextView tv = (TextView) findViewById(R.id.directory_name); + tv.setText(tv.getText().toString()+dirname+"/"); + mParents.push(id_); + mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, id_), + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", + new String[]{mAccount.name}, null); + setListAdapter(new FileListListAdapter(mCursor, this)); + } else { + mIsDisplayingFile = true; + setContentView(R.layout.file_display); + String id_ = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta._ID)); + mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, id_), + null, + null, + null, + null); + mCursor.moveToFirst(); + // filename + TextView tv = (TextView) findViewById(R.id.textView1); + tv.setText(mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME))); + // filetype + tv = (TextView) findViewById(R.id.textView2); + tv.setText(DisplayUtils.convertMIMEtoPrettyPrint(mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)))); + // size + tv = (TextView) findViewById(R.id.textView3); + tv.setText(DisplayUtils.bitsToHumanReadable(mCursor.getLong(mCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH)))); + // modified + tv = (TextView) findViewById(R.id.textView4); + tv.setText(mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_MODIFIED))); + if (!TextUtils.isEmpty(mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)))) { + ImageView iv = (ImageView) findViewById(R.id.imageView1); + Bitmap bmp = BitmapFactory.decodeFile(mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH))); + Matrix m = new Matrix(); + float scale; + if (bmp.getWidth() > bmp.getHeight()) { + scale = (float) (200./bmp.getWidth()); + } else { + scale = (float) (200./bmp.getHeight()); + } + m.postScale(scale, scale); + Bitmap newBmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true); + iv.setImageBitmap(newBmp); + } + setListAdapter(new FileListActionListAdapter(mCursor, this, mAccount)); + } + getListView().invalidate(); + } else { + try { + Intent i = (Intent) getListAdapter().getItem(position); + startActivity(i); + } catch (ClassCastException e) {} + } + } + + private void populateFileList() { + TextView tv = (TextView) findViewById(R.id.directory_name); + tv.setText("/"); + mCursor = getContentResolver().query(ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", + new String[]{mAccount.name}, + null); + setListAdapter(new FileListListAdapter(mCursor, this)); + getListView().invalidate(); + } + + + +} \ No newline at end of file diff --git a/src/eu/alefzero/owncloud/OwnCloudSession.java b/src/eu/alefzero/owncloud/OwnCloudSession.java new file mode 100644 index 00000000..c7caae50 --- /dev/null +++ b/src/eu/alefzero/owncloud/OwnCloudSession.java @@ -0,0 +1,33 @@ +package eu.alefzero.owncloud; + +public class OwnCloudSession { + private String mSessionName; + private String mSessionUrl; + private int mEntryId; + + public OwnCloudSession(String name, String url, int entryId) { + mSessionName = name; + mSessionUrl = url; + mEntryId = entryId; + } + + public void setName(String name) { + mSessionName = name; + } + + public String getName() { + return mSessionName; + } + + public void setUrl(String url) { + mSessionUrl = url; + } + + public String getUrl() { + return mSessionUrl; + } + + public int getEntryId() { + return mEntryId; + } +} diff --git a/src/eu/alefzero/owncloud/OwnCloudUploader.java b/src/eu/alefzero/owncloud/OwnCloudUploader.java new file mode 100644 index 00000000..5f2c194c --- /dev/null +++ b/src/eu/alefzero/owncloud/OwnCloudUploader.java @@ -0,0 +1,495 @@ +package eu.alefzero.owncloud; + +import java.io.File; +import java.sql.Date; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Stack; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.FileEntity; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.BasicHttpContext; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.app.AlertDialog.Builder; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.provider.MediaStore.Images.Media; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.ViewGroup.LayoutParams; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.Toast; +import android.widget.AdapterView.OnItemClickListener; +import eu.alefzero.owncloud.authenticator.AccountAuthenticator; +import eu.alefzero.owncloud.db.ProviderMeta; +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; +import eu.alefzero.webdav.HttpMkCol; +import eu.alefzero.webdav.WebdavUtils; + +public class OwnCloudUploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener { + private static final String TAG = "ownCloudUploader"; + + private Account mAccount; + private AccountManager mAccountManager; + private String mUsername, mPassword; + private Cursor mCursor; + private Stack mParents; + private Thread mUploadThread; + private Handler mHandler; + private ArrayList mStreamsToUpload; + + private final static int DIALOG_NO_ACCOUNT = 0; + private final static int DIALOG_WAITING = 1; + private final static int DIALOG_NO_STREAM = 2; + private final static int DIALOG_MULTIPLE_ACCOUNT = 3; + private final static int DIALOG_GET_DIRNAME = 4; + + private final static int REQUEST_CODE_SETUP_ACCOUNT = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + mParents = new Stack(); + mHandler = new Handler(); + if (getIntent().hasExtra(Intent.EXTRA_STREAM)) { + prepareStreamsToUpload(); + mAccountManager = (AccountManager)getSystemService(Context.ACCOUNT_SERVICE); + Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); + if (accounts.length == 0) { + Log.i(TAG, "No ownCloud account is available"); + showDialog(DIALOG_NO_ACCOUNT); + } else if (accounts.length > 1) { + Log.i(TAG, "More then one ownCloud is available"); + showDialog(DIALOG_MULTIPLE_ACCOUNT); + } else { + mAccount = accounts[0]; + setContentView(R.layout.uploader_layout); + populateDirectoryList(); + } + } else { + showDialog(DIALOG_NO_STREAM); + } + } + + @Override + protected Dialog onCreateDialog(final int id) { + final AlertDialog.Builder builder = new Builder(this); + switch (id) { + case DIALOG_WAITING: + ProgressDialog pDialog = new ProgressDialog(this); + pDialog.setIndeterminate(false); + pDialog.setCancelable(false); + pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading)); + return pDialog; + case DIALOG_NO_ACCOUNT: + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setTitle(R.string.uploader_wrn_no_account_title); + builder.setMessage(R.string.uploader_wrn_no_account_text); + builder.setCancelable(false); + builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) { + // using string value since in API7 this constatn is not defined + // in API7 < this constatant is defined in Settings.ADD_ACCOUNT_SETTINGS + // and Settings.EXTRA_AUTHORITIES + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + intent.putExtra("authorities", new String[] {AccountAuthenticator.AUTH_TOKEN_TYPE}); + startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); + } else { + // since in API7 there is no direct call for account setup, so we need to + // show our own AccountSetupAcricity, get desired results and setup + // everything for ourself + Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class); + startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); + } + } + }); + builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + return builder.create(); + case DIALOG_GET_DIRNAME: + final EditText dirName = new EditText(getBaseContext()); + builder.setView(dirName); + builder.setTitle(R.string.uploader_info_dirname); + String pathToUpload; + if (mParents.empty()) { + pathToUpload = "/"; + } else { + mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()), + null, + null, + null, + null); + mCursor.moveToFirst(); + pathToUpload = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH)) + + mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)).replace(" ", "%20"); + } + a a = new a(pathToUpload, dirName); + builder.setPositiveButton(R.string.common_ok, a); + builder.setNegativeButton(R.string.common_cancel, new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + return builder.create(); + case DIALOG_MULTIPLE_ACCOUNT: + CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length]; + for (int i = 0; i < ac.length; ++i) { + ac[i] = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[i].name; + } + builder.setTitle(R.string.common_choose_account); + builder.setItems(ac, new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mAccount = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[which]; + populateDirectoryList(); + } + }); + builder.setCancelable(true); + builder.setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + dialog.cancel(); + finish(); + } + }); + return builder.create(); + default: + throw new IllegalArgumentException("Unknown dialog id: " + id); + } + } + + class a implements OnClickListener { + String mPath; + EditText mDirname; + public a(String path, EditText dirname) { + mPath = path; mDirname = dirname; + } + public void onClick(DialogInterface dialog, int which) { + showDialog(DIALOG_WAITING); + mUploadThread = new Thread(new BackgroundUploader(mPath+mDirname.getText().toString(), mStreamsToUpload, mHandler, true)); + mUploadThread.start(); + } + } + + @Override + public void onBackPressed() { + + if (mParents.size()==0) { + super.onBackPressed(); + return; + } else if (mParents.size() == 1) { + mParents.pop(); + mCursor = managedQuery(ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_CONTENT_TYPE+"=?", + new String[]{"DIR"}, + null); + } else { + mParents.pop(); + mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, mParents.peek()), + null, + ProviderTableMeta.FILE_CONTENT_TYPE+"=?", + new String[]{"DIR"}, + null); + } + + SimpleCursorAdapter sca = new SimpleCursorAdapter(this, R.layout.uploader_list_item_layout, + mCursor, + new String[]{ProviderTableMeta.FILE_NAME}, + new int[]{R.id.textView1}); + setListAdapter(sca); + } + + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (!mCursor.moveToPosition(position)) { + throw new IndexOutOfBoundsException("Incorrect item selected"); + } + String _id = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta._ID)); + mParents.push(_id); + + mCursor.close(); + mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, _id), + null, + ProviderTableMeta.FILE_CONTENT_TYPE+"=?", + new String[]{"DIR"}, + null); + SimpleCursorAdapter sca = new SimpleCursorAdapter(this, R.layout.uploader_list_item_layout, + mCursor, + new String[]{ProviderTableMeta.FILE_NAME}, + new int[]{R.id.textView1}); + setListAdapter(sca); + getListView().invalidate(); + } + + public void onClick(View v) { + switch (v.getId()) { + case R.id.uploader_choose_folder: + String pathToUpload = null; + if (mParents.empty()) { + pathToUpload = "/"; + } else { + mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()), + null, + null, + null, + null); + mCursor.moveToFirst(); + pathToUpload = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH)) + + mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)).replace(" ", "%20"); + } + + showDialog(DIALOG_WAITING); + mUploadThread = new Thread(new BackgroundUploader(pathToUpload, mStreamsToUpload, mHandler)); + mUploadThread.start(); + + break; + case android.R.id.button1: // dynamic action for create aditional dir + showDialog(DIALOG_GET_DIRNAME); + break; + default: + throw new IllegalArgumentException("Wrong element clicked"); + } + } + + public void onUploadComplete(boolean uploadSucc, String message) { + dismissDialog(DIALOG_WAITING); + Log.i(TAG, "UploadSucc: " + uploadSucc + " message: " + message); + if (uploadSucc) { + Toast.makeText(this, getResources().getString(R.string.uploader_upload_succeed), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, getResources().getString(R.string.uploader_upload_failed) + message, Toast.LENGTH_LONG).show(); + } + finish(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + Log.i(TAG, "result received. req: " + requestCode + " res: " + resultCode); + if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) { + dismissDialog(DIALOG_NO_ACCOUNT); + if (resultCode == RESULT_CANCELED) { + finish(); + } + Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.AUTH_TOKEN_TYPE); + if (accounts.length == 0) { + showDialog(DIALOG_NO_ACCOUNT); + } else { + // there is no need for checking for is there more then one account at this point + // since account setup can set only one account at time + mAccount = accounts[0]; + populateDirectoryList(); + } + } + } + + private void populateDirectoryList() { + mUsername = mAccount.name.substring(0, mAccount.name.indexOf('@')); + mPassword = mAccountManager.getPassword(mAccount); + setContentView(R.layout.uploader_layout); + mCursor = managedQuery(ProviderMeta.ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_CONTENT_TYPE+"=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", + new String[]{"DIR", mAccount.name}, + null); + + ListView lv = getListView(); + lv.setOnItemClickListener(this); + SimpleCursorAdapter sca = new SimpleCursorAdapter(this, + R.layout.uploader_list_item_layout, + mCursor, + new String[]{ProviderTableMeta.FILE_NAME}, + new int[]{R.id.textView1}); + setListAdapter(sca); + Button btn = (Button) findViewById(R.id.uploader_choose_folder); + btn.setOnClickListener(this); + // insert create new directory for multiple items uploading + if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { + Button createDirBtn = new Button(this); + createDirBtn.setId(android.R.id.button1); + createDirBtn.setText(R.string.uploader_btn_create_dir_text); + createDirBtn.setOnClickListener(this); + ((LinearLayout)findViewById(R.id.linearLayout1)).addView(createDirBtn, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); + } + } + + private void prepareStreamsToUpload() { + if (getIntent().getAction().equals(Intent.ACTION_SEND)) { + mStreamsToUpload = new ArrayList(); + mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM)); + } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { + mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); + } else { + // unknow action inserted + throw new IllegalArgumentException("Unknown action given: " + getIntent().getAction()); + } + } + + public void PartialupdateUpload(String fileLocalPath, String filename, String filepath, String contentType, String contentLength) { + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_NAME, filename); + cv.put(ProviderTableMeta.FILE_PATH, filepath); + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, fileLocalPath); + cv.put(ProviderTableMeta.FILE_MODIFIED, WebdavUtils.DISPLAY_DATE_FORMAT.format(new java.util.Date())); + cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, contentType); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, contentLength); + cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); + Log.d(TAG, filename+" ++ "+filepath+" ++ " + contentLength + " ++ " + contentType + " ++ " + fileLocalPath); + if (!mParents.empty()) { + Cursor c = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()), + null, + null, + null, + null); + c.moveToFirst(); + cv.put(ProviderTableMeta.FILE_PARENT, c.getString(c.getColumnIndex(ProviderTableMeta._ID))); + c.close(); + } + getContentResolver().insert(ProviderTableMeta.CONTENT_URI_FILE, cv); + } + + class BackgroundUploader implements Runnable { + private ArrayList mUploadStreams; + private Handler mHandler; + private String mUploadPath; + private boolean mCreateDir; + + public BackgroundUploader(String pathToUpload, ArrayList streamsToUpload, + Handler handler) { + mUploadStreams = streamsToUpload; + mHandler = handler; + mUploadPath = pathToUpload.replace(" ", "%20"); + mCreateDir = false; + } + + public BackgroundUploader(String pathToUpload, ArrayList streamsToUpload, + Handler handler, boolean createDir) { + mUploadStreams = streamsToUpload; + mHandler = handler; + mUploadPath = pathToUpload.replace(" ", "%20"); + mCreateDir = createDir; + } + + public void run() { + boolean any_failed = false; + DefaultHttpClient httpClient = new DefaultHttpClient(); + Uri uri = Uri.parse(mAccountManager.getUserData(mAccount, + AccountAuthenticator.KEY_OC_URL)); + httpClient.getCredentialsProvider().setCredentials( + new AuthScope(uri.getHost(), (uri.getPort() == -1) ? 80 : uri + .getPort()), + new UsernamePasswordCredentials(mUsername, mPassword)); + BasicHttpContext httpContext = new BasicHttpContext(); + BasicScheme basicAuth = new BasicScheme(); + httpContext.setAttribute("preemptive-auth", basicAuth); + HttpHost targetHost = new HttpHost(uri.getHost(), (uri.getPort() == -1) + ? 80 + : uri.getPort(), (uri.getScheme() == "https") ? "https" : "http"); + + // create last directory in path if nessesary + if (mCreateDir) { + HttpMkCol method = new HttpMkCol(uri.toString() + mUploadPath + "/"); + method.setHeader("User-Agent", "Android-ownCloud"); + try { + httpClient.execute(targetHost, method, httpContext); + Log.i(TAG, "Creating dir completed"); + } catch (final Exception e) { + e.printStackTrace(); + mHandler.post(new Runnable() { + public void run() { + OwnCloudUploader.this.onUploadComplete(false, e.getLocalizedMessage()); + } + }); + return; + } + } + + for (int i = 0; i < mUploadStreams.size(); ++i) { + final Cursor c = getContentResolver().query((Uri)mUploadStreams.get(i), null, null, null, null); + c.moveToFirst(); + + HttpPut method = new HttpPut(uri.toString() + mUploadPath + "/" + + c.getString(c.getColumnIndex(Media.DISPLAY_NAME)).replace(" ", "%20")); + method.setHeader("Content-type", c.getString(c.getColumnIndex(Media.MIME_TYPE))); + method.setHeader("User-Agent", "Android-ownCloud"); + + try { + FileBody fb = new FileBody(new File(c.getString(c.getColumnIndex(Media.DATA))), c.getString(c.getColumnIndex(Media.MIME_TYPE))); + MultipartEntity entity = new MultipartEntity(); + final FileEntity fileEntity = new FileEntity(new File(c.getString(c.getColumnIndex(Media.DATA))), + c.getString(c.getColumnIndex(Media.MIME_TYPE))); + + entity.addPart(c.getString(c.getColumnIndex(Media.DISPLAY_NAME)).replace(" ", "%20"), fb); + + method.setEntity(fileEntity); + Log.i(TAG, "executing:" + method.getRequestLine().toString()); + + httpClient.execute(targetHost, method, httpContext); + mHandler.post(new Runnable() { + public void run() { + OwnCloudUploader.this.PartialupdateUpload(c.getString(c.getColumnIndex(Media.DATA)), + c.getString(c.getColumnIndex(Media.DISPLAY_NAME)), + mUploadPath + (mUploadPath.equals("/")?"":"/"), + fileEntity.getContentType().getValue(), + fileEntity.getContentLength()+""); + } + }); + Log.i(TAG, "Uploading, done"); + + } catch (final Exception e) { + any_failed = true; + mHandler.post(new Runnable() { + public void run() { + OwnCloudUploader.this.onUploadComplete(false, c.getString(c.getColumnIndex(Media.DISPLAY_NAME))+ " " + e.getLocalizedMessage()); + } + }); + } + } + if (!any_failed) { + mHandler.post(new Runnable() { + public void run() { + OwnCloudUploader.this.onUploadComplete(true, "Success"); + } + }); + } + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + //ContentResolver.requestSync(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE, bundle); + + } + + } + +} diff --git a/src/eu/alefzero/owncloud/Preferences.java b/src/eu/alefzero/owncloud/Preferences.java new file mode 100644 index 00000000..9ca0e256 --- /dev/null +++ b/src/eu/alefzero/owncloud/Preferences.java @@ -0,0 +1,139 @@ +package eu.alefzero.owncloud; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Vector; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ContextMenu.ContextMenuInfo; +import android.widget.AdapterView.AdapterContextMenuInfo; + +public class Preferences extends PreferenceActivity { + private String TAG = "OwnCloudPreferences"; + private final int mNewSession = 47; + private final int mEditSession = 48; + private DbHandler mDbHandler; + private Vector mSessions; + private int mSelectedMenuItem; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + mDbHandler = new DbHandler(getBaseContext()); + mSessions = new Vector(); + addPreferencesFromResource(R.xml.preferences); + registerForContextMenu(getListView()); + //populateSessionList(); + } + + private void populateSessionList() { + mSessions.clear(); + mSessions = mDbHandler.getSessionList(); + PreferenceScreen ps = getPreferenceScreen(); + ps.removeAll(); + addPreferencesFromResource(R.xml.preferences); + for (int i = 0; i < mSessions.size(); i++) { + Preference preference = new Preference(getBaseContext()); + preference.setTitle(mSessions.get(i).getName()); + URI uri; + try { + uri = new URI(mSessions.get(i).getUrl()); + } catch (URISyntaxException e) { + e.printStackTrace(); // should never happend + continue; + } + preference.setSummary(uri.getScheme() + "://" + uri.getHost()+uri.getPath()); + ps.addPreference(preference); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.prefs_menu, menu); + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + super.onMenuItemSelected(featureId, item); + Intent intent; + + switch (item.getItemId()) { + case R.id.addSessionItem: + intent = new Intent(this, PreferencesNewSession.class); + startActivityForResult(intent, mNewSession); + break; + case R.id.SessionContextEdit: + intent = new Intent(this, PreferencesNewSession.class); + intent.putExtra("sessionId", mSessions.get(mSelectedMenuItem).getEntryId()); + intent.putExtra("sessionName", mSessions.get(mSelectedMenuItem).getName()); + intent.putExtra("sessionURL", mSessions.get(mSelectedMenuItem).getUrl()); + startActivityForResult(intent, mEditSession); + break; + case R.id.SessionContextRemove: + OwnCloudSession ocs = mSessions.get(mSelectedMenuItem); + mDbHandler.removeSessionWithId(ocs.getEntryId()); + mSessions.remove(ocs); + getPreferenceScreen().removePreference(getPreferenceScreen().getPreference(mSelectedMenuItem+1)); + break; + default: + Log.w(TAG, "Unknown menu item triggered"); + return false; + } + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case mNewSession: + mDbHandler.addSession(data.getStringExtra("sessionName"), + data.getStringExtra("sessionURL")); + getPreferenceScreen().removeAll(); + addPreferencesFromResource(R.xml.preferences); + populateSessionList(); + break; + case mEditSession: + mDbHandler.changeSessionFields(data.getIntExtra("sessionId", -1), + data.getStringExtra("sessionName"), + data.getStringExtra("sessionURL")); + populateSessionList(); + break; + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + mSelectedMenuItem = info.position-1; + menu.setHeaderTitle(mSessions.get(mSelectedMenuItem).getName()); + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.session_context_menu, menu); + + } + + @Override + protected void onDestroy() { + mDbHandler.close(); + super.onDestroy(); + } + +} diff --git a/src/eu/alefzero/owncloud/PreferencesNewSession.java b/src/eu/alefzero/owncloud/PreferencesNewSession.java new file mode 100644 index 00000000..bb4a9c0c --- /dev/null +++ b/src/eu/alefzero/owncloud/PreferencesNewSession.java @@ -0,0 +1,143 @@ +package eu.alefzero.owncloud; + +import java.net.URI; +import java.net.URISyntaxException; + +import eu.alefzero.owncloud.authenticator.AccountAuthenticatorService; + +import android.accounts.Account; +import android.accounts.AccountAuthenticatorActivity; +import android.accounts.AccountManager; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +public class PreferencesNewSession extends AccountAuthenticatorActivity implements OnClickListener { + private Intent mReturnData; + private final String TAG = "OwnCloudPreferencesNewSession"; + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + //setContentView(R.layout.add_new_session); + /* + EditText et;// = (EditText) findViewById(R.id.newSession_sessionName); + + et = (EditText) findViewById(R.id.newSession_URL); + if (getIntent().hasExtra("sessionURL")) { + try { + URI uri = new URI(getIntent().getStringExtra("sessionURL")); + String url = uri.getHost(); + if (uri.getPort() != -1) { + url += ":" + String.valueOf(uri.getPort()); + } + if (uri.getPath() != null) { + url += uri.getPath(); + } else { + url += "/"; + } + et.setText(url); + et = (EditText) findViewById(R.id.newSession_username); + if (uri.getAuthority() != null) { + if (uri.getUserInfo().indexOf(':') != -1) { + et.setText(uri.getUserInfo().substring(0, uri.getUserInfo().indexOf(':'))); + et = (EditText) findViewById(R.id.newSession_password); + et.setText(uri.getUserInfo().substring(uri.getUserInfo().indexOf(':')+1)); + } else { + et.setText(uri.getUserInfo()); + } + } + + } catch (URISyntaxException e) { + Log.e(TAG, "Incorrect URI syntax " + e.getLocalizedMessage()); + } + } + + mReturnData = new Intent(); + setResult(Activity.RESULT_OK, mReturnData); + ((Button) findViewById(R.id.button1)).setOnClickListener(this); + ((Button) findViewById(R.id.button2)).setOnClickListener(this);*/ + } + + @Override + protected void onResume() { + super.onResume(); + } + + public void onClick(View v) { + /* switch (v.getId()) { + case R.id.button1: + Intent intent = new Intent(); + if (getIntent().hasExtra("sessionId")) { + intent.putExtra("sessionId", getIntent().getIntExtra("sessionId", -1)); + } + //String sessionName = ((EditText) findViewById(R.id.newSession_sessionName)).getText().toString(); + // if (sessionName.trim().equals("") || !isNameValid(sessionName)) { + // Toast.makeText(this, R.string.new_session_session_name_error, Toast.LENGTH_LONG).show(); + // break; + // } + URI uri = prepareURI(); + if (uri != null) { + //intent.putExtra("sessionName", sessionName); + intent.putExtra("sessionURL", uri.toString()); + setResult(Activity.RESULT_OK, intent); + AccountManager accMgr = AccountManager.get(this); + Account a = new Account("OwnCloud", AccountAuthenticatorService.ACCOUNT_TYPE); + accMgr.addAccountExplicitly(a, "asd", null); + finish(); + } + break; + case R.id.button2: + setResult(Activity.RESULT_CANCELED); + finish(); + break; + }*/ + } + + private URI prepareURI() { + URI uri = null; + /* String url = ""; + try { + String username = ((EditText) findViewById(R.id.newSession_username)).getText().toString().trim(); + String password = ((EditText) findViewById(R.id.newSession_password)).getText().toString().trim(); + String hostname = ((EditText) findViewById(R.id.newSession_URL)).getText().toString().trim(); + String scheme; + if (hostname.matches("[A-Za-z]://")) { + scheme = hostname.substring(0, hostname.indexOf("://")+3); + hostname = hostname.substring(hostname.indexOf("://")+3); + } else { + scheme = "http://"; + } + if (!username.equals("")) { + if (!password.equals("")) { + username += ":" + password + "@"; + } else { + username += "@"; + } + } + url = scheme + username + hostname; + Log.i(TAG, url); + uri = new URI(url); + } catch (URISyntaxException e) { + Log.e(TAG, "Incorrect URI syntax " + e.getLocalizedMessage()); + Toast.makeText(this, R.string.new_session_uri_error, Toast.LENGTH_LONG).show(); + } + */return uri; + } + + private boolean isNameValid(String string) { + return string.matches("[A-Za-z0-9 _-]*"); + } + + @Override + public void onBackPressed() { + setResult(Activity.RESULT_CANCELED); + super.onBackPressed(); + } + +} diff --git a/src/eu/alefzero/owncloud/QuickAction.java b/src/eu/alefzero/owncloud/QuickAction.java new file mode 100644 index 00000000..d1974230 --- /dev/null +++ b/src/eu/alefzero/owncloud/QuickAction.java @@ -0,0 +1,267 @@ +package eu.alefzero.owncloud; + +import android.content.Context; + +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * Popup window, shows action list as icon and text like the one in Gallery3D app. + * + * @author Lorensius. W. T + */ +public class QuickAction extends CustomPopup { + private final View root; + private final ImageView mArrowUp; + private final ImageView mArrowDown; + private final LayoutInflater inflater; + private final Context context; + + protected static final int ANIM_GROW_FROM_LEFT = 1; + protected static final int ANIM_GROW_FROM_RIGHT = 2; + protected static final int ANIM_GROW_FROM_CENTER = 3; + protected static final int ANIM_REFLECT = 4; + protected static final int ANIM_AUTO = 5; + + private int animStyle; + private ViewGroup mTrack; + private ScrollView scroller; + private ArrayList actionList; + + /** + * Constructor + * + * @param anchor {@link View} on where the popup window should be displayed + */ + public QuickAction(View anchor) { + super(anchor); + + actionList = new ArrayList(); + context = anchor.getContext(); + inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + root = (ViewGroup) inflater.inflate(R.layout.popup, null); + + mArrowDown = (ImageView) root.findViewById(R.id.arrow_down); + mArrowUp = (ImageView) root.findViewById(R.id.arrow_up); + + setContentView(root); + + mTrack = (ViewGroup) root.findViewById(R.id.tracks); + scroller = (ScrollView) root.findViewById(R.id.scroller); + animStyle = ANIM_AUTO; + } + + /** + * Set animation style + * + * @param animStyle animation style, default is set to ANIM_AUTO + */ + public void setAnimStyle(int animStyle) { + this.animStyle = animStyle; + } + + /** + * Add action item + * + * @param action {@link ActionItem} object + */ + public void addActionItem(ActionItem action) { + actionList.add(action); + } + + /** + * Show popup window. Popup is automatically positioned, on top or bottom of anchor view. + * + */ + public void show () { + preShow(); + + int xPos, yPos; + + int[] location = new int[2]; + + mAnchor.getLocationOnScreen(location); + + Rect anchorRect = new Rect(location[0], location[1], location[0] + mAnchor.getWidth(), location[1] + + mAnchor.getHeight()); + + createActionList(); + + root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + int rootHeight = root.getMeasuredHeight(); + int rootWidth = root.getMeasuredWidth(); + + int screenWidth = mWManager.getDefaultDisplay().getWidth(); + int screenHeight = mWManager.getDefaultDisplay().getHeight(); + + //automatically get X coord of popup (top left) + if ((anchorRect.left + rootWidth) > screenWidth) { + xPos = anchorRect.left - (rootWidth-mAnchor.getWidth()); + } else { + if (mAnchor.getWidth() > rootWidth) { + xPos = anchorRect.centerX() - (rootWidth/2); + } else { + xPos = anchorRect.left; + } + } + + int dyTop = anchorRect.top; + int dyBottom = screenHeight - anchorRect.bottom; + + boolean onTop = (dyTop > dyBottom) ? true : false; + + if (onTop) { + if (rootHeight > dyTop) { + yPos = 15; + LayoutParams l = scroller.getLayoutParams(); + l.height = dyTop - mAnchor.getHeight(); + } else { + yPos = anchorRect.top - rootHeight; + } + } else { + yPos = anchorRect.bottom; + + if (rootHeight > dyBottom) { + LayoutParams l = scroller.getLayoutParams(); + l.height = dyBottom; + } + } + + showArrow(((onTop) ? R.id.arrow_down : R.id.arrow_up), anchorRect.centerX()-xPos); + + setAnimationStyle(screenWidth, anchorRect.centerX(), onTop); + + mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, xPos, yPos); + } + + /** + * Set animation style + * + * @param screenWidth screen width + * @param requestedX distance from left edge + * @param onTop flag to indicate where the popup should be displayed. Set TRUE if displayed on top of anchor view + * and vice versa + */ + private void setAnimationStyle(int screenWidth, int requestedX, boolean onTop) { + int arrowPos = requestedX - mArrowUp.getMeasuredWidth()/2; + + switch (animStyle) { + case ANIM_GROW_FROM_LEFT: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left); + break; + + case ANIM_GROW_FROM_RIGHT: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right); + break; + + case ANIM_GROW_FROM_CENTER: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center); + break; + + case ANIM_REFLECT: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Reflect : R.style.Animations_PopDownMenu_Reflect); + break; + + case ANIM_AUTO: + if (arrowPos <= screenWidth/4) { + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left); + } else if (arrowPos > screenWidth/4 && arrowPos < 3 * (screenWidth/4)) { + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center); + } else { + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right); + } + + break; + } + } + + /** + * Create action list + */ + private void createActionList() { + View view; + String title; + Drawable icon; + OnClickListener listener; + + for (int i = 0; i < actionList.size(); i++) { + title = actionList.get(i).getTitle(); + icon = actionList.get(i).getIcon(); + listener = actionList.get(i).getOnClickListerner(); + + view = getActionItem(title, icon, listener); + + view.setFocusable(true); + view.setClickable(true); + + mTrack.addView(view); + } + } + + /** + * Get action item {@link View} + * + * @param title action item title + * @param icon {@link Drawable} action item icon + * @param listener {@link View.OnClickListener} action item listener + * @return action item {@link View} + */ + private View getActionItem(String title, Drawable icon, OnClickListener listener) { + LinearLayout container = (LinearLayout) inflater.inflate(R.layout.action_item, null); + + ImageView img = (ImageView) container.findViewById(R.id.icon); + TextView text = (TextView) container.findViewById(R.id.title); + + if (icon != null) { + img.setImageDrawable(icon); + } + + if (title != null) { + text.setText(title); + } + + if (listener != null) { + container.setOnClickListener(listener); + } + + return container; + } + + /** + * Show arrow + * + * @param whichArrow arrow type resource id + * @param requestedX distance from left screen + */ + private void showArrow(int whichArrow, int requestedX) { + final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp : mArrowDown; + final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown : mArrowUp; + + final int arrowWidth = mArrowUp.getMeasuredWidth(); + + showArrow.setVisibility(View.VISIBLE); + + ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams)showArrow.getLayoutParams(); + + param.leftMargin = requestedX - arrowWidth / 2; + + hideArrow.setVisibility(View.INVISIBLE); + } +} \ No newline at end of file diff --git a/src/eu/alefzero/owncloud/authenticator/AccountAuthenticator.java b/src/eu/alefzero/owncloud/authenticator/AccountAuthenticator.java new file mode 100644 index 00000000..c393f0ba --- /dev/null +++ b/src/eu/alefzero/owncloud/authenticator/AccountAuthenticator.java @@ -0,0 +1,238 @@ +package eu.alefzero.owncloud.authenticator; + +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.accounts.NetworkErrorException; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +public class AccountAuthenticator extends AbstractAccountAuthenticator { + public static final String OPTIONS_USERNAME = "username"; + public static final String OPTIONS_PASSWORD = "password"; + public static final String OPTIONS_FILE_LIST_SYNC_ENABLED = "filelist"; + public static final String OPTIONS_PINNED_FILE_SYNC_ENABLED = "pinned"; + + public static final String ACCOUNT_TYPE = "owncloud"; + public static final String AUTH_TOKEN_TYPE = "org.owncloud"; + + public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; + public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; + public static final String KEY_LOGIN_OPTIONS = "loginOptions"; + public static final String KEY_ACCOUNT = "account"; + public static final String KEY_OC_URL = "oc_url"; + + private Context mContext; + + public AccountAuthenticator(Context context) { + super(context); + mContext = context; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, + String accountType, String authTokenType, String[] requiredFeatures, + Bundle options) throws NetworkErrorException { + Log.i(getClass().getName(), "Adding account with type " + accountType + + " and auth token " + authTokenType); + try { + validateAccountType(accountType); + //validateAuthTokenType(authTokenType); + validateRequiredFeatures(requiredFeatures); + } catch (AuthenticatorException e) { + e.printStackTrace(); + return e.getFailureBundle(); + } + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + + setIntentFlags(intent); + Log.i(getClass().getName(), intent.toString()); + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, + Account account, Bundle options) throws NetworkErrorException { + try { + validateAccountType(account.type); + } catch (AuthenticatorException e) { + return e.getFailureBundle(); + } + Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + + setIntentFlags(intent); + + Bundle resultBundle = new Bundle(); + resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); + return resultBundle; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, + String accountType) { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + Log.i(getClass().getName(), "Getting authToken"); + try { + validateAccountType(account.type); + validateAuthTokenType(authTokenType); + } catch (AuthenticatorException e) { + Log.w(getClass().getName(), "Validating failded in getAuthToken"); + return e.getFailureBundle(); + } + final AccountManager am = AccountManager.get(mContext); + final String password = am.getPassword(account); + if (password != null) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); + result.putString(AccountManager.KEY_AUTHTOKEN, password); + return result; + } + + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, + Account account, String[] features) throws NetworkErrorException { + final Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); + return result; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + setIntentFlags(intent); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, + Account account) throws NetworkErrorException { + return super.getAccountRemovalAllowed(response, account); + } + + private void setIntentFlags(Intent intent) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.addFlags(Intent.FLAG_FROM_BACKGROUND); + } + + private void validateAccountType(String type) throws UnsupportedAccountTypeException { + if (!type.equals(ACCOUNT_TYPE)) { + throw new UnsupportedAccountTypeException(); + } + } + + private void validateAuthTokenType(String authTokenType) throws UnsupportedAuthTokenTypeException { + if (!authTokenType.equals(AUTH_TOKEN_TYPE)) { + throw new UnsupportedAuthTokenTypeException(); + } + } + + private void validateRequiredFeatures(String[] requiredFeatures) throws UnsupportedFeaturesException { + // TODO + } + + private void validateCreaditials(String username, String password, String path) throws AccessDeniedException { + + } + + public static class AuthenticatorException extends Exception { + private static final long serialVersionUID = 1L; + private Bundle mFailureBundle; + public AuthenticatorException(int code, String errorMsg) { + mFailureBundle = new Bundle(); + mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); + mFailureBundle.putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); + } + + public Bundle getFailureBundle() { + return mFailureBundle; + } + } + + public static class UnsupportedAccountTypeException extends AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAccountTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, "Unsupported account type"); + } + } + + public static class UnsupportedAuthTokenTypeException extends AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAuthTokenTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, "Unsupported auth token type"); + } + } + + public static class UnsupportedFeaturesException extends AuthenticatorException { + public static final long serialVersionUID = 1L; + + public UnsupportedFeaturesException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, "Unsupported features"); + } + } + + public static class AccessDeniedException extends AuthenticatorException { + public AccessDeniedException(int code, String errorMsg) { + super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); + } + + private static final long serialVersionUID = 1L; + + } +} diff --git a/src/eu/alefzero/owncloud/authenticator/AccountAuthenticatorService.java b/src/eu/alefzero/owncloud/authenticator/AccountAuthenticatorService.java new file mode 100644 index 00000000..7451f1df --- /dev/null +++ b/src/eu/alefzero/owncloud/authenticator/AccountAuthenticatorService.java @@ -0,0 +1,41 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud.authenticator; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class AccountAuthenticatorService extends Service { + + private AccountAuthenticator mAuthenticator; + static final public String ACCOUNT_TYPE = "owncloud"; + + @Override + public void onCreate() { + super.onCreate(); + mAuthenticator = new AccountAuthenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } + +} diff --git a/src/eu/alefzero/owncloud/authenticator/AuthUtils.java b/src/eu/alefzero/owncloud/authenticator/AuthUtils.java new file mode 100644 index 00000000..dbf30ed9 --- /dev/null +++ b/src/eu/alefzero/owncloud/authenticator/AuthUtils.java @@ -0,0 +1,189 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud.authenticator; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; + +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.BasicHttpContext; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; + +public class AuthUtils { + public static final String WEBDAV_PATH_1_2 = "/webdav/owncloud.php"; + public static final String WEBDAV_PATH_2_0 = "/files/webdav.php"; + + private static String mResultMsg = ""; + + public static boolean authenticate(URL url, String username, String password, + Handler handler, Context context) { + String strippedPath = url.toString().endsWith("/") ? + url.toString().substring(0, url.toString().length()-1) : + url.toString(); + String webdatPath = strippedPath + WEBDAV_PATH_2_0; + URL complete_url = null; + try { + complete_url = new URL(webdatPath); + } catch (MalformedURLException e) { + // should never happend + sendResult(false, handler, context, "URL error"); + return false; + } + + // version 2.0 success + if (tryGetWebdav(complete_url, username, password, handler, context)) { + sendResult(true, handler, context, complete_url.toString()); + return true; + } + + if (mResultMsg.equals("401")) { + sendResult(false, handler, context, "Invalid login or/and password"); + return false; + } + + if (!mResultMsg.equals("404")) { + sendResult(false, handler, context, "Server error: " + mResultMsg); + return false; + } + + webdatPath = strippedPath + WEBDAV_PATH_1_2; + try { + complete_url = new URL(webdatPath); + } catch (MalformedURLException e) { + // should never happend + sendResult(false, handler, context, "URL error"); + return false; + } + + // version 1.2 success + if (tryGetWebdav(complete_url, username, password, handler, context)) { + sendResult(true, handler, context, complete_url.toString()); + return true; + } + + if (mResultMsg.equals("401")) { + sendResult(false, handler, context, "Invalid login or/and password"); + return false; + } + + if (mResultMsg.equals("404")) { + sendResult(false, handler, context, "Wrong path given"); + return false; + } + + sendResult(false, handler, context, "Server error: " + mResultMsg); + return false; + } + + public static boolean tryGetWebdav(URL url, String username, String pwd, + Handler handler, Context context) { + DefaultHttpClient c = new DefaultHttpClient(); + c.getCredentialsProvider().setCredentials( + new AuthScope(url.getHost(), (url.getPort() == -1)?80:url.getPort()), + new UsernamePasswordCredentials(username, pwd)); + + BasicHttpContext localcontext = new BasicHttpContext(); + BasicScheme basicAuth = new BasicScheme(); + + localcontext.setAttribute("preemptive-auth", basicAuth); + HttpHost targetHost = new HttpHost(url.getHost(), (url.getPort() == -1) + ? 80 + : url.getPort(), (url.getProtocol() == "https") ? "https" : "http"); + HttpHead httpget = new HttpHead(url.toString()); + HttpResponse response = null; + try { + response = c.execute(targetHost, httpget, localcontext); + } catch (ClientProtocolException e1) { + sendResult(false, handler, context, "Protocol error: " + + e1.getLocalizedMessage()); + return false; + } catch (UnknownHostException e1) { + mResultMsg = "Unknowh host: " + e1.getLocalizedMessage(); + return false; + } catch (IOException e1) { + mResultMsg = "Error: " + e1.getLocalizedMessage(); + return false; + } + String status = response.getStatusLine().toString(); + status = status.split(" ")[1]; + Log.i("AuthUtils", "Status returned: " + status); + if (status.equals("200")) { + return true; + } else if (status.equals("404")) { + mResultMsg = "404"; + return false; + } else if (status.equals("401")) { + mResultMsg = "401"; + return false; + } + mResultMsg = status; + return false; + } + + public static Thread performOnBackgroundThread(final Runnable r) { + final Thread t = new Thread() { + @Override + public void run() { + try { + r.run(); + } finally {} + } + }; + t.start(); + return t; + } + + public static void sendResult(final Boolean result, + final Handler handler, + final Context context, + final String message) { + if (handler == null || context == null) { + return; + } + handler.post(new Runnable() { + public void run() { + ((AuthenticatorActivity) context).onAuthenticationResult(result, message); + } + }); + } + + public static Thread attemptAuth(final URL url, final String username, + final String password, final Handler handler, + final Context context) { + final Runnable r = new Runnable() { + + public void run() { + authenticate(url, username, password, handler, context); + } + }; + return performOnBackgroundThread(r); + } +} diff --git a/src/eu/alefzero/owncloud/authenticator/AuthenticatorActivity.java b/src/eu/alefzero/owncloud/authenticator/AuthenticatorActivity.java new file mode 100644 index 00000000..2eea548f --- /dev/null +++ b/src/eu/alefzero/owncloud/authenticator/AuthenticatorActivity.java @@ -0,0 +1,176 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud.authenticator; + +import java.net.MalformedURLException; +import java.net.URL; + +import android.accounts.Account; +import android.accounts.AccountAuthenticatorActivity; +import android.accounts.AccountManager; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.TextView; +import android.widget.Toast; +import eu.alefzero.owncloud.R; +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; + +public class AuthenticatorActivity extends AccountAuthenticatorActivity { + private Thread mAuthThread; + private final Handler mHandler = new Handler(); + + public static final String PARAM_USERNAME = "param_Username"; + public static final String PARAM_HOSTNAME = "param_Hostname"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.account_setup); + if (getIntent().hasExtra(PARAM_USERNAME)) { + String username = getIntent().getStringExtra(PARAM_HOSTNAME); + TextView host_text, user_text; + host_text = (TextView) findViewById(R.id.host_URL); + user_text = (TextView) findViewById(R.id.account_username); + host_text.setText(host_text.getText() + username.substring(username.lastIndexOf('@'))); + user_text.setText(user_text.getText() + username.substring(0, username.lastIndexOf('@')-1)); + } + } + + @Override + protected Dialog onCreateDialog(int id) { + final ProgressDialog dialog = new ProgressDialog(this); + dialog.setMessage("Trying to login"); + dialog.setIndeterminate(true); + dialog.setCancelable(true); + dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + Log.i(getClass().getName(), "Login canceled"); + if (mAuthThread != null) { + mAuthThread.interrupt(); + finish(); + } + } + }); + return dialog; + } + + public void onAuthenticationResult(boolean result, String message) { + if (result) { + TextView username_text = (TextView) findViewById(R.id.account_username), + password_text = (TextView) findViewById(R.id.account_password); + + URL url = null; + try { + url = new URL(message); + } catch (MalformedURLException e) { + // should never happend + Log.e(getClass().getName(), "Malformed URL: " + message); + return; + } + + Account account = new Account(username_text.getText().toString() + "@" + url.getHost(), AccountAuthenticator.ACCOUNT_TYPE); + AccountManager accManager = AccountManager.get(this); + accManager.addAccountExplicitly(account, password_text.getText().toString(),null); + + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name); + intent.putExtra(AccountManager.KEY_AUTHTOKEN, AccountAuthenticator.ACCOUNT_TYPE); + accManager.setUserData(account, AccountAuthenticator.KEY_OC_URL, url.toString()); + setAccountAuthenticatorResult(intent.getExtras()); + setResult(RESULT_OK, intent); + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + getContentResolver().startSync(ProviderTableMeta.CONTENT_URI, bundle); + + dismissDialog(0); + finish(); + } else { + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + dismissDialog(0); + } + } + + public void onCancelClick(View view) { + Log.i(getClass().getName(), "Account creating canceled"); + this.finish(); + } + + public void onOkClick(View view) { + TextView url_text = (TextView) findViewById(R.id.host_URL); + TextView username_text = (TextView) findViewById(R.id.account_username); + TextView password_text = (TextView) findViewById(R.id.account_password); + Log.i(getClass().getName(), "OK clicked"); + boolean hasErrors = false; + + URL uri = null; + if (url_text.getText().toString().trim().length() == 0) { + url_text.setTextColor(Color.RED); + hasErrors = true; + } else { + url_text.setTextColor(Color.BLACK); + } + try { + String url_str = url_text.getText().toString(); + if (!url_str.startsWith("http://") && + !url_str.startsWith("https://")) { + url_str = "http://" + url_str; + } + uri = new URL(url_str); + } catch (MalformedURLException e) { + url_text.setTextColor(Color.RED); + e.printStackTrace(); + hasErrors = true; + } + + if (username_text.getText().toString().contains(" ") || + username_text.getText().toString().trim().length() == 0) { + username_text.setTextColor(Color.RED); + hasErrors = true; + } else { + username_text.setTextColor(Color.BLACK); + } + + if (password_text.getText().toString().trim().length() == 0) { + password_text.setTextColor(Color.RED); + hasErrors = true; + } else { + password_text.setTextColor(Color.BLACK); + } + if (hasErrors) { + return; + } + showDialog(0); + mAuthThread = AuthUtils.attemptAuth(uri, + username_text.getText().toString(), + password_text.getText().toString(), + mHandler, + AuthenticatorActivity.this); + } +} diff --git a/src/eu/alefzero/owncloud/cp.java b/src/eu/alefzero/owncloud/cp.java new file mode 100644 index 00000000..37a31897 --- /dev/null +++ b/src/eu/alefzero/owncloud/cp.java @@ -0,0 +1,210 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud; + +import java.util.HashMap; + +import eu.alefzero.owncloud.db.ProviderMeta; +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; + +public class cp extends ContentProvider { + + private DataBaseHelper mDbHelper; + + private static HashMap mProjectionMap; + static { + mProjectionMap = new HashMap(); + mProjectionMap.put(ProviderTableMeta._ID, + ProviderTableMeta._ID); + mProjectionMap.put(ProviderTableMeta.FILE_PARENT, + ProviderTableMeta.FILE_PARENT); + mProjectionMap.put(ProviderTableMeta.FILE_PATH, + ProviderTableMeta.FILE_PATH); + mProjectionMap.put(ProviderTableMeta.FILE_NAME, + ProviderTableMeta.FILE_NAME); + mProjectionMap.put(ProviderTableMeta.FILE_CREATION, + ProviderTableMeta.FILE_CREATION); + mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED, + ProviderTableMeta.FILE_MODIFIED); + mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH, + ProviderTableMeta.FILE_CONTENT_LENGTH); + mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE, + ProviderTableMeta.FILE_CONTENT_TYPE); + mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH, + ProviderTableMeta.FILE_STORAGE_PATH); + } + + private static final int SINGLE_FILE = 1; + private static final int DIRECTORY = 2; + private static final int ROOT_DIRECTORY = 3; + private static final UriMatcher mUriMatcher; + static { + mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mUriMatcher.addURI(ProviderMeta.AUTHORITY, "/", ROOT_DIRECTORY); + mUriMatcher.addURI(ProviderMeta.AUTHORITY, "file/", SINGLE_FILE); + mUriMatcher.addURI(ProviderMeta.AUTHORITY, "file/#", SINGLE_FILE); + mUriMatcher.addURI(ProviderMeta.AUTHORITY, "dir/#", DIRECTORY); + } + + private static final String TAG = "OC_ContentProvider"; + + @Override + public int delete(Uri uri, String where, String[] whereArgs) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + int count = 0; + switch (mUriMatcher.match(uri)) { + case SINGLE_FILE: + count = db.delete(ProviderTableMeta.DB_NAME, + ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1) + + (!TextUtils.isEmpty(where)?" AND (" + where +")" : ""), + whereArgs); + break; + case ROOT_DIRECTORY: + count = db.delete(ProviderTableMeta.DB_NAME, where, whereArgs); + break; + default: + throw new IllegalArgumentException("Unknown uri: " + uri.toString()); + } + getContext().getContentResolver().notifyChange(uri, null); + return count; + } + + @Override + public String getType(Uri uri) { + switch (mUriMatcher.match(uri)) { + case ROOT_DIRECTORY: + return ProviderTableMeta.CONTENT_TYPE; + case SINGLE_FILE: + return ProviderTableMeta.CONTENT_TYPE_ITEM; + default: + throw new IllegalArgumentException("Unknown Uri id." + uri.toString()); + } + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + if (mUriMatcher.match(uri) != SINGLE_FILE) { + throw new IllegalArgumentException("Unknown uri id: " + uri); + } + + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + long rowId = db.insert(ProviderTableMeta.DB_NAME, ProviderTableMeta.FILE_NAME, values); + if (rowId > 0) { + Uri insertedFileUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId); + getContext().getContentResolver().notifyChange(insertedFileUri, null); + return insertedFileUri; + } + throw new SQLException("ERROR " + uri); + } + + @Override + public boolean onCreate() { + mDbHelper = new DataBaseHelper(getContext()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder(); + + sqlQuery.setTables(ProviderTableMeta.DB_NAME); + sqlQuery.setProjectionMap(mProjectionMap); + + switch (mUriMatcher.match(uri)) { + case ROOT_DIRECTORY: + sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + " is null"); + break; + case DIRECTORY: + sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "="+uri.getPathSegments().get(1)); + break; + case SINGLE_FILE: + if (uri.getPathSegments().size() > 1) { + sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + + uri.getPathSegments().get(1)); + } + break; + default: + throw new IllegalArgumentException("Unknown uri id: " + uri); + } + + String order; + if (TextUtils.isEmpty(sortOrder)) { + order = ProviderTableMeta.DEFAULT_SORT_ORDER; + } else { + order = sortOrder; + } + + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, null, null, order); + + c.setNotificationUri(getContext().getContentResolver(), uri); + + return c; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + return mDbHelper.getWritableDatabase().update(ProviderTableMeta.DB_NAME, values, selection, selectionArgs); + } + + class DataBaseHelper extends SQLiteOpenHelper { + + public DataBaseHelper(Context context) { + super(context, ProviderMeta.DB_NAME, null, ProviderMeta.DB_VERSION); + + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + ProviderTableMeta.DB_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " + + ProviderTableMeta.FILE_NAME + " TEXT, " + + ProviderTableMeta.FILE_PATH + " TEXT, " + + ProviderTableMeta.FILE_PARENT + " INTEGER, " + + ProviderTableMeta.FILE_CREATION + " INTEGER, " + + ProviderTableMeta.FILE_MODIFIED + " INTEGER, " + + ProviderTableMeta.FILE_CONTENT_TYPE + " TEXT, " + + ProviderTableMeta.FILE_CONTENT_LENGTH + " INTEGER, " + + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT);"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + + } + +} diff --git a/src/eu/alefzero/owncloud/db/ProviderMeta.java b/src/eu/alefzero/owncloud/db/ProviderMeta.java new file mode 100644 index 00000000..636c699f --- /dev/null +++ b/src/eu/alefzero/owncloud/db/ProviderMeta.java @@ -0,0 +1,59 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package eu.alefzero.owncloud.db; + +import android.net.Uri; +import android.provider.BaseColumns; + +public class ProviderMeta { + + public static final String AUTHORITY = "org.owncloud"; + public static final String DB_FILE = "owncloud.db"; + public static final String DB_NAME = "filelist"; + public static final int DB_VERSION = 1; + + private ProviderMeta() { } + + static public class ProviderTableMeta implements BaseColumns { + public static final String DB_NAME = "filelist"; + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/"); + public static final Uri CONTENT_URI_FILE = + Uri.parse("content://" + AUTHORITY + "/file"); + public static final Uri CONTENT_URI_DIR = + Uri.parse("content://" + AUTHORITY + "/dir"); + + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.owncloud.file"; + public static final String CONTENT_TYPE_ITEM = + "vnd.android.cursor.item/vnd.owncloud.file"; + + public static final String FILE_PARENT = "parent"; + public static final String FILE_NAME = "filename"; + public static final String FILE_CREATION = "created"; + public static final String FILE_MODIFIED = "modified"; + public static final String FILE_CONTENT_LENGTH = "content_length"; + public static final String FILE_CONTENT_TYPE = "content_type"; + public static final String FILE_STORAGE_PATH = "media_path"; + public static final String FILE_PATH = "path"; + public static final String FILE_ACCOUNT_OWNER = "file_owner"; + + public static final String DEFAULT_SORT_ORDER = FILE_NAME + " asc"; + + } +} diff --git a/src/eu/alefzero/owncloud/syncadapter/SyncAdapter.java b/src/eu/alefzero/owncloud/syncadapter/SyncAdapter.java new file mode 100644 index 00000000..5bede732 --- /dev/null +++ b/src/eu/alefzero/owncloud/syncadapter/SyncAdapter.java @@ -0,0 +1,225 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.owncloud.syncadapter; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedList; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.ContentValues; +import android.content.Context; +import android.content.SyncResult; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; +import eu.alefzero.owncloud.authenticator.AccountAuthenticator; +import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta; +import eu.alefzero.webdav.HttpPropFind; +import eu.alefzero.webdav.TreeNode; +import eu.alefzero.webdav.WebdavUtils; +import eu.alefzero.webdav.TreeNode.NodeProperty; + +/** + * SyncAdapter implementation for syncing sample SyncAdapter contacts to the + * platform ContactOperations provider. + */ +public class SyncAdapter extends AbstractThreadedSyncAdapter { + private static final String TAG = "SyncAdapter"; + + private final AccountManager mAccountManager; + private Account mAccount; + private ContentProviderClient mContentProvider; + private final Context mContext; + + private Date mLastUpdated; + + public SyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + mContext = context; + mAccountManager = AccountManager.get(context); + } + + @Override + public synchronized void onPerformSync(Account account, Bundle extras, String authority, + ContentProviderClient provider, SyncResult syncResult) { + mAccount = account; + mContentProvider = provider; + try { + String username = account.name.split("@")[0]; + String password = mAccountManager.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE, true); + if (mAccountManager.getUserData(account, AccountAuthenticator.KEY_OC_URL) == null) { + throw new UnknownHostException(); + } + Uri uri = Uri.parse(mAccountManager.getUserData(account, AccountAuthenticator.KEY_OC_URL)); + Log.i(TAG, "Syncing owncloud account: " + account.name + " on url: " + uri.toString()); + + DefaultHttpClient client = new DefaultHttpClient(); + client.getCredentialsProvider().setCredentials( + new AuthScope(uri.getHost(), (uri.getPort() == -1)?80:uri.getPort()), + new UsernamePasswordCredentials(username, password)); + client.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() { + public long getKeepAliveDuration(HttpResponse response, HttpContext context) { + // TODO: change keep alive straategy basing on response: ie forbidden/not found/etc + // should have keep alive 0 + // default return: 5s + return 5 * 1000; + } + }); + + BasicHttpContext httpContext = new BasicHttpContext(); + BasicScheme basicAuth = new BasicScheme(); + httpContext.setAttribute("preemptive-auth", basicAuth); + HttpHost targetHost = new HttpHost(uri.getHost(), (uri.getPort() == -1) + ? 80 + : uri.getPort(), (uri.getScheme() == "https") ? "https" : "http"); + + HttpPropFind query = new HttpPropFind(uri.toString()); + query.setHeader("Content-type", "text/xml"); + query.setHeader("User-Agent", "Android-ownCloud"); + HttpEntity entity = new StringEntity(WebdavUtils.prepareXmlForPropFind()); + query.setEntity(entity); + HttpResponse response = client.execute(targetHost, query, httpContext); + /*try { + mContentProvider.delete(ProviderTableMeta.CONTENT_URI, + ProviderTableMeta.FILE_NAME + " LIKE '%' AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER +"=?" + , new String[]{account.name}); + } catch (RemoteException e) { + e.printStackTrace(); + return; + }*/ + TreeNode root = new TreeNode(); + root.setProperty(TreeNode.NodeProperty.NAME, "/"); + parseResponse(response, uri, client, targetHost, httpContext, root.getChildList()); + + commitToDatabase(root, null); + + } catch (OperationCanceledException e) { + e.printStackTrace(); + } catch (AuthenticatorException e) { + syncResult.stats.numAuthExceptions++; + e.printStackTrace(); + } catch (IOException e) { + syncResult.stats.numIoExceptions++; + e.printStackTrace(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + private void commitToDatabase(TreeNode root, String parentId) throws RemoteException { + for (TreeNode n : root.getChildList()) { + Log.d(TAG, n.toString()); + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, n.getProperty(NodeProperty.CONTENT_LENGTH)); + cv.put(ProviderTableMeta.FILE_MODIFIED, n.getProperty(NodeProperty.LAST_MODIFIED_DATE)); + cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, n.getProperty(NodeProperty.RESOURCE_TYPE)); + cv.put(ProviderTableMeta.FILE_PARENT, parentId); + + String name = n.getProperty(NodeProperty.NAME), + path = n.getProperty(NodeProperty.PATH); + Cursor c = mContentProvider.query(ProviderTableMeta.CONTENT_URI_FILE, + null, + ProviderTableMeta.FILE_NAME+"=? AND " + ProviderTableMeta.FILE_PATH + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", + new String[]{name, path, mAccount.name}, + null); + if (c.moveToFirst()) { + mContentProvider.update(ProviderTableMeta.CONTENT_URI, + cv, + ProviderTableMeta._ID+"=?", + new String[]{c.getString(c.getColumnIndex(ProviderTableMeta._ID))}); + Log.d(TAG, "ID of: "+name+":"+c.getString(c.getColumnIndex(ProviderTableMeta._ID))); + } else { + cv.put(ProviderTableMeta.FILE_NAME, n.getProperty(NodeProperty.NAME)); + cv.put(ProviderTableMeta.FILE_PATH, n.getProperty(NodeProperty.PATH)); + cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); + Uri entry = mContentProvider.insert(ProviderTableMeta.CONTENT_URI_FILE, cv); + Log.d(TAG, "Inserting new entry " + path + name); + c = mContentProvider.query(entry, null, null, null, null); + c.moveToFirst(); + } + if (n.getProperty(NodeProperty.RESOURCE_TYPE).equals("DIR")) { + commitToDatabase(n, c.getString(c.getColumnIndex(ProviderTableMeta._ID))); + } + } + // clean removed files + String[] selection = new String[root.getChildList().size()+2]; + selection[0] = mAccount.name; + selection[1] = parentId; + String qm = ""; + for (int i = 2; i < selection.length-1; ++i) { + qm += "?,"; + selection[i] = root.getChildList().get(i-2).getProperty(NodeProperty.NAME); + } + if (selection.length >= 3) { + selection[selection.length-1] = root.getChildrenNames()[selection.length-3]; + qm += "?"; + } + for (int i = 0; i < selection.length; ++i) { + Log.d(TAG,selection[i]+""); + } + Log.d(TAG,"Removing files "+ parentId); + mContentProvider.delete(ProviderTableMeta.CONTENT_URI, + ProviderTableMeta.FILE_ACCOUNT_OWNER+"=? AND " + ProviderTableMeta.FILE_PARENT + (parentId==null?" IS ":"=")+"? AND " + ProviderTableMeta.FILE_NAME + " NOT IN ("+qm+")", + selection); + } + + private void parseResponse(HttpResponse resp, Uri uri, DefaultHttpClient client, HttpHost targetHost, BasicHttpContext httpContext, LinkedList insertList) throws IOException { + boolean skipFirst = true; + for (TreeNode n :WebdavUtils.parseResponseToNodes(resp.getEntity().getContent())) { + String path = n.stripPathFromFilename(uri.getPath()); + if (skipFirst) { + skipFirst = false; + continue; + } + insertList.add(n); + + if (!TextUtils.isEmpty(n.getProperty(NodeProperty.NAME)) && + n.getProperty(NodeProperty.RESOURCE_TYPE).equals("DIR")) { + HttpPropFind method = new HttpPropFind(uri.getPath() + path + n.getProperty(NodeProperty.NAME).replace(" ", "%20") + "/"); + Log.i(TAG, uri.getPath() + path + n.getProperty(NodeProperty.NAME).replace(" ", "%20") + "/"); + Log.i(TAG, method.getRequestLine().toString()); + HttpResponse response = client.execute(targetHost, method, httpContext); + parseResponse(response, uri, client, targetHost, httpContext, n.getChildList()); + } + } + } +} diff --git a/src/eu/alefzero/owncloud/syncadapter/SyncService.java b/src/eu/alefzero/owncloud/syncadapter/SyncService.java new file mode 100644 index 00000000..cbd00d42 --- /dev/null +++ b/src/eu/alefzero/owncloud/syncadapter/SyncService.java @@ -0,0 +1,31 @@ + +package eu.alefzero.owncloud.syncadapter; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class SyncService extends Service { + private static final Object sSyncAdapterLock = new Object(); + private static SyncAdapter sSyncAdapter = null; + + /* + * {@inheritDoc} + */ + @Override + public void onCreate() { + synchronized (sSyncAdapterLock) { + if (sSyncAdapter == null) { + sSyncAdapter = new SyncAdapter(getApplicationContext(), true); + } + } + } + + /* + * {@inheritDoc} + */ + @Override + public IBinder onBind(Intent intent) { + return sSyncAdapter.getSyncAdapterBinder(); + } +} diff --git a/src/eu/alefzero/webdav/HttpMkCol.java b/src/eu/alefzero/webdav/HttpMkCol.java new file mode 100644 index 00000000..c7acab9c --- /dev/null +++ b/src/eu/alefzero/webdav/HttpMkCol.java @@ -0,0 +1,19 @@ +package eu.alefzero.webdav; + +import java.net.URI; + +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; + +public class HttpMkCol extends HttpEntityEnclosingRequestBase { + + public final static String METHOD_NAME = "MKCOL"; + + public HttpMkCol(final String uri) { + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } +} diff --git a/src/eu/alefzero/webdav/HttpPropFind.java b/src/eu/alefzero/webdav/HttpPropFind.java new file mode 100644 index 00000000..f7b930c3 --- /dev/null +++ b/src/eu/alefzero/webdav/HttpPropFind.java @@ -0,0 +1,32 @@ +package eu.alefzero.webdav; + +import java.net.URI; + +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.protocol.HTTP; + +public class HttpPropFind extends HttpEntityEnclosingRequestBase { + + public final static String METHOD_NAME = "PROPFIND"; + + public HttpPropFind(final URI uri) { + super(); + setURI(uri); + } + + public HttpPropFind(final String uri) { + this.setDepth("1"); + setURI(URI.create(uri)); + this.setHeader(HTTP.CONTENT_TYPE, "text/xml" + HTTP.CHARSET_PARAM + HTTP.UTF_8.toLowerCase()); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + + public void setDepth(String depth) { + this.setHeader("Depth", depth); + } + +} diff --git a/src/eu/alefzero/webdav/HttpPropPatch.java b/src/eu/alefzero/webdav/HttpPropPatch.java new file mode 100644 index 00000000..bdd352ef --- /dev/null +++ b/src/eu/alefzero/webdav/HttpPropPatch.java @@ -0,0 +1,26 @@ +package eu.alefzero.webdav; + +import java.net.URI; + +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; + +public class HttpPropPatch extends HttpEntityEnclosingRequestBase { + + public static final String METHOD_NAME = "PROPPATCH"; + + public HttpPropPatch(URI uri) { + super(); + setURI(uri); + } + + public HttpPropPatch(final String uri) { + super(); + setURI(URI.create(uri)); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + +} diff --git a/src/eu/alefzero/webdav/TreeNode.java b/src/eu/alefzero/webdav/TreeNode.java new file mode 100644 index 00000000..a031eb14 --- /dev/null +++ b/src/eu/alefzero/webdav/TreeNode.java @@ -0,0 +1,112 @@ +/* ownCloud Android client application + * + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.webdav; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map.Entry; + +import org.w3c.dom.Document; + +import android.text.TextUtils; +import android.util.Log; + +public class TreeNode { + public enum NodeProperty { + NAME, + PARENT, + PATH, + RESOURCE_TYPE, + CREATE_DATE, + LAST_MODIFIED_DATE, + CONTENT_LENGTH + } + + private LinkedList mChildren; + + public TreeNode() { + propertyMap_ = new HashMap(); + mChildren = new LinkedList(); + } + + public void setProperty(NodeProperty propertyName, String propertyValue) { + propertyMap_.put(propertyName, propertyValue); + } + + public String getProperty(NodeProperty propertyName) { + return propertyMap_.get(propertyName); + } + + void refreshData(Document document) { + throw new RuntimeException("Unimplemented refreshData"); + } + + public String toString() { + String str = "TreeNode {"; + for (Entry e : propertyMap_.entrySet()) { + str += e.getKey() + ": " + e.getValue() + ","; + } + str += "}"; + return str; + } + + private HashMap propertyMap_; + + public String stripPathFromFilename(String oc_path) { + if (propertyMap_.containsKey(NodeProperty.NAME)) { + String name = propertyMap_.get(NodeProperty.NAME); + name = name.replace(oc_path, ""); + String path = ""; + if (name.endsWith("/")) { + name = name.substring(0, name.length()-1); + } + path = name.substring(0, name.lastIndexOf('/')+1); + name = name.substring(name.lastIndexOf('/')+1); + name = name.replace("%20", " "); + if (TextUtils.isEmpty(name)) { + name = "/"; + } + + propertyMap_.remove(NodeProperty.NAME); + propertyMap_.put(NodeProperty.NAME, name); + propertyMap_.remove(NodeProperty.PATH); + propertyMap_.put(NodeProperty.PATH, path); + Log.i("TreeNode", toString()); + return path; + } + return null; + } + + public LinkedList getChildList() { + return mChildren; + } + + public String[] getChildrenNames() { + String[] names = new String[mChildren.size()]; + for (int i = 0; i < mChildren.size(); ++i) { + names[i] = mChildren.get(i).getProperty(NodeProperty.NAME); + } + return names; + } + + public boolean hasChildren() { + return !mChildren.isEmpty(); + } +} diff --git a/src/eu/alefzero/webdav/TreeNodeContainer.java b/src/eu/alefzero/webdav/TreeNodeContainer.java new file mode 100644 index 00000000..ef092545 --- /dev/null +++ b/src/eu/alefzero/webdav/TreeNodeContainer.java @@ -0,0 +1,21 @@ +package eu.alefzero.webdav; + +import java.util.List; +import java.util.ListIterator; + +import org.w3c.dom.Document; + +import android.util.Xml; + +public class TreeNodeContainer extends TreeNode { + + @Override + void refreshData(Document document) { + ListIterator iterator = children_.listIterator(); + while (iterator.hasNext()) { + iterator.next().refreshData(document); + } + } + + private List children_; +} diff --git a/src/eu/alefzero/webdav/TreeNodeFile.java b/src/eu/alefzero/webdav/TreeNodeFile.java new file mode 100644 index 00000000..df2d9d58 --- /dev/null +++ b/src/eu/alefzero/webdav/TreeNodeFile.java @@ -0,0 +1,56 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.webdav; + +import java.sql.Date; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import eu.alefzero.webdav.TreeNode.NodeProperty; + +import android.util.Xml; + +public class TreeNodeFile extends TreeNode { + + public TreeNodeFile() { + is_pinned_ = false; + pathToDownloadedFile_ = ""; + lastUpdateDate_ = new Date(1970, 1, 1); + } + + @Override + void refreshData(Document document) { + /*if (is_pinned_) { + String fullPath = getProperty(NodeProperty.PATH); + if (document.hasChildNodes()) { + Node child = document.getFirstChild(); + do { + + } while ((child = child.getNextSibling()) != null); + } + + //TODO: update file + }*/ + } + + private boolean is_pinned_; + private String pathToDownloadedFile_; + private Date lastUpdateDate_; +} diff --git a/src/eu/alefzero/webdav/WebdavUtils.java b/src/eu/alefzero/webdav/WebdavUtils.java new file mode 100644 index 00000000..0996470e --- /dev/null +++ b/src/eu/alefzero/webdav/WebdavUtils.java @@ -0,0 +1,174 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package eu.alefzero.webdav; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import eu.alefzero.webdav.TreeNode.NodeProperty; + +import android.text.Html; +import android.util.Log; + +public class WebdavUtils { + + public static final String RESPONSE = "response"; + public static final String HREF = "href"; + public static final String IS_HIDDEN = "ishidden"; + public static final String RESOURCE_TYPE = "resourcetype"; + public static final String CONTENT_TYPE = "getcontenttype"; + public static final String CONTENT_LENGTH = "getcontentlength"; + public static final String LAST_MODIFIED = "getlastmodified"; + public static final String LAST_ACCESS = "lastaccessed"; + public static final String CREATE_DATE = "creationdate"; + + public static final String PROPSTAT = "propstat"; + public static final String STATUS = "status"; + public static final String PROP = "prop"; + + private static final String DAV_NAMESPACE_PREFIX = "DAV:"; + + public static final SimpleDateFormat DISPLAY_DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy hh:mm"); + private static final SimpleDateFormat DATETIME_FORMATS[] = { + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US), + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sss'Z'", Locale.US), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US), + new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)}; + + public static String prepareXmlForPropFind() { + String ret = ""; + return ret; + } + + public static String prepareXmlForPatch() { + return ""; + } + + private static Date parseResponseDate(String date) { + Date returnDate = null; + for (int i = 0; i < DATETIME_FORMATS.length; ++i) { + try { + returnDate = DATETIME_FORMATS[i].parse(date); + return returnDate; + } catch (ParseException e) {} + } + return null; + } + + private static String determineDAVPrefix(Element e) { + for (int i = 0; i < e.getAttributes().getLength(); ++i) { + String attrName = e.getAttributes().item(i).getNodeName(); + if (e.getAttribute(attrName).equals(DAV_NAMESPACE_PREFIX)) { + return attrName.substring(attrName.lastIndexOf(':')+1) + ":"; + } + } + return null; + } + + public static List parseResponseToNodes(InputStream response) { + LinkedList rList = new LinkedList(); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + try { + builder = factory.newDocumentBuilder(); + Document document = builder.parse(response); + String davPrefix = determineDAVPrefix(document.getDocumentElement()); + + NodeList nodes = document.getElementsByTagName(davPrefix + RESPONSE); + Log.i("WebdavUtils", "Parsing " + nodes.getLength() + " response nodes"); + + for (int i = 0; i < nodes.getLength(); ++i) { + Node currentNode = nodes.item(i); + TreeNode resultNode = new TreeNode(); + parseResourceType(currentNode, resultNode, davPrefix); + parseResourceDates(currentNode, resultNode, davPrefix); + parseDisplayName(currentNode, resultNode, davPrefix); + rList.add(resultNode); + } + + + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return rList; + } + + private static void parseDisplayName(Node currentNode, TreeNode resultNode, + String davPrefix) { + Element currentElement = (Element) currentNode; + if (currentElement.getElementsByTagName(davPrefix + HREF).getLength() != 0) { + String filepath = currentElement.getElementsByTagName(davPrefix + HREF).item(0).getFirstChild().getNodeValue(); + resultNode.setProperty(NodeProperty.NAME, filepath); + } + } + + private static void parseResourceDates(Node currentNode, TreeNode resultNode, String davPrefix) { + Element currentElement = (Element)currentNode; + if (currentElement.getElementsByTagName(davPrefix + LAST_MODIFIED).getLength() != 0) { + Date date = parseResponseDate( + currentElement.getElementsByTagName(davPrefix + LAST_MODIFIED).item(0).getFirstChild().getNodeValue()); + resultNode.setProperty(NodeProperty.LAST_MODIFIED_DATE, DISPLAY_DATE_FORMAT.format(date)); + } + if (currentElement.getElementsByTagName(davPrefix + CREATE_DATE).getLength() != 0) { + Date date = parseResponseDate( + currentElement.getElementsByTagName(davPrefix + CREATE_DATE).item(0).getFirstChild().getNodeValue()); + resultNode.setProperty(NodeProperty.CREATE_DATE, DISPLAY_DATE_FORMAT.format(date)); + } + } + + private static void parseResourceType(Node currentNode, TreeNode resultNode, String davPrefix) { + Element currentElement = (Element)currentNode; + if (currentElement.getElementsByTagName(davPrefix + RESOURCE_TYPE).getLength() != 0 && + currentElement.getElementsByTagName(davPrefix + RESOURCE_TYPE).item(0).hasChildNodes()) { + resultNode.setProperty(NodeProperty.RESOURCE_TYPE, "DIR"); + } else { + if (currentElement.getElementsByTagName(davPrefix + CONTENT_TYPE).getLength() != 0) { + resultNode.setProperty(NodeProperty.RESOURCE_TYPE, + currentElement.getElementsByTagName(davPrefix + CONTENT_TYPE).item(0).getFirstChild().getNodeValue()); + } + if (currentElement.getElementsByTagName(davPrefix + CONTENT_LENGTH).getLength() != 0) { + resultNode.setProperty(NodeProperty.CONTENT_LENGTH, + currentElement.getElementsByTagName(davPrefix + CONTENT_LENGTH).item(0).getFirstChild().getNodeValue()); + } + } + } +}