ソースを参照

Merge pull request #1493 from owncloud/reliable_uploads_actions_uploads_view

Uploads view added to monitor and handle uploads progress
David A. Velasco 9 年 前
コミット
b5a9594023
84 ファイル変更5836 行追加2063 行削除
  1. 1 1
      .classpath
  2. 1 0
      .gitignore
  3. 17 9
      AndroidManifest.xml
  4. 1 1
      owncloud-android-library
  5. BIN
      res/drawable-hdpi/ic_refresh.png
  6. BIN
      res/drawable-mdpi/ic_cancel.png
  7. BIN
      res/drawable-mdpi/ic_refresh.png
  8. BIN
      res/drawable-mdpi/ic_uploads.png
  9. BIN
      res/drawable-xhdpi/ic_refresh.png
  10. BIN
      res/drawable-xhdpi/ic_uploads.png
  11. BIN
      res/drawable-xxhdpi/ic_refresh.png
  12. BIN
      res/drawable-xxhdpi/ic_uploads.png
  13. BIN
      res/drawable-xxxhdpi/ic_refresh.png
  14. BIN
      res/drawable-xxxhdpi/ic_uploads.png
  15. 46 0
      res/drawable/btn_small_round.xml
  16. BIN
      res/drawable/upload_failed.png
  17. BIN
      res/drawable/upload_finished.png
  18. BIN
      res/drawable/upload_in_progress.png
  19. 11 0
      res/layout/errorhandling_showerror.xml
  20. 45 0
      res/layout/list_fragment_expandable.xml
  21. 19 0
      res/layout/upload_list_group.xml
  22. 127 0
      res/layout/upload_list_item.xml
  23. 24 0
      res/layout/upload_list_layout.xml
  24. 34 0
      res/menu/upload_list_menu.xml
  25. 2 0
      res/values/drawer_resources.xml
  26. 34 0
      res/values/strings.xml
  27. 2 0
      res/values/styles.xml
  28. 3 1
      src/com/owncloud/android/MainApp.java
  29. 24 8
      src/com/owncloud/android/authentication/AccountUtils.java
  30. 13 0
      src/com/owncloud/android/authentication/AuthenticatorActivity.java
  31. 15 0
      src/com/owncloud/android/datamodel/FileDataStorageManager.java
  32. 5 3
      src/com/owncloud/android/datamodel/OCFile.java
  33. 514 0
      src/com/owncloud/android/datamodel/UploadsStorageManager.java
  34. 0 127
      src/com/owncloud/android/db/DbHandler.java
  35. 427 0
      src/com/owncloud/android/db/OCUpload.java
  36. 59 0
      src/com/owncloud/android/db/PreferenceReader.java
  37. 155 133
      src/com/owncloud/android/db/ProviderMeta.java
  38. 118 0
      src/com/owncloud/android/db/UploadResult.java
  39. 3 6
      src/com/owncloud/android/files/FileMenuFilter.java
  40. 27 11
      src/com/owncloud/android/files/FileOperationsHelper.java
  41. 83 183
      src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
  42. 206 0
      src/com/owncloud/android/files/services/ConnectivityActionReceiver.java
  43. 37 41
      src/com/owncloud/android/files/services/FileDownloader.java
  44. 567 401
      src/com/owncloud/android/files/services/FileUploader.java
  45. 23 23
      src/com/owncloud/android/files/services/IndexedForest.java
  46. 19 12
      src/com/owncloud/android/media/MediaService.java
  47. 7 9
      src/com/owncloud/android/media/MediaServiceBinder.java
  48. 65 0
      src/com/owncloud/android/operations/CheckCurrentCredentialsOperation.java
  49. 3 1
      src/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java
  50. 86 94
      src/com/owncloud/android/operations/SynchronizeFileOperation.java
  51. 13 3
      src/com/owncloud/android/operations/SynchronizeFolderOperation.java
  52. 561 197
      src/com/owncloud/android/operations/UploadFileOperation.java
  53. 336 217
      src/com/owncloud/android/providers/FileContentProvider.java
  54. 6 0
      src/com/owncloud/android/services/OperationsService.java
  55. 7 8
      src/com/owncloud/android/services/SyncFolderHandler.java
  56. 1 1
      src/com/owncloud/android/services/observer/FileObserverService.java
  57. 1 1
      src/com/owncloud/android/services/observer/FolderObserver.java
  58. 3 7
      src/com/owncloud/android/syncadapter/FileSyncAdapter.java
  59. 7 4
      src/com/owncloud/android/ui/activity/ComponentsGetter.java
  60. 31 30
      src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
  61. 53 27
      src/com/owncloud/android/ui/activity/FileActivity.java
  62. 148 131
      src/com/owncloud/android/ui/activity/FileDisplayActivity.java
  63. 4 10
      src/com/owncloud/android/ui/activity/FolderPickerActivity.java
  64. 12 11
      src/com/owncloud/android/ui/activity/Preferences.java
  65. 4 3
      src/com/owncloud/android/ui/activity/UploadFilesActivity.java
  66. 361 0
      src/com/owncloud/android/ui/activity/UploadListActivity.java
  67. 171 175
      src/com/owncloud/android/ui/activity/Uploader.java
  68. 720 0
      src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java
  69. 6 4
      src/com/owncloud/android/ui/adapter/FileListListAdapter.java
  70. 0 138
      src/com/owncloud/android/ui/adapter/MyExpandableListAdapter.java
  71. 3 3
      src/com/owncloud/android/ui/dialog/UploadSourceDialogFragment.java
  72. 45 0
      src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java
  73. 85 0
      src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java
  74. 90 0
      src/com/owncloud/android/ui/fragment/ExpandableListFragment.java
  75. 11 13
      src/com/owncloud/android/ui/fragment/ExtendedListFragment.java
  76. 3 1
      src/com/owncloud/android/ui/fragment/FileDetailFragment.java
  77. 5 5
      src/com/owncloud/android/ui/fragment/OCFileListFragment.java
  78. 152 0
      src/com/owncloud/android/ui/fragment/UploadListFragment.java
  79. 1 1
      src/com/owncloud/android/ui/preview/PreviewImageActivity.java
  80. 6 6
      src/com/owncloud/android/ui/preview/PreviewMediaFragment.java
  81. 47 0
      src/com/owncloud/android/utils/ConnectivityUtils.java
  82. 33 3
      src/com/owncloud/android/utils/FileStorageUtils.java
  83. 34 0
      src/com/owncloud/android/utils/MimetypeIconUtil.java
  84. 53 0
      src/com/owncloud/android/utils/UploadUtils.java

+ 1 - 1
.classpath

@@ -3,7 +3,7 @@
 	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
 	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
 	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
-	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="output" path="bin/classes"/>
 </classpath>

+ 1 - 0
.gitignore

@@ -21,6 +21,7 @@ oc_workaround/local.properties
 oc_framework/local.properties
 oc_framework-test-project/local.properties
 tests/local.properties
+lint.xml
 
 # Mac .DS_Store files
 .DS_Store

+ 17 - 9
AndroidManifest.xml

@@ -163,12 +163,23 @@
         <service android:name=".media.MediaService" />
 
         <activity android:name=".ui.activity.PassCodeActivity" />
-        <activity android:name=".ui.activity.ConflictsResolveActivity" />
-        <activity android:name=".ui.activity.GenericExplanationActivity" />
-        <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity" />
-        <activity android:name=".ui.activity.LogHistoryActivity" />
-
-        <receiver android:name=".files.InstantUploadBroadcastReceiver" >
+        <activity android:name=".ui.activity.ConflictsResolveActivity"/>
+        <activity android:name=".ui.activity.GenericExplanationActivity"/>
+        <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>
+        
+        <activity android:name=".ui.activity.LogHistoryActivity"/>
+
+        <activity android:name=".ui.errorhandling.ErrorShowActivity" />
+        <activity android:name=".ui.activity.UploadListActivity" />
+        
+        <receiver android:name=".files.services.ConnectivityActionReceiver"
+		    android:enabled="true" android:label="ConnectivityActionReceiver">
+		    <intent-filter>
+		        <!--action android:name="android.net.conn.CONNECTIVITY_CHANGE"/-->
+		        <action android:name="android.net.wifi.STATE_CHANGE"/>
+		    </intent-filter>
+		</receiver>
+		<receiver android:name=".files.InstantUploadBroadcastReceiver">
             <intent-filter>
 
                 <!-- unofficially supported by many Android phones but not by HTC devices: -->
@@ -183,9 +194,6 @@
 
                 <data android:mimeType="video/*" />
             </intent-filter>
-            <intent-filter>
-                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
-            </intent-filter>
         </receiver>
         <receiver android:name=".files.BootupBroadcastReceiver" >
             <intent-filter>

+ 1 - 1
owncloud-android-library

@@ -1 +1 @@
-Subproject commit 573afa15382b67cc84f67fc9a7b2329a72ecb352
+Subproject commit 39e3ddaa07b0943b034b34a84a33b4dc4c7475d0

BIN
res/drawable-hdpi/ic_refresh.png


BIN
res/drawable-mdpi/ic_cancel.png


BIN
res/drawable-mdpi/ic_refresh.png


BIN
res/drawable-mdpi/ic_uploads.png


BIN
res/drawable-xhdpi/ic_refresh.png


BIN
res/drawable-xhdpi/ic_uploads.png


BIN
res/drawable-xxhdpi/ic_refresh.png


BIN
res/drawable-xxhdpi/ic_uploads.png


BIN
res/drawable-xxxhdpi/ic_refresh.png


BIN
res/drawable-xxxhdpi/ic_uploads.png


+ 46 - 0
res/drawable/btn_small_round.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- pressed state of button, only change: color of actual button -->
+    <item android:state_pressed="true" >
+        <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+            <!-- shadow, a little down and a little to the right -->
+            <item><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+                    <gradient android:angle="270" android:endColor="#99000000" android:startColor="#99000000" />
+                </shape></item>
+            <!-- this is the actual button -->
+            <item android:bottom="1px" android:right="1px"><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+                    <gradient android:angle="270" android:endColor="#47ffffff" android:startColor="#97ffffff" />
+                </shape></item>
+        </layer-list>
+    </item>
+ 
+    <!-- focused state of button, only change: color of actual button -->
+    <item android:state_focused="true" >
+        <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+            <!-- shadow, a little down and a little to the right -->
+            <item><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+                    <gradient android:angle="270" android:endColor="#22000000" android:startColor="#22000000" />
+                </shape></item>
+            <!-- this is the actual button -->
+            <item android:bottom="1px" android:right="1px"><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+                    <gradient android:angle="270" android:endColor="#47ffffff" android:startColor="#97ffffff" />
+                </shape></item>
+        </layer-list>
+    </item>

   <!-- normal state of button. Template for other states. -->
+    <item>
+        <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+            <!-- shadow, a little down and a little to the right -->
+            <item>
+                <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+                    <gradient android:angle="270" android:endColor="#55000000" android:startColor="#55000000" />
+                </shape></item>
+            <!-- this is the actual button -->
+            <item android:bottom="1px" android:right="1px">
+                <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+                    <gradient android:angle="270" android:endColor="#47ffffff" android:startColor="#97ffffff" />
+                </shape>
+            </item>
+        </layer-list>
+    </item>
+</selector>

BIN
res/drawable/upload_failed.png


BIN
res/drawable/upload_finished.png


BIN
res/drawable/upload_in_progress.png


+ 11 - 0
res/layout/errorhandling_showerror.xml

@@ -0,0 +1,11 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:id="@+id/errorTextView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</ScrollView>

+ 45 - 0
res/layout/list_fragment_expandable.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+This must be a clone of list_fragment.xml 
+
+EXCEPT: ExpandableListView must be used for @+id/swipe_refresh_files_emptyView
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+	android:layout_width="0dp"
+	android:layout_height="match_parent"
+	android:layout_weight="1" >
+
+    <android.support.v4.widget.SwipeRefreshLayout
+        android:id="@+id/swipe_refresh_files"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+
+        <ExpandableListView 
+            android:id="@+id/list_root"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+        
+    </android.support.v4.widget.SwipeRefreshLayout>
+
+    <android.support.v4.widget.SwipeRefreshLayout
+        android:id="@+id/swipe_refresh_files_emptyView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone" >
+
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" >
+
+	            <TextView
+			        android:id="@+id/empty_list_view"
+			        android:layout_width="match_parent"
+			        android:layout_height="wrap_content"
+			        android:gravity="center_vertical|center_horizontal"
+			        android:text="@string/empty"
+					android:layout_gravity="center"
+			        android:visibility="visible" />
+
+        </ScrollView>
+    </android.support.v4.widget.SwipeRefreshLayout>
+</FrameLayout>

+ 19 - 0
res/layout/upload_list_group.xml

@@ -0,0 +1,19 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="3dp" >
+
+   <TextView
+       android:id="@+id/uploadListGroupName"
+       style="?android:attr/listSeparatorTextViewStyle"
+       android:layout_height="wrap_content"
+       android:layout_gravity="center_vertical"
+       android:divider="@null"
+       android:dividerHeight="0dp"
+       android:ellipsize="middle"
+       android:showDividers="none"
+       android:textColor="@color/color_accent"
+       android:paddingLeft="16dp"
+	/>
+
+</RelativeLayout>

+ 127 - 0
res/layout/upload_list_item.xml

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/ListItemLayout"
+    android:layout_width="match_parent"
+    android:orientation="horizontal"
+    android:layout_height="wrap_content"
+    android:paddingTop="@dimen/standard_half_padding"
+    android:paddingBottom="@dimen/standard_half_padding"
+    >
+
+    <FrameLayout
+        android:layout_width="60dp"
+        android:layout_height="72dp"
+        android:focusable="false"
+        android:focusableInTouchMode="false">
+
+        <ImageView
+            android:id="@+id/thumbnail"
+            android:layout_width="@dimen/file_icon_size"
+            android:layout_height="@dimen/file_icon_size"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_menu_archive" />
+  
+    </FrameLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center_vertical"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical" >
+
+        <TextView
+            android:id="@+id/upload_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:ellipsize="middle"
+            android:singleLine="true"
+            android:textColor="@color/textColor"
+            android:text="@string/placeholder_filename"
+            android:textSize="16sp" />
+
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/upload_file_size"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="left"
+                android:textColor="@color/list_item_lastmod_and_filesize_text"
+                android:ellipsize="middle"
+                android:singleLine="true"
+                android:text="@string/placeholder_filesize"
+                android:textSize="12sp"/>
+            <TextView
+                android:id="@+id/upload_date"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/list_item_lastmod_and_filesize_text"
+                android:layout_marginStart="4dp"
+                android:layout_marginEnd="4dp"
+                android:text="@string/placeholder_timestamp"
+                android:textSize="12sp"/>
+            <TextView
+                android:id="@+id/upload_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/list_item_lastmod_and_filesize_text"
+                android:text="@string/uploads_view_upload_status_succeeded"
+                android:textSize="12sp"/>
+
+        </LinearLayout>
+
+        <ProgressBar
+            android:id="@+id/upload_progress_bar"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"/>
+
+        <TextView
+            android:id="@+id/upload_account"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/list_item_lastmod_and_filesize_text"
+            android:singleLine="true"
+            android:text="@string/auth_username"
+            android:textSize="12dip"/>
+
+        <TextView
+            android:id="@+id/upload_remote_path"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/list_item_lastmod_and_filesize_text"
+            android:ellipsize="middle"
+            android:singleLine="true"
+            android:text="@string/instant_upload_path"
+            android:textSize="12dip"/>
+
+    </LinearLayout>
+
+    
+    <FrameLayout
+        android:layout_width="56dp"
+        android:layout_height="72dp"
+        android:focusable="false"
+        android:focusableInTouchMode="false"
+        android:layout_gravity="center_vertical"
+        android:paddingLeft="8dp"
+     >
+
+        <ImageButton
+            android:id="@+id/upload_right_button"
+            android:layout_width="35dp"
+            android:layout_height="35dp"
+            android:layout_gravity="center"
+            android:background="@android:color/transparent"
+     />
+
+	</FrameLayout>
+    
+    
+</LinearLayout>

+ 24 - 0
res/layout/upload_list_layout.xml

@@ -0,0 +1,24 @@
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/drawer_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clickable="true" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/background_color">
+
+        <FrameLayout
+            android:id="@+id/upload_list_fragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </LinearLayout>
+
+    <include
+        layout="@layout/drawer"
+        android:layout_width="240dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"/>
+
+</android.support.v4.widget.DrawerLayout>

+ 34 - 0
res/menu/upload_list_menu.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ownCloud Android client application
+
+  Copyright (C) 2012  Bartek Przybylski
+  Copyright (C) 2012-2013 ownCloud Inc.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License version 2,
+  as published by the Free Software Foundation.
+
+  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 <http://www.gnu.org/licenses/>.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/action_retry_uploads"
+        android:title="@string/action_retry_uploads"/>
+    <item
+        android:id="@+id/action_clear_failed_uploads"
+        android:title="@string/action_clear_failed_uploads"/>
+    <item
+        android:id="@+id/action_clear_successfull_uploads"
+        android:title="@string/action_clear_successful_uploads"/>
+    <item
+        android:id="@+id/action_clear_finished_uploads"
+        android:title="@string/action_clear_finished_uploads"/>
+</menu>

+ 2 - 0
res/values/drawer_resources.xml

@@ -24,6 +24,7 @@
         <!--<item>@string/prefs_accounts</item>-->
         <item>@string/drawer_item_all_files</item>
         <!--<item>@string/drawer_item_on_device</item>-->
+        <item>@string/drawer_item_uploads_list</item>
         <item>@string/actionbar_settings</item>
         <item>@string/actionbar_logger</item>
     </string-array>
@@ -34,6 +35,7 @@
         <!--<item>@string/drawer_item_accounts</item>-->
         <item>@string/drawer_item_all_files</item>
         <!--<item>@string/drawer_item_on_device</item>-->
+        <item>@string/drawer_item_uploads_list</item>
         <item>@string/drawer_item_settings</item>
         <item>@string/drawer_item_logs</item>
     </string-array>

+ 34 - 0
res/values/strings.xml

@@ -26,6 +26,7 @@
     <!-- TODO re-enable when "On Device" is available
     <string name="drawer_item_on_device">On device</string>-->
     <string name="drawer_item_settings">Settings</string>
+    <string name="drawer_item_uploads_list">Uploads</string>
     <string name="drawer_item_logs">Logs</string>
 	<string name="drawer_close">Close</string>
     <string name="drawer_open">Open</string>
@@ -76,6 +77,11 @@
     <string name="file_list_loading">Loading&#8230;</string>
     <string name="file_list_no_app_for_file_type">No app found for file type!</string>
     <string name="local_file_list_empty">There are no files in this folder.</string>
+    <string name="upload_list_empty">No uploads available.</string>
+    <string name="file_list_folder">folder</string>
+    <string name="file_list_folders">folders</string>
+    <string name="file_list_file">file</string>
+    <string name="file_list_files">files</string>
     <string name="filedetails_select_file">Tap on a file to display additional information.</string>
     <string name="filedetails_size">Size:</string>
     <string name="filedetails_type">Type:</string>
@@ -89,6 +95,8 @@
     <string name="common_yes">Yes</string>
     <string name="common_no">No</string>
     <string name="common_ok">OK</string>
+    <string name="common_remove_upload">Remove upload</string>
+    <string name="common_retry_upload">Retry upload</string>
     <string name="common_cancel_sync">Cancel sync</string>
     <string name="common_cancel">Cancel</string>
     <string name="common_save_exit">Save &amp; exit</string>
@@ -108,6 +116,25 @@
     <string name="uploader_upload_failed_ticker">Upload failed</string>
     <string name="uploader_upload_failed_content_single">Upload of %1$s could not be completed</string>
     <string name="uploader_upload_failed_credentials_error">Upload failed, you need to log in again</string>
+    <string name="uploads_view_title">Uploads</string>
+    <string name="uploads_view_group_current_uploads">Current</string>
+    <string name="uploads_view_group_failed_uploads">Failed (tap to retry)</string>
+    <string name="uploads_view_group_finished_uploads">Uploaded</string>
+    <string name="uploads_view_upload_status_succeeded">Completed</string>
+    <string name="uploads_view_upload_status_cancelled">Cancelled</string>
+    <string name="uploads_view_upload_status_paused">Paused</string>
+    <string name="uploads_view_upload_status_failed_connection_error">Connection error</string>
+    <string name="uploads_view_upload_status_failed_retry">Upload will be retried shortly</string>
+    <string name="uploads_view_upload_status_failed_credentials_error">Credentials error</string>
+    <string name="uploads_view_upload_status_failed_folder_error">Folder error</string>
+    <string name="uploads_view_upload_status_failed_file_error">File error</string>
+    <string name="uploads_view_upload_status_failed_localfile_error">Local file not found</string>
+    <string name="uploads_view_upload_status_failed_permission_error">Permission error</string>
+    <string name="uploads_view_upload_status_conflict">Conflict</string>
+    <string name="uploads_view_upload_status_service_interrupted">App was terminated</string>
+    <string name="uploads_view_upload_status_unknown_fail">Unknown error</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Waiting for wifi connectivity</string>
+    <string name="uploads_view_later_waiting_to_upload">Waiting to upload</string>
     <string name="downloader_download_in_progress_ticker">Downloading &#8230;</string>
     <string name="downloader_download_in_progress_content">%1$d%% Downloading %2$s</string>
     <string name="downloader_download_succeeded_ticker">Download succeeded</string>
@@ -219,6 +246,7 @@
     <string name="filename_forbidden_charaters_from_server">File name contains at least one invalid character</string>
     <string name="filename_empty">File name cannot be empty</string>
     <string name="wait_a_moment">Wait a moment</string>
+    <string name="wait_checking_credentials">Checking stored credentials</string>
     <string name="filedisplay_unexpected_bad_get_content">"Unexpected problem ; please select the file from a different app"</string>
     <string name="filedisplay_no_file_selected">No file was selected</string>
     <string name="activity_chooser_title">Send link to &#8230;</string>
@@ -405,6 +433,11 @@
     <string name="edit_share_unshare">Stop sharing</string>
     <string name="edit_share_done">done</string>
 
+    <string name="action_retry_uploads">Retry failed</string>
+    <string name="action_clear_failed_uploads">Clear failed</string>
+    <string name="action_clear_successful_uploads">Clear successful</string>
+    <string name="action_clear_finished_uploads">Clear all finished</string>
+
     <string name="action_switch_grid_view">Grid view</string>
     <string name="action_switch_list_view">List view</string>
 
@@ -414,5 +447,6 @@
     <string name="manage_space_error">Some files could not be deleted.</string>
 
     <string name="permission_storage_access">Additional permissions required to upload &amp; download files.</string>
+    <string name="local_file_not_found_toast">The file was not found locally</string>
 
 </resources>

+ 2 - 0
res/values/styles.xml

@@ -29,6 +29,7 @@
 		<item name="colorAccent">@color/color_accent</item>
 		<item name="android:alertDialogTheme">@style/Theme.ownCloud.Dialog</item>
 		<item name="alertDialogTheme">@style/ownCloud.AlertDialog</item>
+		<item name="android:windowBackground">@color/background_color</item>
 		<item name="searchViewStyle">@style/ownCloud.SearchView</item>
 	</style>
 
@@ -41,6 +42,7 @@
 		<item name="colorAccent">@color/color_accent</item>
 		<item name="android:alertDialogTheme">@style/Theme.ownCloud.Dialog</item>
 		<item name="alertDialogTheme">@style/ownCloud.AlertDialog</item>
+		<item name="android:windowBackground">@color/background_color</item>
 		<item name="searchViewStyle">@style/ownCloud.SearchView</item>
     </style>
 

+ 3 - 1
src/com/owncloud/android/MainApp.java

@@ -166,7 +166,9 @@ public class MainApp extends Application {
         return getAppContext().getResources().getString(R.string.db_name);
     }
      
-    //  data_folder
+    /**
+     * name of data_folder, e.g., "owncloud"
+     */
     public static String getDataFolder() {
         return getAppContext().getResources().getString(R.string.data_folder);
     }

+ 24 - 8
src/com/owncloud/android/authentication/AccountUtils.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   Copyright (C) 2012  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -20,7 +20,12 @@
 
 package com.owncloud.android.authentication;
 
-import java.util.Locale;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.preference.PreferenceManager;
 
 import com.owncloud.android.MainApp;
 import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
@@ -28,12 +33,7 @@ import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.preference.PreferenceManager;
+import java.util.Locale;
 
 public class AccountUtils {
 
@@ -108,6 +108,22 @@ public class AccountUtils {
         return false;
     }
     
+    /**
+     * Returns owncloud account identified by accountName or null if it does not exist.
+     * @param context
+     * @param accountName name of account to be returned
+     * @return owncloud account named accountName
+     */
+    public static Account getOwnCloudAccountByName(Context context, String accountName) {
+        Account[] ocAccounts = AccountManager.get(context).getAccountsByType(
+                MainApp.getAccountType());
+        for (Account account : ocAccounts) {
+            if(account.name.equals(accountName))
+                return account;
+        }
+        return null;
+    }
+    
 
     public static boolean setCurrentOwnCloudAccount(Context context, String accountName) {
         boolean result = false;

+ 13 - 0
src/com/owncloud/android/authentication/AuthenticatorActivity.java

@@ -65,6 +65,8 @@ import android.widget.Toast;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.OwnCloudCredentials;
 import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
 import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
@@ -925,6 +927,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         } else {
             checkBasicAuthorization();
         }
+
     }
 
 
@@ -1465,6 +1468,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
      * the new credentials when needed.
      */
     private void updateAccountAuthentication() throws AccountNotFoundException {
+
+
         
         Bundle response = new Bundle();
         response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
@@ -1489,7 +1494,15 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());
             mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());
         }
+
+        // remove managed clients for this account to enforce creation with fresh credentials
+        OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, this);
+        OwnCloudClientManagerFactory.getDefaultSingleton().removeClientFor(ocAccount);
+
         setAccountAuthenticatorResult(response);
+        final Intent intent = new Intent();
+        intent.putExtras(response);
+        setResult(RESULT_OK, intent);
 
     }
 

+ 15 - 0
src/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -254,6 +254,20 @@ public class FileDataStorageManager {
     }
 
 
+    public void saveNewFile(OCFile newFile) {
+        String remoteParentPath = new File(newFile.getRemotePath()).getParent();
+        remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+                remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+        OCFile parent = getFileByPath(remoteParentPath);
+        if (parent != null) {
+            newFile.setParentId(parent.getFileId());
+            saveFile(newFile);
+        } else {
+            throw new IllegalArgumentException("Saving a new file in an unexisting folder");
+        }
+    }
+
+
     /**
      * Inserts or updates the list of files contained in a given folder.
      * <p/>
@@ -2043,4 +2057,5 @@ public class FileDataStorageManager {
         }
         return capability;
     }
+
 }

+ 5 - 3
src/com/owncloud/android/datamodel/OCFile.java

@@ -4,7 +4,7 @@
  *   @author Bartek Przybylski
  *   @author David A. Velasco
  *   Copyright (C) 2012  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -22,6 +22,9 @@
 
 package com.owncloud.android.datamodel;
 
+
+import java.io.File;
+
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Parcel;
@@ -30,9 +33,8 @@ import android.webkit.MimeTypeMap;
 
 import com.owncloud.android.lib.common.utils.Log_OC;
 
-import java.io.File;
-
 import third_parties.daveKoeller.AlphanumComparator;
+
 public class OCFile implements Parcelable, Comparable<OCFile> {
 
     public static final Parcelable.Creator<OCFile> CREATOR = new Parcelable.Creator<OCFile>() {

+ 514 - 0
src/com/owncloud/android/datamodel/UploadsStorageManager.java

@@ -0,0 +1,514 @@
+/**
+ *  ownCloud Android client application
+ *
+ *  @author LukeOwncloud
+ *  @author David A. Velasco
+ *  @author masensio
+ *  Copyright (C) 2016 ownCloud Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.datamodel;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
+import com.owncloud.android.db.UploadResult;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.UploadFileOperation;
+
+import java.util.Calendar;
+import java.util.Observable;
+
+/**
+ * Database helper for storing list of files to be uploaded, including status
+ * information for each file.
+ */
+public class UploadsStorageManager extends Observable {
+
+    private ContentResolver mContentResolver;
+
+    static private final String TAG = UploadsStorageManager.class.getSimpleName();
+
+
+    public enum UploadStatus {
+
+        /**
+         * Upload currently in progress or scheduled to be executed.
+         */
+        UPLOAD_IN_PROGRESS(0),
+
+        /**
+         * Last upload failed.
+         */
+        UPLOAD_FAILED(1),
+
+        /**
+         * Upload was successful.
+         */
+        UPLOAD_SUCCEEDED(2);
+
+        private final int value;
+
+        UploadStatus(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public static UploadStatus fromValue(int value) {
+            switch (value) {
+                case 0:
+                    return UPLOAD_IN_PROGRESS;
+                case 1:
+                    return UPLOAD_FAILED;
+                case 2:
+                    return UPLOAD_SUCCEEDED;
+            }
+            return null;
+        }
+
+    }
+
+    public UploadsStorageManager(ContentResolver contentResolver) {
+        if (contentResolver == null) {
+            throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver");
+        }
+        mContentResolver = contentResolver;
+    }
+
+    /**
+     * Stores an upload object in DB.
+     *
+     * @param ocUpload      Upload object to store
+     * @return upload id, -1 if the insert process fails.
+     */
+    public long storeUpload(OCUpload ocUpload) {
+        Log_OC.v(TAG, "Inserting " + ocUpload.getLocalPath() + " with status=" + ocUpload.getUploadStatus());
+
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderTableMeta.UPLOADS_LOCAL_PATH, ocUpload.getLocalPath());
+        cv.put(ProviderTableMeta.UPLOADS_REMOTE_PATH, ocUpload.getRemotePath());
+        cv.put(ProviderTableMeta.UPLOADS_ACCOUNT_NAME, ocUpload.getAccountName());
+        cv.put(ProviderTableMeta.UPLOADS_FILE_SIZE, ocUpload.getFileSize());
+        cv.put(ProviderTableMeta.UPLOADS_STATUS, ocUpload.getUploadStatus().value);
+        cv.put(ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR, ocUpload.getLocalAction());
+        cv.put(ProviderTableMeta.UPLOADS_FORCE_OVERWRITE, ocUpload.isForceOverwrite() ? 1 : 0);
+        cv.put(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER, ocUpload.isCreateRemoteFolder() ? 1 : 0);
+        cv.put(ProviderTableMeta.UPLOADS_LAST_RESULT, ocUpload.getLastResult().getValue());
+        cv.put(ProviderTableMeta.UPLOADS_CREATED_BY, ocUpload.getCreadtedBy());
+
+        Uri result = getDB().insert(ProviderTableMeta.CONTENT_URI_UPLOADS, cv);
+
+        Log_OC.d(TAG, "storeUpload returns with: " + result + " for file: " + ocUpload.getLocalPath());
+        if (result == null) {
+            Log_OC.e(TAG, "Failed to insert item " + ocUpload.getLocalPath() + " into upload db.");
+            return -1;
+        } else {
+            long new_id = Long.parseLong(result.getPathSegments().get(1));
+            ocUpload.setUploadId(new_id);
+            notifyObserversNow();
+            return new_id;
+        }
+    }
+
+    /**
+     * Update an upload object in DB.
+     *
+     * @param ocUpload      Upload object with state to update
+     * @return num of updated uploads.
+     */
+    public int updateUpload(OCUpload ocUpload) {
+        Log_OC.v(TAG, "Updating " + ocUpload.getLocalPath() + " with status=" + ocUpload.getUploadStatus());
+
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderTableMeta.UPLOADS_LOCAL_PATH, ocUpload.getLocalPath());
+        cv.put(ProviderTableMeta.UPLOADS_REMOTE_PATH, ocUpload.getRemotePath());
+        cv.put(ProviderTableMeta.UPLOADS_ACCOUNT_NAME, ocUpload.getAccountName());
+        cv.put(ProviderTableMeta.UPLOADS_STATUS, ocUpload.getUploadStatus().value);
+        cv.put(ProviderTableMeta.UPLOADS_LAST_RESULT, ocUpload.getLastResult().getValue());
+        cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, ocUpload.getUploadEndTimestamp());
+
+        int result = getDB().update(ProviderTableMeta.CONTENT_URI_UPLOADS,
+            cv,
+            ProviderTableMeta._ID + "=?",
+            new String[]{String.valueOf(ocUpload.getUploadId())}
+        );
+
+        Log_OC.d(TAG, "updateUpload returns with: " + result + " for file: " + ocUpload.getLocalPath());
+        if (result != 1) {
+            Log_OC.e(TAG, "Failed to update item " + ocUpload.getLocalPath() + " into upload db.");
+        } else {
+            notifyObserversNow();
+        }
+
+        return result;
+    }
+
+    private int updateUploadInternal(Cursor c, UploadStatus status, UploadResult result, String remotePath,
+                                     String localPath) {
+
+        int r = 0;
+        while (c.moveToNext()) {
+            // read upload object and update
+            OCUpload upload = createOCUploadFromCursor(c);
+
+            String path = c.getString(c.getColumnIndex(ProviderTableMeta.UPLOADS_LOCAL_PATH));
+            Log_OC.v(
+                TAG,
+                "Updating " + path + " with status:" + status + " and result:"
+                    + (result == null ? "null" : result.toString()) + " (old:"
+                    + upload.toFormattedString() + ")");
+
+            upload.setUploadStatus(status);
+            upload.setLastResult(result);
+            upload.setRemotePath(remotePath);
+            if(localPath != null) {
+                upload.setLocalPath(localPath);
+            }
+            if (status == UploadStatus.UPLOAD_SUCCEEDED) {
+                upload.setUploadEndTimestamp(Calendar.getInstance().getTimeInMillis());
+            }
+
+            // store update upload object to db
+            r = updateUpload(upload);
+
+        }
+
+        return r;
+    }
+
+    /**
+     * Update upload status of file uniquely referenced by id.
+     *
+     * @param id     upload id.
+     * @param status new status.
+     * @param result new result of upload operation
+     * @param remotePath path of the file to upload in the ownCloud storage
+     * @param localPath path of the file to upload in the device storage
+     * @return 1 if file status was updated, else 0.
+     */
+    public int updateUploadStatus(long id, UploadStatus status, UploadResult result, String remotePath,
+                                  String localPath) {
+        //Log_OC.v(TAG, "Updating "+filepath+" with uploadStatus="+status +" and result="+result);
+
+        int returnValue = 0;
+        Cursor c = getDB().query(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                null,
+                ProviderTableMeta._ID + "=?",
+                new String[]{String.valueOf(id)},
+                null
+        );
+
+        if (c.getCount() != 1) {
+            Log_OC.e(TAG, c.getCount() + " items for id=" + id
+                + " available in UploadDb. Expected 1. Failed to update upload db.");
+        } else {
+            returnValue = updateUploadInternal(c, status, result, remotePath, localPath);
+        }
+        c.close();
+        return returnValue;
+    }
+
+
+    /**
+     * Should be called when some value of this DB was changed. All observers
+     * are informed.
+     */
+    public void notifyObserversNow() {
+        Log_OC.d(TAG, "notifyObserversNow");
+        setChanged();
+        notifyObservers();
+    }
+
+
+    /**
+     * Remove an upload from the uploads list, known its target account and remote path.
+     *
+     * @param upload            Upload instance to remove from persisted storage.
+     *
+     * @return true when the upload was stored and could be removed.
+     */
+    public int removeUpload(OCUpload upload) {
+        int result = getDB().delete(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                ProviderTableMeta._ID + "=?" ,
+                new String[]{Long.toString(upload.getUploadId())}
+        );
+        Log_OC.d(TAG, "delete returns " + result + " for upload " + upload);
+        if (result > 0) {
+            notifyObserversNow();
+        }
+        return result;
+    }
+
+
+    /**
+     * Remove an upload from the uploads list, known its target account and remote path.
+     *
+     * @param accountName       Name of the OC account target of the upload to remove.
+     * @param remotePath        Absolute path in the OC account target of the upload to remove.
+     * @return true when one or more upload entries were removed
+     */
+    public int removeUpload(String accountName, String remotePath) {
+        int result = getDB().delete(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=? AND " + ProviderTableMeta.UPLOADS_REMOTE_PATH + "=?" ,
+                new String[]{accountName, remotePath}
+        );
+        Log_OC.d(TAG, "delete returns " + result + " for file " + remotePath + " in " + accountName);
+        if (result > 0) {
+            notifyObserversNow();
+        }
+        return result;
+    }
+
+
+    /**
+     * Remove all the uploads of a given account from the uploads list.
+     *
+     * @param accountName       Name of the OC account target of the uploads to remove.
+     * @return true when one or more upload entries were removed
+     */
+    public int removeUploads(String accountName) {
+        int result = getDB().delete(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=?",
+                new String[]{accountName}
+        );
+        Log_OC.d(TAG, "delete returns " + result + " for uploads in " + accountName);
+        if (result > 0) {
+            notifyObserversNow();
+        }
+        return result;
+    }
+
+    public OCUpload[] getAllStoredUploads() {
+        return getUploads(null, null);
+    }
+
+
+    private OCUpload[] getUploads(String selection, String[] selectionArgs) {
+        Cursor c = getDB().query(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                null,
+                selection,
+                selectionArgs,
+                null
+        );
+        OCUpload[] list = new OCUpload[c.getCount()];
+        if (c.moveToFirst()) {
+            do {
+                OCUpload upload = createOCUploadFromCursor(c);
+                if (upload == null) {
+                    Log_OC.e(TAG, "OCUpload could not be created from cursor");
+                } else {
+                    list[c.getPosition()] = upload;
+                }
+            } while (c.moveToNext());
+
+        }
+        c.close();
+
+        return list;
+    }
+
+
+    private OCUpload createOCUploadFromCursor(Cursor c) {
+        OCUpload upload = null;
+        if (c != null) {
+            String localPath = c.getString(c.getColumnIndex(ProviderTableMeta.UPLOADS_LOCAL_PATH));
+            String remotePath = c.getString(c.getColumnIndex(ProviderTableMeta.UPLOADS_REMOTE_PATH));
+            String accountName = c.getString(c.getColumnIndex(ProviderTableMeta.UPLOADS_ACCOUNT_NAME));
+            upload = new OCUpload(localPath, remotePath, accountName);
+
+            upload.setFileSize(c.getLong(c.getColumnIndex(ProviderTableMeta.UPLOADS_FILE_SIZE)));
+            upload.setUploadId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID)));
+            upload.setUploadStatus(
+                    UploadStatus.fromValue(c.getInt(c.getColumnIndex(ProviderTableMeta.UPLOADS_STATUS)))
+            );
+            upload.setLocalAction(c.getInt(c.getColumnIndex((ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR))));
+            upload.setForceOverwrite(c.getInt(
+                    c.getColumnIndex(ProviderTableMeta.UPLOADS_FORCE_OVERWRITE)) == 1);
+            upload.setCreateRemoteFolder(c.getInt(
+                    c.getColumnIndex(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER)) == 1);
+            upload.setUploadEndTimestamp(c.getLong(c.getColumnIndex(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP)));
+            upload.setLastResult(UploadResult.fromValue(
+                    c.getInt(c.getColumnIndex(ProviderTableMeta.UPLOADS_LAST_RESULT))));
+            upload.setCreatedBy(c.getInt(c.getColumnIndex(ProviderTableMeta.UPLOADS_CREATED_BY)));
+        }
+        return upload;
+    }
+
+    /**
+     * Get all uploads which are currently being uploaded or waiting in the queue to be uploaded.
+     */
+    public OCUpload[] getCurrentAndPendingUploads() {
+        return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value, null);
+    }
+
+    /**
+     * Get all unrecoverably failed. Upload of these should/must/will not be
+     * retried.
+     */
+    public OCUpload[] getFailedUploads() {
+        return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value, null);
+    }
+
+    /**
+     * Get all uploads which where successfully completed.
+     */
+    public OCUpload[] getFinishedUploads() {
+        return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value, null);
+    }
+
+    private ContentResolver getDB() {
+        return mContentResolver;
+    }
+
+    public long clearFailedUploads() {
+        long result = getDB().delete(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value, null
+        );
+        Log_OC.d(TAG, "delete all failed uploads");
+        if (result > 0) {
+            notifyObserversNow();
+        }
+        return result;
+    }
+
+    public long clearSuccessfulUploads() {
+        long result = getDB().delete(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                ProviderTableMeta.UPLOADS_STATUS + "=="+ UploadStatus.UPLOAD_SUCCEEDED.value, null
+        );
+        Log_OC.d(TAG, "delete all successful uploads");
+        if (result > 0) {
+            notifyObserversNow();
+        }
+        return result;
+    }
+
+    public long clearAllFinishedUploads() {
+        String[] whereArgs = new String[2];
+        whereArgs[0] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value);
+        whereArgs[1] = String.valueOf(UploadStatus.UPLOAD_FAILED.value);
+        long result = getDB().delete(
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                ProviderTableMeta.UPLOADS_STATUS + "=? OR " + ProviderTableMeta.UPLOADS_STATUS + "=?",
+                whereArgs
+        );
+        Log_OC.d(TAG, "delete all finished uploads");
+        if (result > 0) {
+            notifyObserversNow();
+        }
+        return result;
+    }
+
+    /**
+     * Updates the persistent upload database with upload result.
+     */
+    public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) {
+        // result: success or fail notification
+        Log_OC.d(TAG, "updateDataseUploadResult uploadResult: " + uploadResult + " upload: " + upload);
+
+        if (uploadResult.isCancelled()) {
+            removeUpload(
+                upload.getAccount().name,
+                upload.getRemotePath()
+            );
+        } else {
+            String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
+                ? upload.getStoragePath() : null;
+
+            if (uploadResult.isSuccess()) {
+                updateUploadStatus(
+                    upload.getOCUploadId(),
+                    UploadStatus.UPLOAD_SUCCEEDED,
+                    UploadResult.UPLOADED,
+                    upload.getRemotePath(),
+                    localPath
+                );
+            } else {
+                updateUploadStatus(
+                    upload.getOCUploadId(),
+                    UploadStatus.UPLOAD_FAILED,
+                    UploadResult.fromOperationResult(uploadResult),
+                    upload.getRemotePath(),
+                    localPath
+                );
+            }
+        }
+    }
+
+    /**
+     * Updates the persistent upload database with an upload now in progress.
+     */
+    public void updateDatabaseUploadStart(UploadFileOperation upload) {
+        String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
+            ? upload.getStoragePath() : null;
+
+        updateUploadStatus(
+            upload.getOCUploadId(),
+            UploadStatus.UPLOAD_IN_PROGRESS,
+            UploadResult.UNKNOWN,
+            upload.getRemotePath(),
+            localPath
+        );
+    }
+
+
+    /**
+     * Changes the status of any in progress upload from UploadStatus.UPLOAD_IN_PROGRESS
+     * to UploadStatus.UPLOAD_FAILED
+     *
+     * @return      Number of uploads which status was changed.
+     */
+    public int failInProgressUploads(UploadResult fail) {
+        Log_OC.v(TAG, "Updating state of any killed upload");
+
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderTableMeta.UPLOADS_STATUS, UploadStatus.UPLOAD_FAILED.getValue());
+        cv.put(
+            ProviderTableMeta.UPLOADS_LAST_RESULT,
+            fail != null ? fail.getValue() : UploadResult.UNKNOWN.getValue()
+        );
+        cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, Calendar.getInstance().getTimeInMillis());
+
+        int result = getDB().update(
+            ProviderTableMeta.CONTENT_URI_UPLOADS,
+            cv,
+            ProviderTableMeta.UPLOADS_STATUS + "=?",
+            new String[]{String.valueOf(UploadStatus.UPLOAD_IN_PROGRESS.getValue())}
+        );
+
+        if (result == 0) {
+            Log_OC.v(TAG, "No upload was killed");
+        } else {
+            Log_OC.w(TAG, Integer.toString(result) + " uploads where abruptly interrupted");
+            notifyObserversNow();
+        }
+        return result;
+    }
+
+}

+ 0 - 127
src/com/owncloud/android/db/DbHandler.java

@@ -1,127 +0,0 @@
-/**
- *   ownCloud Android client application
- *
- *   @author Bartek Przybylski
- *   Copyright (C) 2011-2012  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   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 <http://www.gnu.org/licenses/>.
- *
- */
-package com.owncloud.android.db;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.lib.common.utils.Log_OC;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
-/**
- * Custom database helper for ownCloud
- */
-public class DbHandler {
-    private SQLiteDatabase mDB;
-    private OpenerHelper mHelper;
-    private final String mDatabaseName;
-    private final int mDatabaseVersion = 3;
-
-    private final String TABLE_INSTANT_UPLOAD = "instant_upload";
-
-    public static final int UPLOAD_STATUS_UPLOAD_LATER = 0;
-    public static final int UPLOAD_STATUS_UPLOAD_FAILED = 1;
-
-    public DbHandler(Context context) {
-        mDatabaseName = MainApp.getDBName();
-        mHelper = new OpenerHelper(context);
-        mDB = mHelper.getWritableDatabase();
-    }
-
-    public void close() {
-        mDB.close();
-    }
-
-    public boolean putFileForLater(String filepath, String account, String message) {
-        ContentValues cv = new ContentValues();
-        cv.put("path", filepath);
-        cv.put("account", account);
-        cv.put("attempt", UPLOAD_STATUS_UPLOAD_LATER);
-        cv.put("message", message);
-        long result = mDB.insert(TABLE_INSTANT_UPLOAD, null, cv);
-        Log_OC.d(TABLE_INSTANT_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath);
-        return result != -1;
-    }
-
-    public int updateFileState(String filepath, Integer status, String message) {
-        ContentValues cv = new ContentValues();
-        cv.put("attempt", status);
-        cv.put("message", message);
-        int result = mDB.update(TABLE_INSTANT_UPLOAD, cv, "path=?", new String[] { filepath });
-        Log_OC.d(TABLE_INSTANT_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath);
-        return result;
-    }
-
-    public Cursor getAwaitingFiles() {
-        return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt=" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null);
-    }
-
-    public Cursor getFailedFiles() {
-        return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null);
-    }
-
-    public void clearFiles() {
-        mDB.delete(TABLE_INSTANT_UPLOAD, null, null);
-    }
-
-    /**
-     * 
-     * @param localPath
-     * @return true when one or more pending files was removed
-     */
-    public boolean removeIUPendingFile(String localPath) {
-        long result = mDB.delete(TABLE_INSTANT_UPLOAD, "path = ?", new String[] { localPath });
-        Log_OC.d(TABLE_INSTANT_UPLOAD, "delete returns with: " + result + " for file: " + localPath);
-        return result != 0;
-
-    }
-
-    private class OpenerHelper extends SQLiteOpenHelper {
-        public OpenerHelper(Context context) {
-            super(context, mDatabaseName, null, mDatabaseVersion);
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " (" + " _id INTEGER PRIMARY KEY, " + " path TEXT,"
-                    + " account TEXT,attempt INTEGER,message TEXT);");
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            if (oldVersion < 2) {
-                db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN attempt INTEGER;");
-            }
-            db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN message TEXT;");
-        }
-        
-        @Override
-        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            //downgrading is the exception, so deleting and re-creating is acceptable.
-            //otherwise exception will be thrown (cannot downgrade) and oc app will crash.
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_INSTANT_UPLOAD + ";");
-            onCreate(db);
-        }
-    }
-}

+ 427 - 0
src/com/owncloud/android/db/OCUpload.java

@@ -0,0 +1,427 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author LukeOwncloud
+ *   @author masensio
+ *   @author David A. Velasco
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.db;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.utils.MimetypeIconUtil;
+
+import java.io.File;
+
+/**
+ * Stores all information in order to start upload operations. PersistentUploadObject can
+ * be stored persistently by {@link UploadsStorageManager}.
+ * 
+ */
+public class OCUpload implements Parcelable {
+
+    private static final String TAG = OCUpload.class.getSimpleName();
+
+    private long mId;
+
+    /**
+     * Absolute path in the local file system to the file to be uploaded
+     */
+    private String mLocalPath;
+
+    /**
+     * Absolute path in the remote account to set to the uploaded file (not for its parent folder!)
+     */
+    private String mRemotePath;
+
+    /**
+     * Name of Owncloud account to upload file to.
+     */
+    private String mAccountName;
+
+    /**
+     * File size
+     */
+    private long mFileSize;
+
+    /**
+     * Local action for upload. (0 - COPY, 1 - MOVE, 2 - FORGET)
+     */
+    private int mLocalAction;
+
+    /**
+     * Overwrite destination file?
+     */
+    private boolean mForceOverwrite;
+    /**
+     * Create destination folder?
+     */
+    private boolean mIsCreateRemoteFolder;
+    /**
+     * Status of upload (later, in_progress, ...).
+     */
+    private UploadStatus mUploadStatus;
+    /**
+     * Result from last upload operation. Can be null.
+     */
+    private UploadResult mLastResult;
+
+    /**
+     * Defines the origin of the upload; see constants CREATED_ in {@link UploadFileOperation}
+     */
+    private int mCreatedBy;
+
+    /*
+     * When the upload ended
+     */
+    private long mUploadEndTimeStamp;
+
+
+    /**
+     * Main constructor
+     *
+     * @param localPath         Absolute path in the local file system to the file to be uploaded.
+     * @param remotePath        Absolute path in the remote account to set to the uploaded file.
+     * @param accountName       Name of an ownCloud account to update the file to.
+     */
+    public OCUpload(String localPath, String remotePath, String accountName) {
+        if (localPath == null || !localPath.startsWith(File.separator)) {
+            throw new IllegalArgumentException("Local path must be an absolute path in the local file system");
+        }
+        if (remotePath == null || !remotePath.startsWith(OCFile.PATH_SEPARATOR)) {
+            throw new IllegalArgumentException("Remote path must be an absolute path in the local file system");
+        }
+        if (accountName == null || accountName.length() < 1) {
+            throw new IllegalArgumentException("Invalid account name");
+        }
+        resetData();
+        mLocalPath = localPath;
+        mRemotePath = remotePath;
+        mAccountName = accountName;
+    }
+
+
+    /**
+     * Convenience constructor to reupload already existing {@link OCFile}s
+     *
+     * @param  ocFile           {@link OCFile} instance to update in the remote server.
+     * @param  account          ownCloud {@link Account} where ocFile is contained.
+     */
+    public OCUpload(OCFile ocFile, Account account) {
+        this(ocFile.getStoragePath(), ocFile.getRemotePath(), account.name);
+    }
+
+
+    /**
+     * Reset all the fields to default values.
+     */
+    private void resetData() {
+        mRemotePath = "";
+        mLocalPath = "";
+        mAccountName = "";
+        mFileSize = -1;
+        mId = -1;
+        mLocalAction = FileUploader.LOCAL_BEHAVIOUR_COPY;
+        mForceOverwrite = false;
+        mIsCreateRemoteFolder = false;
+        mUploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
+        mLastResult = UploadResult.UNKNOWN;
+        mCreatedBy = UploadFileOperation.CREATED_BY_USER;
+    }
+
+    // Getters & Setters
+    public void setUploadId(long id) {
+        mId = id;
+    }
+    public long getUploadId() {
+        return mId;
+    }
+
+    /**
+     * @return the uploadStatus
+     */
+    public UploadStatus getUploadStatus() {
+        return mUploadStatus;
+    }
+
+    /**
+     * Sets uploadStatus AND SETS lastResult = null;
+     * @param uploadStatus the uploadStatus to set
+     */
+    public void setUploadStatus(UploadStatus uploadStatus) {
+        this.mUploadStatus = uploadStatus;
+        setLastResult(UploadResult.UNKNOWN);
+    }
+
+    /**
+     * @return the lastResult
+     */
+    public UploadResult getLastResult() {
+        return mLastResult;
+    }
+
+    /**
+     * @param lastResult the lastResult to set
+     */
+    public void setLastResult(UploadResult lastResult) {
+        this.mLastResult = ((lastResult != null) ? lastResult : UploadResult.UNKNOWN);
+    }
+
+
+    /**
+     * @return the localPath
+     */
+    public String getLocalPath() {
+        return mLocalPath;
+    }
+
+    public void setLocalPath(String localPath) {
+        mLocalPath = localPath;
+    }
+
+    /**
+     * @return the remotePath
+     */
+    public String getRemotePath() {
+        return mRemotePath;
+    }
+
+    /**
+     * @param remotePath
+     */
+    public void setRemotePath(String remotePath) {
+        mRemotePath = remotePath;
+    }
+
+
+    /**
+     * @return  File size
+     */
+    public long getFileSize() {
+        return mFileSize;
+    }
+
+    public void setFileSize(long fileSize) {
+        mFileSize = fileSize;
+    }
+
+
+    /**
+     * @return the mimeType
+     */
+    public String getMimeType() {
+        return MimetypeIconUtil.getBestMimeTypeByFilename(mLocalPath);
+    }
+
+    /**
+     * @return the localAction
+     */
+    public int getLocalAction() {
+        return mLocalAction;
+    }
+
+    /**
+     * @param localAction the localAction to set
+     */
+    public void setLocalAction(int localAction) {
+        this.mLocalAction = localAction;
+    }
+
+    /**
+     * @return the forceOverwrite
+     */
+    public boolean isForceOverwrite() {
+        return mForceOverwrite;
+    }
+
+    /**
+     * @param forceOverwrite the forceOverwrite to set
+     */
+    public void setForceOverwrite(boolean forceOverwrite) {
+        this.mForceOverwrite = forceOverwrite;
+    }
+
+    /**
+     * @return the isCreateRemoteFolder
+     */
+    public boolean isCreateRemoteFolder() {
+        return mIsCreateRemoteFolder;
+    }
+
+    /**
+     * @param isCreateRemoteFolder the isCreateRemoteFolder to set
+     */
+    public void setCreateRemoteFolder(boolean isCreateRemoteFolder) {
+        this.mIsCreateRemoteFolder = isCreateRemoteFolder;
+    }
+
+    /**
+     * @return the accountName
+     */
+    public String getAccountName() {
+        return mAccountName;
+    }
+
+    /**
+     * Returns owncloud account as {@link Account} object.  
+     */
+    public Account getAccount(Context context) {
+        return AccountUtils.getOwnCloudAccountByName(context, getAccountName());
+    }
+
+    public void setCreatedBy(int createdBy) {
+        mCreatedBy = createdBy;
+    }
+
+    public int getCreadtedBy() {
+        return mCreatedBy;
+    }
+
+    public void setUploadEndTimestamp(long uploadEndTimestamp) {
+        mUploadEndTimeStamp = uploadEndTimestamp;
+    }
+
+    public long getUploadEndTimestamp(){
+        return mUploadEndTimeStamp;
+    }
+
+    /**
+     * For debugging purposes only.
+     */
+    public String toFormattedString() {
+        try {
+            String localPath = getLocalPath() != null ? getLocalPath() : "";
+            return localPath + " status:" + getUploadStatus() + " result:" +
+                    (getLastResult() == null ? "null" : getLastResult().getValue());
+        } catch (NullPointerException e){
+            Log_OC.d(TAG, "Exception " + e.toString() );
+            return (e.toString());
+        }
+    }
+
+    /****
+     *
+     */
+    public static final Parcelable.Creator<OCUpload> CREATOR = new Parcelable.Creator<OCUpload>() {
+
+        @Override
+        public OCUpload createFromParcel(Parcel source) {
+            return new OCUpload(source);
+        }
+
+        @Override
+        public OCUpload[] newArray(int size) {
+            return new OCUpload[size];
+        }
+    };
+
+    /**
+     * Reconstruct from parcel
+     *
+     * @param source The source parcel
+     */
+    protected OCUpload(Parcel source) {
+        readFromParcel(source);
+    }
+
+    public void readFromParcel(Parcel source) {
+        mId = source.readLong();
+        mLocalPath = source.readString();
+        mRemotePath = source.readString();
+        mAccountName = source.readString();
+        mLocalAction = source.readInt();
+        mForceOverwrite = (source.readInt() == 1);
+        mIsCreateRemoteFolder = (source.readInt() == 1);
+        try {
+            mUploadStatus = UploadStatus.valueOf(source.readString());
+        } catch (IllegalArgumentException x) {
+            mUploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
+        }
+        mUploadEndTimeStamp = source.readLong();
+        try {
+            mLastResult = UploadResult.valueOf(source.readString());
+        } catch (IllegalArgumentException x) {
+            mLastResult = UploadResult.UNKNOWN;
+        }
+        mCreatedBy = source.readInt();
+    }
+
+
+    @Override
+    public int describeContents() {
+        return this.hashCode();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mId);
+        dest.writeString(mLocalPath);
+        dest.writeString(mRemotePath);
+        dest.writeString(mAccountName);
+        dest.writeInt(mLocalAction);
+        dest.writeInt(mForceOverwrite ? 1 : 0);
+        dest.writeInt(mIsCreateRemoteFolder ? 1 : 0);
+        dest.writeString(mUploadStatus.name());
+        dest.writeLong(mUploadEndTimeStamp);
+        dest.writeString(((mLastResult == null) ? "" : mLastResult.name()));
+        dest.writeInt(mCreatedBy);
+    }
+
+    enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR};
+
+    /**
+     * Returns true when the file may be uploaded now. This methods checks all
+     * restraints of the passed {@link OCUpload}, these include
+     * isUseWifiOnly(), check if local file exists, check if file was already
+     * uploaded...
+     *
+     * If return value is CanUploadFileNowStatus.NOW, uploadFile() may be
+     * called.
+     *
+     * @return CanUploadFileNowStatus.NOW is upload may proceed, <br>
+     *         CanUploadFileNowStatus.LATER if upload should be performed at a
+     *         later time, <br>
+     *         CanUploadFileNowStatus.ERROR if a severe error happened, calling
+     *         entity should remove upload from queue.
+     *
+     */
+    private CanUploadFileNowStatus canUploadFileNow(Context context) {
+
+        if (getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) {
+            Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!");
+            return CanUploadFileNowStatus.ERROR;
+        }
+
+        if (!new File(getLocalPath()).exists()) {
+            Log_OC.d(TAG, "Do not start upload because local file does not exist.");
+            return CanUploadFileNowStatus.FILE_GONE;
+        }
+        return CanUploadFileNowStatus.NOW;
+    }
+
+}

+ 59 - 0
src/com/owncloud/android/db/PreferenceReader.java

@@ -0,0 +1,59 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author David A. Velasco
+ * Copyright (C) 2016 ownCloud Inc.
+ * <p/>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ * <p/>
+ * 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.
+ * <p/>
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.db;
+
+import android.content.Context;
+import android.preference.PreferenceManager;
+
+/**
+ * Helper to simplify reading of Preferences all around the app
+ */
+
+public class PreferenceReader {
+
+    public static boolean instantPictureUploadEnabled(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_uploading",
+                false
+        );
+    }
+
+    public static boolean instantVideoUploadEnabled(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_video_uploading",
+                false
+        );
+    }
+
+    public static boolean instantPictureUploadViaWiFiOnly(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_upload_on_wifi",
+                false
+        );
+    }
+
+    public static boolean instantVideoUploadViaWiFiOnly(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_video_upload_on_wifi",
+                false
+        );
+    }
+
+}

+ 155 - 133
src/com/owncloud/android/db/ProviderMeta.java

@@ -1,133 +1,155 @@
-/**
- *   ownCloud Android client application
- *
- *   @author Bartek Przybylski
- *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   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 <http://www.gnu.org/licenses/>.
- *
- */
-package com.owncloud.android.db;
-
-import android.net.Uri;
-import android.provider.BaseColumns;
-
-import com.owncloud.android.MainApp;
-
-/**
- * Meta-Class that holds various static field information
- */
-public class ProviderMeta {
-
-    public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 13;
-
-    private ProviderMeta() {
-    }
-
-    static public class ProviderTableMeta implements BaseColumns {
-        public static final String FILE_TABLE_NAME = "filelist";
-        public static final String OCSHARES_TABLE_NAME = "ocshares";
-        public static final String CAPABILITIES_TABLE_NAME = "capabilities";
-        public static final Uri CONTENT_URI = Uri.parse("content://"
-                + MainApp.getAuthority() + "/");
-        public static final Uri CONTENT_URI_FILE = Uri.parse("content://"
-                + MainApp.getAuthority() + "/file");
-        public static final Uri CONTENT_URI_DIR = Uri.parse("content://"
-                + MainApp.getAuthority() + "/dir");
-        public static final Uri CONTENT_URI_SHARE = Uri.parse("content://"
-                + MainApp.getAuthority() + "/shares");
-        public static final Uri CONTENT_URI_CAPABILITIES = Uri.parse("content://"
-                + MainApp.getAuthority() + "/capabilities");
-
-        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";
-
-        // Columns of filelist table
-        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_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data";
-        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 FILE_LAST_SYNC_DATE = "last_sync_date";// _for_properties, but let's keep it as it is
-        public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";
-        public static final String FILE_KEEP_IN_SYNC = "keep_in_sync";
-        public static final String FILE_ETAG = "etag";
-        public static final String FILE_SHARED_VIA_LINK = "share_by_link";
-        public static final String FILE_SHARED_WITH_SHAREE = "shared_via_users";
-        public static final String FILE_PUBLIC_LINK = "public_link";
-        public static final String FILE_PERMISSIONS = "permissions";
-        public static final String FILE_REMOTE_ID = "remote_id";
-        public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";
-        public static final String FILE_IS_DOWNLOADING= "is_downloading";
-        public static final String FILE_ETAG_IN_CONFLICT = "etag_in_conflict";
-
-        public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME
-                + " collate nocase asc";
-        
-        // Columns of ocshares table
-        public static final String OCSHARES_FILE_SOURCE = "file_source";
-        public static final String OCSHARES_ITEM_SOURCE = "item_source";
-        public static final String OCSHARES_SHARE_TYPE = "share_type";
-        public static final String OCSHARES_SHARE_WITH = "shate_with";
-        public static final String OCSHARES_PATH = "path";
-        public static final String OCSHARES_PERMISSIONS = "permissions";
-        public static final String OCSHARES_SHARED_DATE = "shared_date";
-        public static final String OCSHARES_EXPIRATION_DATE = "expiration_date";
-        public static final String OCSHARES_TOKEN = "token";
-        public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name";
-        public static final String OCSHARES_IS_DIRECTORY = "is_directory";
-        public static final String OCSHARES_USER_ID = "user_id";
-        public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared";
-        public static final String OCSHARES_ACCOUNT_OWNER = "owner_share";
-        
-        public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE 
-                + " collate nocase asc";
-
-        // Columns of capabilities table
-        public static final String CAPABILITIES_ACCOUNT_NAME = "account";
-        public static final String CAPABILITIES_VERSION_MAYOR = "version_mayor";
-        public static final String CAPABILITIES_VERSION_MINOR = "version_minor";
-        public static final String CAPABILITIES_VERSION_MICRO = "version_micro";
-        public static final String CAPABILITIES_VERSION_STRING = "version_string";
-        public static final String CAPABILITIES_VERSION_EDITION = "version_edition";
-        public static final String CAPABILITIES_CORE_POLLINTERVAL = "core_pollinterval";
-        public static final String CAPABILITIES_SHARING_API_ENABLED = "sharing_api_enabled";
-        public static final String CAPABILITIES_SHARING_PUBLIC_ENABLED = "sharing_public_enabled";
-        public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED = "sharing_public_password_enforced";
-        public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENABLED =
-                "sharing_public_expire_date_enabled";
-        public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS =
-                "sharing_public_expire_date_days";
-        public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENFORCED =
-                "sharing_public_expire_date_enforced";
-        public static final String CAPABILITIES_SHARING_PUBLIC_SEND_MAIL = "sharing_public_send_mail";
-        public static final String CAPABILITIES_SHARING_PUBLIC_UPLOAD = "sharing_public_upload";
-        public static final String CAPABILITIES_SHARING_USER_SEND_MAIL = "sharing_user_send_mail";
-        public static final String CAPABILITIES_SHARING_RESHARING = "sharing_resharing";
-        public static final String CAPABILITIES_SHARING_FEDERATION_OUTGOING = "sharing_federation_outgoing";
-        public static final String CAPABILITIES_SHARING_FEDERATION_INCOMING = "sharing_federation_incoming";
-        public static final String CAPABILITIES_FILES_BIGFILECHUNKING = "files_bigfilechunking";
-        public static final String CAPABILITIES_FILES_UNDELETE = "files_undelete";
-        public static final String CAPABILITIES_FILES_VERSIONING = "files_versioning";
-
-        public static final String CAPABILITIES_DEFAULT_SORT_ORDER = CAPABILITIES_ACCOUNT_NAME
-                + " collate nocase asc";
-    }
-}
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Bartek Przybylski
+ *   @author David A. Velasco
+ *   @author masensio
+ *   Copyright (C) 2011  Bartek Przybylski
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.db;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+import com.owncloud.android.MainApp;
+
+/**
+ * Meta-Class that holds various static field information
+ */
+public class ProviderMeta {
+
+    public static final String DB_NAME = "filelist";
+    public static final int DB_VERSION = 14;
+
+    private ProviderMeta() {
+    }
+
+    static public class ProviderTableMeta implements BaseColumns {
+        public static final String FILE_TABLE_NAME = "filelist";
+        public static final String OCSHARES_TABLE_NAME = "ocshares";
+        public static final String CAPABILITIES_TABLE_NAME = "capabilities";
+        public static final String UPLOADS_TABLE_NAME = "list_of_uploads";
+        public static final Uri CONTENT_URI = Uri.parse("content://"
+                + MainApp.getAuthority() + "/");
+        public static final Uri CONTENT_URI_FILE = Uri.parse("content://"
+                + MainApp.getAuthority() + "/file");
+        public static final Uri CONTENT_URI_DIR = Uri.parse("content://"
+                + MainApp.getAuthority() + "/dir");
+        public static final Uri CONTENT_URI_SHARE = Uri.parse("content://"
+                + MainApp.getAuthority() + "/shares");
+        public static final Uri CONTENT_URI_CAPABILITIES = Uri.parse("content://"
+                + MainApp.getAuthority() + "/capabilities");
+        public static final Uri CONTENT_URI_UPLOADS = Uri.parse("content://"
+                + MainApp.getAuthority() + "/uploads");
+
+        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";
+
+        // Columns of filelist table
+        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_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data";
+        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 FILE_LAST_SYNC_DATE = "last_sync_date";// _for_properties, but let's keep it as it is
+        public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";
+        public static final String FILE_KEEP_IN_SYNC = "keep_in_sync";
+        public static final String FILE_ETAG = "etag";
+        public static final String FILE_SHARED_VIA_LINK = "share_by_link";
+        public static final String FILE_SHARED_WITH_SHAREE = "shared_via_users";
+        public static final String FILE_PUBLIC_LINK = "public_link";
+        public static final String FILE_PERMISSIONS = "permissions";
+        public static final String FILE_REMOTE_ID = "remote_id";
+        public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";
+        public static final String FILE_IS_DOWNLOADING= "is_downloading";
+        public static final String FILE_ETAG_IN_CONFLICT = "etag_in_conflict";
+
+        public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME
+                + " collate nocase asc";
+        
+        // Columns of ocshares table
+        public static final String OCSHARES_FILE_SOURCE = "file_source";
+        public static final String OCSHARES_ITEM_SOURCE = "item_source";
+        public static final String OCSHARES_SHARE_TYPE = "share_type";
+        public static final String OCSHARES_SHARE_WITH = "shate_with";
+        public static final String OCSHARES_PATH = "path";
+        public static final String OCSHARES_PERMISSIONS = "permissions";
+        public static final String OCSHARES_SHARED_DATE = "shared_date";
+        public static final String OCSHARES_EXPIRATION_DATE = "expiration_date";
+        public static final String OCSHARES_TOKEN = "token";
+        public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name";
+        public static final String OCSHARES_IS_DIRECTORY = "is_directory";
+        public static final String OCSHARES_USER_ID = "user_id";
+        public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared";
+        public static final String OCSHARES_ACCOUNT_OWNER = "owner_share";
+        
+        public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE 
+                + " collate nocase asc";
+
+        // Columns of capabilities table
+        public static final String CAPABILITIES_ACCOUNT_NAME = "account";
+        public static final String CAPABILITIES_VERSION_MAYOR = "version_mayor";
+        public static final String CAPABILITIES_VERSION_MINOR = "version_minor";
+        public static final String CAPABILITIES_VERSION_MICRO = "version_micro";
+        public static final String CAPABILITIES_VERSION_STRING = "version_string";
+        public static final String CAPABILITIES_VERSION_EDITION = "version_edition";
+        public static final String CAPABILITIES_CORE_POLLINTERVAL = "core_pollinterval";
+        public static final String CAPABILITIES_SHARING_API_ENABLED = "sharing_api_enabled";
+        public static final String CAPABILITIES_SHARING_PUBLIC_ENABLED = "sharing_public_enabled";
+        public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED = "sharing_public_password_enforced";
+        public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENABLED =
+                "sharing_public_expire_date_enabled";
+        public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS =
+                "sharing_public_expire_date_days";
+        public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENFORCED =
+                "sharing_public_expire_date_enforced";
+        public static final String CAPABILITIES_SHARING_PUBLIC_SEND_MAIL = "sharing_public_send_mail";
+        public static final String CAPABILITIES_SHARING_PUBLIC_UPLOAD = "sharing_public_upload";
+        public static final String CAPABILITIES_SHARING_USER_SEND_MAIL = "sharing_user_send_mail";
+        public static final String CAPABILITIES_SHARING_RESHARING = "sharing_resharing";
+        public static final String CAPABILITIES_SHARING_FEDERATION_OUTGOING = "sharing_federation_outgoing";
+        public static final String CAPABILITIES_SHARING_FEDERATION_INCOMING = "sharing_federation_incoming";
+        public static final String CAPABILITIES_FILES_BIGFILECHUNKING = "files_bigfilechunking";
+        public static final String CAPABILITIES_FILES_UNDELETE = "files_undelete";
+        public static final String CAPABILITIES_FILES_VERSIONING = "files_versioning";
+
+        public static final String CAPABILITIES_DEFAULT_SORT_ORDER = CAPABILITIES_ACCOUNT_NAME
+                + " collate nocase asc";
+
+        //Columns of Uploads table
+        public static final String UPLOADS_LOCAL_PATH = "local_path";
+        public static final String UPLOADS_REMOTE_PATH = "remote_path";
+        public static final String UPLOADS_ACCOUNT_NAME = "account_name";
+        public static final String UPLOADS_FILE_SIZE = "file_size";
+        public static final String UPLOADS_STATUS = "status";
+        public static final String UPLOADS_LOCAL_BEHAVIOUR = "local_behaviour";
+        public static final String UPLOADS_UPLOAD_TIME = "upload_time";
+        public static final String UPLOADS_FORCE_OVERWRITE = "force_overwrite";
+        public static final String UPLOADS_IS_CREATE_REMOTE_FOLDER = "is_create_remote_folder";
+        public static final String UPLOADS_UPLOAD_END_TIMESTAMP = "upload_end_timestamp";
+        public static final String UPLOADS_LAST_RESULT = "last_result";
+        public static final String UPLOADS_CREATED_BY = "created_by";
+
+        public static final String UPLOADS_DEFAULT_SORT_ORDER = ProviderTableMeta._ID  + " collate nocase desc";
+
+    }
+}

+ 118 - 0
src/com/owncloud/android/db/UploadResult.java

@@ -0,0 +1,118 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author masensio
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.db;
+
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+
+public enum UploadResult {
+    UNKNOWN(-1),
+    UPLOADED(0),
+    NETWORK_CONNECTION(1),
+    CREDENTIAL_ERROR(2),
+    FOLDER_ERROR(3),
+    CONFLICT_ERROR(4),
+    FILE_ERROR(5),
+    PRIVILEDGES_ERROR(6),
+    CANCELLED(7),
+    FILE_NOT_FOUND(8),
+    DELAYED_FOR_WIFI(9),
+    SERVICE_INTERRUPTED(10);
+
+    private final int value;
+
+    UploadResult(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+    public static UploadResult fromValue(int value) {
+        switch (value) {
+            case -1:
+                return UNKNOWN;
+            case 0:
+                return UPLOADED;
+            case 1:
+                return NETWORK_CONNECTION;
+            case 2:
+                return CREDENTIAL_ERROR;
+            case 3:
+                return FOLDER_ERROR;
+            case 4:
+                return CONFLICT_ERROR;
+            case 5:
+                return FILE_ERROR;
+            case 6:
+                return PRIVILEDGES_ERROR;
+            case 7:
+                return CANCELLED;
+            case 8:
+                return FILE_NOT_FOUND;
+            case 9:
+                return DELAYED_FOR_WIFI;
+            case 10:
+                return SERVICE_INTERRUPTED;
+        }
+        return null;
+    }
+
+    public static UploadResult fromOperationResult(RemoteOperationResult result){
+        // messy :(
+        switch (result.getCode()){
+            case OK:
+                return UPLOADED;
+            case NO_NETWORK_CONNECTION:
+            case HOST_NOT_AVAILABLE:
+            case TIMEOUT:
+            case WRONG_CONNECTION:
+            case INCORRECT_ADDRESS:
+            case SSL_ERROR:
+            case SSL_RECOVERABLE_PEER_UNVERIFIED:
+                return NETWORK_CONNECTION;
+            case ACCOUNT_EXCEPTION:
+            case UNAUTHORIZED:
+                return CREDENTIAL_ERROR;
+            case FILE_NOT_FOUND:
+                return FOLDER_ERROR;
+            case LOCAL_FILE_NOT_FOUND:
+                return FILE_NOT_FOUND;
+            case CONFLICT:
+                return CONFLICT_ERROR;
+            case LOCAL_STORAGE_NOT_COPIED:
+                return FILE_ERROR;
+            case FORBIDDEN:
+                return PRIVILEDGES_ERROR;
+            case CANCELLED:
+                return CANCELLED;
+            case DELAYED_FOR_WIFI:
+                return DELAYED_FOR_WIFI;
+            case UNKNOWN_ERROR:
+                if (result.getException() instanceof java.io.FileNotFoundException) {
+                    return FILE_ERROR;
+                }
+                return UNKNOWN;
+            default:
+                return UNKNOWN;
+        }
+
+    }
+}

+ 3 - 6
src/com/owncloud/android/files/FileMenuFilter.java

@@ -20,26 +20,23 @@
 
 package com.owncloud.android.files;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import android.accounts.Account;
 import android.content.Context;
 import android.view.Menu;
 import android.view.MenuItem;
 
 import com.owncloud.android.R;
-import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
-import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Filters out the file actions available in a given {@link Menu} for a given {@link OCFile}
  * according to the current state of the latest. 

+ 27 - 11
src/com/owncloud/android/files/FileOperationsHelper.java

@@ -29,6 +29,7 @@ import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.Parcelable;
 import android.support.v4.app.DialogFragment;
 import android.webkit.MimeTypeMap;
 import android.widget.Toast;
@@ -36,7 +37,9 @@ import android.widget.Toast;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.network.WebdavUtils;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -50,7 +53,6 @@ import com.owncloud.android.ui.activity.ShareActivity;
 import com.owncloud.android.ui.dialog.ShareLinkToDialog;
 import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
 
-
 import java.util.List;
 
 /**
@@ -58,8 +60,8 @@ import java.util.List;
  */
 public class FileOperationsHelper {
 
-    private static final String TAG = FileOperationsHelper.class.getName();
-
+    private static final String TAG = FileOperationsHelper.class.getSimpleName();
+    
     private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
 
     protected FileActivity mFileActivity = null;
@@ -91,9 +93,8 @@ public class FileOperationsHelper {
                 );
                 if (guessedMimeType != null && !guessedMimeType.equals(file.getMimetype())) {
                     intentForGuessedMimeType = new Intent(Intent.ACTION_VIEW);
-                    intentForGuessedMimeType.
-                            setDataAndType(Uri.parse("file://"+ encodedStoragePath),
-                                    guessedMimeType);
+                    intentForGuessedMimeType.setDataAndType(Uri.parse("file://" +
+                            encodedStoragePath), guessedMimeType);
                     intentForGuessedMimeType.setFlags(
                             Intent.FLAG_GRANT_READ_URI_PERMISSION |
                                     Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -235,7 +236,7 @@ public class FileOperationsHelper {
         if (file != null) {
             // TODO check capability?
             mFileActivity.showLoadingDialog(mFileActivity.getApplicationContext().
-                    getString(R.string.wait_a_moment));
+                getString(R.string.wait_a_moment));
 
             Intent service = new Intent(mFileActivity, OperationsService.class);
             service.setAction(OperationsService.ACTION_CREATE_SHARE_WITH_SHAREE);
@@ -323,7 +324,7 @@ public class FileOperationsHelper {
      */
     public void showShareFile(OCFile file){
         Intent intent = new Intent(mFileActivity, ShareActivity.class);
-        intent.putExtra(mFileActivity.EXTRA_FILE, file);
+        intent.putExtra(mFileActivity.EXTRA_FILE, (Parcelable) file);
         intent.putExtra(mFileActivity.EXTRA_ACCOUNT, mFileActivity.getAccount());
         mFileActivity.startActivity(intent);
 
@@ -342,8 +343,8 @@ public class FileOperationsHelper {
         SharePasswordDialogFragment dialog =
                 SharePasswordDialogFragment.newInstance(file, createShare);
         dialog.show(
-                mFileActivity.getSupportFragmentManager(),
-                SharePasswordDialogFragment.PASSWORD_FRAGMENT
+            mFileActivity.getSupportFragmentManager(),
+            SharePasswordDialogFragment.PASSWORD_FRAGMENT
         );
     }
 
@@ -475,7 +476,7 @@ public class FileOperationsHelper {
             intent.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true);
             mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(intent);
             mFileActivity.showLoadingDialog(mFileActivity.getApplicationContext().
-                    getString(R.string.wait_a_moment));
+                getString(R.string.wait_a_moment));
             
         } else {
             Intent intent = new Intent(mFileActivity, OperationsService.class);
@@ -630,4 +631,19 @@ public class FileOperationsHelper {
         return false;
     }
 
+    /**
+     * Starts a check of the currenlty stored credentials for the given account.
+     *
+     * @param account       OC account which credentials will be checked.
+     */
+    public void checkCurrentCredentials(Account account) {
+        Intent service = new Intent(mFileActivity, OperationsService.class);
+        service.setAction(OperationsService.ACTION_CHECK_CURRENT_CREDENTIALS);
+        service.putExtra(OperationsService.EXTRA_ACCOUNT, account);
+        mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
+
+        mFileActivity.showLoadingDialog(
+            mFileActivity.getApplicationContext().getString(R.string.wait_checking_credentials)
+        );
+    }
 }

+ 83 - 183
src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java

@@ -1,35 +1,26 @@
 /**
- *   ownCloud Android client application
+ *  ownCloud Android client application
  *
- *   Copyright (C) 2012  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *  @author Bartek Przybylski
+ *  @author David A. Velasco
+ *  Copyright (C) 2012  Bartek Przybylski
+ *  Copyright (C) 2016 ownCloud Inc.
  *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
  *
- *   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 <http://www.gnu.org/licenses/>.
+ *  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 <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.files;
 
-import java.io.File;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.db.DbHandler;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.utils.FileStorageUtils;
-
-
 import android.Manifest;
 import android.accounts.Account;
 import android.content.BroadcastReceiver;
@@ -37,13 +28,17 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.database.Cursor;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo.State;
 import android.preference.PreferenceManager;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 import android.support.v4.content.ContextCompat;
-import android.webkit.MimeTypeMap;
+
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.db.PreferenceReader;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.utils.FileStorageUtils;
 
 
 public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
@@ -52,40 +47,47 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
     // Image action
     // Unofficial action, works for most devices but not HTC. See: https://github.com/owncloud/android/issues/6
     private static String NEW_PHOTO_ACTION_UNOFFICIAL = "com.android.camera.NEW_PICTURE";
-    // Officially supported action since SDK 14: http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_PICTURE
+    // Officially supported action since SDK 14:
+    // http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_PICTURE
     private static String NEW_PHOTO_ACTION = "android.hardware.action.NEW_PICTURE";
     // Video action
-    // Officially supported action since SDK 14: http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_VIDEO
+    // Officially supported action since SDK 14:
+    // http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_VIDEO
     private static String NEW_VIDEO_ACTION = "android.hardware.action.NEW_VIDEO";
 
     @Override
     public void onReceive(Context context, Intent intent) {
         Log_OC.d(TAG, "Received: " + intent.getAction());
-        if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) {
-            handleConnectivityAction(context, intent);
-        }else if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) {
-            handleNewPictureAction(context, intent); 
+        if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) {
+            handleNewPictureAction(context, intent);
             Log_OC.d(TAG, "UNOFFICIAL processed: com.android.camera.NEW_PICTURE");
         } else if (intent.getAction().equals(NEW_PHOTO_ACTION)) {
-            handleNewPictureAction(context, intent); 
+            handleNewPictureAction(context, intent);
             Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE");
         } else if (intent.getAction().equals(NEW_VIDEO_ACTION)) {
-            Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO");
             handleNewVideoAction(context, intent);
+            Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO");
         } else {
-            Log_OC.e(TAG, "Incorrect intent sent: " + intent.getAction());
+            Log_OC.e(TAG, "Incorrect intent received: " + intent.getAction());
         }
     }
 
+    /**
+     * Because we support NEW_PHOTO_ACTION and NEW_PHOTO_ACTION_UNOFFICIAL it can happen that 
+     * handleNewPictureAction is called twice for the same photo. Use this simple static variable to
+     * remember last uploaded photo to filter duplicates. Must not be null!
+     */
+    static String lastUploadedPhotoPath = "";
+
     private void handleNewPictureAction(Context context, Intent intent) {
         Cursor c = null;
         String file_path = null;
         String file_name = null;
         String mime_type = null;
 
-        Log_OC.w(TAG, "New photo received");
-        
-        if (!instantPictureUploadEnabled(context)) {
+        Log_OC.i(TAG, "New photo received");
+
+        if (!PreferenceReader.instantPictureUploadEnabled(context)) {
             Log_OC.d(TAG, "Instant picture upload disabled, ignoring new picture");
             return;
         }
@@ -96,7 +98,8 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
             return;
         }
 
-        String[] CONTENT_PROJECTION = { Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE };
+        String[] CONTENT_PROJECTION = {
+                Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE };
 
         int permissionCheck = ContextCompat.checkSelfPermission(context,
                 Manifest.permission.READ_EXTERNAL_STORAGE);
@@ -115,44 +118,43 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
         file_name = c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME));
         mime_type = c.getString(c.getColumnIndex(Images.Media.MIME_TYPE));
         c.close();
-        
-        Log_OC.d(TAG, file_path + "");
 
-        // save always temporally the picture to upload
-        DbHandler db = new DbHandler(context);
-        db.putFileForLater(file_path, account.name, null);
-        db.close();
-
-        if (!isOnline(context) || (instantPictureUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
+        if (file_path.equals(lastUploadedPhotoPath)) {
+            Log_OC.d(TAG, "Duplicate detected: " + file_path + ". Ignore.");
             return;
         }
 
-        Intent i = new Intent(context, FileUploader.class);
-        i.putExtra(FileUploader.KEY_ACCOUNT, account);
-        i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
-        i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name));
-        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-        i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type);
-        i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
-
-        // instant upload behaviour
-        i = addInstantUploadBehaviour(i, context);
-
-        context.startService(i);
+        lastUploadedPhotoPath = file_path;
+        Log_OC.d(TAG, "Path: " + file_path + "");
+
+        new FileUploader.UploadRequester();
+
+        int behaviour = getUploadBehaviour(context);
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        requester.uploadNewFile(
+                context,
+                account,
+                file_path,
+                FileStorageUtils.getInstantUploadFilePath(context, file_name),
+                behaviour,
+                mime_type,
+                true,           // create parent folder if not existent
+                UploadFileOperation.CREATED_AS_INSTANT_PICTURE
+        );
     }
 
-    private Intent addInstantUploadBehaviour(Intent i, Context context){
+    private Integer getUploadBehaviour(Context context) {
         SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context);
         String behaviour = appPreferences.getString("prefs_instant_behaviour", "NOTHING");
 
         if (behaviour.equalsIgnoreCase("NOTHING")) {
             Log_OC.d(TAG, "upload file and do nothing");
-            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_FORGET);
+            return FileUploader.LOCAL_BEHAVIOUR_FORGET;
         } else if (behaviour.equalsIgnoreCase("MOVE")) {
-            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
             Log_OC.d(TAG, "upload file and move file to oc folder");
+            return FileUploader.LOCAL_BEHAVIOUR_MOVE;
         }
-        return i;
+        return null;
     }
 
     private void handleNewVideoAction(Context context, Intent intent) {
@@ -161,9 +163,9 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
         String file_name = null;
         String mime_type = null;
 
-        Log_OC.w(TAG, "New video received");
-        
-        if (!instantVideoUploadEnabled(context)) {
+        Log_OC.i(TAG, "New video received");
+
+        if (!PreferenceReader.instantVideoUploadEnabled(context)) {
             Log_OC.d(TAG, "Instant video upload disabled, ignoring new video");
             return;
         }
@@ -174,133 +176,31 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
             return;
         }
 
-        String[] CONTENT_PROJECTION = { Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE, Video.Media.SIZE };
+        String[] CONTENT_PROJECTION = {Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE,
+                Video.Media.SIZE};
         c = context.getContentResolver().query(intent.getData(), CONTENT_PROJECTION, null, null, null);
         if (!c.moveToFirst()) {
             Log_OC.e(TAG, "Couldn't resolve given uri: " + intent.getDataString());
             return;
-        } 
+        }
         file_path = c.getString(c.getColumnIndex(Video.Media.DATA));
         file_name = c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME));
         mime_type = c.getString(c.getColumnIndex(Video.Media.MIME_TYPE));
         c.close();
         Log_OC.d(TAG, file_path + "");
 
-        if (!isOnline(context) || (instantVideoUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
-            return;
-        }
-
-        Intent i = new Intent(context, FileUploader.class);
-        i.putExtra(FileUploader.KEY_ACCOUNT, account);
-        i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
-        i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantVideoUploadFilePath(context, file_name));
-        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-        i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type);
-        i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
-
-        // instant upload behaviour
-        i = addInstantUploadBehaviour(i, context);
-
-        context.startService(i);
-
-    }
-
-    private void handleConnectivityAction(Context context, Intent intent) {
-        if (!instantPictureUploadEnabled(context)) {
-            Log_OC.d(TAG, "Instant upload disabled, don't upload anything");
-            return;
-        }
-
-        if (instantPictureUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context)){
-            Account account = AccountUtils.getCurrentOwnCloudAccount(context);
-            if (account == null) {
-                Log_OC.w(TAG, "No account found for instant upload, aborting");
-                return;
-            }
-
-            Intent i = new Intent(context, FileUploader.class);
-            i.putExtra(FileUploader.KEY_ACCOUNT, account);
-            i.putExtra(FileUploader.KEY_CANCEL_ALL, true);
-            context.startService(i);
-        }
-
-        if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY)
-                && isOnline(context)
-                && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) {
-            DbHandler db = new DbHandler(context);
-            Cursor c = db.getAwaitingFiles();
-            if (c.moveToFirst()) {
-                do {
-                    if (instantPictureUploadViaWiFiOnly(context) &&
-                            !isConnectedViaWiFi(context)){
-                        break;
-                    }
-
-                    String account_name = c.getString(c.getColumnIndex("account"));
-                    String file_path = c.getString(c.getColumnIndex("path"));
-                    File f = new File(file_path);
-                    if (f.exists()) {
-                        Account account = new Account(account_name, MainApp.getAccountType());
-
-                        String mimeType = null;
-                        try {
-                            mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
-                                    f.getName().substring(f.getName().lastIndexOf('.') + 1));
-
-                        } catch (Throwable e) {
-                            Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + f.getName());
-                        }
-                        if (mimeType == null)
-                            mimeType = "application/octet-stream";
-
-                        Intent i = new Intent(context, FileUploader.class);
-                        i.putExtra(FileUploader.KEY_ACCOUNT, account);
-                        i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
-                        i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, f.getName()));
-                        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-                        i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
-
-                        // instant upload behaviour
-                        i = addInstantUploadBehaviour(i, context);
-
-                        context.startService(i);
-
-                    } else {
-                        Log_OC.w(TAG, "Instant upload file " + f.getAbsolutePath() + " dont exist anymore");
-                    }
-                } while (c.moveToNext());
-            }
-            c.close();
-            db.close();
-        }
-
-    }
-
-    public static boolean isOnline(Context context) {
-        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
-    }
-
-    public static boolean isConnectedViaWiFi(Context context) {
-        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        return cm != null && cm.getActiveNetworkInfo() != null
-                && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI
-                && cm.getActiveNetworkInfo().getState() == State.CONNECTED;
-    }
-
-    public static boolean instantPictureUploadEnabled(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_uploading", false);
+        int behaviour = getUploadBehaviour(context);
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        requester.uploadNewFile(
+                context,
+                account,
+                file_path,
+                FileStorageUtils.getInstantVideoUploadFilePath(context, file_name),
+                behaviour,
+                mime_type,
+                true,           // create parent folder if not existent
+                UploadFileOperation.CREATED_AS_INSTANT_VIDEO
+        );
     }
 
-    public static boolean instantVideoUploadEnabled(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_video_uploading", false);
-    }
-
-    public static boolean instantPictureUploadViaWiFiOnly(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_upload_on_wifi", false);
-    }
-    
-    public static boolean instantVideoUploadViaWiFiOnly(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_video_upload_on_wifi", false);
-    }
 }

+ 206 - 0
src/com/owncloud/android/files/services/ConnectivityActionReceiver.java

@@ -0,0 +1,206 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author LukeOwncloud
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.files.services;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+
+import com.owncloud.android.db.PreferenceReader;
+import com.owncloud.android.db.UploadResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+/**
+ * Receives all connectivity action from Android OS at all times and performs
+ * required OC actions. For now that are: - Signal connectivity to
+ * {@link FileUploader}.
+ * 
+ * Later can be added: - Signal connectivity to download service, deletion
+ * service, ... - Handle offline mode (cf.
+ * https://github.com/owncloud/android/issues/162)
+ *
+ * Have fun with the comments :S
+ */
+public class ConnectivityActionReceiver extends BroadcastReceiver {
+    private static final String TAG = ConnectivityActionReceiver.class.getSimpleName();
+
+    /**
+     * Magic keyword, by Google.
+     *
+     * {@See http://developer.android.com/intl/es/reference/android/net/wifi/WifiInfo.html#getSSID()}
+     */
+    private static final String UNKNOWN_SSID = "<unknown ssid>";
+
+
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        // LOG ALL EVENTS:
+        Log_OC.v(TAG, "action: " + intent.getAction());
+        Log_OC.v(TAG, "component: " + intent.getComponent());
+        Bundle extras = intent.getExtras();
+        if (extras != null) {
+            for (String key : extras.keySet()) {
+                Log_OC.v(TAG, "key [" + key + "]: " + extras.get(key));
+            }
+        } else {
+            Log_OC.v(TAG, "no extras");
+        }
+
+        /**
+         * There is an interesting mess to process WifiManager.NETWORK_STATE_CHANGED_ACTION and
+         * ConnectivityManager.CONNECTIVITY_ACTION in a simple and reliable way.
+         *
+         * The former triggers much more events than what we really need to know about Wifi connection.
+         *
+         * But there are annoying uncertainties about ConnectivityManager.CONNECTIVITY_ACTION due
+         * to the deprecation of ConnectivityManager.EXTRA_NETWORK_INFO in API level 14, and the absence
+         * of ConnectivityManager.EXTRA_NETWORK_TYPE until API level 17. Dear Google, how should we
+         * handle API levels 14 to 16?
+         *
+         * In the end maybe we need to keep in memory the current knowledge about connectivity
+         * and update it taking into account several Intents received in a row
+         *
+         * But first let's try something "simple" to keep a basic retry of instant uploads in
+         * version 1.9.2, similar to the existent until 1.9.1. To be improved.
+         */
+        if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            NetworkInfo networkInfo =
+                intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+            WifiInfo wifiInfo =
+                intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+            String bssid =
+                intent.getStringExtra(WifiManager.EXTRA_BSSID);
+            if(networkInfo.isConnected()   &&      // not enough; see (*) right below
+                wifiInfo != null    &&
+                !UNKNOWN_SSID.equals(wifiInfo.getSSID().toLowerCase()) &&
+                bssid != null
+                    ) {
+                Log_OC.d(TAG, "WiFi connected");
+
+                wifiConnected(context);
+            } else {
+                // TODO tons of things to check to conclude disconnection;
+                // TODO maybe alternative commented below, based on CONNECTIVITY_ACTION is better
+                Log_OC.d(TAG, "WiFi disconnected ... but don't know if right now");
+            }
+         }
+        // (*) When WiFi is lost, an Intent with network state CONNECTED and SSID "<unknown ssid>" is
+        //      received right before another Intent with network state DISCONNECTED; needs to
+        //      be differentiated of a new Wifi connection.
+        //
+        //  Besides, with a new connection two Intents are received, having only the second the extra
+        //  WifiManager.EXTRA_BSSID, with the BSSID of the access point accessed.
+        //
+        //  Not sure if this protocol is exact, since it's not documented. Only found mild references in
+        //   - http://developer.android.com/intl/es/reference/android/net/wifi/WifiInfo.html#getSSID()
+        //   - http://developer.android.com/intl/es/reference/android/net/wifi/WifiManager.html#EXTRA_BSSID
+        //  and reproduced in Nexus 5 with Android 6.
+
+
+        /**
+         * Possible alternative attending ConnectivityManager.CONNECTIVITY_ACTION.
+         *
+         * Let's see what QA has to say
+         *
+        if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+            NetworkInfo networkInfo = intent.getParcelableExtra(
+                    ConnectivityManager.EXTRA_NETWORK_INFO      // deprecated in API 14
+            );
+            int networkType = intent.getIntExtra(
+                    ConnectivityManager.EXTRA_NETWORK_TYPE,     // only from API level 17
+                    -1
+            );
+            boolean couldBeWifiAction =
+                    (networkInfo == null && networkType < 0)    ||      // cases of lack of info
+                    networkInfo.getType() == ConnectivityManager.TYPE_WIFI  ||
+                    networkType == ConnectivityManager.TYPE_WIFI;
+
+            if (couldBeWifiAction) {
+                if (ConnectivityUtils.isAppConnectedViaWiFi(context)) {
+                    Log_OC.d(TAG, "WiFi connected");
+                    wifiConnected(context);
+                } else {
+                    Log_OC.d(TAG, "WiFi disconnected");
+                    wifiDisconnected(context);
+                }
+            } /* else, CONNECTIVIY_ACTION is (probably) about other network interface (mobile, bluetooth, ...)
+        }
+        */
+    }
+
+    private void wifiConnected(Context context) {
+        // for the moment, only recovery of instant uploads, similar to behaviour in release 1.9.1
+        if (
+                (PreferenceReader.instantPictureUploadEnabled(context) &&
+                        PreferenceReader.instantPictureUploadViaWiFiOnly(context)) ||
+                (PreferenceReader.instantVideoUploadEnabled(context) &&
+                        PreferenceReader.instantVideoUploadViaWiFiOnly(context))
+                ) {
+            Log_OC.d(TAG, "Requesting retry of instant uploads (& friends)");
+            FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+            requester.retryFailedUploads(
+                context,
+                null,
+                UploadResult.NETWORK_CONNECTION     // for the interrupted when Wifi fell, if any
+                // (side effect: any upload failed due to network error will be retried too, instant or not)
+            );
+            requester.retryFailedUploads(
+                context,
+                null,
+                UploadResult.DELAYED_FOR_WIFI       // for the rest of enqueued when Wifi fell
+            );
+        }
+    }
+
+    private void wifiDisconnected(Context context) {
+        // TODO something smart
+
+        // NOTE: explicit cancellation of only-wifi instant uploads is not needed anymore, since currently:
+        //  - any upload in progress will be interrupted due to the lack of connectivity while the device
+        //      reconnects through other network interface;
+        //  - FileUploader checks instant upload settings and connection state before executing each
+        //    upload operation, so other pending instant uploads after the current one will not be run
+        //    (currently are silently moved to FAILED state)
+    }
+
+
+    static public void enableActionReceiver(Context context) {
+        PackageManager pm = context.getPackageManager();
+        ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class);
+        pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+    static public void disableActionReceiver(Context context) {
+        PackageManager pm = context.getPackageManager();
+        ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class);
+        pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+
+}

+ 37 - 41
src/com/owncloud/android/files/services/FileDownloader.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2012-2015 ownCloud Inc.
+ *   Copyright (C) 2012-2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -20,29 +20,38 @@
 
 package com.owncloud.android.files.services;
 
-import java.io.File;
-import java.util.AbstractList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Vector;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.OnAccountsUpdateListener;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.support.v4.app.NotificationCompat;
+import android.util.Pair;
 
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-
-import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
-import com.owncloud.android.notifications.NotificationBuilderWithProgressBar;
-import com.owncloud.android.notifications.NotificationDelayer;
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.notifications.NotificationBuilderWithProgressBar;
+import com.owncloud.android.notifications.NotificationDelayer;
 import com.owncloud.android.operations.DownloadFileOperation;
 import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
@@ -50,22 +59,12 @@ import com.owncloud.android.ui.preview.PreviewImageActivity;
 import com.owncloud.android.ui.preview.PreviewImageFragment;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.support.v4.app.NotificationCompat;
-import android.util.Pair;
+import java.io.File;
+import java.util.AbstractList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
 
 public class FileDownloader extends Service
         implements OnDatatransferProgressListener, OnAccountsUpdateListener {
@@ -81,7 +80,7 @@ public class FileDownloader extends Service
     public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
     public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
 
-    private static final String TAG = "FileDownloader";
+    private static final String TAG = FileDownloader.class.getSimpleName();
 
     private Looper mServiceLooper;
     private ServiceHandler mServiceHandler;
@@ -172,8 +171,7 @@ public class FileDownloader extends Service
                 newDownload.addDatatransferProgressListener(this);
                 newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
                 Pair<String, String> putResult = mPendingDownloads.putIfAbsent(
-                        account, file.getRemotePath(), newDownload
-                );
+                        account.name, file.getRemotePath(), newDownload);
                 if (putResult != null) {
                     String downloadKey = putResult.first;
                     requestedDownloads.add(downloadKey);
@@ -191,7 +189,6 @@ public class FileDownloader extends Service
                 msg.obj = requestedDownloads;
                 mServiceHandler.sendMessage(msg);
             }
-            //}
         }
 
         return START_NOT_STICKY;
@@ -253,7 +250,8 @@ public class FileDownloader extends Service
          * @param file    A file in the queue of pending downloads
          */
         public void cancel(Account account, OCFile file) {
-            Pair<DownloadFileOperation, String> removeResult = mPendingDownloads.remove(account, file.getRemotePath());
+            Pair<DownloadFileOperation, String> removeResult =
+                    mPendingDownloads.remove(account.name, file.getRemotePath());
             DownloadFileOperation download = removeResult.first;
             if (download != null) {
                 download.cancel();
@@ -301,7 +299,7 @@ public class FileDownloader extends Service
          */
         public boolean isDownloading(Account account, OCFile file) {
             if (account == null || file == null) return false;
-            return (mPendingDownloads.contains(account, file.getRemotePath()));
+            return (mPendingDownloads.contains(account.name, file.getRemotePath()));
         }
 
 
@@ -350,7 +348,6 @@ public class FileDownloader extends Service
 
     }
 
-
     /**
      * Download worker. Performs the pending downloads in the order they were requested.
 
@@ -432,8 +429,10 @@ public class FileDownloader extends Service
 
                 } finally {
                     Pair<DownloadFileOperation, String> removeResult =
-                            mPendingDownloads.removePayload(mCurrentAccount,
-                                    mCurrentDownload.getRemotePath());
+                            mPendingDownloads.removePayload(
+                                    mCurrentAccount.name,
+                                    mCurrentDownload.getRemotePath()
+                            );
 
                     /// notify result
                     notifyDownloadResult(mCurrentDownload, downloadResult);
@@ -546,10 +545,7 @@ public class FileDownloader extends Service
             int tickerId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_ticker :
                     R.string.downloader_download_failed_ticker;
 
-            boolean needsToUpdateCredentials = (
-                    downloadResult.getCode() == ResultCode.UNAUTHORIZED ||
-                            downloadResult.isIdPRedirection()
-            );
+            boolean needsToUpdateCredentials = (ResultCode.UNAUTHORIZED.equals(downloadResult.getCode()));
             tickerId = (needsToUpdateCredentials) ?
                     R.string.downloader_download_failed_credentials_error : tickerId;
 
@@ -653,6 +649,6 @@ public class FileDownloader extends Service
      */
     private void cancelDownloadsForAccount(Account account) {
         // Cancel pending downloads
-        mPendingDownloads.remove(account);
+        mPendingDownloads.remove(account.name);
     }
 }

ファイルの差分が大きいため隠しています
+ 567 - 401
src/com/owncloud/android/files/services/FileUploader.java


+ 23 - 23
src/com/owncloud/android/files/services/IndexedForest.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -20,7 +20,6 @@
 
 package com.owncloud.android.files.services;
 
-import android.accounts.Account;
 import android.util.Pair;
 
 import com.owncloud.android.datamodel.OCFile;
@@ -98,8 +97,9 @@ public class IndexedForest<V> {
     }
 
 
-    public /* synchronized */ Pair<String, String> putIfAbsent(Account account, String remotePath, V value) {
-        String targetKey = buildKey(account, remotePath);
+    public /* synchronized */ Pair<String, String> putIfAbsent(String accountName, String remotePath, V value) {
+        String targetKey = buildKey(accountName, remotePath);
+
         Node<V> valuedNode = new Node(targetKey, value);
         Node<V> previousValue = mMap.putIfAbsent(
             targetKey,
@@ -119,7 +119,7 @@ public class IndexedForest<V> {
                 if (!parentPath.endsWith(OCFile.PATH_SEPARATOR)) {
                     parentPath += OCFile.PATH_SEPARATOR;
                 }
-                parentKey = buildKey(account, parentPath);
+                parentKey = buildKey(accountName, parentPath);
                 parentNode = mMap.get(parentKey);
                 if (parentNode == null) {
                     parentNode = new Node(parentKey, null);
@@ -135,7 +135,7 @@ public class IndexedForest<V> {
 
             String linkedTo = OCFile.ROOT_PATH;
             if (linked) {
-                linkedTo = parentNode.getKey().substring(account.name.length());
+                linkedTo = parentNode.getKey().substring(accountName.length());
             }
 
             return new Pair<String, String>(targetKey, linkedTo);
@@ -143,21 +143,21 @@ public class IndexedForest<V> {
     };
 
 
-    public Pair<V, String> removePayload(Account account, String remotePath) {
-        String targetKey = buildKey(account, remotePath);
+    public Pair<V, String> removePayload(String accountName, String remotePath) {
+        String targetKey = buildKey(accountName, remotePath);
         Node<V> target = mMap.get(targetKey);
         if (target != null) {
             target.clearPayload();
             if (!target.hasChildren()) {
-                return remove(account, remotePath);
+                return remove(accountName, remotePath);
             }
         }
         return new Pair<V, String>(null, null);
     }
 
 
-    public /* synchronized */ Pair<V, String> remove(Account account, String remotePath) {
-        String targetKey = buildKey(account, remotePath);
+    public /* synchronized */ Pair<V, String> remove(String accountName, String remotePath) {
+        String targetKey = buildKey(accountName, remotePath);
         Node<V> firstRemoved = mMap.remove(targetKey);
         String unlinkedFrom = null;
 
@@ -180,7 +180,7 @@ public class IndexedForest<V> {
             }
 
             if (parent != null) {
-                unlinkedFrom = parent.getKey().substring(account.name.length());
+                unlinkedFrom = parent.getKey().substring(accountName.length());
             }
 
             return new Pair<V, String>(firstRemoved.getPayload(), unlinkedFrom);
@@ -199,8 +199,8 @@ public class IndexedForest<V> {
         }
     }
 
-    public boolean contains(Account account, String remotePath) {
-        String targetKey = buildKey(account, remotePath);
+    public boolean contains(String accountName, String remotePath) {
+        String targetKey = buildKey(accountName, remotePath);
         return mMap.containsKey(targetKey);
     }
 
@@ -213,22 +213,22 @@ public class IndexedForest<V> {
         }
     }
 
-    public V get(Account account, String remotePath) {
-        String key = buildKey(account, remotePath);
+    public V get(String accountName, String remotePath) {
+        String key = buildKey(accountName, remotePath);
         return get(key);
     }
 
 
     /**
      * Remove the elements that contains account as a part of its key
-     * @param account
+     * @param accountName
      */
-    public void remove(Account account){
+    public void remove(String accountName){
         Iterator<String> it = mMap.keySet().iterator();
         while (it.hasNext()) {
             String key = it.next();
             Log_OC.d("IndexedForest", "Number of pending downloads= "  + mMap.size());
-            if (key.startsWith(account.name)) {
+            if (key.startsWith(accountName)) {
                 mMap.remove(key);
             }
         }
@@ -237,11 +237,11 @@ public class IndexedForest<V> {
     /**
      * Builds a key to index files
      *
-     * @param account       Account where the file to download is stored
-     * @param remotePath    Path of the file in the server
+     * @param accountName   Local name of the ownCloud account where the file to download is stored.
+     * @param remotePath    Path of the file in the server.
      */
-    private String buildKey(Account account, String remotePath) {
-        return account.name + remotePath;
+    private String buildKey(String accountName, String remotePath) {
+        return accountName + remotePath;
     }
 
 }

+ 19 - 12
src/com/owncloud/android/media/MediaService.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -21,7 +21,6 @@
 package com.owncloud.android.media;
 
 import android.accounts.Account;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -39,14 +38,14 @@ import android.os.PowerManager;
 import android.support.v7.app.NotificationCompat;
 import android.widget.Toast;
 
-import java.io.IOException;
-
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
 
+import java.io.IOException;
+
 
 /**
  * Service that handles media playback, both audio and video. 
@@ -59,7 +58,8 @@ public class MediaService extends Service implements OnCompletionListener, OnPre
 
     private static final String TAG = MediaService.class.getSimpleName();
 
-    private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "com.owncloud.android.media";
+    private static final String MY_PACKAGE = MediaService.class.getPackage() != null ?
+            MediaService.class.getPackage().getName() : "com.owncloud.android.media";
     
     /// Intent actions that we are prepared to handle
     public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".action.PLAY_FILE";
@@ -162,7 +162,8 @@ public class MediaService extends Service implements OnCompletionListener, OnPre
                 
         } else if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) {
             /*  Added in API level 17
-                Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature.
+                Bitstream is conforming to the related coding standard or file spec,
+                but the media framework does not support the feature.
                 Constant Value: -1010 (0xfffffc0e)
              */
             messageId = R.string.media_err_unsupported;
@@ -190,7 +191,8 @@ public class MediaService extends Service implements OnCompletionListener, OnPre
 
         } else if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
             /*  Added in API level 3
-                The video is streamed and its container is not valid for progressive playback i.e the video's index (e.g moov atom) is not at the start of the file.
+                The video is streamed and its container is not valid for progressive playback i.e the video's index
+                (e.g moov atom) is not at the start of the file.
                 Constant Value: 200 (0x000000c8)
             */
             messageId = R.string.media_err_invalid_progressive_playback;
@@ -203,7 +205,8 @@ public class MediaService extends Service implements OnCompletionListener, OnPre
             */
             /*  MediaPlayer.MEDIA_ERROR_SERVER_DIED)
                 Added in API level 1
-                Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one.
+                Media server died. In this case, the application must release the MediaPlayer
+                object and instantiate a new one.
                 Constant Value: 100 (0x00000064) 
              */
             messageId = R.string.media_err_unknown;
@@ -471,22 +474,26 @@ public class MediaService extends Service implements OnCompletionListener, OnPre
             
         } catch (SecurityException e) {
             Log_OC.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e);
-            Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
+            Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()),
+                    Toast.LENGTH_LONG).show();
             processStopRequest(true);
             
         } catch (IOException e) {
             Log_OC.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e);
-            Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
+            Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()),
+                    Toast.LENGTH_LONG).show();
             processStopRequest(true);
             
         } catch (IllegalStateException e) {
             Log_OC.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e);
-            Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
+            Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()),
+                    Toast.LENGTH_LONG).show();
             processStopRequest(true);
             
         } catch (IllegalArgumentException e) {
             Log_OC.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e);
-            Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
+            Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()),
+                    Toast.LENGTH_LONG).show();
             processStopRequest(true);
         }
     }

+ 7 - 9
src/com/owncloud/android/media/MediaServiceBinder.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -21,16 +21,16 @@
 package com.owncloud.android.media;
 
 
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.media.MediaService.State;
-
 import android.accounts.Account;
 import android.content.Intent;
 import android.media.MediaPlayer;
 import android.os.Binder;
 import android.widget.MediaController;
 
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.media.MediaService.State;
+
 
 /**
  *  Binder allowing client components to perform operations on on the MediaPlayer managed by a MediaService instance.
@@ -94,8 +94,7 @@ public class MediaServiceBinder extends Binder implements MediaController.MediaP
     public int getCurrentPosition() {
         MediaPlayer currentPlayer = mService.getPlayer();
         if (currentPlayer != null) {
-            int pos = currentPlayer.getCurrentPosition();
-            return pos;
+            return currentPlayer.getCurrentPosition();
         } else {
             return 0;
         }
@@ -105,8 +104,7 @@ public class MediaServiceBinder extends Binder implements MediaController.MediaP
     public int getDuration() {
         MediaPlayer currentPlayer = mService.getPlayer();
         if (currentPlayer != null) {
-            int dur = currentPlayer.getDuration();
-            return dur;
+            return currentPlayer.getDuration();
         } else {
             return 0;
         }

+ 65 - 0
src/com/owncloud/android/operations/CheckCurrentCredentialsOperation.java

@@ -0,0 +1,65 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author David A. Velasco
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.operations;
+
+import android.accounts.Account;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
+import com.owncloud.android.operations.common.SyncOperation;
+
+import java.util.ArrayList;
+
+/**
+ * Checks validity of currently stored credentials for a given OC account
+ */
+public class CheckCurrentCredentialsOperation extends SyncOperation {
+
+    private Account mAccount = null;
+
+    public CheckCurrentCredentialsOperation(Account account) {
+        if (account == null) {
+            throw new IllegalArgumentException("NULL account");
+        }
+        mAccount = account;
+    }
+
+    @Override
+    protected RemoteOperationResult run(OwnCloudClient client) {
+        RemoteOperationResult result = null;
+        if (!getStorageManager().getAccount().name.equals(mAccount.name)) {
+            result = new RemoteOperationResult(new IllegalStateException(
+                "Account to validate is not the account connected to!")
+            );
+        } else {
+            RemoteOperation check = new ExistenceCheckRemoteOperation(OCFile.ROOT_PATH, false);
+            result = check.execute(client);
+            ArrayList<Object> data = new ArrayList<Object>();
+            data.add(mAccount);
+            result.setData(data);
+        }
+        return result;
+    }
+
+}

+ 3 - 1
src/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java

@@ -34,6 +34,8 @@ import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
 import android.content.Context;
 import android.net.Uri;
 
+import org.apache.commons.httpclient.HttpStatus;
+
 /**
  * Operation to find out what authentication method requires
  * the server to access files.
@@ -99,7 +101,7 @@ public class DetectAuthenticationMethodOperation extends RemoteOperation {
         } 
 
         // analyze response  
-        if (result.getCode() == ResultCode.UNAUTHORIZED) {
+        if (result.getHttpCode() == HttpStatus.SC_UNAUTHORIZED) {
             String authRequest = ((result.getAuthenticateHeader()).trim()).toLowerCase();
             if (authRequest.startsWith("basic")) {
                 authMethod = AuthenticationMethod.BASIC_HTTP_AUTH;

+ 86 - 94
src/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -1,43 +1,42 @@
 /**
- *   ownCloud Android client application
+ *  ownCloud Android client application
  *
- *   @author David A. Velasco
- *   @author masensio
- *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *  @author David A. Velasco
+ *  @author masensio
+ *  Copyright (C) 2012 Bartek Przybylski
+ *  Copyright (C) 2015 ownCloud Inc.
  *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
  *
- *   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 <http://www.gnu.org/licenses/>.
+ *  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 <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.operations;
 
+import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
+
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.resources.files.RemoteFile;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
 import com.owncloud.android.operations.common.SyncOperation;
 import com.owncloud.android.utils.FileStorageUtils;
 
-import android.accounts.Account;
-import android.content.Context;
-import android.content.Intent;
-
 /**
  * Remote operation performing the read of remote file in the ownCloud server.
  */
@@ -45,44 +44,44 @@ import android.content.Intent;
 public class SynchronizeFileOperation extends SyncOperation {
 
     private String TAG = SynchronizeFileOperation.class.getSimpleName();
-    
+
     private OCFile mLocalFile;
     private String mRemotePath;
     private OCFile mServerFile;
     private Account mAccount;
     private boolean mSyncFileContents;
     private Context mContext;
-    
+
     private boolean mTransferWasRequested = false;
 
-    /** 
-     * When 'false', uploads to the server are not done; only downloads or conflict detection.  
-     * This is a temporal field. 
+    /**
+     * When 'false', uploads to the server are not done; only downloads or conflict detection.
+     * This is a temporal field.
      * TODO Remove when 'folder synchronization' replaces 'folder download'.
-     */    
+     */
     private boolean mAllowUploads;
 
-    
+
     /**
      * Constructor for "full synchronization mode".
-     * 
+     * <p/>
      * Uses remotePath to retrieve all the data both in local cache and in the remote OC server
      * when the operation is executed, instead of reusing {@link OCFile} instances.
-     * 
+     * <p/>
      * Useful for direct synchronization of a single file.
-     * 
-     * @param 
-     * @param account               ownCloud account holding the file.
-     * @param syncFileContents      When 'true', transference of data will be started by the 
-     *                              operation if needed and no conflict is detected.
-     * @param context               Android context; needed to start transfers.
+     *
+     * @param
+     * @param account          ownCloud account holding the file.
+     * @param syncFileContents When 'true', transference of data will be started by the
+     *                         operation if needed and no conflict is detected.
+     * @param context          Android context; needed to start transfers.
      */
     public SynchronizeFileOperation(
-            String remotePath,  
-            Account account, 
+            String remotePath,
+            Account account,
             boolean syncFileContents,
             Context context) {
-        
+
         mRemotePath = remotePath;
         mLocalFile = null;
         mServerFile = null;
@@ -92,33 +91,33 @@ public class SynchronizeFileOperation extends SyncOperation {
         mAllowUploads = true;
     }
 
-    
+
     /**
      * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or
      * from remote OC server.
-     * 
+     *
      * Useful to include this operation as part of the synchronization of a folder
      * (or a full account), avoiding the repetition of fetch operations (both in local database
      * or remote server).
-     * 
+     *
      * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them,
      * use the other constructor.
-     * 
-     * @param localFile             Data of file (just) retrieved from local cache/database.
-     * @param serverFile            Data of file (just) retrieved from a remote server. If null,
-     *                              will be retrieved from network by the operation when executed.
-     * @param account               ownCloud account holding the file.
-     * @param syncFileContents      When 'true', transference of data will be started by the 
-     *                              operation if needed and no conflict is detected.
-     * @param context               Android context; needed to start transfers.
+     *
+     * @param localFile        Data of file (just) retrieved from local cache/database.
+     * @param serverFile       Data of file (just) retrieved from a remote server. If null,
+     *                         will be retrieved from network by the operation when executed.
+     * @param account          ownCloud account holding the file.
+     * @param syncFileContents When 'true', transference of data will be started by the
+     *                         operation if needed and no conflict is detected.
+     * @param context          Android context; needed to start transfers.
      */
     public SynchronizeFileOperation(
             OCFile localFile,
-            OCFile serverFile, 
-            Account account, 
+            OCFile serverFile,
+            Account account,
             boolean syncFileContents,
             Context context) {
-        
+
         mLocalFile = localFile;
         mServerFile = serverFile;
         if (mLocalFile != null) {
@@ -137,55 +136,55 @@ public class SynchronizeFileOperation extends SyncOperation {
         mContext = context;
         mAllowUploads = true;
     }
-    
+
 
     /**
      * Temporal constructor.
-     * 
+     *
      * Extends the previous one to allow constrained synchronizations where uploads are never
      * performed - only downloads or conflict detection.
-     * 
+     *
      * Do not use unless you are involved in 'folder synchronization' or 'folder download' work
      * in progress.
-     * 
+     *
      * TODO Remove when 'folder synchronization' replaces 'folder download'.
-     * 
-     * @param localFile             Data of file (just) retrieved from local cache/database.
-     *                              MUSTN't be null.
-     * @param serverFile            Data of file (just) retrieved from a remote server.
-     *                              If null, will be retrieved from network by the operation
-     *                              when executed.
-     * @param account               ownCloud account holding the file.
-     * @param syncFileContents      When 'true', transference of data will be started by the 
-     *                              operation if needed and no conflict is detected.
-     * @param allowUploads          When 'false', uploads to the server are not done;
-     *                              only downloads or conflict detection.
-     * @param context               Android context; needed to start transfers.
+     *
+     * @param localFile        Data of file (just) retrieved from local cache/database.
+     *                         MUSTN't be null.
+     * @param serverFile       Data of file (just) retrieved from a remote server.
+     *                         If null, will be retrieved from network by the operation
+     *                         when executed.
+     * @param account          ownCloud account holding the file.
+     * @param syncFileContents When 'true', transference of data will be started by the
+     *                         operation if needed and no conflict is detected.
+     * @param allowUploads     When 'false', uploads to the server are not done;
+     *                         only downloads or conflict detection.
+     * @param context          Android context; needed to start transfers.
      */
     public SynchronizeFileOperation(
             OCFile localFile,
-            OCFile serverFile, 
-            Account account, 
+            OCFile serverFile,
+            Account account,
             boolean syncFileContents,
-            boolean allowUploads, 
+            boolean allowUploads,
             Context context) {
-        
+
         this(localFile, serverFile, account, syncFileContents, context);
         mAllowUploads = allowUploads;
     }
-    
+
 
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
 
         RemoteOperationResult result = null;
         mTransferWasRequested = false;
-        
+
         if (mLocalFile == null) {
             // Get local file from the DB
             mLocalFile = getStorageManager().getFileByPath(mRemotePath);
         }
-        
+
         if (!mLocalFile.isDown()) {
             /// easy decision
             requestForDownload(mLocalFile);
@@ -197,13 +196,13 @@ public class SynchronizeFileOperation extends SyncOperation {
             if (mServerFile == null) {
                 ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mRemotePath);
                 result = operation.execute(client);
-                if (result.isSuccess()){
+                if (result.isSuccess()) {
                     mServerFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
                     mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
                 }
             }
 
-            if (mServerFile != null) {   
+            if (mServerFile != null) {
 
                 /// check changes in server and local file
                 boolean serverChanged = false;
@@ -215,7 +214,7 @@ public class SynchronizeFileOperation extends SyncOperation {
                     serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag()));
                 }
                 boolean localChanged = (
-                    mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()
+                        mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()
                 );
 
                 /// decide action to perform depending upon changes
@@ -240,7 +239,7 @@ public class SynchronizeFileOperation extends SyncOperation {
 
                 } else if (serverChanged) {
                     mLocalFile.setRemoteId(mServerFile.getRemoteId());
-                    
+
                     if (mSyncFileContents) {
                         requestForDownload(mLocalFile); // local, not server; we won't to keep
                         // the value of favorite!
@@ -277,31 +276,24 @@ public class SynchronizeFileOperation extends SyncOperation {
         return result;
     }
 
-    
+
     /**
      * Requests for an upload to the FileUploader service
-     * 
-     * @param file     OCFile object representing the file to upload
+     *
+     * @param file OCFile object representing the file to upload
      */
     private void requestForUpload(OCFile file) {
-        Intent i = new Intent(mContext, FileUploader.class);
-        i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
-        i.putExtra(FileUploader.KEY_FILE, file);
-        /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);
-        // doing this we would lose the value of isFavorite in the road, and maybe
-        // it's not updated in the database when the FileUploader service gets it!
-        i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
-        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-        i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
-        mContext.startService(i);
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        requester.uploadUpdate(mContext, mAccount, file, FileUploader.LOCAL_BEHAVIOUR_MOVE, true);
+
         mTransferWasRequested = true;
     }
 
 
     /**
      * Requests for a download to the FileDownloader service
-     * 
-     * @param file     OCFile object representing the file to download
+     *
+     * @param file OCFile object representing the file to download
      */
     private void requestForDownload(OCFile file) {
         Intent i = new Intent(mContext, FileDownloader.class);

+ 13 - 3
src/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -50,8 +50,6 @@ import com.owncloud.android.utils.FileStorageUtils;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
-//import android.support.v4.content.LocalBroadcastManager;
-
 
 /**
  *  Remote operation performing the synchronization of the list of files contained 
@@ -479,6 +477,18 @@ public class SynchronizeFolderOperation extends SyncOperation {
     }
 
     
+    /**
+     * Creates and populates a new {@link com.owncloud.android.datamodel.OCFile}
+     * object with the data read from the server.
+     *
+     * @param remote    remote file read from the server (remote file or folder).
+     * @return          New OCFile instance representing the remote resource described by we.
+     */
+    private OCFile fillOCFile(RemoteFile remote) {
+        return FileStorageUtils.fillOCFile(remote);
+    }
+
+
     /**
      * Scans the default location for saving local copies of files searching for
      * a 'lost' file with the same full name as the {@link com.owncloud.android.datamodel.OCFile}

+ 561 - 197
src/com/owncloud/android/operations/UploadFileOperation.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -20,28 +20,14 @@
 
 package com.owncloud.android.operations;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.FileChannel;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.methods.RequestEntity;
-
 import android.accounts.Account;
 import android.content.Context;
 import android.net.Uri;
 
-import com.owncloud.android.MainApp;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.db.PreferenceReader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
@@ -53,46 +39,140 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.ChunkedUploadRemoteFileOperation;
 import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
 import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
+import com.owncloud.android.operations.common.SyncOperation;
+import com.owncloud.android.utils.ConnectivityUtils;
 import com.owncloud.android.utils.FileStorageUtils;
+import com.owncloud.android.utils.MimetypeIconUtil;
 import com.owncloud.android.utils.UriUtils;
 
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.RequestEntity;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 
 /**
- * Remote operation performing the upload of a file to an ownCloud server
+ * Operation performing the update in the ownCloud server
+ * of a file that was modified locally.
  */
-public class UploadFileOperation extends RemoteOperation {
+public class UploadFileOperation extends SyncOperation {
+
+
+    private static final String MIME_TYPE_PDF = "application/pdf";
+    private static final String FILE_EXTENSION_PDF = ".pdf";
+
+    public static final int CREATED_BY_USER = 0;
+    public static final int CREATED_AS_INSTANT_PICTURE = 1;
+    public static final int CREATED_AS_INSTANT_VIDEO = 2;
+
+
+    /**
+     * Checks if content provider, using the content:// scheme, returns a file with mime-type
+     * 'application/pdf' but file has not extension
+     * @param localPath         Full path to a file in the local file system.
+     * @param mimeType          MIME type of the file.
+     * @return true if is needed to add the pdf file extension to the file
+     *
+     * TODO - move to OCFile or Utils class
+     */
+    private static boolean isPdfFileFromContentProviderWithoutExtension(String localPath,
+                                                                        String mimeType) {
+        return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) &&
+                mimeType.equals(MIME_TYPE_PDF) &&
+                !localPath.endsWith(FILE_EXTENSION_PDF);
+    }
+
+    public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
+
+        // MIME type
+        if (mimeType == null || mimeType.length() <= 0) {
+            mimeType = MimetypeIconUtil.getBestMimeTypeByFilename(localPath);
+        }
+
+        // TODO - this is a horrible special case that should not be handled this way
+        if (isPdfFileFromContentProviderWithoutExtension(localPath, mimeType)){
+            remotePath += FILE_EXTENSION_PDF;
+        }
+
+        OCFile newFile = new OCFile(remotePath);
+        newFile.setStoragePath(localPath);
+        newFile.setLastSyncDateForProperties(0);
+        newFile.setLastSyncDateForData(0);
+
+        // size
+        if (localPath != null && localPath.length() > 0) {
+            File localFile = new File(localPath);
+            newFile.setFileLength(localFile.length());
+            newFile.setLastSyncDateForData(localFile.lastModified());
+        } // don't worry about not assigning size, the problems with localPath
+        // are checked when the UploadFileOperation instance is created
+
+
+        newFile.setMimetype(mimeType);
+
+        return newFile;
+    }
+
+
 
     private static final String TAG = UploadFileOperation.class.getSimpleName();
 
     private Account mAccount;
+    /**
+     * OCFile which is to be uploaded.
+     */
     private OCFile mFile;
+    /**
+     * Original OCFile which is to be uploaded in case file had to be renamed
+     * (if forceOverwrite==false and remote file already exists).
+     */
     private OCFile mOldFile;
     private String mRemotePath = null;
     private boolean mChunked = false;
-    private boolean mIsInstant = false;
     private boolean mRemoteFolderToBeCreated = false;
     private boolean mForceOverwrite = false;
     private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
+    private int mCreatedBy = CREATED_BY_USER;
+
     private boolean mWasRenamed = false;
     private String mOriginalFileName = null;
+    private long mOCUploadId = -1;
+    /**
+     * Local path to file which is to be uploaded (before any possible renaming or moving).
+     */
     private String mOriginalStoragePath = null;
     private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
-    private AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+    private OnRenameListener mRenameUploadListener;
+
+    private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+    private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
+
     private Context mContext;
-    
+
     private UploadRemoteFileOperation mUploadOperation;
 
     protected RequestEntity mEntity = null;
 
-    
-    public UploadFileOperation( Account account,
-                                OCFile file,
-                                boolean chunked,
-                                boolean isInstant, 
-                                boolean forceOverwrite,
-                                int localBehaviour, 
-                                Context context) {
+    public UploadFileOperation(Account account,
+                               OCFile file,
+                               boolean chunked,
+                               boolean forceOverwrite,
+                               int localBehaviour,
+                               Context context
+    ) {
         if (account == null)
             throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " +
                     "creation");
@@ -108,7 +188,6 @@ public class UploadFileOperation extends RemoteOperation {
         mFile = file;
         mRemotePath = file.getRemotePath();
         mChunked = chunked;
-        mIsInstant = isInstant;
         mForceOverwrite = forceOverwrite;
         mLocalBehaviour = localBehaviour;
         mOriginalStoragePath = mFile.getStoragePath();
@@ -116,6 +195,42 @@ public class UploadFileOperation extends RemoteOperation {
         mContext = context;
     }
 
+    public UploadFileOperation(Account account,
+                               OCUpload upload,
+                               boolean chunked,
+                               boolean forceOverwrite,
+                               int localBehaviour,
+                               Context context
+    ) {
+        if (account == null)
+            throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " +
+                    "creation");
+        if (upload == null)
+            throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
+        if (upload.getLocalPath() == null || upload.getLocalPath().length() <= 0) {
+            throw new IllegalArgumentException(
+                    "Illegal file in UploadFileOperation; storage path invalid: "
+                            + upload.getLocalPath());
+        }
+
+        mAccount = account;
+        mFile = obtainNewOCFileToUpload(
+                upload.getRemotePath(),
+                upload.getLocalPath(),
+                upload.getMimeType()
+        );
+        mRemotePath = upload.getRemotePath();
+        mChunked = chunked;
+        mForceOverwrite = forceOverwrite;
+        mLocalBehaviour = localBehaviour;
+        mOriginalStoragePath = mFile.getStoragePath();
+        mOriginalFileName = mFile.getFileName();
+        mContext = context;
+        mOCUploadId = upload.getUploadId();
+        mCreatedBy = upload.getCreadtedBy();
+        mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
+    }
+
     public Account getAccount() {
         return mAccount;
     }
@@ -128,6 +243,10 @@ public class UploadFileOperation extends RemoteOperation {
         return mFile;
     }
 
+    /**
+     * If remote file was renamed, return original OCFile which was uploaded. Is
+     * null is file was not renamed.
+     */
     public OCFile getOldFile() {
         return mOldFile;
     }
@@ -148,30 +267,48 @@ public class UploadFileOperation extends RemoteOperation {
         return mFile.getMimetype();
     }
 
-    public boolean isInstant() {
-        return mIsInstant;
-    }
-
-    public boolean isRemoteFolderToBeCreated() {
-        return mRemoteFolderToBeCreated;
+    public int getLocalBehaviour() {
+        return mLocalBehaviour;
     }
 
     public void setRemoteFolderToBeCreated() {
         mRemoteFolderToBeCreated = true;
     }
 
-    public boolean getForceOverwrite() {
-        return mForceOverwrite;
-    }
-
     public boolean wasRenamed() {
         return mWasRenamed;
     }
 
+    public void setCreatedBy(int createdBy) {
+        mCreatedBy = createdBy;
+        if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) {
+            mCreatedBy = CREATED_BY_USER;
+        }
+    }
+
+    public int getCreatedBy () {
+        return mCreatedBy;
+    }
+
+    public boolean isInstantPicture() {
+        return mCreatedBy == CREATED_AS_INSTANT_PICTURE;
+    }
+
+    public boolean isInstantVideo() {
+        return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
+    }
+
+    public void setOCUploadId(long id){
+        mOCUploadId = id;
+    }
+    public long getOCUploadId() {
+        return mOCUploadId;
+    }
+
     public Set<OnDatatransferProgressListener> getDataTransferListeners() {
         return mDataTransferListeners;
     }
-    
+
     public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
         synchronized (mDataTransferListeners) {
             mDataTransferListeners.add(listener);
@@ -179,8 +316,11 @@ public class UploadFileOperation extends RemoteOperation {
         if (mEntity != null) {
             ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
         }
+        if(mUploadOperation != null){
+            mUploadOperation.addDatatransferProgressListener(listener);
+        }
     }
-    
+
     public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
         synchronized (mDataTransferListeners) {
             mDataTransferListeners.remove(listener);
@@ -188,125 +328,87 @@ public class UploadFileOperation extends RemoteOperation {
         if (mEntity != null) {
             ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
         }
+        if(mUploadOperation != null){
+            mUploadOperation.removeDatatransferProgressListener(listener);
+        }
+    }
+
+    public void addRenameUploadListener (OnRenameListener listener) {
+        mRenameUploadListener = listener;
     }
 
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
+        mCancellationRequested.set(false);
+        mUploadStarted.set(true);
         RemoteOperationResult result = null;
-        boolean localCopyPassed = false, nameCheckPassed = false;
         File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
+
         try {
-            // / rename the file to upload, if necessary
+
+            /// Check that connectivity conditions are met and delays the upload otherwise
+            if (delayForWifi()) {
+                Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
+                return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
+            }
+
+            /// check if the file continues existing before schedule the operation
+            if (!originalFile.exists()) {
+                Log_OC.d(TAG, mOriginalStoragePath.toString() + " not exists anymore");
+                return new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
+            }
+
+            /// check the existence of the parent folder for the file to upload
+            String remoteParentPath = new File(getRemotePath()).getParent();
+            remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+                    remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+            result = grantFolderExistence(remoteParentPath, client);
+            if (!result.isSuccess()) {
+
+                return result;
+            }
+
+            /// set parent local id in uploading file
+            OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
+            mFile.setParentId(parent.getFileId());
+
+            /// automatic rename of file to upload in case of name collision in server
+            Log_OC.d(TAG, "Checking name collision in server");
             if (!mForceOverwrite) {
                 String remotePath = getAvailableRemotePath(client, mRemotePath);
                 mWasRenamed = !remotePath.equals(mRemotePath);
                 if (mWasRenamed) {
                     createNewOCFile(remotePath);
+                    Log_OC.d(TAG, "File renamed as " + remotePath);
                 }
+                mRemotePath = remotePath;
+                mRenameUploadListener.onRenameUpload();
             }
-            nameCheckPassed = true;
-
-            String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); // /
-                                                                                                // not
-                                                                                                // before
-                                                                                                // getAvailableRemotePath()
-                                                                                                // !!!
-            expectedFile = new File(expectedPath);
-
-            // check location of local file; if not the expected, copy to a
-            // temporal file before upload (if COPY is the expected behaviour)
-            if (!mOriginalStoragePath.equals(expectedPath) &&
-                    mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
-
-                if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
-                    result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
-                    return result; // error condition when the file should be
-                                   // copied
-
-                } else {
 
-                    String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) +
-                            mFile.getRemotePath();
-                    mFile.setStoragePath(temporalPath);
-                    temporalFile = new File(temporalPath);
+            if (mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
 
-                    File temporalParent = temporalFile.getParentFile();
-                    temporalParent.mkdirs();
-                    if (!temporalParent.isDirectory()) {
-                        throw new IOException("Unexpected error: parent directory could not be created");
-                    }
-                    temporalFile.createNewFile();
-                    if (!temporalFile.isFile()) {
-                        throw new IOException("Unexpected error: target file could not be created");
-                    }
+            String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
+            expectedFile = new File(expectedPath);
 
-                    InputStream in = null;
-                    OutputStream out = null;
+            /// copy the file locally before uploading
+            if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY &&
+                    !mOriginalStoragePath.equals(expectedPath)) {
 
-                    try {
+                String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
+                mFile.setStoragePath(temporalPath);
+                temporalFile = new File(temporalPath);
 
-                        // In case document provider schema as 'content://'
-                        if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
-
-                            Uri uri = Uri.parse(mOriginalStoragePath);
-
-                            in = MainApp.getAppContext().getContentResolver().openInputStream(uri);
-                            out = new FileOutputStream(temporalFile);
-
-                            int nRead;
-                            byte[] data = new byte[16384];
-
-                            while (!mCancellationRequested.get() &&
-                                    (nRead = in.read(data, 0, data.length)) != -1) {
-                                out.write(data, 0, nRead);
-                            }
-                            out.flush();
-
-                        } else {
-                            if (!mOriginalStoragePath.equals(temporalPath)) { // preventing
-                                                                          // weird
-                                                                          // but
-                                                                          // possible
-                                                                          // situation
-
-                                in = new FileInputStream(originalFile);
-                                out = new FileOutputStream(temporalFile);
-                                byte[] buf = new byte[1024];
-                                int len;
-                                while (!mCancellationRequested.get() && (len = in.read(buf)) > 0) {
-                                    out.write(buf, 0, len);
-                                }
-                            }
-                        }
-
-                        if (mCancellationRequested.get()) {
-                            result = new RemoteOperationResult(new OperationCancelledException());
-                        }
-
-
-                    } catch (Exception e) {
-                        result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
-                        return result;
-
-                    } finally {
-                        try {
-                            if (in != null)
-                                in.close();
-                        } catch (Exception e) {
-                            Log_OC.d(TAG, "Weird exception while closing input stream for " +
-                                    mOriginalStoragePath + " (ignoring)", e);
-                        }
-                        try {
-                            if (out != null)
-                                out.close();
-                        } catch (Exception e) {
-                            Log_OC.d(TAG, "Weird exception while closing output stream for " +
-                                    expectedPath + " (ignoring)", e);
-                        }
-                    }
+                result = copy(originalFile, temporalFile);
+                if (result != null) {
+                    return result;
                 }
             }
-            localCopyPassed = (result == null);
+
+            if (mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
 
             /// perform the upload
             if ( mChunked &&
@@ -322,6 +424,7 @@ public class UploadFileOperation extends RemoteOperation {
             while (listener.hasNext()) {
                 mUploadOperation.addDatatransferProgressListener(listener.next());
             }
+
             if (mCancellationRequested.get()) {
                 throw new OperationCancelledException();
             }
@@ -335,53 +438,17 @@ public class UploadFileOperation extends RemoteOperation {
                     mFile.setStoragePath("");
                 } else {
                     mFile.setStoragePath(expectedPath);
-                    File fileToMove = null;
-                    if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
-                        // ; see where temporalFile was
-                        // set
+                    File fileToMove;
+                    if (temporalFile != null) {         // FileUploader.LOCAL_BEHAVIOUR_COPY
                         fileToMove = temporalFile;
-                    } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
+                    } else {                            // FileUploader.LOCAL_BEHAVIOUR_MOVE
                         fileToMove = originalFile;
                     }
-                    if (!expectedFile.equals(fileToMove)) {
-                        File expectedFolder = expectedFile.getParentFile();
-                        expectedFolder.mkdirs();
-
-                        if (expectedFolder.isDirectory()){
-                            if (!fileToMove.renameTo(expectedFile)){
-                                // try to copy and then delete
-                                expectedFile.createNewFile();
-                                FileChannel inChannel = new FileInputStream(fileToMove).getChannel();
-                                FileChannel outChannel = new FileOutputStream(expectedFile).getChannel();
-
-                                try {
-                                    inChannel.transferTo(0, inChannel.size(), outChannel);
-                                    fileToMove.delete();
-                                } catch (Exception e){
-                                    mFile.setStoragePath(null); // forget the local file
-                                    // by now, treat this as a success; the file was
-                                    // uploaded; the user won't like that the local file
-                                    // is not linked, but this should be a very rare
-                                    // fail;
-                                    // the best option could be show a warning message
-                                    // (but not a fail)
-                                    // result = new
-                                    // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
-                                    // return result;
-                                }
-                                finally {
-                                    if (inChannel != null) inChannel.close();
-                                    if (outChannel != null) outChannel.close();
-                                }
-                            }
-
-                        } else {
-                            mFile.setStoragePath(null);
-                        }
-                    }
+                    move(fileToMove, expectedFile);
                 }
                 FileDataStorageManager.triggerMediaScan(originalFile.getAbsolutePath());
                 FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
+
             } else if (result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED ) {
                 result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
             }
@@ -390,6 +457,7 @@ public class UploadFileOperation extends RemoteOperation {
             result = new RemoteOperationResult(e);
 
         } finally {
+            mUploadStarted.set(false);
             if (temporalFile != null && !originalFile.equals(temporalFile)) {
                 temporalFile.delete();
             }
@@ -401,16 +469,14 @@ public class UploadFileOperation extends RemoteOperation {
                         result.getLogMessage());
             } else {
                 if (result.getException() != null) {
-                    String complement = "";
-                    if (!nameCheckPassed) {
-                        complement = " (while checking file existence in server)";
-                    } else if (!localCopyPassed) {
-                        complement = " (while copying local file to " +
-                                FileStorageUtils.getSavePath(mAccount.name)
-                                + ")";
+                    if(result.isCancelled()){
+                        Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
+                                ": " + result.getLogMessage());
+                    } else {
+                        Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
+                                ": " + result.getLogMessage(), result.getException());
                     }
-                    Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
-                            ": " + result.getLogMessage() + complement, result.getException());
+
                 } else {
                     Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
                             ": " + result.getLogMessage());
@@ -418,9 +484,93 @@ public class UploadFileOperation extends RemoteOperation {
             }
         }
 
+        if (result.isSuccess()) {
+            saveUploadedFile(client);
+
+        } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+            getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
+        }
+
         return result;
     }
 
+
+    /**
+     * Checks origin of current upload and network type to decide if should be delayed, according to
+     * current user preferences.
+     *
+     * @return      'True' if the upload was delayed until WiFi connectivity is available, 'false' otherwise.
+     */
+    private boolean delayForWifi() {
+        boolean delayInstantPicture = (
+            isInstantPicture() &&  PreferenceReader.instantPictureUploadViaWiFiOnly(mContext)
+        );
+        boolean delayInstantVideo = (
+            isInstantVideo() && PreferenceReader.instantVideoUploadViaWiFiOnly(mContext)
+        );
+        return (
+            (delayInstantPicture || delayInstantVideo) &&
+            !ConnectivityUtils.isAppConnectedViaWiFi(mContext)
+        );
+    }
+
+
+    /**
+     * Checks the existence of the folder where the current file will be uploaded both
+     * in the remote server and in the local database.
+     * <p/>
+     * If the upload is set to enforce the creation of the folder, the method tries to
+     * create it both remote and locally.
+     *
+     * @param pathToGrant Full remote path whose existence will be granted.
+     * @return An {@link OCFile} instance corresponding to the folder where the file
+     * will be uploaded.
+     */
+    private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
+        RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, mContext, false);
+        RemoteOperationResult result = operation.execute(client);
+        if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
+            SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
+            result = syncOp.execute(client, getStorageManager());
+        }
+        if (result.isSuccess()) {
+            OCFile parentDir = getStorageManager().getFileByPath(pathToGrant);
+            if (parentDir == null) {
+                parentDir = createLocalFolder(pathToGrant);
+            }
+            if (parentDir != null) {
+                result = new RemoteOperationResult(ResultCode.OK);
+            } else {
+                result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
+            }
+        }
+        return result;
+    }
+
+    private OCFile createLocalFolder(String remotePath) {
+        String parentPath = new File(remotePath).getParent();
+        parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+                parentPath : parentPath + OCFile.PATH_SEPARATOR;
+        OCFile parent = getStorageManager().getFileByPath(parentPath);
+        if (parent == null) {
+            parent = createLocalFolder(parentPath);
+        }
+        if (parent != null) {
+            OCFile createdFolder = new OCFile(remotePath);
+            createdFolder.setMimetype("DIR");
+            createdFolder.setParentId(parent.getFileId());
+            getStorageManager().saveFile(createdFolder);
+            return createdFolder;
+        }
+        return null;
+    }
+
+
+    /**
+     * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
+     * New file is stored as mFile, original as mOldFile. 
+     * @param newRemotePath new remote path
+     */
     private void createNewOCFile(String newRemotePath) {
         // a new OCFile instance must be created for a new remote path
         OCFile newFile = new OCFile(newRemotePath);
@@ -429,7 +579,8 @@ public class UploadFileOperation extends RemoteOperation {
         newFile.setMimetype(mFile.getMimetype());
         newFile.setModificationTimestamp(mFile.getModificationTimestamp());
         newFile.setModificationTimestampAtLastSyncForData(
-                mFile.getModificationTimestampAtLastSyncForData());
+                mFile.getModificationTimestampAtLastSyncForData()
+        );
         newFile.setEtag(mFile.getEtag());
         newFile.setFavorite(mFile.isFavorite());
         newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
@@ -443,12 +594,12 @@ public class UploadFileOperation extends RemoteOperation {
     /**
      * Checks if remotePath does not exist in the server and returns it, or adds
      * a suffix to it in order to avoid the server file is overwritten.
-     * 
+     *
      * @param wc
      * @param remotePath
      * @return
      */
-    private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) throws Exception {
+    private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) {
         boolean check = existsFile(wc, remotePath);
         if (!check) {
             return remotePath;
@@ -486,11 +637,224 @@ public class UploadFileOperation extends RemoteOperation {
         RemoteOperationResult result = existsOperation.execute(client);
         return result.isSuccess();
     }
-    
+
+    /**
+     * Allows to cancel the actual upload operation. If actual upload operating
+     * is in progress it is cancelled, if upload preparation is being performed
+     * upload will not take place.
+     */
     public void cancel() {
-        mCancellationRequested = new AtomicBoolean(true);
-        if (mUploadOperation != null) {
+        if (mUploadOperation == null) {
+            if (mUploadStarted.get()) {
+                Log_OC.d(TAG, "Cancelling upload during upload preparations.");
+                mCancellationRequested.set(true);
+            } else {
+                Log_OC.e(TAG, "No upload in progress. This should not happen.");
+            }
+        } else {
+            Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
             mUploadOperation.cancel();
         }
     }
+
+    /**
+     * As soon as this method return true, upload can be cancel via cancel().
+     */
+    public boolean isUploadInProgress() {
+        return mUploadStarted.get();
+
+    }
+
+    /**
+     * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
+     * TODO     use Exceptions instead
+     *
+     * @param   sourceFile      Source file to copy.
+     * @param   targetFile      Target location to copy the file.
+     * @return  {@link RemoteOperationResult}
+     * @throws  IOException
+     */
+    private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
+        Log_OC.d(TAG, "Copying local file");
+
+        RemoteOperationResult result = null;
+
+        if (FileStorageUtils.getUsableSpace(mAccount.name) < sourceFile.length()) {
+            result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
+            return result;  // error condition when the file should be copied
+
+        } else {
+            Log_OC.d(TAG, "Creating temporal folder");
+            File temporalParent = targetFile.getParentFile();
+            temporalParent.mkdirs();
+            if (!temporalParent.isDirectory()) {
+                throw new IOException(
+                        "Unexpected error: parent directory could not be created");
+            }
+            Log_OC.d(TAG, "Creating temporal file");
+            targetFile.createNewFile();
+            if (!targetFile.isFile()) {
+                throw new IOException(
+                        "Unexpected error: target file could not be created");
+            }
+
+            Log_OC.d(TAG, "Copying file contents");
+            InputStream in = null;
+            OutputStream out = null;
+
+            try {
+                if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) {
+                    // In case document provider schema as 'content://'
+                    if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
+                        Uri uri = Uri.parse(mOriginalStoragePath);
+                        in = mContext.getContentResolver().openInputStream(uri);
+                    } else {
+                        in = new FileInputStream(sourceFile);
+                    }
+                    out = new FileOutputStream(targetFile);
+                    int nRead;
+                    byte[] buf = new byte[4096];
+                    while (!mCancellationRequested.get() &&
+                            (nRead = in.read(buf)) > -1) {
+                        out.write(buf, 0, nRead);
+                    }
+                    out.flush();
+
+                } // else: weird but possible situation, nothing to copy
+
+                if (mCancellationRequested.get()) {
+                    result = new RemoteOperationResult(new OperationCancelledException());
+                    return result;
+                }
+
+            } catch (Exception e) {
+                result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
+                return result;
+
+            } finally {
+                try {
+                    if (in != null)
+                        in.close();
+                } catch (Exception e) {
+                    Log_OC.d(TAG, "Weird exception while closing input stream for " +
+                            mOriginalStoragePath + " (ignoring)", e);
+                }
+                try {
+                    if (out != null)
+                        out.close();
+                } catch (Exception e) {
+                    Log_OC.d(TAG, "Weird exception while closing output stream for " +
+                            targetFile.getAbsolutePath() + " (ignoring)", e);
+                }
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
+     * TODO     use Exceptions instead
+     *
+     * TODO refactor both this and 'copy' in a single method
+     *
+     * @param   sourceFile      Source file to move.
+     * @param   targetFile      Target location to move the file.
+     * @return  {@link RemoteOperationResult}
+     * @throws  IOException
+     */
+    private void move(File sourceFile, File targetFile) throws IOException {
+
+        if (!targetFile.equals(sourceFile)) {
+            File expectedFolder = targetFile.getParentFile();
+            expectedFolder.mkdirs();
+
+            if (expectedFolder.isDirectory()){
+                if (!sourceFile.renameTo(targetFile)){
+                    // try to copy and then delete
+                    targetFile.createNewFile();
+                    FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
+                    FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
+                    try {
+                        inChannel.transferTo(0, inChannel.size(), outChannel);
+                        sourceFile.delete();
+                    } catch (Exception e){
+                        mFile.setStoragePath(""); // forget the local file
+                        // by now, treat this as a success; the file was uploaded
+                        // the best option could be show a warning message
+                    }
+                    finally {
+                        if (inChannel != null) inChannel.close();
+                        if (outChannel != null) outChannel.close();
+                    }
+                }
+
+            } else {
+                mFile.setStoragePath("");
+            }
+        }
+    }
+
+    /**
+     * Saves a OC File after a successful upload.
+     * <p/>
+     * A PROPFIND is necessary to keep the props in the local database
+     * synchronized with the server, specially the modification time and Etag
+     * (where available)
+     * <p/>
+     */
+    private void saveUploadedFile(OwnCloudClient client) {
+        OCFile file = mFile;
+        if (file.fileExists()) {
+            file = getStorageManager().getFileById(file.getFileId());
+        }
+        long syncDate = System.currentTimeMillis();
+        file.setLastSyncDateForData(syncDate);
+
+        // new PROPFIND to keep data consistent with server
+        // in theory, should return the same we already have
+        // TODO from the appropriate OC server version, get data from last PUT response headers, instead
+        // TODO     of a new PROPFIND; the latter may fail, specially for chunked uploads
+        ReadRemoteFileOperation operation = new ReadRemoteFileOperation(getRemotePath());
+        RemoteOperationResult result = operation.execute(client);
+        if (result.isSuccess()) {
+            updateOCFile(file, (RemoteFile) result.getData().get(0));
+            file.setLastSyncDateForProperties(syncDate);
+        } else {
+            Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
+        }
+
+        if (mWasRenamed) {
+            OCFile oldFile = mOldFile;
+            if (oldFile.fileExists()) {
+                oldFile.setStoragePath(null);
+                getStorageManager().saveFile(oldFile);
+                getStorageManager().saveConflict(oldFile, null);
+            }
+            // else: it was just an automatic renaming due to a name
+            // coincidence; nothing else is needed, the storagePath is right
+            // in the instance returned by mCurrentUpload.getFile()
+        }
+        file.setNeedsUpdateThumbnail(true);
+        getStorageManager().saveFile(file);
+        getStorageManager().saveConflict(file, null);
+
+        FileDataStorageManager.triggerMediaScan(file.getStoragePath());
+    }
+
+    private void updateOCFile(OCFile file, RemoteFile remoteFile) {
+        file.setCreationTimestamp(remoteFile.getCreationTimestamp());
+        file.setFileLength(remoteFile.getLength());
+        file.setMimetype(remoteFile.getMimeType());
+        file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
+        file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
+        file.setEtag(remoteFile.getEtag());
+        file.setRemoteId(remoteFile.getRemoteId());
+    }
+
+    public interface OnRenameListener {
+
+        void onRenameUpload();
+    }
+
 }

+ 336 - 217
src/com/owncloud/android/providers/FileContentProvider.java

@@ -3,8 +3,9 @@
  *
  *   @author Bartek Przybylski
  *   @author David A. Velasco
+ *   @author masensio
  *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -43,6 +44,7 @@ import android.text.TextUtils;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.ProviderMeta;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.lib.common.accounts.AccountUtils;
@@ -65,9 +67,12 @@ public class FileContentProvider extends ContentProvider {
     private static final int ROOT_DIRECTORY = 3;
     private static final int SHARES = 4;
     private static final int CAPABILITIES = 5;
+    private static final int UPLOADS = 6;
 
     private static final String TAG = FileContentProvider.class.getSimpleName();
 
+    private final String MAX_SUCCESSFUL_UPLOADS = "30";
+
     private UriMatcher mUriMatcher;
 
     @Override
@@ -89,25 +94,25 @@ public class FileContentProvider extends ContentProvider {
     private int delete(SQLiteDatabase db, Uri uri, String where, String[] whereArgs) {
         int count = 0;
         switch (mUriMatcher.match(uri)) {
-        case SINGLE_FILE:
-            Cursor c = query(db, uri, null, where, whereArgs, null);
-            String remoteId = "";
-            if (c != null && c.moveToFirst()) {
-                remoteId = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID));
-                //ThumbnailsCacheManager.removeFileFromCache(remoteId);
-                c.close();
-            }
-            Log_OC.d(TAG, "Removing FILE " + remoteId);
-
-            count = db.delete(ProviderTableMeta.FILE_TABLE_NAME,
-                    ProviderTableMeta._ID
-                            + "="
-                            + uri.getPathSegments().get(1)
-                            + (!TextUtils.isEmpty(where) ? " AND (" + where
-                                    + ")" : ""), whereArgs);
-            break;
-        case DIRECTORY:
-            // deletion of folder is recursive
+            case SINGLE_FILE:
+                Cursor c = query(db, uri, null, where, whereArgs, null);
+                String remoteId = "";
+                if (c != null && c.moveToFirst()) {
+                    remoteId = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID));
+                    //ThumbnailsCacheManager.removeFileFromCache(remoteId);
+                    c.close();
+                }
+                Log_OC.d(TAG, "Removing FILE " + remoteId);
+
+                count = db.delete(ProviderTableMeta.FILE_TABLE_NAME,
+                        ProviderTableMeta._ID
+                                + "="
+                                + uri.getPathSegments().get(1)
+                                + (!TextUtils.isEmpty(where) ? " AND (" + where
+                                + ")" : ""), whereArgs);
+                break;
+            case DIRECTORY:
+                // deletion of folder is recursive
             /*
             Uri folderUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, Long.parseLong(uri.getPathSegments().get(1)));
             Cursor folder = query(db, folderUri, null, null, null, null);
@@ -116,63 +121,66 @@ public class FileContentProvider extends ContentProvider {
                 folderName = folder.getString(folder.getColumnIndex(ProviderTableMeta.FILE_PATH));
             }
             */
-            Cursor children = query(uri, null, null, null, null);
-            if (children != null && children.moveToFirst())  {
-                long childId;
-                boolean isDir;
-                while (!children.isAfterLast()) {
-                    childId = children.getLong(children.getColumnIndex(ProviderTableMeta._ID));
-                    isDir = "DIR".equals(children.getString(
-                            children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)
-                    ));
-                    //remotePath = children.getString(children.getColumnIndex(ProviderTableMeta.FILE_PATH));
-                    if (isDir) {
-                        count += delete(
-                            db,
-                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId),
-                            null,
-                            null
-                        );
-                    } else {
-                        count += delete(
-                            db,
-                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId),
-                            null,
-                            null
-                        );
+                Cursor children = query(uri, null, null, null, null);
+                if (children != null && children.moveToFirst())  {
+                    long childId;
+                    boolean isDir;
+                    while (!children.isAfterLast()) {
+                        childId = children.getLong(children.getColumnIndex(ProviderTableMeta._ID));
+                        isDir = "DIR".equals(children.getString(
+                                children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)
+                        ));
+                        //remotePath = children.getString(children.getColumnIndex(ProviderTableMeta.FILE_PATH));
+                        if (isDir) {
+                            count += delete(
+                                    db,
+                                    ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId),
+                                    null,
+                                    null
+                            );
+                        } else {
+                            count += delete(
+                                    db,
+                                    ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId),
+                                    null,
+                                    null
+                            );
+                        }
+                        children.moveToNext();
                     }
-                    children.moveToNext();
-                }
-                children.close();
-            } /*else {
+                    children.close();
+                } /*else {
                 Log_OC.d(TAG, "No child to remove in DIRECTORY " + folderName);
             }
             Log_OC.d(TAG, "Removing DIRECTORY " + folderName + " (or maybe not) ");
             */
-            count += db.delete(ProviderTableMeta.FILE_TABLE_NAME,
-                    ProviderTableMeta._ID
-                    + "="
-                    + uri.getPathSegments().get(1)
-                    + (!TextUtils.isEmpty(where) ? " AND (" + where
-                            + ")" : ""), whereArgs);
+                count += db.delete(ProviderTableMeta.FILE_TABLE_NAME,
+                        ProviderTableMeta._ID
+                                + "="
+                                + uri.getPathSegments().get(1)
+                                + (!TextUtils.isEmpty(where) ? " AND (" + where
+                                + ")" : ""), whereArgs);
             /* Just for log
              if (folder != null) {
                 folder.close();
             }*/
-            break;
-        case ROOT_DIRECTORY:
-            //Log_OC.d(TAG, "Removing ROOT!");
-            count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, where, whereArgs);
-            break;
-        case SHARES:
-            count = db.delete(ProviderTableMeta.OCSHARES_TABLE_NAME, where, whereArgs);
-            break;
-        case CAPABILITIES:
-            count = db.delete(ProviderTableMeta.CAPABILITIES_TABLE_NAME, where, whereArgs);
-            break;
-        default:
-            //Log_OC.e(TAG, "Unknown uri " + uri);
-            throw new IllegalArgumentException("Unknown uri: " + uri.toString());
+                break;
+            case ROOT_DIRECTORY:
+                //Log_OC.d(TAG, "Removing ROOT!");
+                count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, where, whereArgs);
+                break;
+            case SHARES:
+                count = db.delete(ProviderTableMeta.OCSHARES_TABLE_NAME, where, whereArgs);
+                break;
+            case CAPABILITIES:
+                count = db.delete(ProviderTableMeta.CAPABILITIES_TABLE_NAME, where, whereArgs);
+                break;
+            case UPLOADS:
+                count = db.delete(ProviderTableMeta.UPLOADS_TABLE_NAME, where, whereArgs);
+                break;
+            default:
+                //Log_OC.e(TAG, "Unknown uri " + uri);
+                throw new IllegalArgumentException("Unknown uri: " + uri.toString());
         }
         return count;
     }
@@ -207,68 +215,80 @@ public class FileContentProvider extends ContentProvider {
 
     private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) {
         switch (mUriMatcher.match(uri)){
-        case ROOT_DIRECTORY:
-        case SINGLE_FILE:
-            String remotePath = values.getAsString(ProviderTableMeta.FILE_PATH);
-            String accountName = values.getAsString(ProviderTableMeta.FILE_ACCOUNT_OWNER);
-            String[] projection = new String[] {
-                    ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH,
-                    ProviderTableMeta.FILE_ACCOUNT_OWNER
-            };
-            String where = ProviderTableMeta.FILE_PATH + "=? AND " +
-                    ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
-            String[] whereArgs = new String[] {remotePath, accountName};
-            Cursor doubleCheck = query(db, uri, projection, where, whereArgs, null);
-            // ugly patch; serious refactorization is needed to reduce work in
-            // FileDataStorageManager and bring it to FileContentProvider
-            if (doubleCheck == null || !doubleCheck.moveToFirst()) {
-                if (doubleCheck != null) {
+            case ROOT_DIRECTORY:
+            case SINGLE_FILE:
+                String remotePath = values.getAsString(ProviderTableMeta.FILE_PATH);
+                String accountName = values.getAsString(ProviderTableMeta.FILE_ACCOUNT_OWNER);
+                String[] projection = new String[] {
+                        ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH,
+                        ProviderTableMeta.FILE_ACCOUNT_OWNER
+                };
+                String where = ProviderTableMeta.FILE_PATH + "=? AND " +
+                        ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
+                String[] whereArgs = new String[] {remotePath, accountName};
+                Cursor doubleCheck = query(db, uri, projection, where, whereArgs, null);
+                // ugly patch; serious refactorization is needed to reduce work in
+                // FileDataStorageManager and bring it to FileContentProvider
+                if (doubleCheck == null || !doubleCheck.moveToFirst()) {
+                    if (doubleCheck != null) {
+                        doubleCheck.close();
+                    }
+                    long rowId = db.insert(ProviderTableMeta.FILE_TABLE_NAME, null, values);
+                    if (rowId > 0) {
+                        return ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId);
+                    } else {
+                        throw new SQLException("ERROR " + uri);
+                    }
+                } else {
+                    // file is already inserted; race condition, let's avoid a duplicated entry
+                    Uri insertedFileUri = ContentUris.withAppendedId(
+                            ProviderTableMeta.CONTENT_URI_FILE,
+                            doubleCheck.getLong(doubleCheck.getColumnIndex(ProviderTableMeta._ID))
+                    );
                     doubleCheck.close();
+
+                    return insertedFileUri;
                 }
-                long rowId = db.insert(ProviderTableMeta.FILE_TABLE_NAME, null, values);
-                if (rowId > 0) {
-                    return ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId);
+
+            case SHARES:
+                Uri insertedShareUri = null;
+                long rowId = db.insert(ProviderTableMeta.OCSHARES_TABLE_NAME, null, values);
+                if (rowId >0) {
+                    insertedShareUri =
+                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, rowId);
                 } else {
                     throw new SQLException("ERROR " + uri);
-                }
-            } else {
-                // file is already inserted; race condition, let's avoid a duplicated entry
-                Uri insertedFileUri = ContentUris.withAppendedId(
-                        ProviderTableMeta.CONTENT_URI_FILE,
-                        doubleCheck.getLong(doubleCheck.getColumnIndex(ProviderTableMeta._ID))
-                );
-                doubleCheck.close();
-
-                return insertedFileUri;
-            }
 
-        case SHARES:
-            Uri insertedShareUri = null;
-            long rowId = db.insert(ProviderTableMeta.OCSHARES_TABLE_NAME, null, values);
-            if (rowId >0) {
-                insertedShareUri =
-                        ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, rowId);
-            } else {
-                throw new SQLException("ERROR " + uri);
+                }
+                updateFilesTableAccordingToShareInsertion(db, values);
+                return insertedShareUri;
 
-            }
-            updateFilesTableAccordingToShareInsertion(db, values);
-            return insertedShareUri;
-
-        case CAPABILITIES:
-            Uri insertedCapUri = null;
-            long id = db.insert(ProviderTableMeta.CAPABILITIES_TABLE_NAME, null, values);
-            if (id >0) {
-                insertedCapUri =
-                        ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_CAPABILITIES, id);
-            } else {
-                throw new SQLException("ERROR " + uri);
+            case CAPABILITIES:
+                Uri insertedCapUri = null;
+                long id = db.insert(ProviderTableMeta.CAPABILITIES_TABLE_NAME, null, values);
+                if (id >0) {
+                    insertedCapUri =
+                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_CAPABILITIES, id);
+                } else {
+                    throw new SQLException("ERROR " + uri);
 
-            }
-            return insertedCapUri;
+                }
+                return insertedCapUri;
+
+            case UPLOADS:
+                Uri insertedUploadUri = null;
+                long uploadId = db.insert(ProviderTableMeta.UPLOADS_TABLE_NAME, null, values);
+                if (uploadId >0) {
+                    insertedUploadUri =
+                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_UPLOADS, uploadId);
+                    trimSuccessfulUploads(db);
+                } else {
+                    throw new SQLException("ERROR " + uri);
 
-        default:
-            throw new IllegalArgumentException("Unknown uri id: " + uri);
+                }
+                return insertedUploadUri;
+            default:
+                throw new IllegalArgumentException("Unknown uri id: " + uri);
         }
 
     }
@@ -312,6 +332,8 @@ public class FileContentProvider extends ContentProvider {
         mUriMatcher.addURI(authority, "shares/#", SHARES);
         mUriMatcher.addURI(authority, "capabilities/", CAPABILITIES);
         mUriMatcher.addURI(authority, "capabilities/#", CAPABILITIES);
+        mUriMatcher.addURI(authority, "uploads/", UPLOADS);
+        mUriMatcher.addURI(authority, "uploads/#", UPLOADS);
 
         return true;
     }
@@ -352,35 +374,42 @@ public class FileContentProvider extends ContentProvider {
         sqlQuery.setTables(ProviderTableMeta.FILE_TABLE_NAME);
 
         switch (mUriMatcher.match(uri)) {
-        case ROOT_DIRECTORY:
-            break;
-        case DIRECTORY:
-            String folderId = uri.getPathSegments().get(1);
-            sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "="
-                    + folderId);
-            break;
-        case SINGLE_FILE:
-            if (uri.getPathSegments().size() > 1) {
-                sqlQuery.appendWhere(ProviderTableMeta._ID + "="
-                        + uri.getPathSegments().get(1));
-            }
-            break;
-        case SHARES:
-            sqlQuery.setTables(ProviderTableMeta.OCSHARES_TABLE_NAME);
-            if (uri.getPathSegments().size() > 1) {
-                sqlQuery.appendWhere(ProviderTableMeta._ID + "="
-                        + uri.getPathSegments().get(1));
-            }
-            break;
-        case CAPABILITIES:
-            sqlQuery.setTables(ProviderTableMeta.CAPABILITIES_TABLE_NAME);
-            if (uri.getPathSegments().size() > 1) {
-                sqlQuery.appendWhere(ProviderTableMeta._ID + "="
-                        + uri.getPathSegments().get(1));
-            }
-            break;
-        default:
-            throw new IllegalArgumentException("Unknown uri id: " + uri);
+            case ROOT_DIRECTORY:
+                break;
+            case DIRECTORY:
+                String folderId = uri.getPathSegments().get(1);
+                sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "="
+                        + folderId);
+                break;
+            case SINGLE_FILE:
+                if (uri.getPathSegments().size() > 1) {
+                    sqlQuery.appendWhere(ProviderTableMeta._ID + "="
+                            + uri.getPathSegments().get(1));
+                }
+                break;
+            case SHARES:
+                sqlQuery.setTables(ProviderTableMeta.OCSHARES_TABLE_NAME);
+                if (uri.getPathSegments().size() > 1) {
+                    sqlQuery.appendWhere(ProviderTableMeta._ID + "="
+                            + uri.getPathSegments().get(1));
+                }
+                break;
+            case CAPABILITIES:
+                sqlQuery.setTables(ProviderTableMeta.CAPABILITIES_TABLE_NAME);
+                if (uri.getPathSegments().size() > 1) {
+                    sqlQuery.appendWhere(ProviderTableMeta._ID + "="
+                            + uri.getPathSegments().get(1));
+                }
+                break;
+            case UPLOADS:
+                sqlQuery.setTables(ProviderTableMeta.UPLOADS_TABLE_NAME);
+                if (uri.getPathSegments().size() > 1) {
+                    sqlQuery.appendWhere(ProviderTableMeta._ID + "="
+                            + uri.getPathSegments().get(1));
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown uri id: " + uri);
         }
 
         String order;
@@ -392,6 +421,9 @@ public class FileContentProvider extends ContentProvider {
                 case CAPABILITIES:
                     order = ProviderTableMeta.CAPABILITIES_DEFAULT_SORT_ORDER;
                     break;
+                case UPLOADS:
+                    order = ProviderTableMeta.UPLOADS_DEFAULT_SORT_ORDER;
+                    break;
                 default: // Files
                     order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
                     break;
@@ -443,6 +475,12 @@ public class FileContentProvider extends ContentProvider {
                 return db.update(
                         ProviderTableMeta.CAPABILITIES_TABLE_NAME, values, selection, selectionArgs
                 );
+            case UPLOADS:
+                int ret = db.update(
+                        ProviderTableMeta.UPLOADS_TABLE_NAME, values, selection, selectionArgs
+                );
+                trimSuccessfulUploads(db);
+                return ret;
             default:
                 return db.update(
                         ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs
@@ -485,53 +523,17 @@ public class FileContentProvider extends ContentProvider {
         public void onCreate(SQLiteDatabase db) {
             // files table
             Log_OC.i("SQL", "Entering in onCreate");
-            db.execSQL("CREATE TABLE " + ProviderTableMeta.FILE_TABLE_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, "
-                            + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "
-                            + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "
-                            + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "
-                            + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER, "
-                            + ProviderTableMeta.FILE_ETAG + " TEXT, "
-                            + ProviderTableMeta.FILE_SHARED_VIA_LINK + " INTEGER, "
-                            + ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT, "
-                            + ProviderTableMeta.FILE_PERMISSIONS + " TEXT null,"
-                            + ProviderTableMeta.FILE_REMOTE_ID + " TEXT null,"
-                            + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER," //boolean
-                            + ProviderTableMeta.FILE_IS_DOWNLOADING + " INTEGER," //boolean
-                            + ProviderTableMeta.FILE_ETAG_IN_CONFLICT + " TEXT,"
-                            + ProviderTableMeta.FILE_SHARED_WITH_SHAREE + " INTEGER);"
-            );
+            createFilesTable(db);
 
-            // Create table ocshares
-            db.execSQL("CREATE TABLE " + ProviderTableMeta.OCSHARES_TABLE_NAME + "("
-                    + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
-                    + ProviderTableMeta.OCSHARES_FILE_SOURCE + " INTEGER, "
-                    + ProviderTableMeta.OCSHARES_ITEM_SOURCE + " INTEGER, "
-                    + ProviderTableMeta.OCSHARES_SHARE_TYPE + " INTEGER, "
-                    + ProviderTableMeta.OCSHARES_SHARE_WITH + " TEXT, "
-                    + ProviderTableMeta.OCSHARES_PATH + " TEXT, "
-                    + ProviderTableMeta.OCSHARES_PERMISSIONS+ " INTEGER, "
-                    + ProviderTableMeta.OCSHARES_SHARED_DATE + " INTEGER, "
-                    + ProviderTableMeta.OCSHARES_EXPIRATION_DATE + " INTEGER, "
-                    + ProviderTableMeta.OCSHARES_TOKEN + " TEXT, "
-                    + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME + " TEXT, "
-                    + ProviderTableMeta.OCSHARES_IS_DIRECTORY + " INTEGER, "  // boolean
-                    + ProviderTableMeta.OCSHARES_USER_ID + " INTEGER, "
-                    + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + " INTEGER,"
-                    + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " TEXT );" );
-
-            // Create table capabilities
+            // Create ocshares table
+            createOCSharesTable(db);
+
+            // Create capabilities table
             createCapabilitiesTable(db);
 
+            // Create uploads table
+            createUploadsTable(db);
+
         }
 
         @Override
@@ -619,22 +621,7 @@ public class FileContentProvider extends ContentProvider {
                             " DEFAULT NULL");
 
                     // Create table ocshares
-                    db.execSQL("CREATE TABLE " + ProviderTableMeta.OCSHARES_TABLE_NAME + "("
-                            + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
-                            + ProviderTableMeta.OCSHARES_FILE_SOURCE + " INTEGER, "
-                            + ProviderTableMeta.OCSHARES_ITEM_SOURCE + " INTEGER, "
-                            + ProviderTableMeta.OCSHARES_SHARE_TYPE + " INTEGER, "
-                            + ProviderTableMeta.OCSHARES_SHARE_WITH + " TEXT, "
-                            + ProviderTableMeta.OCSHARES_PATH + " TEXT, "
-                            + ProviderTableMeta.OCSHARES_PERMISSIONS + " INTEGER, "
-                            + ProviderTableMeta.OCSHARES_SHARED_DATE + " INTEGER, "
-                            + ProviderTableMeta.OCSHARES_EXPIRATION_DATE + " INTEGER, "
-                            + ProviderTableMeta.OCSHARES_TOKEN + " TEXT, "
-                            + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME + " TEXT, "
-                            + ProviderTableMeta.OCSHARES_IS_DIRECTORY + " INTEGER, "  // boolean
-                            + ProviderTableMeta.OCSHARES_USER_ID + " INTEGER, "
-                            + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + " INTEGER,"
-                            + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " TEXT );");
+                    createOCSharesTable(db);
 
                     upgraded = true;
                     db.setTransactionSuccessful();
@@ -760,6 +747,22 @@ public class FileContentProvider extends ContentProvider {
                     db.endTransaction();
                 }
             }
+
+            if (oldVersion < 14 && newVersion >= 14) {
+                Log_OC.i("SQL", "Entering in the #14 ADD in onUpgrade");
+                db.beginTransaction();
+                try {
+                    // drop old instant_upload table
+                    db.execSQL("DROP TABLE IF EXISTS " + "instant_upload" + ";");
+                    // Create uploads table
+                    createUploadsTable(db);
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
             if (!upgraded)
                 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
                         ", newVersion == " + newVersion);
@@ -767,8 +770,56 @@ public class FileContentProvider extends ContentProvider {
         }
     }
 
+    private void createFilesTable(SQLiteDatabase db){
+        db.execSQL("CREATE TABLE " + ProviderTableMeta.FILE_TABLE_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, "
+                        + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "
+                        + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "
+                        + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "
+                        + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER, "
+                        + ProviderTableMeta.FILE_ETAG + " TEXT, "
+                        + ProviderTableMeta.FILE_SHARED_VIA_LINK + " INTEGER, "
+                        + ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT, "
+                        + ProviderTableMeta.FILE_PERMISSIONS + " TEXT null,"
+                        + ProviderTableMeta.FILE_REMOTE_ID + " TEXT null,"
+                        + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER," //boolean
+                        + ProviderTableMeta.FILE_IS_DOWNLOADING + " INTEGER," //boolean
+                        + ProviderTableMeta.FILE_ETAG_IN_CONFLICT + " TEXT,"
+                        + ProviderTableMeta.FILE_SHARED_WITH_SHAREE + " INTEGER);"
+        );
+    }
+
+    private void createOCSharesTable(SQLiteDatabase db) {
+        // Create ocshares table
+        db.execSQL("CREATE TABLE " + ProviderTableMeta.OCSHARES_TABLE_NAME + "("
+                + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
+                + ProviderTableMeta.OCSHARES_FILE_SOURCE + " INTEGER, "
+                + ProviderTableMeta.OCSHARES_ITEM_SOURCE + " INTEGER, "
+                + ProviderTableMeta.OCSHARES_SHARE_TYPE + " INTEGER, "
+                + ProviderTableMeta.OCSHARES_SHARE_WITH + " TEXT, "
+                + ProviderTableMeta.OCSHARES_PATH + " TEXT, "
+                + ProviderTableMeta.OCSHARES_PERMISSIONS+ " INTEGER, "
+                + ProviderTableMeta.OCSHARES_SHARED_DATE + " INTEGER, "
+                + ProviderTableMeta.OCSHARES_EXPIRATION_DATE + " INTEGER, "
+                + ProviderTableMeta.OCSHARES_TOKEN + " TEXT, "
+                + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME + " TEXT, "
+                + ProviderTableMeta.OCSHARES_IS_DIRECTORY + " INTEGER, "  // boolean
+                + ProviderTableMeta.OCSHARES_USER_ID + " INTEGER, "
+                + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + " INTEGER,"
+                + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " TEXT );" );
+    }
+
     private void createCapabilitiesTable(SQLiteDatabase db){
-        // Create table capabilities
+        // Create capabilities table
         db.execSQL("CREATE TABLE " + ProviderTableMeta.CAPABILITIES_TABLE_NAME + "("
                 + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
                 + ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME + " TEXT, "
@@ -795,6 +846,35 @@ public class FileContentProvider extends ContentProvider {
                 + ProviderTableMeta.CAPABILITIES_FILES_VERSIONING + " INTEGER );" );   // boolean
     }
 
+    private void createUploadsTable(SQLiteDatabase db){
+        // Create uploads table
+        db.execSQL("CREATE TABLE " + ProviderTableMeta.UPLOADS_TABLE_NAME + "("
+                + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
+                + ProviderTableMeta.UPLOADS_LOCAL_PATH + " TEXT, "
+                + ProviderTableMeta.UPLOADS_REMOTE_PATH + " TEXT, "
+                + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + " TEXT, "
+                + ProviderTableMeta.UPLOADS_FILE_SIZE + " LONG, "
+                + ProviderTableMeta.UPLOADS_STATUS + " INTEGER, "               // UploadStatus
+                + ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR + " INTEGER, "      // Upload LocalBehaviour
+                + ProviderTableMeta.UPLOADS_UPLOAD_TIME + " INTEGER, "
+                + ProviderTableMeta.UPLOADS_FORCE_OVERWRITE + " INTEGER, "  // boolean
+                + ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + " INTEGER, "  // boolean
+                + ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + " INTEGER, "
+                + ProviderTableMeta.UPLOADS_LAST_RESULT + " INTEGER, "     // Upload LastResult
+                + ProviderTableMeta.UPLOADS_CREATED_BY + " INTEGER );"    // Upload createdBy
+        );
+
+
+        /* before:
+        // PRIMARY KEY should always imply NOT NULL. Unfortunately, due to a
+        // bug in some early versions, this is not the case in SQLite.
+        //db.execSQL("CREATE TABLE " + TABLE_UPLOAD + " (" + " path TEXT PRIMARY KEY NOT NULL UNIQUE,"
+        //        + " uploadStatus INTEGER NOT NULL, uploadObject TEXT NOT NULL);");
+        // uploadStatus is used to easy filtering, it has precedence over
+        // uploadObject.getUploadStatus()
+        */
+    }
+
     /**
      * Version 10 of database does not modify its scheme. It coincides with the upgrade of the ownCloud account names
      * structure to include in it the path to the server instance. Updating the account names and path to local files
@@ -805,7 +885,7 @@ public class FileContentProvider extends ContentProvider {
      * @param db        Database where table of files is included.
      */
     private void updateAccountName(SQLiteDatabase db){
-        Log_OC.d("SQL", "THREAD:  "+ Thread.currentThread().getName());
+        Log_OC.d("SQL", "THREAD:  " + Thread.currentThread().getName());
         AccountManager ama = AccountManager.get(getContext());
         try {
             // get accounts from AccountManager ;  we can't be sure if accounts in it are updated or not although
@@ -866,10 +946,10 @@ public class FileContentProvider extends ContentProvider {
                 ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL";
 
         Cursor c = db.query(ProviderTableMeta.FILE_TABLE_NAME,
-                null,
-                whereClause,
-                new String[] { newAccountName },
-                null, null, null);
+            null,
+            whereClause,
+            new String[]{newAccountName},
+            null, null, null);
 
         try {
             if (c.moveToFirst()) {
@@ -909,4 +989,43 @@ public class FileContentProvider extends ContentProvider {
 
     }
 
+    /**
+     * Grants that total count of successful uploads stored is not greater than MAX_SUCCESSFUL_UPLOADS.
+     *
+     * Removes older uploads if needed.
+     */
+    private void trimSuccessfulUploads(SQLiteDatabase db) {
+        Cursor c = null;
+        try {
+            c = db.rawQuery(
+                "delete from " + ProviderTableMeta.UPLOADS_TABLE_NAME +
+                    " where " + ProviderTableMeta.UPLOADS_STATUS + " == "
+                    + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED.getValue() +
+                    " and " + ProviderTableMeta._ID +
+                    " not in (select " + ProviderTableMeta._ID +
+                    " from " + ProviderTableMeta.UPLOADS_TABLE_NAME +
+                    " where " + ProviderTableMeta.UPLOADS_STATUS + " == "
+                    + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED.getValue() +
+                    " order by " + ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP +
+                    " desc limit " + MAX_SUCCESSFUL_UPLOADS +
+                    ")",
+                null
+            );
+            c.moveToFirst(); // do something with the cursor, or deletion doesn't happen; true story
+
+        } catch (Exception e) {
+            Log_OC.e(
+                TAG,
+                "Something wrong trimming successful uploads, database could grow more than expected",
+                e
+            );
+
+        } finally {
+            if (c!= null) {
+                c.close();
+            }
+        }
+    }
+
+
 }

+ 6 - 0
src/com/owncloud/android/services/OperationsService.java

@@ -52,6 +52,7 @@ import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
 import com.owncloud.android.lib.resources.users.GetRemoteUserNameOperation;
+import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
 import com.owncloud.android.operations.CopyFileOperation;
 import com.owncloud.android.operations.CreateFolderOperation;
 import com.owncloud.android.operations.CreateShareViaLinkOperation;
@@ -114,6 +115,7 @@ public class OperationsService extends Service {
     public static final String ACTION_SYNC_FOLDER = "SYNC_FOLDER";
     public static final String ACTION_MOVE_FILE = "MOVE_FILE";
     public static final String ACTION_COPY_FILE = "COPY_FILE";
+    public static final String ACTION_CHECK_CURRENT_CREDENTIALS = "CHECK_CURRENT_CREDENTIALS";
 
     public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() +
             ".OPERATION_ADDED";
@@ -693,6 +695,10 @@ public class OperationsService extends Service {
                     String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
                     String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
                     operation = new CopyFileOperation(remotePath, newParentPath, account);
+
+                } else if (action.equals(ACTION_CHECK_CURRENT_CREDENTIALS)) {
+                    // Check validity of currently stored credentials for a given account
+                    operation = new CheckCurrentCredentialsOperation(account);
                 }
             }
                 

+ 7 - 8
src/com/owncloud/android/services/SyncFolderHandler.java

@@ -1,7 +1,7 @@
 /**
  *   ownCloud Android client application
  *
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -27,7 +27,6 @@ import android.os.Looper;
 import android.os.Message;
 import android.util.Pair;
 
-import com.owncloud.android.MainApp;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
@@ -82,7 +81,7 @@ class SyncFolderHandler extends Handler {
      */
     public boolean isSynchronizing(Account account, String remotePath) {
         if (account == null || remotePath == null) return false;
-        return (mPendingOperations.contains(account, remotePath));
+        return (mPendingOperations.contains(account.name, remotePath));
     }
 
 
@@ -100,7 +99,7 @@ class SyncFolderHandler extends Handler {
      */
     private void doOperation(Account account, String remotePath) {
 
-        mCurrentSyncOperation = mPendingOperations.get(account, remotePath);
+        mCurrentSyncOperation = mPendingOperations.get(account.name, remotePath);
 
         if (mCurrentSyncOperation != null) {
             RemoteOperationResult result = null;
@@ -127,7 +126,7 @@ class SyncFolderHandler extends Handler {
             } catch (IOException e) {
                 Log_OC.e(TAG, "Error while trying to get authorization", e);
             } finally {
-                mPendingOperations.removePayload(account, remotePath);
+                mPendingOperations.removePayload(account.name, remotePath);
 
                 mService.dispatchResultToOperationListeners(mCurrentSyncOperation, result);
 
@@ -139,7 +138,7 @@ class SyncFolderHandler extends Handler {
     public void add(Account account, String remotePath,
                     SynchronizeFolderOperation syncFolderOperation){
         Pair<String, String> putResult =
-                mPendingOperations.putIfAbsent(account, remotePath, syncFolderOperation);
+                mPendingOperations.putIfAbsent(account.name, remotePath, syncFolderOperation);
         if (putResult != null) {
             sendBroadcastNewSyncFolder(account, remotePath);    // TODO upgrade!
         }
@@ -149,7 +148,7 @@ class SyncFolderHandler extends Handler {
     /**
      * Cancels a pending or current sync' operation.
      *
-     * @param account       ownCloud account where the remote file is stored.
+     * @param account       ownCloud {@link Account} where the remote file is stored.
      * @param file          A file in the queue of pending synchronizations
      */
     public void cancel(Account account, OCFile file){
@@ -158,7 +157,7 @@ class SyncFolderHandler extends Handler {
             return;
         }
         Pair<SynchronizeFolderOperation, String> removeResult =
-                mPendingOperations.remove(account, file.getRemotePath());
+                mPendingOperations.remove(account.name, file.getRemotePath());
         SynchronizeFolderOperation synchronization = removeResult.first;
         if (synchronization != null) {
             synchronization.cancel();

+ 1 - 1
src/com/owncloud/android/services/observer/FileObserverService.java

@@ -3,7 +3,7 @@
  *
  *   @author David A. Velasco
  *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,

+ 1 - 1
src/com/owncloud/android/services/observer/FolderObserver.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,

+ 3 - 7
src/com/owncloud/android/syncadapter/FileSyncAdapter.java

@@ -300,9 +300,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
             
         } else if (result.getCode() != ResultCode.FILE_NOT_FOUND) {
             // in failures, the statistics for the global result are updated
-            if (    result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED ||
-                    result.isIdPRedirection()
-                ) {
+            if (RemoteOperationResult.ResultCode.UNAUTHORIZED.equals(result.getCode())) {
                 mSyncResult.stats.numAuthExceptions++;
                 
             } else if (result.getException() instanceof DavException) {
@@ -395,10 +393,8 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
     private void notifyFailedSynchronization() {
         NotificationCompat.Builder notificationBuilder = createNotificationBuilder();
         boolean needsToUpdateCredentials = (
-                mLastFailedResult != null && (  
-                        mLastFailedResult.getCode() == ResultCode.UNAUTHORIZED ||
-                        mLastFailedResult.isIdPRedirection()
-                )
+                mLastFailedResult != null &&
+                ResultCode.UNAUTHORIZED.equals(mLastFailedResult.getCode())
         );
         if (needsToUpdateCredentials) {
             // let the user update credentials with one click

+ 7 - 4
src/com/owncloud/android/ui/activity/ComponentsGetter.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -29,19 +29,22 @@ import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 public interface ComponentsGetter {
 
     /**
-     * To be invoked when the parent activity is fully created to get a reference  to the FileDownloader service API.
+     * To be invoked when the parent activity is fully created to get a reference
+     * to the FileDownloader service API.
      */
     public FileDownloaderBinder getFileDownloaderBinder();
 
     
     /**
-     * To be invoked when the parent activity is fully created to get a reference to the FileUploader service API.
+     * To be invoked when the parent activity is fully created to get a reference
+     * to the FileUploader service API.
      */
     public FileUploaderBinder getFileUploaderBinder();
 
     
     /**
-     * To be invoked when the parent activity is fully created to get a reference to the OperationsSerivce service API.
+     * To be invoked when the parent activity is fully created to get a reference
+     * to the OperationsSerivce service API.
      */
     public OperationsServiceBinder getOperationsServiceBinder();
 

+ 31 - 30
src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java

@@ -1,23 +1,22 @@
 /**
- *   ownCloud Android client application
+ *  ownCloud Android client application
  *
- *   @author Bartek Przybylski
- *   @author David A. Velasco
- *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *  @author Bartek Przybylski
+ *  @author David A. Velasco
+ *  Copyright (C) 2012 Bartek Przybylski
+ *  Copyright (C) 2016 ownCloud Inc.
  *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   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 <http://www.gnu.org/licenses/>.
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
  *
+ *  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.
+ *  <p/>
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.ui.activity;
@@ -33,6 +32,7 @@ import com.owncloud.android.ui.dialog.ConflictsResolveDialog;
 import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision;
 import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener;
 
+
 /**
  * Wrapper activity which will be launched if keep-in-sync file will be modified by external
  * application.
@@ -40,7 +40,7 @@ import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionM
 public class ConflictsResolveActivity extends FileActivity implements OnConflictDecisionMadeListener {
 
     private String TAG = ConflictsResolveActivity.class.getSimpleName();
-    
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -48,18 +48,20 @@ public class ConflictsResolveActivity extends FileActivity implements OnConflict
 
     @Override
     public void conflictDecisionMade(Decision decision) {
-        Intent i = new Intent(getApplicationContext(), FileUploader.class);
-        
+
+        Integer behaviour = null;
+        Boolean forceOverwrite = null;
+
         switch (decision) {
             case CANCEL:
                 finish();
                 return;
             case OVERWRITE:
                 // use local version -> overwrite on server
-                i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
+                forceOverwrite = true;
                 break;
             case KEEP_BOTH:
-                i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
+                behaviour = FileUploader.LOCAL_BEHAVIOUR_MOVE;
                 break;
             case SERVER:
                 // use server version -> delete local, request download
@@ -73,11 +75,9 @@ public class ConflictsResolveActivity extends FileActivity implements OnConflict
                 Log_OC.wtf(TAG, "Unhandled conflict decision " + decision);
                 return;
         }
-        i.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
-        i.putExtra(FileUploader.KEY_FILE, getFile());
-        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-        
-        startService(i);
+
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        requester.uploadUpdate(this, getAccount(), getFile(), behaviour, forceOverwrite);
         finish();
     }
 
@@ -91,21 +91,22 @@ public class ConflictsResolveActivity extends FileActivity implements OnConflict
                 finish();
             } else {
                 /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account
-                file = getStorageManager().getFileByPath(file.getRemotePath());   // file = null if not in the current Account
+                file = getStorageManager().getFileByPath(file.getRemotePath());   // file = null if not in the
+                // current Account
                 if (file != null) {
                     setFile(file);
                     ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(file.getRemotePath(), this);
                     d.showDialog(this);
-                    
+
                 } else {
                     // account was changed to a different one - just finish
                     finish();
                 }
             }
-            
+
         } else {
             finish();
         }
-        
+
     }
 }

+ 53 - 27
src/com/owncloud/android/ui/activity/FileActivity.java

@@ -3,7 +3,7 @@
  *
  *   @author David A. Velasco
  *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -111,8 +111,10 @@ public class FileActivity extends AppCompatActivity
     private static final String DIALOG_SHARE_PASSWORD = "DIALOG_SHARE_PASSWORD";
     private static final String KEY_ACTION_BAR_TITLE = "ACTION_BAR_TITLE";
 
-    protected static final long DELAY_TO_REQUEST_OPERATIONS_LATER = 200;
+    public static final int REQUEST_CODE__UPDATE_CREDENTIALS = 0;
+    public static final int REQUEST_CODE__LAST_SHARED = REQUEST_CODE__UPDATE_CREDENTIALS;
 
+    protected static final long DELAY_TO_REQUEST_OPERATIONS_LATER = 200;
 
     /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located.*/
     private Account mAccount;
@@ -278,7 +280,6 @@ public class FileActivity extends AppCompatActivity
         super.onPause();
     }
 
-
     @Override
     protected void onDestroy() {
         if (mOperationsServiceConnection != null) {
@@ -400,13 +401,18 @@ public class FileActivity extends AppCompatActivity
         //mDrawerItems.add(new NavigationDrawerItem(mDrawerTitles[2],
         //        mDrawerContentDescriptions[2]));
 
+        // Uploads
+        mDrawerItems.add(new NavigationDrawerItem(mDrawerTitles[1], mDrawerContentDescriptions[2],
+                R.drawable.ic_uploads));
+
         // Settings
-        mDrawerItems.add(new NavigationDrawerItem(mDrawerTitles[1], mDrawerContentDescriptions[1],
+        mDrawerItems.add(new NavigationDrawerItem(mDrawerTitles[2], mDrawerContentDescriptions[1],
                 R.drawable.ic_settings));
+
         // Logs
         if (BuildConfig.DEBUG) {
-            mDrawerItems.add(new NavigationDrawerItem(mDrawerTitles[2],
-                    mDrawerContentDescriptions[2],R.drawable.ic_log));
+            mDrawerItems.add(new NavigationDrawerItem(mDrawerTitles[3],
+                    mDrawerContentDescriptions[3], R.drawable.ic_log));
         }
 
         // setting the nav drawer list adapter
@@ -417,6 +423,7 @@ public class FileActivity extends AppCompatActivity
 
         mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,R.string.drawer_open,R.string.drawer_close) {
 
+
             /** Called when a drawer has settled in a completely closed state. */
             public void onDrawerClosed(View view) {
                 super.onDrawerClosed(view);
@@ -448,7 +455,7 @@ public class FileActivity extends AppCompatActivity
      * @param navigationDrawerLayout the drawer layout to be used
      * @param account                the account to be set in the drawer
      */
-    protected void setUsernameInDrawer(RelativeLayout navigationDrawerLayout, Account account) {
+    protected void setUsernameInDrawer(View navigationDrawerLayout, Account account) {
         if (navigationDrawerLayout != null && account != null) {
             TextView username = (TextView) navigationDrawerLayout.findViewById(R.id.drawer_username);
             int lastAtPos = account.name.lastIndexOf("@");
@@ -462,7 +469,7 @@ public class FileActivity extends AppCompatActivity
      * Assumes that navigation drawer is NOT visible.
      */
     protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) {
-        String title = getString(R.string.default_display_name_for_root_folder);    // default
+        String title = getDefaultTitle();    // default
         boolean inRoot;
 
         /// choose the appropiate title
@@ -495,6 +502,9 @@ public class FileActivity extends AppCompatActivity
 
     }
 
+    protected String getDefaultTitle() {
+        return getString(R.string.default_display_name_for_root_folder);
+    }
 
     /**
      *  Sets and validates the ownCloud {@link Account} associated to the Activity.
@@ -725,7 +735,7 @@ public class FileActivity extends AppCompatActivity
 
     /**
      *
-     * @param operation     Removal operation performed.
+     * @param operation     Operation performed.
      * @param result        Result of the removal.
      */
     @Override
@@ -739,14 +749,12 @@ public class FileActivity extends AppCompatActivity
 
         if (!result.isSuccess() && (
                 result.getCode() == ResultCode.UNAUTHORIZED ||
-                result.isIdPRedirection() ||
                 (result.isException() && result.getException() instanceof AuthenticatorException)
                 )) {
 
             requestCredentialsUpdate(this);
 
             if (result.getCode() == ResultCode.UNAUTHORIZED) {
-                dismissLoadingDialog();
                 Toast t = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result,
                                 operation, getResources()),
                         Toast.LENGTH_LONG);
@@ -793,16 +801,34 @@ public class FileActivity extends AppCompatActivity
      * Invalidates the credentials stored for the current OC account and requests new credentials to the user,
      * navigating to {@link AuthenticatorActivity}
      *
+     * Equivalent to call requestCredentialsUpdate(context, null);
+     *
      * @param context   Android Context needed to access the {@link AccountManager}. Received as a parameter
      *                  to make the method accessible to {@link android.content.BroadcastReceiver}s.
      */
     protected void requestCredentialsUpdate(Context context) {
+        requestCredentialsUpdate(context, null);
+    }
+
+    /**
+     * Invalidates the credentials stored for the given OC account and requests new credentials to the user,
+     * navigating to {@link AuthenticatorActivity}
+     *
+     * @param context   Android Context needed to access the {@link AccountManager}. Received as a parameter
+     *                  to make the method accessible to {@link android.content.BroadcastReceiver}s.
+     * @param account   Stored OC account to request credentials update for. If null, current account will
+     *                  be used.
+     */
+    protected void requestCredentialsUpdate(Context context, Account account) {
 
         try {
             /// step 1 - invalidate credentials of current account
+            if (account == null) {
+                account = getAccount();
+            }
             OwnCloudClient client;
             OwnCloudAccount ocAccount =
-                    new OwnCloudAccount(getAccount(), context);
+                    new OwnCloudAccount(account, context);
             client = (OwnCloudClientManagerFactory.getDefaultSingleton().
                     removeClientFor(ocAccount));
             if (client != null) {
@@ -811,23 +837,23 @@ public class FileActivity extends AppCompatActivity
                     AccountManager am = AccountManager.get(context);
                     if (cred.authTokenExpires()) {
                         am.invalidateAuthToken(
-                                getAccount().type,
+                                account.type,
                                 cred.getAuthToken()
                         );
                     } else {
-                        am.clearPassword(getAccount());
+                        am.clearPassword(account);
                     }
                 }
             }
 
             /// step 2 - request credentials to user
             Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
-            updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, getAccount());
+            updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account);
             updateAccountCredentials.putExtra(
                     AuthenticatorActivity.EXTRA_ACTION,
                     AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN);
             updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            startActivity(updateAccountCredentials);
+            startActivityForResult(updateAccountCredentials, REQUEST_CODE__UPDATE_CREDENTIALS);
 
         } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
             Toast.makeText(context, R.string.auth_account_does_not_exist, Toast.LENGTH_SHORT).show();
@@ -997,11 +1023,6 @@ public class FileActivity extends AppCompatActivity
         startActivity(i);
     }
 
-//    TODO re-enable when "Accounts" is available in Navigation Drawer
-//    public void closeDrawer() {
-//        mDrawerLayout.closeDrawers();
-//    }
-
     public void allFilesOption(){
         restart();
     }
@@ -1023,29 +1044,34 @@ public class FileActivity extends AppCompatActivity
 
                 case 0: // All Files
                     allFilesOption();
-                    mDrawerLayout.closeDrawers();
                     break;
 
                 // TODO Enable when "On Device" is recovered ?
 //                case 2:
 //                    MainApp.showOnlyFilesOnDevice(true);
-//                    mDrawerLayout.closeDrawers();
 //                    break;
 
-                case 1: // Settings
+                case 1: // Uploads
+                    Intent uploadListIntent = new Intent(getApplicationContext(),
+                            UploadListActivity.class);
+                    uploadListIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                    startActivity(uploadListIntent);
+                    break;
+
+                case 2: // Settings
                     Intent settingsIntent = new Intent(getApplicationContext(),
                             Preferences.class);
                     startActivity(settingsIntent);
-                    mDrawerLayout.closeDrawers();
                     break;
 
-                case 2: // Logs
+                case 3: // Logs
                     Intent loggerIntent = new Intent(getApplicationContext(),
                             LogHistoryActivity.class);
                     startActivity(loggerIntent);
-                    mDrawerLayout.closeDrawers();
                     break;
             }
+            mDrawerLayout.closeDrawers();
         }
     }
+
 }

+ 148 - 131
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -1,23 +1,22 @@
 /**
- *   ownCloud Android client application
+ *  ownCloud Android client application
  *
- *   @author Bartek Przybylski
- *   @author David A. Velasco
- *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *  @author Bartek Przybylski
+ *  @author David A. Velasco
+ *  Copyright (C) 2011  Bartek Przybylski
+ *  Copyright (C) 2016 ownCloud Inc.
  *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
  *
- *   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 <http://www.gnu.org/licenses/>.
+ *  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 <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.ui.activity;
@@ -57,7 +56,6 @@ import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
 import android.widget.Toast;
 
 import com.owncloud.android.MainApp;
@@ -80,10 +78,10 @@ import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.services.observer.FileObserverService;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
-import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
 import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
 import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
 import com.owncloud.android.ui.fragment.FileDetailFragment;
@@ -103,11 +101,11 @@ import com.owncloud.android.utils.UriUtils;
 import java.io.File;
 
 /**
- * Displays, what files the user has available in his ownCloud.
+ * Displays, what files the user has available in his ownCloud. This is the main view.
  */
 
-public class FileDisplayActivity extends HookActivity
-        implements FileFragment.ContainerActivity,
+public class FileDisplayActivity extends HookActivity implements
+        FileFragment.ContainerActivity,
         OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
     private SyncBroadcastReceiver mSyncBroadcastReceiver;
@@ -126,10 +124,10 @@ public class FileDisplayActivity extends HookActivity
 
     public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS";
 
-    public static final int ACTION_SELECT_CONTENT_FROM_APPS = 1;
-    public static final int ACTION_SELECT_MULTIPLE_FILES = 2;
-    public static final int ACTION_MOVE_FILES = 3;
-    public static final int ACTION_COPY_FILES = 4;
+    public static final int REQUEST_CODE__SELECT_CONTENT_FROM_APPS = REQUEST_CODE__LAST_SHARED + 1;
+    public static final int REQUEST_CODE__SELECT_MULTIPLE_FILES = REQUEST_CODE__LAST_SHARED + 2;
+    public static final int REQUEST_CODE__MOVE_FILES = REQUEST_CODE__LAST_SHARED + 3;
+    public static final int REQUEST_CODE__COPY_FILES = REQUEST_CODE__LAST_SHARED + 4;
 
     private static final String TAG = FileDisplayActivity.class.getSimpleName();
 
@@ -152,7 +150,7 @@ public class FileDisplayActivity extends HookActivity
         Log_OC.v(TAG, "onCreate() start");
 
         super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account
-                                            // is valid
+        // is valid
 
         /// grant that FileObserverService is watching favorite files
         if (savedInstanceState == null) {
@@ -161,7 +159,7 @@ public class FileDisplayActivity extends HookActivity
         }
 
         /// Load of saved instance state
-        if(savedInstanceState != null) {
+        if (savedInstanceState != null) {
             mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(
                     FileDisplayActivity.KEY_WAITING_TO_PREVIEW);
             mSyncInProgress = savedInstanceState.getBoolean(KEY_SYNC_IN_PROGRESS);
@@ -192,8 +190,8 @@ public class FileDisplayActivity extends HookActivity
 
         // Action bar setup
         getSupportActionBar().setHomeButtonEnabled(true);       // mandatory since Android ICS,
-                                                                // according to the official
-                                                                // documentation
+        // according to the official
+        // documentation
 
         // enable ActionBar app icon to behave as action to toggle nav drawer
         //getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -297,7 +295,7 @@ public class FileDisplayActivity extends HookActivity
                     // cache until the upload is successful get parent from path
                     parentPath = file.getRemotePath().substring(0,
                             file.getRemotePath().lastIndexOf(file.getFileName()));
-                    if (getStorageManager().getFileByPath(parentPath) ==  null)
+                    if (getStorageManager().getFileByPath(parentPath) == null)
                         file = null; // not able to know the directory where the file is uploading
                 } else {
                     file = getStorageManager().getFileByPath(file.getRemotePath());
@@ -311,7 +309,7 @@ public class FileDisplayActivity extends HookActivity
             setFile(file);
 
             if (mAccountWasSet) {
-                setUsernameInDrawer((RelativeLayout) findViewById(R.id.left_drawer), getAccount());
+                setUsernameInDrawer(findViewById(R.id.left_drawer), getAccount());
             }
 
             if (!stateWasRecovered) {
@@ -355,7 +353,6 @@ public class FileDisplayActivity extends HookActivity
                 setSecondFragment(secondFragment);
                 updateFragmentsVisibility(true);
                 updateActionBarTitleAndHomeButton(file);
-
             } else {
                 cleanSecondFragment();
                 if (file.isDown() && PreviewTextFragment.canBePreviewed(file))
@@ -389,8 +386,8 @@ public class FileDisplayActivity extends HookActivity
             } else if (file.isDown() && PreviewTextFragment.canBePreviewed(file)) {
                 secondFragment = null;
             } else {
-            secondFragment = FileDetailFragment.newInstance(file, getAccount());
-        }
+                secondFragment = FileDetailFragment.newInstance(file, getAccount());
+            }
         }
         return secondFragment;
     }
@@ -488,7 +485,7 @@ public class FileDisplayActivity extends HookActivity
             OCFile fileInFragment = detailsFragment.getFile();
             if (fileInFragment != null &&
                     !downloadedRemotePath.equals(fileInFragment.getRemotePath())) {
-                // the user browsed to other file ; forget the automatic preview 
+                // the user browsed to other file ; forget the automatic preview
                 mWaitingToPreview = null;
 
             } else if (downloadEvent.equals(FileDownloader.getDownloadAddedMessage())) {
@@ -503,7 +500,7 @@ public class FileDisplayActivity extends HookActivity
                     if (success) {
                         mWaitingToPreview = getStorageManager().getFileById(
                                 mWaitingToPreview.getFileId());   // update the file from database,
-                                                                  // for the local storage path
+                        // for the local storage path
                         if (PreviewMediaFragment.canBePreviewed(mWaitingToPreview)) {
                             startMediaPreview(mWaitingToPreview, 0, true);
                             detailsFragmentChanged = true;
@@ -540,7 +537,7 @@ public class FileDisplayActivity extends HookActivity
         menu.findItem(R.id.action_create_dir).setVisible(false);
         return true;
     }
-    
+
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
@@ -555,7 +552,7 @@ public class FileDisplayActivity extends HookActivity
                 OCFile currentDir = getCurrentDir();
                 if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                     mDrawerLayout.closeDrawer(GravityCompat.START);
-                } else if((currentDir != null && currentDir.getParentId() != 0) ||
+                } else if ((currentDir != null && currentDir.getParentId() != 0) ||
                         (second != null && second.getFile() != null)) {
                     onBackPressed();
 
@@ -574,26 +571,26 @@ public class FileDisplayActivity extends HookActivity
 
                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
                 builder.setTitle(R.string.actionbar_sort_title)
-                        .setSingleChoiceItems(R.array.actionbar_sortby, sortOrder ,
+                        .setSingleChoiceItems(R.array.actionbar_sortby, sortOrder,
                                 new DialogInterface.OnClickListener() {
-                            public void onClick(DialogInterface dialog, int which) {
-                                switch (which){
-                                    case 0:
-                                        sortByName(true);
-                                        break;
-                                    case 1:
-                                        sortByDate(false);
-                                        break;
-                                }
-
-                                dialog.dismiss();
-                            }
-                        });
+                                    public void onClick(DialogInterface dialog, int which) {
+                                        switch (which) {
+                                            case 0:
+                                                sortByName(true);
+                                                break;
+                                            case 1:
+                                                sortByDate(false);
+                                                break;
+                                        }
+
+                                        dialog.dismiss();
+                                    }
+                                });
                 builder.create().show();
                 break;
             }
-            case R.id.action_switch_view:{
-                if (isGridView()){
+            case R.id.action_switch_view: {
+                if (isGridView()) {
                     item.setTitle(getString(R.string.action_switch_grid_view));
                     item.setIcon(ContextCompat.getDrawable(getApplicationContext(),
                             R.drawable.ic_view_module));
@@ -606,8 +603,8 @@ public class FileDisplayActivity extends HookActivity
                 }
                 return true;
             }
-        default:
-            retval = super.onOptionsItemSelected(item);
+            default:
+                retval = super.onOptionsItemSelected(item);
         }
         return retval;
     }
@@ -646,13 +643,12 @@ public class FileDisplayActivity extends HookActivity
 
     /**
      * Called, when the user selected something for uploading
-     *
      */
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 
-        if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK ||
+        if (requestCode == REQUEST_CODE__SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK ||
                 resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
 
             //getClipData is only supported on api level 16+, Jelly Bean
@@ -660,7 +656,7 @@ public class FileDisplayActivity extends HookActivity
                     data.getClipData() != null &&
                     data.getClipData().getItemCount() > 0) {
 
-                for( int i = 0; i < data.getClipData().getItemCount(); i++){
+                for (int i = 0; i < data.getClipData().getItemCount(); i++) {
                     Intent intent = new Intent();
                     intent.setData(data.getClipData().getItemAt(i).getUri());
                     requestSimpleUpload(intent, resultCode);
@@ -669,11 +665,11 @@ public class FileDisplayActivity extends HookActivity
             } else {
                 requestSimpleUpload(data, resultCode);
             }
-        } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK ||
+        } else if (requestCode == REQUEST_CODE__SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK ||
                 resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
             requestMultipleUpload(data, resultCode);
 
-        } else if (requestCode == ACTION_MOVE_FILES && resultCode == RESULT_OK){
+        } else if (requestCode == REQUEST_CODE__MOVE_FILES && resultCode == RESULT_OK) {
             final Intent fData = data;
             final int fResultCode = resultCode;
             getHandler().postDelayed(
@@ -686,7 +682,7 @@ public class FileDisplayActivity extends HookActivity
                     DELAY_TO_REQUEST_OPERATIONS_LATER
             );
 
-        } else if (requestCode == ACTION_COPY_FILES && resultCode == RESULT_OK) {
+        } else if (requestCode == REQUEST_CODE__COPY_FILES && resultCode == RESULT_OK) {
 
             final Intent fData = data;
             final int fResultCode = resultCode;
@@ -711,18 +707,23 @@ public class FileDisplayActivity extends HookActivity
         if (filePaths != null) {
             String[] remotePaths = new String[filePaths.length];
             String remotePathBase = getCurrentDir().getRemotePath();
-            for (int j = 0; j< remotePaths.length; j++) {
+            for (int j = 0; j < remotePaths.length; j++) {
                 remotePaths[j] = remotePathBase + (new File(filePaths[j])).getName();
             }
 
-            Intent i = new Intent(this, FileUploader.class);
-            i.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
-            i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths);
-            i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
-            i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
-            if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)
-                i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
-            startService(i);
+            int behaviour = (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) ? FileUploader
+                    .LOCAL_BEHAVIOUR_MOVE : FileUploader.LOCAL_BEHAVIOUR_COPY;
+            FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+            requester.uploadNewFile(
+                    this,
+                    getAccount(),
+                    filePaths,
+                    remotePaths,
+                    null,           // MIME type will be detected from file name
+                    behaviour,
+                    false,          // do not create parent folder if not existent
+                    UploadFileOperation.CREATED_BY_USER
+            );
 
         } else {
             Log_OC.d(TAG, "User clicked on 'Update' with no selection");
@@ -768,9 +769,10 @@ public class FileDisplayActivity extends HookActivity
         }
 
         Intent i = new Intent(this, FileUploader.class);
-        i.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
+        i.putExtra(FileUploader.KEY_ACCOUNT,
+                getAccount());
         OCFile currentDir = getCurrentDir();
-        String remotePath =  (currentDir != null) ? currentDir.getRemotePath() : OCFile.ROOT_PATH;
+        String remotePath = (currentDir != null) ? currentDir.getRemotePath() : OCFile.ROOT_PATH;
 
         if (filePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
             Cursor cursor = getContentResolver().query(Uri.parse(filePath), null, null, null, null);
@@ -778,7 +780,7 @@ public class FileDisplayActivity extends HookActivity
                 if (cursor != null && cursor.moveToFirst()) {
                     String displayName = cursor.getString(cursor.getColumnIndex(
                             OpenableColumns.DISPLAY_NAME));
-                    Log_OC.v(TAG, "Display Name: " + displayName );
+                    Log_OC.v(TAG, "Display Name: " + displayName);
 
                     displayName.replace(File.separatorChar, '_');
                     displayName.replace(File.pathSeparatorChar, '_');
@@ -794,13 +796,20 @@ public class FileDisplayActivity extends HookActivity
             remotePath += new File(filePath).getName();
         }
 
-        i.putExtra(FileUploader.KEY_LOCAL_FILE, filePath);
-        i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePath);
-        i.putExtra(FileUploader.KEY_MIME_TYPE, mimeType);
-        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-        if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)
-        i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
-        startService(i);
+        int behaviour = (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) ? FileUploader.LOCAL_BEHAVIOUR_MOVE :
+                FileUploader.LOCAL_BEHAVIOUR_COPY;
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        requester.uploadNewFile(
+                this,
+                getAccount(),
+                filePath,
+                remotePath,
+                behaviour,
+                mimeType,
+                false,          // do not create parent folder if not existent
+                UploadFileOperation.CREATED_BY_USER
+        );
+
     }
 
     /**
@@ -882,7 +891,6 @@ public class FileDisplayActivity extends HookActivity
         Log_OC.v(TAG, "onSaveInstanceState() end");
     }
 
-
     @Override
     protected void onResume() {
         Log_OC.v(TAG, "onResume() start");
@@ -964,6 +972,7 @@ public class FileDisplayActivity extends HookActivity
                 String event = intent.getAction();
                 Log_OC.d(TAG, "Received broadcast " + event);
                 String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
+
                 String synchFolderRemotePath =
                         intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH);
                 RemoteOperationResult synchResult =
@@ -982,16 +991,17 @@ public class FileDisplayActivity extends HookActivity
                                 getStorageManager().getFileByPath(getFile().getRemotePath());
                         OCFile currentDir = (getCurrentDir() == null) ? null :
                                 getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
-    
+
                         if (currentDir == null) {
                             // current folder was removed from the server 
-                            Toast.makeText( FileDisplayActivity.this, 
-                                            String.format(
-                                                    getString(R.string.
-                                                            sync_current_folder_was_removed),
-                                                   synchFolderRemotePath),
-                                            Toast.LENGTH_LONG)
-                                .show();
+                            Toast.makeText(FileDisplayActivity.this,
+                                    String.format(
+                                            getString(R.string.
+                                                    sync_current_folder_was_removed),
+                                            synchFolderRemotePath),
+
+                                    Toast.LENGTH_LONG)
+                                    .show();
 
                             browseToRoot();
 
@@ -1019,13 +1029,12 @@ public class FileDisplayActivity extends HookActivity
                         mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
                                 !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED
                                         .equals(event));
-                                
+
                         if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
-                                    equals(event) &&/// TODO refactor and make common
+                                equals(event) &&/// TODO refactor and make common
 
                                 synchResult != null && !synchResult.isSuccess() &&
-                                (synchResult.getCode() == ResultCode.UNAUTHORIZED ||
-                                        synchResult.isIdPRedirection() ||
+                                (ResultCode.UNAUTHORIZED.equals(synchResult.getCode()) ||
                                         (synchResult.isException() && synchResult.getException()
                                                 instanceof AuthenticatorException))) {
 
@@ -1049,7 +1058,7 @@ public class FileDisplayActivity extends HookActivity
                     }
                 }
             } catch (RuntimeException e) {
-                // avoid app crashes after changing the serial id of RemoteOperationResult 
+                // avoid app crashes after changing the serial id of RemoteOperationResult
                 // in owncloud library with broadcast notifications pending to process
                 removeStickyBroadcast(intent);
             }
@@ -1102,10 +1111,12 @@ public class FileDisplayActivity extends HookActivity
                     }
                 }
 
-                boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT,
+                boolean uploadWasFine = intent.getBooleanExtra(
+                        FileUploader.EXTRA_UPLOAD_RESULT,
                         false);
                 boolean renamedInUpload = getFile().getRemotePath().
                         equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));
+
                 boolean sameFile = getFile().getRemotePath().equals(uploadedRemotePath) ||
                         renamedInUpload;
                 FileFragment details = getSecondFragment();
@@ -1115,6 +1126,8 @@ public class FileDisplayActivity extends HookActivity
                 if (sameAccount && sameFile && detailFragmentIsShown) {
                     if (uploadWasFine) {
                         setFile(getStorageManager().getFileByPath(uploadedRemotePath));
+                    } else {
+                        //TODO remove upload progress bar after upload failed.
                     }
                     if (renamedInUpload) {
                         String newName = (new File(uploadedRemotePath)).getName();
@@ -1144,6 +1157,7 @@ public class FileDisplayActivity extends HookActivity
                 }
 
                 mProgressBar.setIndeterminate(false);
+
             } finally {
                 if (intent != null) {
                     removeStickyBroadcast(intent);
@@ -1161,13 +1175,12 @@ public class FileDisplayActivity extends HookActivity
             );
         }
 
-
     }
 
 
     /**
      * Class waiting for broadcast events from the {@link FileDownloader} service.
-     *
+     * <p/>
      * Updates the UI when a download is started or finished, provided that it is relevant for the
      * current folder.
      */
@@ -1212,17 +1225,17 @@ public class FileDisplayActivity extends HookActivity
         private boolean isDescendant(String downloadedRemotePath) {
             OCFile currentDir = getCurrentDir();
             return (
-                currentDir != null &&
-                downloadedRemotePath != null &&
-                downloadedRemotePath.startsWith(currentDir.getRemotePath())
+                    currentDir != null &&
+                            downloadedRemotePath != null &&
+                            downloadedRemotePath.startsWith(currentDir.getRemotePath())
             );
         }
 
         private boolean isAscendant(String linkedToRemotePath) {
             OCFile currentDir = getCurrentDir();
             return (
-                currentDir != null &&
-                currentDir.getRemotePath().startsWith(linkedToRemotePath)
+                    currentDir != null &&
+                            currentDir.getRemotePath().startsWith(linkedToRemotePath)
             );
         }
 
@@ -1250,7 +1263,6 @@ public class FileDisplayActivity extends HookActivity
 
     /**
      * {@inheritDoc}
-     * <p/>
      * Updates action bar and second fragment, if in dual pane mode.
      */
     @Override
@@ -1306,7 +1318,7 @@ public class FileDisplayActivity extends HookActivity
                 mDownloaderBinder = (FileDownloaderBinder) service;
                 if (mWaitingToPreview != null)
                     if (getStorageManager() != null) {
-                         // update the file
+                        // update the file
                         mWaitingToPreview =
                                 getStorageManager().getFileById(mWaitingToPreview.getFileId());
                         if (!mWaitingToPreview.isDown()) {
@@ -1425,9 +1437,9 @@ public class FileDisplayActivity extends HookActivity
     /**
      * Updates the view associated to the activity after the finish of an operation trying to
      * remove a file.
-     * 
-     * @param operation     Removal operation performed.
-     * @param result        Result of the removal.
+     *
+     * @param operation Removal operation performed.
+     * @param result    Result of the removal.
      */
     private void onRemoveFileOperationFinish(RemoveFileOperation operation,
                                              RemoteOperationResult result) {
@@ -1446,7 +1458,7 @@ public class FileDisplayActivity extends HookActivity
                 setFile(getStorageManager().getFileById(removedFile.getParentId()));
                 cleanSecondFragment();
             }
-            if (getStorageManager().getFileById(removedFile.getParentId()).equals(getCurrentDir())){
+            if (getStorageManager().getFileById(removedFile.getParentId()).equals(getCurrentDir())) {
                 refreshListOfFilesFragment();
             }
             invalidateOptionsMenu();
@@ -1509,9 +1521,9 @@ public class FileDisplayActivity extends HookActivity
     /**
      * Updates the view associated to the activity after the finish of an operation trying to rename
      * a file.
-     * 
-     * @param operation     Renaming operation performed.
-     * @param result        Result of the renaming.
+     *
+     * @param operation Renaming operation performed.
+     * @param result    Result of the renaming.
      */
     private void onRenameFileOperationFinish(RenameFileOperation operation,
                                              RemoteOperationResult result) {
@@ -1520,7 +1532,7 @@ public class FileDisplayActivity extends HookActivity
             FileFragment details = getSecondFragment();
             if (details != null) {
                 if (details instanceof FileDetailFragment &&
-                        renamedFile.equals(details.getFile()) ) {
+                        renamedFile.equals(details.getFile())) {
                     ((FileDetailFragment) details).updateFileDetails(renamedFile, getAccount());
                     showDetails(renamedFile);
 
@@ -1544,7 +1556,7 @@ public class FileDisplayActivity extends HookActivity
                 }
             }
 
-            if (getStorageManager().getFileById(renamedFile.getParentId()).equals(getCurrentDir())){
+            if (getStorageManager().getFileById(renamedFile.getParentId()).equals(getCurrentDir())) {
                 refreshListOfFilesFragment();
             }
 
@@ -1561,6 +1573,7 @@ public class FileDisplayActivity extends HookActivity
         }
     }
 
+
     private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation,
                                                   RemoteOperationResult result) {
         if (result.isSuccess()) {
@@ -1576,9 +1589,9 @@ public class FileDisplayActivity extends HookActivity
     /**
      * Updates the view associated to the activity after the finish of an operation trying create a
      * new folder
-     * 
-     * @param operation     Creation operation performed.
-     * @param result        Result of the creation.
+     *
+     * @param operation Creation operation performed.
+     * @param result    Result of the creation.
      */
     private void onCreateFolderOperationFinish(CreateFolderOperation operation,
                                                RemoteOperationResult result) {
@@ -1606,7 +1619,7 @@ public class FileDisplayActivity extends HookActivity
         refreshListOfFilesFragment();
         FileFragment details = getSecondFragment();
         if (details != null && details instanceof FileDetailFragment &&
-                file.equals(details.getFile()) ) {
+                file.equals(details.getFile())) {
             if (downloading || uploading) {
                 ((FileDetailFragment) details).updateFileDetails(file, getAccount());
             } else {
@@ -1649,16 +1662,16 @@ public class FileDisplayActivity extends HookActivity
 
     /**
      * Starts an operation to refresh the requested folder.
-     *
+     * <p/>
      * The operation is run in a new background thread created on the fly.
-     *
+     * <p/>
      * The refresh updates is a "light sync": properties of regular files in folder are updated (including
      * associated shares), but not their contents. Only the contents of files marked to be kept-in-sync are
      * synchronized too.
      *
-     * @param folder        Folder to refresh.
-     * @param ignoreETag    If 'true', the data from the server will be fetched and sync'ed even if the eTag
-     *                      didn't change.
+     * @param folder     Folder to refresh.
+     * @param ignoreETag If 'true', the data from the server will be fetched and sync'ed even if the eTag
+     *                   didn't change.
      */
     public void startSyncFolderOperation(final OCFile folder, final boolean ignoreETag) {
 
@@ -1755,16 +1768,17 @@ public class FileDisplayActivity extends HookActivity
         showDetailsIntent.putExtra(EXTRA_FILE, file);
         showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
         startActivity(showDetailsIntent);
+
     }
 
     /**
      * Stars the preview of an already down media {@link OCFile}.
      *
-     * @param file                      Media {@link OCFile} to preview.
-     * @param startPlaybackPosition     Media position where the playback will be started,
-     *                                  in milliseconds.
-     * @param autoplay                  When 'true', the playback will start without user
-     *                                  interactions.
+     * @param file                  Media {@link OCFile} to preview.
+     * @param startPlaybackPosition Media position where the playback will be started,
+     *                              in milliseconds.
+     * @param autoplay              When 'true', the playback will start without user
+     *                              interactions.
      */
     public void startMediaPreview(OCFile file, int startPlaybackPosition, boolean autoplay) {
         Fragment mediaFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition,
@@ -1856,9 +1870,12 @@ public class FileDisplayActivity extends HookActivity
     private void sortByName(boolean ascending) {
         getListOfFilesFragment().sortByName(ascending);
     }
-    private boolean isGridView(){ return getListOfFilesFragment().isGridView(); }
 
-   public void allFilesOption() {
-       browseToRoot();
-   }
+    private boolean isGridView() {
+        return getListOfFilesFragment().isGridView();
+    }
+
+    public void allFilesOption() {
+        browseToRoot();
+    }
 }

+ 4 - 10
src/com/owncloud/android/ui/activity/FolderPickerActivity.java

@@ -1,7 +1,7 @@
 /**
  *   ownCloud Android client application
  *
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -20,7 +20,6 @@
 package com.owncloud.android.ui.activity;
 
 import android.accounts.Account;
-import android.accounts.AccountManager;
 import android.accounts.AuthenticatorException;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -44,11 +43,6 @@ import android.widget.Toast;
 
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
-import com.owncloud.android.lib.common.OwnCloudCredentials;
-import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
@@ -379,6 +373,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                 data.putExtra(EXTRA_FILE, targetFile);
             }
             setResult(RESULT_OK, data);
+
             finish();
         }
     }
@@ -486,9 +481,8 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                                     equals(event) &&
                                 /// TODO refactor and make common
                                 synchResult != null && !synchResult.isSuccess() &&  
-                                (synchResult.getCode() == ResultCode.UNAUTHORIZED   || 
-                                    synchResult.isIdPRedirection()                  ||
-                                    (synchResult.isException() && synchResult.getException() 
+                                (ResultCode.UNAUTHORIZED.equals(synchResult.getCode()) ||
+                                    (synchResult.isException() && synchResult.getException()
                                             instanceof AuthenticatorException))) {
 
                             requestCredentialsUpdate(context);

+ 12 - 11
src/com/owncloud/android/ui/activity/Preferences.java

@@ -4,7 +4,7 @@
  *   @author Bartek Przybylski
  *   @author David A. Velasco
  *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -71,7 +71,6 @@ import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.db.DbHandler;
 import com.owncloud.android.files.FileOperationsHelper;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileUploader;
@@ -92,12 +91,12 @@ public class Preferences extends PreferenceActivity
     
     private static final String TAG = Preferences.class.getSimpleName();
 
+
     private static final int ACTION_SELECT_UPLOAD_PATH = 1;
     private static final int ACTION_SELECT_UPLOAD_VIDEO_PATH = 2;
     private static final int ACTION_REQUEST_PASSCODE = 5;
     private static final int ACTION_CONFIRM_PASSCODE = 6;
 
-    private DbHandler mDbHandler;
     private CheckBoxPreference pCode;
     private Preference pAboutApp;
     private AppCompatDelegate mDelegate;
@@ -128,7 +127,6 @@ public class Preferences extends PreferenceActivity
         getDelegate().installViewFactory();
         getDelegate().onCreate(savedInstanceState);
         super.onCreate(savedInstanceState);
-        mDbHandler = new DbHandler(getBaseContext());
         addPreferencesFromResource(R.xml.preferences);
 
         ActionBar actionBar = getSupportActionBar();
@@ -307,7 +305,8 @@ public class Preferences extends PreferenceActivity
                         String username = currentAccount.name.substring(0,
                                 currentAccount.name.lastIndexOf('@'));
                         
-                        String recommendSubject = String.format(getString(R.string.recommend_subject),
+                        String recommendSubject =
+                                String.format(getString(R.string.recommend_subject),
                                 appName);
                         String recommendText = String.format(getString(R.string.recommend_text),
                                 appName, downloadUrl);
@@ -334,7 +333,8 @@ public class Preferences extends PreferenceActivity
                     @Override
                     public boolean onPreferenceClick(Preference preference) {
                         String feedbackMail   =(String) getText(R.string.mail_feedback);
-                        String feedback   =(String) getText(R.string.prefs_feedback) + " - android v" + appVersion;
+                        String feedback   =(String) getText(R.string.prefs_feedback) +
+                                " - android v" + appVersion;
                         Intent intent = new Intent(Intent.ACTION_SENDTO); 
                         intent.setType("text/plain");
                         intent.putExtra(Intent.EXTRA_SUBJECT, feedback);
@@ -453,7 +453,8 @@ public class Preferences extends PreferenceActivity
         /* About App */
        pAboutApp = (Preference) findPreference("about_app");
        if (pAboutApp != null) { 
-               pAboutApp.setTitle(String.format(getString(R.string.about_android), getString(R.string.app_name)));
+               pAboutApp.setTitle(String.format(getString(R.string.about_android),
+                       getString(R.string.app_name)));
                pAboutApp.setSummary(String.format(getString(R.string.about_version), appVersion));
        }
 
@@ -692,8 +693,6 @@ public class Preferences extends PreferenceActivity
 
     @Override
     protected void onDestroy() {
-        mDbHandler.close();
-
         if (mDownloadServiceConnection != null) {
             unbindService(mDownloadServiceConnection);
             mDownloadServiceConnection = null;
@@ -912,7 +911,8 @@ public class Preferences extends PreferenceActivity
             if (component.equals(new ComponentName(Preferences.this, FileDownloader.class))) {
                 mDownloaderBinder = (FileDownloader.FileDownloaderBinder) service;
 
-            } else if (component.equals(new ComponentName(Preferences.this, FileUploader.class))) {
+            } else if (component.equals(new ComponentName(Preferences.this,
+                    FileUploader.class))) {
                 Log_OC.d(TAG, "Upload service connected");
                 mUploaderBinder = (FileUploader.FileUploaderBinder) service;
             } else {
@@ -926,7 +926,8 @@ public class Preferences extends PreferenceActivity
             if (component.equals(new ComponentName(Preferences.this, FileDownloader.class))) {
                 Log_OC.d(TAG, "Download service suddenly disconnected");
                 mDownloaderBinder = null;
-            } else if (component.equals(new ComponentName(Preferences.this, FileUploader.class))) {
+            } else if (component.equals(new ComponentName(Preferences.this,
+                    FileUploader.class))) {
                 Log_OC.d(TAG, "Upload service suddenly disconnected");
                 mUploaderBinder = null;
             }

+ 4 - 3
src/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -363,8 +363,8 @@ public class UploadFilesActivity extends FileActivity implements
         /**
          * Updates the activity UI after the check of space is done.
          * 
-         * If there is not space enough. shows a new dialog to query the user if wants to move the files instead
-         * of copy them.
+         * If there is not space enough. shows a new dialog to query the user if wants to move the
+         * files instead of copy them.
          * 
          * @param result        'True' when there is space enough to copy all the selected files.
          */
@@ -398,7 +398,8 @@ public class UploadFilesActivity extends FileActivity implements
                 // to the ownCloud folder instead of copying
                 String[] args = {getString(R.string.app_name)};
                 ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(
-                    R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1, R.string.common_no
+                    R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1,
+                        R.string.common_no
                 );
                 dialog.setOnConfirmationListener(UploadFilesActivity.this);
                 dialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG);

+ 361 - 0
src/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -0,0 +1,361 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author LukeOwncloud
+ *   @author David A. Velasco
+ *   @author masensio
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.activity;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.GravityCompat;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.db.UploadResult;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
+import com.owncloud.android.ui.fragment.UploadListFragment;
+import com.owncloud.android.utils.MimetypeIconUtil;
+
+import java.io.File;
+
+/**
+ * Activity listing pending, active, and completed uploads. User can delete
+ * completed uploads from view. Content of this list of coming from
+ * {@link UploadsStorageManager}.
+ *
+ */
+public class UploadListActivity extends FileActivity implements UploadListFragment.ContainerActivity {
+
+    private static final String TAG = UploadListActivity.class.getSimpleName();
+
+    private static final String TAG_UPLOAD_LIST_FRAGMENT = "UPLOAD_LIST_FRAGMENT";
+
+    private UploadMessagesReceiver mUploadMessagesReceiver;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.upload_list_layout);
+
+        // this activity has no file really bound, it's for mulitple accounts at the same time; should no inherit
+        // from FileActivity; moreover, some behaviours inherited from FileActivity should be delegated to Fragments;
+        // but that's other story
+        setFile(null);
+
+        // Navigation Drawer
+        initDrawer();
+
+        // Add fragment with a transaction for setting a tag
+        if(savedInstanceState == null) {
+            createUploadListFragment();
+        } // else, the Fragment Manager makes the job on configuration changes
+
+        // enable ActionBar app icon to behave as action to toggle nav drawer
+        getSupportActionBar().setHomeButtonEnabled(true);
+        mDrawerToggle.setDrawerIndicatorEnabled(true);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        getSupportActionBar().setTitle(R.string.uploads_view_title);
+
+    }
+
+    private void createUploadListFragment(){
+        UploadListFragment uploadList = new UploadListFragment();
+        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+        transaction.add(R.id.upload_list_fragment, uploadList, TAG_UPLOAD_LIST_FRAGMENT);
+        transaction.commit();
+    }
+
+
+    @Override
+    protected void onResume() {
+        Log_OC.v(TAG, "onResume() start");
+        super.onResume();
+
+        // Listen for upload messages
+        mUploadMessagesReceiver = new UploadMessagesReceiver();
+        IntentFilter uploadIntentFilter = new IntentFilter();
+        uploadIntentFilter.addAction(FileUploader.getUploadsAddedMessage());
+        uploadIntentFilter.addAction(FileUploader.getUploadStartMessage());
+        uploadIntentFilter.addAction(FileUploader.getUploadFinishMessage());
+        registerReceiver(mUploadMessagesReceiver, uploadIntentFilter);
+
+        Log_OC.v(TAG, "onResume() end");
+
+    }
+
+    @Override
+    protected void onPause() {
+        Log_OC.v(TAG, "onPause() start");
+        if (mUploadMessagesReceiver != null) {
+            unregisterReceiver(mUploadMessagesReceiver);
+            mUploadMessagesReceiver = null;
+        }
+        super.onPause();
+        Log_OC.v(TAG, "onPause() end");
+    }
+
+    // ////////////////////////////////////////
+    // UploadListFragment.ContainerActivity
+    // ////////////////////////////////////////
+    @Override
+    public boolean onUploadItemClick(OCUpload file) {
+        /// TODO is this path still active?
+        File f = new File(file.getLocalPath());
+        if(!f.exists()) {
+            Toast.makeText(this, "Cannot open. Local file does not exist.",
+                    Toast.LENGTH_SHORT).show();
+        } else {
+            openFileWithDefault(file.getLocalPath());
+        }
+        return true;
+    }
+
+    /**
+     * Open file with app associates with its MIME type. If MIME type unknown, show list with all apps.
+     */
+    private void openFileWithDefault(String localPath) {
+        Intent myIntent = new Intent(android.content.Intent.ACTION_VIEW);
+        File file = new File(localPath);
+        String mimetype = MimetypeIconUtil.getBestMimeTypeByFilename(localPath);
+        if ("application/octet-stream".equals(mimetype)) {
+            mimetype = "*/*";
+        }
+        myIntent.setDataAndType(Uri.fromFile(file), mimetype);
+        try {
+            startActivity(myIntent);
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(this, "Found no app to open this file.", Toast.LENGTH_LONG).show();
+            Log_OC.i(TAG, "Could not find app for sending log history.");
+
+        }        
+    }
+    
+    /**
+     * Same as openFileWithDefault() but user cannot save default app.
+     * @param ocFile
+     */
+    private void openFileWithDefaultNoDefault(OCFile ocFile) {
+        getFileOperationsHelper().openFile(ocFile);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        boolean retval = true;
+        UploadsStorageManager storageManager = null;
+        UploadListFragment uploadListFragment =
+                (UploadListFragment) getSupportFragmentManager().findFragmentByTag(TAG_UPLOAD_LIST_FRAGMENT);
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
+                    mDrawerLayout.closeDrawer(GravityCompat.START);
+                } else {
+                    mDrawerLayout.openDrawer(GravityCompat.START);
+                }
+                break;
+            case R.id.action_retry_uploads:
+                FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+                requester.retryFailedUploads(this, null, null);
+                break;
+
+            case R.id.action_clear_failed_uploads:
+                storageManager = new UploadsStorageManager(getContentResolver());
+                storageManager.clearFailedUploads();
+                uploadListFragment.updateUploads();
+                break;
+
+            case R.id.action_clear_successfull_uploads:
+                storageManager = new UploadsStorageManager(getContentResolver());
+                storageManager.clearSuccessfulUploads();
+                uploadListFragment.updateUploads();
+                break;
+
+            case R.id.action_clear_finished_uploads:
+                storageManager = new UploadsStorageManager(getContentResolver());
+                storageManager.clearAllFinishedUploads();
+                uploadListFragment.updateUploads();
+                break;
+
+            default:
+                retval = super.onOptionsItemSelected(item);
+        }
+
+        return retval;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.upload_list_menu, menu);
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == FileActivity.REQUEST_CODE__UPDATE_CREDENTIALS && resultCode == RESULT_OK) {
+            // Retry uploads of the updated account
+            Account account = AccountUtils.getOwnCloudAccountByName(
+                this,
+                data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
+            );
+            FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+            requester.retryFailedUploads(
+                this,
+                account,
+                UploadResult.CREDENTIAL_ERROR
+            );
+        }
+    }
+
+    /**
+     *
+     * @param operation     Operation performed.
+     * @param result        Result of the removal.
+     */
+    @Override
+    public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
+        if (operation instanceof CheckCurrentCredentialsOperation) {
+            // Do not call super in this case; more refactoring needed around onRemoteOeprationFinish :'(
+            getFileOperationsHelper().setOpIdWaitingFor(Long.MAX_VALUE);
+            dismissLoadingDialog();
+            Account account = (Account) result.getData().get(0);
+            if (!result.isSuccess()) {
+                requestCredentialsUpdate(this, account);
+
+            } else {
+                // already updated -> just retry!
+                FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+                requester.retryFailedUploads(this, account, UploadResult.CREDENTIAL_ERROR);
+            }
+
+        } else {
+            super.onRemoteOperationFinish(operation, result);
+        }
+    }
+
+
+    @Override
+    protected ServiceConnection newTransferenceServiceConnection() {
+        return new UploadListServiceConnection();
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private class UploadListServiceConnection implements ServiceConnection {
+
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder service) {
+            if (service instanceof FileUploaderBinder) {
+                if(mUploaderBinder == null)
+                {
+                    mUploaderBinder = (FileUploaderBinder) service;
+                    Log_OC.d(TAG, "UploadListActivity connected to Upload service. component: " +
+                            component + " service: "
+                            + service);
+                    // Say to UploadListFragment that the Binder is READY in the Activity
+                    UploadListFragment uploadListFragment =
+                            (UploadListFragment) getSupportFragmentManager().findFragmentByTag(TAG_UPLOAD_LIST_FRAGMENT);
+                    if (uploadListFragment != null) {
+                        uploadListFragment.binderReady();
+                    }
+                } else {
+                    Log_OC.d(TAG, "mUploaderBinder already set. mUploaderBinder: " +
+                            mUploaderBinder + " service:" + service);
+                }
+            } else {
+                Log_OC.d(TAG, "UploadListActivity not connected to Upload service. component: " +
+                        component + " service: " + service);
+                return;
+            }            
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            if (component.equals(new ComponentName(UploadListActivity.this, FileUploader.class))) {
+                Log_OC.d(TAG, "UploadListActivity suddenly disconnected from Upload service");
+                mUploaderBinder = null;
+            }
+        }
+    };
+
+    /**
+     * Once the file upload has changed its status -> update uploads list view
+     */
+    private class UploadMessagesReceiver extends BroadcastReceiver {
+        /**
+         * {@link BroadcastReceiver} to enable syncing feedback in UI
+         */
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                UploadListFragment uploadListFragment =
+                    (UploadListFragment) getSupportFragmentManager().findFragmentByTag(TAG_UPLOAD_LIST_FRAGMENT);
+
+                uploadListFragment.updateUploads();
+            } finally {
+                if (intent != null) {
+                    removeStickyBroadcast(intent);
+                }
+            }
+
+        }
+    }
+
+    @Override
+    protected String getDefaultTitle() {
+        return getString(R.string.uploads_view_title);
+    }
+
+
+    /**
+     * Called when the ownCloud {@link Account} associated to the Activity was just updated.
+     */
+    @Override
+    protected void onAccountSet(boolean stateWasRecovered) {
+        super.onAccountSet(stateWasRecovered);
+        updateActionBarTitleAndHomeButton(null);
+        if (mAccountWasSet) {
+            setUsernameInDrawer(findViewById(R.id.left_drawer), getAccount());
+        }
+    }
+
+}

+ 171 - 175
src/com/owncloud/android/ui/activity/Uploader.java

@@ -1,51 +1,30 @@
 /**
- *   ownCloud Android client application
+ *  ownCloud Android client application
  *
- *   @author Bartek Przybylski
- *   @author masensio
- *   Copyright (C) 2012  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *  @author Bartek Przybylski
+ *  @author masensio
+ *  Copyright (C) 2012  Bartek Przybylski
+ *  Copyright (C) 2016 ownCloud Inc.
  *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
  *
- *   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 <http://www.gnu.org/licenses/>.
+ *  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 <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.ui.activity;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Stack;
-import java.util.Vector;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.authentication.AccountAuthenticator;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.operations.RefreshFolderOperation;
-
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorException;
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
+import android.annotation.SuppressLint;
 import android.app.Dialog;
 import android.app.ProgressDialog;
 import android.content.BroadcastReceiver;
@@ -62,6 +41,7 @@ import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.preference.PreferenceManager;
+import android.provider.MediaStore;
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
@@ -69,6 +49,8 @@ import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v7.app.ActionBar;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AlertDialog.Builder;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -81,7 +63,18 @@ import android.widget.ListView;
 import android.widget.ProgressBar;
 import android.widget.Toast;
 
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountAuthenticator;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CreateFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
+import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.adapter.UploaderAdapter;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
@@ -90,6 +83,14 @@ import com.owncloud.android.utils.CopyTmpFileAsyncTask;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
 
 /**
  * This can be used to upload things to an ownCloud instance.
@@ -114,13 +115,13 @@ public class Uploader extends FileActivity
 
     private ArrayList<String> mRemoteCacheData;
     private int mNumCacheFile;
-    
+
     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 REQUEST_CODE_SETUP_ACCOUNT = 0;
+    private final static int REQUEST_CODE__SETUP_ACCOUNT = REQUEST_CODE__LAST_SHARED + 1;
 
     private final static String KEY_PARENTS = "PARENTS";
     private final static String KEY_FILE = "FILE";
@@ -200,7 +201,7 @@ public class Uploader extends FileActivity
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-         Log_OC.d(TAG, "onSaveInstanceState() start");
+        Log_OC.d(TAG, "onSaveInstanceState() start");
         super.onSaveInstanceState(outState);
         outState.putSerializable(KEY_PARENTS, mParents);
         //outState.putParcelable(KEY_ACCOUNT, mAccount);
@@ -245,7 +246,8 @@ public class Uploader extends FileActivity
             builder.setIcon(R.drawable.ic_warning);
             builder.setTitle(R.string.uploader_wrn_no_account_title);
             builder.setMessage(String.format(
-                    getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name)));
+                    getString(R.string.uploader_wrn_no_account_text),
+                    getString(R.string.app_name)));
             builder.setCancelable(false);
             builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {
                 @Override
@@ -253,13 +255,13 @@ public class Uploader extends FileActivity
                     if (android.os.Build.VERSION.SDK_INT >
                             android.os.Build.VERSION_CODES.ECLAIR_MR1) {
                         // using string value since in API7 this
-                        // constant is not defined
-                        // in API7 < this constant is defined in
+                        // constatn is not defined
+                        // in API7 < this constatant is defined in
                         // Settings.ADD_ACCOUNT_SETTINGS
                         // and Settings.EXTRA_AUTHORITIES
                         Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);
-                        intent.putExtra("authorities", new String[] { MainApp.getAuthTokenType() });
-                        startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
+                        intent.putExtra("authorities", new String[]{MainApp.getAuthTokenType()});
+                        startActivityForResult(intent, REQUEST_CODE__SETUP_ACCOUNT);
                     } else {
                         // since in API7 there is no direct call for
                         // account setup, so we need to
@@ -267,7 +269,7 @@ public class Uploader extends FileActivity
                         // desired results and setup
                         // everything for ourself
                         Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class);
-                        startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
+                        startActivityForResult(intent, REQUEST_CODE__SETUP_ACCOUNT);
                     }
                 }
             });
@@ -328,7 +330,7 @@ public class Uploader extends FileActivity
         EditText mDirname;
 
         public a(String path, EditText dirname) {
-            mPath = path; 
+            mPath = path;
             mDirname = dirname;
         }
 
@@ -379,24 +381,24 @@ public class Uploader extends FileActivity
     public void onClick(View v) {
         // click on button
         switch (v.getId()) {
-        case R.id.uploader_choose_folder:
-            mUploadPath = "";   // first element in mParents is root dir, represented by "";
-                                // init mUploadPath with "/" results in a "//" prefix
-            for (String p : mParents)
-                mUploadPath += p + OCFile.PATH_SEPARATOR;
-            Log_OC.d(TAG, "Uploading file to dir " + mUploadPath);
+            case R.id.uploader_choose_folder:
+                mUploadPath = "";   // first element in mParents is root dir, represented by "";
+                // init mUploadPath with "/" results in a "//" prefix
+                for (String p : mParents)
+                    mUploadPath += p + OCFile.PATH_SEPARATOR;
+                Log_OC.d(TAG, "Uploading file to dir " + mUploadPath);
 
-            uploadFiles();
+                uploadFiles();
 
-            break;
-            
-        case R.id.uploader_cancel:
-            finish();
-            break;
-            
-            
-        default:
-            throw new IllegalArgumentException("Wrong element clicked");
+                break;
+
+            case R.id.uploader_cancel:
+                finish();
+                break;
+
+
+            default:
+                throw new IllegalArgumentException("Wrong element clicked");
         }
     }
 
@@ -404,7 +406,7 @@ public class Uploader extends FileActivity
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode);
-        if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) {
+        if (requestCode == REQUEST_CODE__SETUP_ACCOUNT) {
             dismissDialog(DIALOG_NO_ACCOUNT);
             if (resultCode == RESULT_CANCELED) {
                 finish();
@@ -424,14 +426,13 @@ public class Uploader extends FileActivity
 
     private void populateDirectoryList() {
         setContentView(R.layout.uploader_layout);
-        
+
         ListView mListView = (ListView) findViewById(android.R.id.list);
 
         String current_dir = mParents.peek();
-        if(current_dir.equals("")){
+        if (current_dir.equals("")) {
             getSupportActionBar().setTitle(getString(R.string.default_display_name_for_root_folder));
-        }
-        else{
+        } else {
             getSupportActionBar().setTitle(current_dir);
         }
         boolean notRoot = (mParents.size() > 1);
@@ -453,7 +454,7 @@ public class Uploader extends FileActivity
                     h.put("dirname", f);
                     data.add(h);
             }
-            
+
             UploaderAdapter sa = new UploaderAdapter(this,
                                                 data,
                                                 R.layout.uploader_list_item_layout,
@@ -464,10 +465,10 @@ public class Uploader extends FileActivity
             mListView.setAdapter(sa);
             Button btnChooseFolder = (Button) findViewById(R.id.uploader_choose_folder);
             btnChooseFolder.setOnClickListener(this);
-            
+
             Button btnNewFolder = (Button) findViewById(R.id.uploader_cancel);
             btnNewFolder.setOnClickListener(this);
-            
+
             mListView.setOnItemClickListener(this);
         }
     }
@@ -512,85 +513,81 @@ public class Uploader extends FileActivity
         return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null);
     }
 
+    @SuppressLint("NewApi")
     public void uploadFiles() {
         try {
-
-            // ArrayList for files with path in external storage
-            ArrayList<String> local = new ArrayList<String>();
-            ArrayList<String> remote = new ArrayList<String>();
-            
-            // this checks the mimeType 
+            // this checks the mimeType
             for (Parcelable mStream : mStreamsToUpload) {
-                
+
                 Uri uri = (Uri) mStream;
                 String data = null;
                 String filePath = "";
 
                 if (uri != null) {
                     if (uri.getScheme().equals("content")) {
-                       String mimeType = getContentResolver().getType(uri);
-
-                       if (mimeType.contains("image")) {
-                           String[] CONTENT_PROJECTION = { Images.Media.DATA, Images.Media.DISPLAY_NAME,
-                                                           Images.Media.MIME_TYPE, Images.Media.SIZE};
-                           Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
-                           c.moveToFirst();
-                           int index = c.getColumnIndex(Images.Media.DATA);
-                           data = c.getString(index);
-                           local.add(data);
-                           remote.add(mUploadPath + c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME)));
-
-                       }
-                       else if (mimeType.contains("video")) {
-                           String[] CONTENT_PROJECTION = { Video.Media.DATA, Video.Media.DISPLAY_NAME,
-                                                           Video.Media.MIME_TYPE, Video.Media.SIZE,
-                                                           Video.Media.DATE_MODIFIED };
-                           Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
-                           c.moveToFirst();
-                           int index = c.getColumnIndex(Video.Media.DATA);
-                           data = c.getString(index);
-                           local.add(data);
-                           remote.add(mUploadPath + c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME)));
-
-                       }
-                       else if (mimeType.contains("audio")) {
-                           String[] CONTENT_PROJECTION = { Audio.Media.DATA, Audio.Media.DISPLAY_NAME,
-                                                           Audio.Media.MIME_TYPE, Audio.Media.SIZE };
-                           Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
-                           c.moveToFirst();
-                           int index = c.getColumnIndex(Audio.Media.DATA);
-                           data = c.getString(index);
-                           local.add(data);
-                           remote.add(mUploadPath + c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME)));
-
-                       }
-                       else {
-                           filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", "");
-                           // cut everything whats before mnt. It occured to me that sometimes apps send
-                           // their name into the URI
-                           if (filePath.contains("mnt")) {
-                              String splitedFilePath[] = filePath.split("/mnt");
-                              filePath = splitedFilePath[1];
-                           }
-                           final File file = new File(filePath);
-                           local.add(file.getAbsolutePath());
-                           remote.add(mUploadPath + file.getName());
-                       }
+                        String mimeType = getContentResolver().getType(uri);
+
+                        if (mimeType.contains("image")) {
+                            String[] CONTENT_PROJECTION = {Images.Media.DATA,
+                                    Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE,
+                                    Images.Media.SIZE};
+                            Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null,
+                                    null, null);
+                            c.moveToFirst();
+                            int index = c.getColumnIndex(Images.Media.DATA);
+                            data = c.getString(index);
+                            filePath = mUploadPath +
+                                    c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME));
+
+                        } else if (mimeType.contains("video")) {
+                            String[] CONTENT_PROJECTION = {Video.Media.DATA,
+                                    Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE,
+                                    Video.Media.SIZE, Video.Media.DATE_MODIFIED};
+                            Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null,
+                                    null, null);
+                            c.moveToFirst();
+                            int index = c.getColumnIndex(Video.Media.DATA);
+                            data = c.getString(index);
+                            filePath = mUploadPath +
+                                    c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME));
+
+                        } else if (mimeType.contains("audio")) {
+                            String[] CONTENT_PROJECTION = {Audio.Media.DATA,
+                                    Audio.Media.DISPLAY_NAME, Audio.Media.MIME_TYPE,
+                                    Audio.Media.SIZE};
+                            Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null,
+                                    null, null);
+                            c.moveToFirst();
+                            int index = c.getColumnIndex(Audio.Media.DATA);
+                            data = c.getString(index);
+                            filePath = mUploadPath +
+                                    c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME));
+
+                        } else {
+                            Cursor cursor = getContentResolver().query(uri,
+                                    new String[]{MediaStore.MediaColumns.DISPLAY_NAME},
+                                    null, null, null);
+                            cursor.moveToFirst();
+                            int nameIndex = cursor.getColumnIndex(cursor.getColumnNames()[0]);
+                            if (nameIndex >= 0) {
+                                filePath = mUploadPath + cursor.getString(nameIndex);
+                            }
+                        }
+
                     } else if (uri.getScheme().equals("file")) {
                         filePath = Uri.decode(uri.toString()).replace(uri.getScheme() +
                                 "://", "");
                         if (filePath.contains("mnt")) {
-                           String splitedFilePath[] = filePath.split("/mnt");
-                           filePath = splitedFilePath[1];
+                            String splitedFilePath[] = filePath.split("/mnt");
+                            filePath = splitedFilePath[1];
                         }
                         final File file = new File(filePath);
                         data = file.getAbsolutePath();
                         filePath = mUploadPath + file.getName();
-                    }
-                    else {
+                    } else {
                         throw new SecurityException();
                     }
-                    if (data == null) {
+                    //if (data == null) {
                         mRemoteCacheData.add(filePath);
                         CopyTmpFileAsyncTask copyTask = new CopyTmpFileAsyncTask(this);
                         Object[] params = {uri, filePath, mRemoteCacheData.size() - 1,
@@ -598,20 +595,14 @@ public class Uploader extends FileActivity
                         mNumCacheFile++;
                         showWaitingCopyDialog();
                         copyTask.execute(params);
-                    }
-                }
-                else {
+
+                    //} else {
+                    // TODO request to FileUploader with data as source file, resulting in lazy temporary copy
+                    //}
+                } else {
                     throw new SecurityException();
                 }
 
-                Intent intent = new Intent(getApplicationContext(), FileUploader.class);
-                intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
-                intent.putExtra(FileUploader.KEY_LOCAL_FILE, local.toArray(new String[local.size()]));
-                intent.putExtra(FileUploader.KEY_REMOTE_FILE,
-                        remote.toArray(new String[remote.size()]));
-                intent.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
-                startService(intent);
-
                 //Save the path to shared preferences
                 SharedPreferences.Editor appPrefs = PreferenceManager
                         .getDefaultSharedPreferences(getApplicationContext()).edit();
@@ -620,31 +611,31 @@ public class Uploader extends FileActivity
 
                 finish();
             }
-            
+
         } catch (SecurityException e) {
             String message = String.format(getString(R.string.uploader_error_forbidden_content),
                     getString(R.string.app_name));
-            Toast.makeText(this, message, Toast.LENGTH_LONG).show();            
+            Toast.makeText(this, message, Toast.LENGTH_LONG).show();
         }
     }
-    
+
     @Override
     public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
         super.onRemoteOperationFinish(operation, result);
-        
-      
+
+
         if (operation instanceof CreateFolderOperation) {
-            onCreateFolderOperationFinish((CreateFolderOperation)operation, result);
+            onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
         }
-        
+
     }
-    
+
     /**
      * Updates the view associated to the activity after the finish of an operation
      * trying create a new folder
-     * 
-     * @param operation     Creation operation performed.
-     * @param result        Result of the creation.
+     *
+     * @param operation Creation operation performed.
+     * @param result    Result of the creation.
      */
     private void onCreateFolderOperationFinish(CreateFolderOperation operation,
                                                RemoteOperationResult result) {
@@ -652,44 +643,44 @@ public class Uploader extends FileActivity
             populateDirectoryList();
         } else {
             try {
-                Toast msg = Toast.makeText(this, 
-                        ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), 
-                        Toast.LENGTH_LONG); 
+                Toast msg = Toast.makeText(this,
+                        ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+                        Toast.LENGTH_LONG);
                 msg.show();
 
             } catch (NotFoundException e) {
-                Log_OC.e(TAG, "Error while trying to show fail message " , e);
+                Log_OC.e(TAG, "Error while trying to show fail message ", e);
             }
         }
     }
-    
-    
+
+
     /**
-     *  Loads the target folder initialize shown to the user.
-     * 
-     *  The target account has to be chosen before this method is called. 
+     * Loads the target folder initialize shown to the user.
+     * <p/>
+     * The target account has to be chosen before this method is called.
      */
     private void initTargetFolder() {
         if (getStorageManager() == null) {
             throw new IllegalStateException("Do not call this method before " +
                     "initializing mStorageManager");
         }
-        
+
         SharedPreferences appPreferences = PreferenceManager
                 .getDefaultSharedPreferences(getApplicationContext());
 
         String last_path = appPreferences.getString("last_upload_path", "");
         // "/" equals root-directory
-        if(last_path.equals("/")) {
+        if (last_path.equals("/")) {
             mParents.add("");
-        } else{
+        } else {
             String[] dir_names = last_path.split("/");
             mParents.clear();
             for (String dir : dir_names)
                 mParents.add(dir);
         }
         //Make sure that path still exists, if it doesn't pop the stack and try the previous path
-        while(!getStorageManager().fileExists(generatePath(mParents)) && mParents.size() > 1){
+        while (!getStorageManager().fileExists(generatePath(mParents)) && mParents.size() > 1) {
             mParents.pop();
         }
     }
@@ -702,7 +693,7 @@ public class Uploader extends FileActivity
         menu.findItem(R.id.action_sync_account).setVisible(false);
         return true;
     }
-    
+
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         boolean retval = true;
@@ -714,7 +705,7 @@ public class Uploader extends FileActivity
                         CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT);
                 break;
             case android.R.id.home:
-                if((mParents.size() > 1)) {
+                if ((mParents.size() > 1)) {
                     onBackPressed();
                 }
                 break;
@@ -824,21 +815,26 @@ public class Uploader extends FileActivity
     }
     /**
      * Process the result of CopyTmpFileAsyncTask
+     *
      * @param result
      * @param index
      */
     @Override
     public void onTmpFileCopied(String result, int index) {
-        if (mNumCacheFile -- == 0) {
+        if (mNumCacheFile-- == 0) {
             dismissWaitingCopyDialog();
         }
         if (result != null) {
-            Intent intent = new Intent(getApplicationContext(), FileUploader.class);
-            intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-            intent.putExtra(FileUploader.KEY_LOCAL_FILE, result);
-            intent.putExtra(FileUploader.KEY_REMOTE_FILE, mRemoteCacheData.get(index));
-            intent.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
-            startService(intent);
+            FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+            requester.uploadNewFile(
+                    this, getAccount(),
+                    result,
+                    mRemoteCacheData.get(index),
+                    FileUploader.LOCAL_BEHAVIOUR_FORGET,
+                    null,       // MIME type will be detected from file name
+                    false,      // do not create parent folder if not existent
+                    UploadFileOperation.CREATED_BY_USER
+            );
 
         } else {
             String message = String.format(getString(R.string.uploader_error_forbidden_content),
@@ -865,7 +861,7 @@ public class Uploader extends FileActivity
     /**
      * Dismiss waiting for copy dialog
      */
-    public void dismissWaitingCopyDialog(){
+    public void dismissWaitingCopyDialog() {
         Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_COPY_FILE);
         if (frag != null) {
             LoadingDialog loading = (LoadingDialog) frag;

+ 720 - 0
src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java

@@ -0,0 +1,720 @@
+/**
+ *  ownCloud Android client application
+ *
+ *  @author LukeOwncloud
+ *  @author masensio
+ *  Copyright (C) 2016 ownCloud Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.adapter;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Bitmap;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.db.UploadResult;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.MimetypeIconUtil;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Observable;
+import java.util.Observer;
+
+/**
+ * This Adapter populates a ListView with following types of uploads: pending,
+ * active, completed. Filtering possible.
+ *
+ */
+public class ExpandableUploadListAdapter extends BaseExpandableListAdapter implements Observer {
+
+    private static final String TAG = ExpandableUploadListAdapter.class.getSimpleName();
+    private FileActivity mParentActivity;
+
+    private UploadsStorageManager mUploadsStorageManager;
+
+    public ProgressListener mProgressListener;
+
+    interface Refresh {
+        public void refresh();
+    }
+
+    abstract class UploadGroup implements Refresh {
+        OCUpload[] items;
+        String name;
+
+        public UploadGroup(String groupName) {
+            this.name = groupName;
+            items = new OCUpload[0];
+        }
+
+        public String getGroupName() {
+            return name;
+        }
+
+        public Comparator<OCUpload> comparator = new Comparator<OCUpload>() {
+
+            @Override
+            public int compare(OCUpload upload1, OCUpload upload2) {
+                if (upload1.getUploadStatus().equals(UploadStatus.UPLOAD_IN_PROGRESS)) {
+                    FileUploader.FileUploaderBinder binder = mParentActivity.getFileUploaderBinder();
+                    if (binder != null) {
+                        if (binder.isUploadingNow(upload1)) {
+                            return -1;
+                        } else if (binder.isUploadingNow(upload2)) {
+                            return 1;
+                        }
+                    }
+                }
+                if (upload1.getUploadEndTimestamp() == 0) {
+                    return compareUploadId(upload1, upload2);
+                } else {
+                    return compareUpdateTime(upload1, upload2);
+                }
+            }
+
+            private int compareUploadId(OCUpload upload1, OCUpload upload2) {
+                return Long.valueOf(upload1.getUploadId()).compareTo(upload2.getUploadId());
+            }
+
+            private int compareUpdateTime(OCUpload upload1, OCUpload upload2) {
+                return Long.valueOf(upload2.getUploadEndTimestamp()).compareTo(upload1.getUploadEndTimestamp());
+            }
+        };
+
+        abstract public int getGroupIcon();
+    }
+
+    private UploadGroup[] mUploadGroups = null;
+
+    public ExpandableUploadListAdapter(FileActivity parentActivity) {
+        Log_OC.d(TAG, "ExpandableUploadListAdapter");
+        mParentActivity = parentActivity;
+        mUploadsStorageManager = new UploadsStorageManager(mParentActivity.getContentResolver());
+        mUploadGroups = new UploadGroup[3];
+        mUploadGroups[0] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_current_uploads)) {
+            @Override
+            public void refresh() {
+                items = mUploadsStorageManager.getCurrentAndPendingUploads();
+                Arrays.sort(items, comparator);
+            }
+
+            @Override
+            public int getGroupIcon() {
+                return R.drawable.upload_in_progress;
+            }
+        };
+        mUploadGroups[1] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_failed_uploads)) {
+            @Override
+            public void refresh() {
+                items = mUploadsStorageManager.getFailedUploads();
+                Arrays.sort(items, comparator);
+            }
+
+            @Override
+            public int getGroupIcon() {
+                return R.drawable.upload_failed;
+            }
+
+        };
+        mUploadGroups[2] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_finished_uploads)) {
+            @Override
+            public void refresh() {
+                items = mUploadsStorageManager.getFinishedUploads();
+                Arrays.sort(items, comparator);
+            }
+
+            @Override
+            public int getGroupIcon() {
+                return R.drawable.upload_finished;
+            }
+
+        };
+        loadUploadItemsFromDb();
+    }
+
+
+    @Override
+    public void registerDataSetObserver(DataSetObserver observer) {
+        super.registerDataSetObserver(observer);
+        mUploadsStorageManager.addObserver(this);
+        Log_OC.d(TAG, "registerDataSetObserver");
+    }
+
+    @Override
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        super.unregisterDataSetObserver(observer);
+        mUploadsStorageManager.deleteObserver(this);
+        Log_OC.d(TAG, "unregisterDataSetObserver");
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    private View getView(OCUpload[] uploadsItems, int position, View convertView, ViewGroup parent) {
+        View view = convertView;
+        if (view == null) {
+            LayoutInflater inflator =
+                    (LayoutInflater) mParentActivity.getSystemService(
+                            Context.LAYOUT_INFLATER_SERVICE
+                    );
+            view = inflator.inflate(R.layout.upload_list_item, parent, false);
+        }
+
+        if (uploadsItems != null && uploadsItems.length > position) {
+            final OCUpload upload = uploadsItems[position];
+
+            // local file name
+            TextView fileTextView = (TextView) view.findViewById(R.id.upload_name);
+            File remoteFile = new File(upload.getRemotePath());
+            String fileName = remoteFile.getName();
+            if (fileName.length() == 0) {
+                fileName = File.separator;
+            }
+            fileTextView.setText(fileName);
+
+            // remote path to parent folder
+            TextView pathTextView = (TextView) view.findViewById(R.id.upload_remote_path);
+            String remoteParentPath = upload.getRemotePath();
+            remoteParentPath = new File(remoteParentPath).getParent();
+            pathTextView.setText(mParentActivity.getString(R.string.app_name) + remoteParentPath);
+
+            // file size
+            TextView fileSizeTextView = (TextView) view.findViewById(R.id.upload_file_size);
+            fileSizeTextView.setText(DisplayUtils.bytesToHumanReadable(upload.getFileSize()) + ", ");
+
+            //* upload date
+            TextView uploadDateTextView = (TextView) view.findViewById(R.id.upload_date);
+            long updateTime = upload.getUploadEndTimestamp();
+            CharSequence dateString = DisplayUtils.getRelativeDateTimeString(
+                    mParentActivity,
+                    updateTime,
+                    DateUtils.SECOND_IN_MILLIS,
+                    DateUtils.WEEK_IN_MILLIS,
+                    0
+            );
+            uploadDateTextView.setText(dateString);
+
+            TextView accountNameTextView = (TextView) view.findViewById(R.id.upload_account);
+            accountNameTextView.setText(upload.getAccountName());
+
+            TextView statusTextView = (TextView) view.findViewById(R.id.upload_status);
+
+            ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.upload_progress_bar);
+
+            /// Reset fields visibility
+            uploadDateTextView.setVisibility(View.VISIBLE);
+            pathTextView.setVisibility(View.VISIBLE);
+            fileSizeTextView.setVisibility(View.VISIBLE);
+            accountNameTextView.setVisibility(View.VISIBLE);
+            statusTextView.setVisibility(View.VISIBLE);
+            progressBar.setVisibility(View.GONE);
+
+            /// Update information depending of upload details
+            String status = getStatusText(upload);
+            switch (upload.getUploadStatus()) {
+                case UPLOAD_IN_PROGRESS:
+                    progressBar.setProgress(0);
+                    progressBar.setVisibility(View.VISIBLE);
+
+                    FileUploader.FileUploaderBinder binder = mParentActivity.getFileUploaderBinder();
+                    if (binder != null) {
+                        if (binder.isUploadingNow(upload)) {
+                            /// really uploading, so...
+                            /// ... unbind the old progress bar, if any; ...
+                            if (mProgressListener != null) {
+                                binder.removeDatatransferProgressListener(
+                                    mProgressListener,
+                                    mProgressListener.getUpload()   // the one that was added
+                                );
+                            }
+                            /// ... then, bind the current progress bar to listen for updates
+                            mProgressListener = new ProgressListener(upload, progressBar);
+                            binder.addDatatransferProgressListener(
+                                mProgressListener,
+                                upload
+                            );
+
+                        } else {
+                            /// not really uploading; stop listening progress if view is reused!
+                            if (convertView != null &&
+                                    mProgressListener != null &&
+                                    mProgressListener.isWrapping(progressBar))  {
+                                binder.removeDatatransferProgressListener(
+                                    mProgressListener,
+                                    mProgressListener.getUpload()   // the one that was added
+                                );
+                                mProgressListener = null;
+                            }
+                        }
+                    } else {
+                        Log_OC.w(
+                            TAG,
+                            "FileUploaderBinder not ready yet for upload " + upload.getRemotePath()
+                        );
+                    }
+                    uploadDateTextView.setVisibility(View.GONE);
+                    pathTextView.setVisibility(View.GONE);
+                    fileSizeTextView.setVisibility(View.GONE);
+                    progressBar.invalidate();
+                    break;
+
+                case UPLOAD_FAILED:
+                    uploadDateTextView.setVisibility(View.GONE);
+                    break;
+
+                case UPLOAD_SUCCEEDED:
+                    statusTextView.setVisibility(View.GONE);
+                    break;
+            }
+            statusTextView.setText(status);
+
+            /// bind listeners to perform actions
+            ImageButton rightButton = (ImageButton) view.findViewById(R.id.upload_right_button);
+            if (upload.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS) {
+                //Cancel
+                rightButton.setImageResource(R.drawable.ic_cancel);
+                rightButton.setVisibility(View.VISIBLE);
+                rightButton.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        FileUploader.FileUploaderBinder uploaderBinder = mParentActivity.getFileUploaderBinder();
+                        if (uploaderBinder != null) {
+                            uploaderBinder.cancel(upload);
+                            refreshView();
+                        }
+                    }
+                });
+
+            } else if (upload.getUploadStatus() == UploadStatus.UPLOAD_FAILED) {
+                //Delete
+                rightButton.setImageResource(R.drawable.ic_action_delete);
+                rightButton.setVisibility(View.VISIBLE);
+                rightButton.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        mUploadsStorageManager.removeUpload(upload);
+                        refreshView();
+                    }
+                });
+
+            } else {    // UploadStatus.UPLOAD_SUCCESS
+                rightButton.setVisibility(View.INVISIBLE);
+            }
+
+            // retry
+            if (upload.getUploadStatus() == UploadStatus.UPLOAD_FAILED) {
+                if (UploadResult.CREDENTIAL_ERROR.equals(upload.getLastResult())) {
+                    view.setOnClickListener(new OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            mParentActivity.getFileOperationsHelper().checkCurrentCredentials(
+                                upload.getAccount(mParentActivity)
+                            );
+                        }
+                    });
+
+                } else {
+                    // not a credentials error
+                    view.setOnClickListener(new OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                        File file = new File(upload.getLocalPath());
+                        if (file.exists()) {
+                            FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+                            requester.retry(mParentActivity, upload);
+                            refreshView();
+                        } else {
+                            final String message = String.format(
+                                mParentActivity.getString(R.string.local_file_not_found_toast)
+                            );
+                            Toast.makeText(mParentActivity, message, Toast.LENGTH_SHORT).show();
+                        }
+                        }
+                    });
+                }
+            } else {
+                view.setOnClickListener(null);
+            }
+
+            /// Set icon or thumbnail
+            ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
+            fileIcon.setImageResource(R.drawable.file);
+
+            /** Cancellation needs do be checked and done before changing the drawable in fileIcon, or
+             * {@link ThumbnailsCacheManager#cancelPotentialWork} will NEVER cancel any task.
+             **/
+            OCFile fakeFileToCheatThumbnailsCacheManagerInterface = new OCFile(upload.getRemotePath());
+            fakeFileToCheatThumbnailsCacheManagerInterface.setStoragePath(upload.getLocalPath());
+            fakeFileToCheatThumbnailsCacheManagerInterface.setMimetype(upload.getMimeType());
+
+            boolean allowedToCreateNewThumbnail = (ThumbnailsCacheManager.cancelPotentialWork(
+                    fakeFileToCheatThumbnailsCacheManagerInterface,
+                    fileIcon)
+            );
+
+            // TODO this code is duplicated; refactor to a common place
+            if ((fakeFileToCheatThumbnailsCacheManagerInterface.isImage()
+                    && fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId() != null &&
+                    upload.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED)) {
+                // Thumbnail in Cache?
+                Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
+                        String.valueOf(fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId())
+                );
+                if (thumbnail != null && !fakeFileToCheatThumbnailsCacheManagerInterface.needsUpdateThumbnail()) {
+                    fileIcon.setImageBitmap(thumbnail);
+                } else {
+                    // generate new Thumbnail
+                    if (allowedToCreateNewThumbnail) {
+                        final ThumbnailsCacheManager.ThumbnailGenerationTask task =
+                                new ThumbnailsCacheManager.ThumbnailGenerationTask(
+                                        fileIcon, mParentActivity.getStorageManager(), mParentActivity.getAccount()
+                                );
+                        if (thumbnail == null) {
+                            thumbnail = ThumbnailsCacheManager.mDefaultImg;
+                        }
+                        final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
+                                new ThumbnailsCacheManager.AsyncDrawable(
+                                        mParentActivity.getResources(),
+                                        thumbnail,
+                                        task
+                                );
+                        fileIcon.setImageDrawable(asyncDrawable);
+                        task.execute(fakeFileToCheatThumbnailsCacheManagerInterface);
+                    }
+                }
+
+                if ("image/png".equals(upload.getMimeType())) {
+                    fileIcon.setBackgroundColor(mParentActivity.getResources()
+                            .getColor(R.color.background_color));
+                }
+
+
+            } else if (fakeFileToCheatThumbnailsCacheManagerInterface.isImage()) {
+                File file = new File(upload.getLocalPath());
+                // Thumbnail in Cache?
+                Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
+                        String.valueOf(file.hashCode()));
+                if (thumbnail != null) {
+                    fileIcon.setImageBitmap(thumbnail);
+                } else {
+                    // generate new Thumbnail
+                    if (allowedToCreateNewThumbnail) {
+                        final ThumbnailsCacheManager.ThumbnailGenerationTask task =
+                                new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon);
+                        if (thumbnail == null) {
+                            thumbnail = ThumbnailsCacheManager.mDefaultImg;
+                        }
+                        final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
+                                new ThumbnailsCacheManager.AsyncDrawable(
+                                        mParentActivity.getResources(),
+                                        thumbnail,
+                                        task
+                                );
+                        fileIcon.setImageDrawable(asyncDrawable);
+                        task.execute(file);
+                        Log_OC.v(TAG, "Executing task to generate a new thumbnail");
+                    }
+                }
+
+                if ("image/png".equalsIgnoreCase(upload.getMimeType())) {
+                    fileIcon.setBackgroundColor(mParentActivity.getResources()
+                            .getColor(R.color.background_color));
+                }
+            } else {
+                fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(
+                        upload.getMimeType(),
+                        fileName
+                ));
+            }
+
+        }
+
+        return view;
+    }
+
+    /**
+     * Gets the status text to show to the user according to the status and last result of the
+     * the given upload.
+     *
+     * @param upload        Upload to describe.
+     * @return              Text describing the status of the given upload.
+     */
+    private String getStatusText(OCUpload upload) {
+        String status;
+        switch (upload.getUploadStatus()) {
+
+            case UPLOAD_IN_PROGRESS:
+                status = mParentActivity.getString(R.string.uploads_view_later_waiting_to_upload);
+                FileUploader.FileUploaderBinder binder = mParentActivity.getFileUploaderBinder();
+                if (binder != null && binder.isUploadingNow(upload)) {
+                    /// really uploading, bind the progress bar to listen for progress updates
+                    status = mParentActivity.getString(R.string.uploader_upload_in_progress_ticker);
+                }
+                break;
+
+            case UPLOAD_SUCCEEDED:
+                status = mParentActivity.getString(R.string.uploads_view_upload_status_succeeded);
+                break;
+
+            case UPLOAD_FAILED:
+                switch (upload.getLastResult()) {
+                    case CREDENTIAL_ERROR:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_failed_credentials_error
+                        );
+                        break;
+                    case FOLDER_ERROR:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_failed_folder_error
+                        );
+                        break;
+                    case FILE_NOT_FOUND:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_failed_localfile_error
+                        );
+                        break;
+                    case FILE_ERROR:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_failed_file_error
+                        );
+                        break;
+                    case PRIVILEDGES_ERROR:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_failed_permission_error
+                        );
+                        break;
+                    case NETWORK_CONNECTION:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_failed_connection_error
+                        );
+                        break;
+                    case DELAYED_FOR_WIFI:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_waiting_for_wifi
+                        );
+                        break;
+                    case CONFLICT_ERROR:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_conflict
+                        );
+                        break;
+                    case SERVICE_INTERRUPTED:
+                        status =  mParentActivity.getString(
+                            R.string.uploads_view_upload_status_service_interrupted
+                        );
+                        break;
+                    case UNKNOWN:
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_unknown_fail
+                        );
+                        break;
+                    case CANCELLED:
+                        // should not get here ; cancelled uploads should be wiped out
+                        status = mParentActivity.getString(
+                            R.string.uploads_view_upload_status_cancelled
+                        );
+                        break;
+                    case UPLOADED:
+                        // should not get here ; status should be UPLOAD_SUCCESS
+                        status =  mParentActivity.getString(R.string.uploads_view_upload_status_succeeded);
+                        break;
+                    default:
+                        status = "Naughty devs added a new fail result but no description for the user";
+                        break;
+                }
+                break;
+
+            default:
+                status = "Uncontrolled status: " + upload.getUploadStatus().toString();
+        }
+        return status;
+    }
+
+
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+
+    /**
+     * Load upload items from {@link UploadsStorageManager}.
+     */
+    private void loadUploadItemsFromDb() {
+        Log_OC.d(TAG, "loadUploadItemsFromDb");
+
+        for (UploadGroup group : mUploadGroups) {
+            group.refresh();
+        }
+
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public void update(Observable arg0, Object arg1) {
+        Log_OC.d(TAG, "update");
+        loadUploadItemsFromDb();
+    }
+
+
+    public void refreshView() {
+        Log_OC.d(TAG, "refreshView");
+        loadUploadItemsFromDb();
+    }
+
+    @Override
+    public Object getChild(int groupPosition, int childPosition) {
+        return mUploadGroups[(int) getGroupId(groupPosition)].items[childPosition];
+    }
+
+    @Override
+    public long getChildId(int groupPosition, int childPosition) {
+        return childPosition;
+    }
+
+    @Override
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
+                             ViewGroup parent) {
+        return getView(mUploadGroups[(int) getGroupId(groupPosition)].items, childPosition, convertView, parent);
+    }
+
+    @Override
+    public int getChildrenCount(int groupPosition) {
+        return mUploadGroups[(int) getGroupId(groupPosition)].items.length;
+    }
+
+    @Override
+    public Object getGroup(int groupPosition) {
+        return mUploadGroups[(int) getGroupId(groupPosition)];
+    }
+
+    @Override
+    public int getGroupCount() {
+        int size = 0;
+        for (UploadGroup uploadGroup : mUploadGroups) {
+            if (uploadGroup.items.length > 0) {
+                size++;
+            }
+        }
+        return size;
+    }
+
+    /**
+     * Returns the groupId (that is, index in mUploadGroups) for group at position groupPosition (0-based).
+     * Could probably be done more intuitive but this tested methods works as intended.
+     */
+    @Override
+    public long getGroupId(int groupPosition) {
+        int id = -1;
+        for (int i = 0; i <= groupPosition; ) {
+            id++;
+            if (mUploadGroups[id].items.length > 0) {
+                i++;
+            }
+        }
+        return id;
+    }
+
+    @Override
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
+        //force group to stay unfolded
+        ExpandableListView listView = (ExpandableListView) parent;
+        listView.expandGroup(groupPosition);
+
+        listView.setGroupIndicator(null);
+        UploadGroup group = (UploadGroup) getGroup(groupPosition);
+        if (convertView == null) {
+            LayoutInflater inflaInflater = (LayoutInflater) mParentActivity
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            convertView = inflaInflater.inflate(R.layout.upload_list_group, null);
+        }
+        TextView tv = (TextView) convertView.findViewById(R.id.uploadListGroupName);
+        tv.setText(group.getGroupName());
+        return convertView;
+    }
+
+    @Override
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+
+    public class ProgressListener implements OnDatatransferProgressListener {
+        int mLastPercent = 0;
+        OCUpload mUpload = null;
+        WeakReference<ProgressBar> mProgressBar = null;
+
+        public ProgressListener(OCUpload upload, ProgressBar progressBar) {
+            mUpload = upload;
+            mProgressBar = new WeakReference<ProgressBar>(progressBar);
+        }
+
+        @Override
+        public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String
+                filename) {
+            int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
+            if (percent != mLastPercent) {
+                ProgressBar pb = mProgressBar.get();
+                if (pb != null) {
+                    pb.setProgress(percent);
+                    pb.postInvalidate();
+                }
+            }
+            mLastPercent = percent;
+        }
+
+        public boolean isWrapping(ProgressBar progressBar) {
+            ProgressBar wrappedProgressBar = mProgressBar.get();
+            return (
+                wrappedProgressBar != null &&
+                wrappedProgressBar == progressBar   // on purpose; don't replace with equals
+            );
+        }
+
+        public OCUpload getUpload() {
+            return mUpload;
+        }
+
+    }
+
+    public void addBinder() {
+        notifyDataSetChanged();
+    }
+}

+ 6 - 4
src/com/owncloud/android/ui/adapter/FileListListAdapter.java

@@ -6,7 +6,7 @@
  *   @author David A. Velasco
  *   @author masensio
  *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -24,9 +24,6 @@
 package com.owncloud.android.ui.adapter;
 
 
-import java.io.File;
-import java.util.Vector;
-
 import android.accounts.Account;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -57,6 +54,8 @@ import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.MimetypeIconUtil;
 
+import java.util.Vector;
+
 
 /**
  * This Adapter populates a ListView with all files and folders in an ownCloud
@@ -88,12 +87,14 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         mJustFolders = justFolders;
         mContext = context;
         mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
+
         mTransferServiceGetter = transferServiceGetter;
 
         mAppPreferences = PreferenceManager
                 .getDefaultSharedPreferences(mContext);
         
         // Read sorting order, default to sort by name ascending
+
         FileStorageUtils.mSortOrder = mAppPreferences.getInt("sortOrder", 0);
         FileStorageUtils.mSortAscending = mAppPreferences.getBoolean("sortAscending", true);
         
@@ -356,6 +357,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                             file.getFileName()));
                 }
 
+
             } else {
                 // Folder
                 fileIcon.setImageResource(

+ 0 - 138
src/com/owncloud/android/ui/adapter/MyExpandableListAdapter.java

@@ -1,138 +0,0 @@
-/**
- *   ownCloud Android client application
- *
- *   Copyright (C) 2015  ownCloud Inc.
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   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 <http://www.gnu.org/licenses/>.
- *
- */
-package com.owncloud.android.ui.adapter;
-
-import android.app.Activity;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.owncloud.android.R;
-
-public class MyExpandableListAdapter extends BaseExpandableListAdapter {
-
-    private final SparseArray<GroupAdapter> groups;
-    public LayoutInflater inflater;
-    public Activity activity;
-
-    public MyExpandableListAdapter(Activity act, SparseArray<GroupAdapter> groups) {
-        activity = act;
-        this.groups = groups;
-        inflater = act.getLayoutInflater();
-
-    }
-
-
-    @Override
-    public Object getChild(int groupPosition, int childPosition) {
-        return groups.get(groupPosition).children.get(childPosition);
-    }
-
-    @Override
-    public long getChildId(int groupPosition, int childPosition) {
-        return 0;
-    }
-
-    @Override
-    public View getChildView(int groupPosition, final int childPosition,
-                             boolean isLastChild, View convertView, ViewGroup parent) {
-        final String children = (String) getChild(groupPosition, childPosition);
-        TextView text = null;
-        if (convertView == null) {
-            convertView = inflater.inflate(R.layout.listrow_details, null);
-        }
-
-
-        text = (TextView) convertView.findViewById(R.id.textView1);
-        text.setText(children);
-        convertView.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Toast.makeText(activity, children, Toast.LENGTH_SHORT).show();
-            }
-        });
-        return convertView;
-    }
-
-    @Override
-    public View getGroupView(int groupPosition, boolean isExpanded,
-                             View convertView, ViewGroup parent) {
-        if (convertView == null) {
-            convertView = inflater.inflate(R.layout.listrow_group, null);
-        }
-
-        final GroupAdapter groupAdapter = (GroupAdapter) getGroup(groupPosition);
-        if (groupAdapter.children.size() == 0){
-            convertView.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    Toast.makeText(activity, groupAdapter.string, Toast.LENGTH_SHORT).show();
-                }
-            });
-        }
-        ((TextView) convertView).setText(groupAdapter.string);
-
-        return convertView;
-    }
-
-    @Override
-    public int getChildrenCount(int groupPosition) {
-        return groups.get(groupPosition).children.size();
-    }
-
-    @Override
-    public Object getGroup(int groupPosition) {
-        return groups.get(groupPosition);
-    }
-
-    @Override
-    public int getGroupCount() {
-        return groups.size();
-    }
-
-    @Override
-    public void onGroupCollapsed(int groupPosition) {
-        super.onGroupCollapsed(groupPosition);
-    }
-
-    @Override
-    public void onGroupExpanded(int groupPosition) {
-        super.onGroupExpanded(groupPosition);
-    }
-
-    @Override
-    public long getGroupId(int groupPosition) {
-        return 0;
-    }
-
-    @Override
-    public boolean hasStableIds() {
-        return false;
-    }
-
-    @Override
-    public boolean isChildSelectable(int groupPosition, int childPosition) {
-        return false;
-    }
-} 

+ 3 - 3
src/com/owncloud/android/ui/dialog/UploadSourceDialogFragment.java

@@ -77,12 +77,12 @@ public class UploadSourceDialogFragment extends DialogFragment {
                             UploadFilesActivity.EXTRA_ACCOUNT,
                             ((FileActivity)getActivity()).getAccount()
                     );
-                    //startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);
+                    //startActivityForResult(action, REQUEST_CODE__SELECT_MULTIPLE_FILES);
                     // this flow seems broken;
                     // Actionbarsherlock, maybe?
                     getActivity().startActivityForResult(
                             action,
-                            FileDisplayActivity.ACTION_SELECT_MULTIPLE_FILES
+                            FileDisplayActivity.REQUEST_CODE__SELECT_MULTIPLE_FILES
                     );
 
                 } else if (item == 1) {
@@ -94,7 +94,7 @@ public class UploadSourceDialogFragment extends DialogFragment {
                     }
                     getActivity().startActivityForResult(
                             Intent.createChooser(action, getString(R.string.upload_chooser_title)),
-                            FileDisplayActivity.ACTION_SELECT_CONTENT_FROM_APPS
+                            FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS
                     );
                 }
             }

+ 45 - 0
src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java

@@ -0,0 +1,45 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author LukeOwncloud
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.errorhandling;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+
+public class ErrorShowActivity extends Activity {
+
+	private static final String TAG = ErrorShowActivity.class.getSimpleName();
+
+	TextView mError;
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		Log.e(TAG, "ErrorShowActivity was called. See above for StackTrace.");
+		Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
+		setContentView(R.layout.errorhandling_showerror);
+		mError = (TextView) findViewById(R.id.errorTextView);
+		mError.setText(getIntent().getStringExtra("error"));
+
+	}
+}

+ 85 - 0
src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java

@@ -0,0 +1,85 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author LukeOwncloud
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.errorhandling;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+
+public class ExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler {
+	private final Activity mContext;
+	private final String LINE_SEPARATOR = "\n";
+
+	private static final String TAG = ExceptionHandler.class.getSimpleName();
+
+	public ExceptionHandler(Activity context) {
+		mContext = context;
+	}
+
+	public void uncaughtException(Thread thread, Throwable exception) {
+	    Log.e(TAG, "ExceptionHandler caught UncaughtException", exception);
+		StringWriter stackTrace = new StringWriter();
+		exception.printStackTrace(new PrintWriter(stackTrace));
+		StringBuilder errorReport = new StringBuilder();
+		errorReport.append("************ CAUSE OF ERROR ************\n\n");
+		errorReport.append(stackTrace.toString());
+
+		errorReport.append("\n************ DEVICE INFORMATION ***********\n");
+		errorReport.append("Brand: ");
+		errorReport.append(Build.BRAND);
+		errorReport.append(LINE_SEPARATOR);
+		errorReport.append("Device: ");
+		errorReport.append(Build.DEVICE);
+		errorReport.append(LINE_SEPARATOR);
+		errorReport.append("Model: ");
+		errorReport.append(Build.MODEL);
+		errorReport.append(LINE_SEPARATOR);
+		errorReport.append("Id: ");
+		errorReport.append(Build.ID);
+		errorReport.append(LINE_SEPARATOR);
+		errorReport.append("Product: ");
+		errorReport.append(Build.PRODUCT);
+		errorReport.append(LINE_SEPARATOR);
+		errorReport.append("\n************ FIRMWARE ************\n");
+		errorReport.append("SDK: ");
+		errorReport.append(Build.VERSION.SDK_INT);
+		errorReport.append(LINE_SEPARATOR);
+		errorReport.append("Release: ");
+		errorReport.append(Build.VERSION.RELEASE);
+		errorReport.append(LINE_SEPARATOR);
+		errorReport.append("Incremental: ");
+		errorReport.append(Build.VERSION.INCREMENTAL);
+		errorReport.append(LINE_SEPARATOR);
+
+		Log.e(TAG, "An exception was thrown and handled by ExceptionHandler:", exception);
+
+		Intent intent = new Intent(mContext, ErrorShowActivity.class);
+		intent.putExtra("error", errorReport.toString());
+		mContext.startActivity(intent);
+
+		android.os.Process.killProcess(android.os.Process.myPid());
+		System.exit(1000);
+	}
+
+}

+ 90 - 0
src/com/owncloud/android/ui/fragment/ExpandableListFragment.java

@@ -0,0 +1,90 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   Copyright (C) 2012 Bartek Przybylski
+ *   Copyright (C) 2012-2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.ui.fragment;
+
+import android.os.Bundle;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.OnChildClickListener;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+/**
+ *  Extending ExtendedListFragment. This allows dividing list in groups.
+ */
+public class ExpandableListFragment extends ExtendedListFragment implements OnChildClickListener
+ {
+    protected static final String TAG = ExpandableListFragment.class.getSimpleName();
+    
+    protected ExpandableListView mList;
+    
+    public void setListAdapter(ExpandableListAdapter listAdapter) {
+        mList.setAdapter(listAdapter);
+        mList.invalidate();
+    }
+
+    public ExpandableListView getListView() {
+        return mList;
+    }
+    
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        Log_OC.e(TAG, "onCreateView");
+        
+        View v = inflater.inflate(R.layout.list_fragment_expandable, null);
+        mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view);
+        mList = (ExpandableListView)(v.findViewById(R.id.list_root));
+        mList.setOnChildClickListener(this);
+
+        mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator));
+        mList.setDividerHeight(1);
+
+//        if (savedInstanceState != null) {
+//            int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION);
+//            setReferencePosition(referencePosition);
+//        }
+        
+        // Pull down refresh
+        mRefreshListLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files);
+        mRefreshEmptyLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files_emptyView);
+        
+        onCreateSwipeToRefresh(mRefreshListLayout);
+        onCreateSwipeToRefresh(mRefreshEmptyLayout);
+        
+        mList.setEmptyView(mRefreshEmptyLayout);
+
+        return v;
+    }
+
+    @Override
+    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
+        // to be @overriden
+        Log_OC.e(TAG, "onChildClick(). This method should be overriden!");
+        return false;
+    }
+
+}

+ 11 - 13
src/com/owncloud/android/ui/fragment/ExtendedListFragment.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2012-2015 ownCloud Inc.
+ *   Copyright (C) 2012-2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -20,8 +20,6 @@
 
 package com.owncloud.android.ui.fragment;
 
-import java.util.ArrayList;
-
 import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
@@ -45,33 +43,33 @@ import com.owncloud.android.ui.ExtendedListView;
 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
 import com.owncloud.android.ui.adapter.FileListListAdapter;
 
+import java.util.ArrayList;
+
 import third_parties.in.srain.cube.GridViewWithHeaderAndFooter;
 
-/**
- * TODO extending SherlockListFragment instead of SherlockFragment
- */
 public class ExtendedListFragment extends Fragment
         implements OnItemClickListener, OnEnforceableRefreshListener {
 
-    private static final String TAG = ExtendedListFragment.class.getSimpleName();
+    protected static final String TAG = ExtendedListFragment.class.getSimpleName();
+
+    protected static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION"; 
 
-    private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION";
     private static final String KEY_INDEXES = "INDEXES";
     private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
     private static final String KEY_TOPS = "TOPS";
     private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
     private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE";
 
-    private SwipeRefreshLayout mRefreshListLayout;
+    protected SwipeRefreshLayout mRefreshListLayout;
     private SwipeRefreshLayout mRefreshGridLayout;
-    private SwipeRefreshLayout mRefreshEmptyLayout;
-    private TextView mEmptyListMessage;
+    protected SwipeRefreshLayout mRefreshEmptyLayout;
+    protected TextView mEmptyListMessage;
 
     private FloatingActionsMenu mFabMain;
     private FloatingActionButton mFabUpload;
     private FloatingActionButton mFabMkdir;
     private FloatingActionButton mFabUploadFromApp;
-    
+
     // Save the state of the scroll in browsing
     private ArrayList<Integer> mIndexes;
     private ArrayList<Integer> mFirstPositions;
@@ -386,7 +384,7 @@ public class ExtendedListFragment extends Fragment
         return (mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : "";
     }
 
-    private void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
+    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
         // Colors in animations
         refreshLayout.setColorSchemeResources(R.color.color_accent, R.color.primary,
                 R.color.primary_dark);

+ 3 - 1
src/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -4,7 +4,7 @@
  *   @author Bartek Przybylski
  *   @author David A. Velasco
  *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -549,6 +549,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
                 mContainerActivity.getFileUploaderBinder().
                         addDatatransferProgressListener(mProgressListener, mAccount, getFile());
             }
+        } else {
+            Log_OC.d(TAG, "mProgressListener == null");
         }
     }
 

+ 5 - 5
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -5,7 +5,7 @@
  *   @author masensio
  *   @author David A. Velasco
  *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -229,7 +229,7 @@ public class OCFileListFragment extends ExtendedListFragment
             @Override
             public void onClick(View v) {
                 UploadFilesActivity.startUploadActivityForResult(getActivity(), ((FileActivity)getActivity())
-                        .getAccount(), FileDisplayActivity.ACTION_SELECT_MULTIPLE_FILES);
+                        .getAccount(), FileDisplayActivity.REQUEST_CODE__SELECT_MULTIPLE_FILES);
                 getFabMain().collapse();
                 recordMiniFabClick();
             }
@@ -285,7 +285,7 @@ public class OCFileListFragment extends ExtendedListFragment
                 }
                 getActivity().startActivityForResult(
                         Intent.createChooser(action, getString(R.string.upload_chooser_title)),
-                        FileDisplayActivity.ACTION_SELECT_CONTENT_FROM_APPS
+                        FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS
                 );
                 getFabMain().collapse();
                 recordMiniFabClick();
@@ -586,7 +586,7 @@ public class OCFileListFragment extends ExtendedListFragment
 
                 // Pass mTargetFile that contains info of selected file/folder
                 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
-                getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
+                getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES);
                 return true;
             }
             case R.id.action_favorite_file: {
@@ -602,7 +602,7 @@ public class OCFileListFragment extends ExtendedListFragment
 
                 // Pass mTargetFile that contains info of selected file/folder
                 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
-                getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_COPY_FILES);
+                getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES);
                 return true;
             default:
                 return false;

+ 152 - 0
src/com/owncloud/android/ui/fragment/UploadListFragment.java

@@ -0,0 +1,152 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author LukeOwncloud
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.fragment;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ExpandableListView;
+import android.widget.ListView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.adapter.ExpandableUploadListAdapter;
+
+/**
+ * A Fragment that lists all files and folders in a given LOCAL path.
+ * 
+ */
+public class UploadListFragment extends ExpandableListFragment {
+    static private String TAG = UploadListFragment.class.getSimpleName();
+
+    /**
+     * Reference to the Activity which this fragment is attached to. For
+     * callbacks
+     */
+    private UploadListFragment.ContainerActivity mContainerActivity;
+
+    private ExpandableUploadListAdapter mAdapter;
+
+    /** Is binder ready in the Activity? */
+    private boolean mBinderReady = false;
+
+    public void setBinderReady(boolean ready) {
+        mBinderReady = ready;
+    }
+    public boolean isBinderReady(){
+        return mBinderReady;
+    }
+
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View v = super.onCreateView(inflater, container, savedInstanceState);
+        getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+        setMessageForEmptyList(getString(R.string.upload_list_empty));
+        setOnRefreshListener(this);
+        return v;
+    }
+    
+    @Override
+    public void onRefresh() {
+        // remove the progress circle as soon as pull is triggered, like in the list of files
+        mRefreshEmptyLayout.setRefreshing(false);
+        mRefreshListLayout.setRefreshing(false);
+
+        mAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mContainerActivity = (ContainerActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement "
+                    + UploadListFragment.ContainerActivity.class.getSimpleName());
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        Log_OC.d(TAG, "onActivityCreated() start");
+        super.onActivityCreated(savedInstanceState);
+
+    }
+
+    @Override
+    public void onStart() {
+        Log_OC.d(TAG, "onStart() start");
+        super.onStart();
+        mAdapter = new ExpandableUploadListAdapter((FileActivity)getActivity());
+        setListAdapter(mAdapter);
+    }
+
+    @Override
+    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
+        boolean handled = false;
+        OCUpload OCUpload = (OCUpload) mAdapter.getChild(groupPosition, childPosition);
+        if (OCUpload != null) {
+            // notify the click to container Activity
+            handled = mContainerActivity.onUploadItemClick(OCUpload);
+        } else {
+            Log_OC.w(TAG, "Null object in ListAdapter!!");
+        }
+        return handled;
+    }
+
+    /**
+     * Interface to implement by any Activity that includes some instance of
+     * UploadListFragment
+     * 
+     * @author LukeOwncloud
+     */
+    public interface ContainerActivity {
+
+        /**
+         * Callback method invoked when an upload item is clicked by the user on
+         * the upload list
+         * 
+         * @param file
+         * @return return true if click was handled.
+         */
+        public boolean onUploadItemClick(OCUpload file);
+
+    }
+
+    public void binderReady(){
+        setBinderReady(true);
+
+        if (mAdapter != null) {
+            mAdapter.addBinder();
+        }
+    }
+
+    public void updateUploads(){
+        if (mAdapter != null) {
+            mAdapter.refreshView();
+        }
+    }
+
+}

+ 1 - 1
src/com/owncloud/android/ui/preview/PreviewImageActivity.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015  ownCloud Inc.
+ *   Copyright (C) 2016  ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,

+ 6 - 6
src/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -21,10 +21,6 @@ package com.owncloud.android.ui.preview;
 
 import android.accounts.Account;
 import android.app.Activity;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.MediaMetadataRetriever;
-import android.support.v7.app.AlertDialog;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -32,12 +28,16 @@ import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaMetadataRetriever;
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
 import android.media.MediaPlayer.OnPreparedListener;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.support.v7.app.AlertDialog;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -557,7 +557,7 @@ public class PreviewMediaFragment extends FileFragment implements
         i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying());
         mVideoPreview.pause();
         i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition());
-        startActivityForResult(i, 0);
+        startActivityForResult(i, FileActivity.REQUEST_CODE__LAST_SHARED + 1);
     }
 
     @Override

+ 47 - 0
src/com/owncloud/android/utils/ConnectivityUtils.java

@@ -0,0 +1,47 @@
+/**
+ *  ownCloud Android client application
+ *
+ *  @author David A. Velasco
+ *  Copyright (C) 2016 ownCloud Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2,
+ *  as published by the Free Software Foundation.
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.utils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+public class ConnectivityUtils {
+
+    private final static String TAG = ConnectivityUtils.class.getName();
+
+    public static boolean isAppConnectedViaWiFi(Context context) {
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        boolean result =
+                cm != null && cm.getActiveNetworkInfo() != null
+                && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI
+                && cm.getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED;
+        Log_OC.d(TAG, "is AppConnectedViaWifi returns " + result);
+        return result;
+    }
+
+    public static boolean isAppConnected(Context context) {
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
+    }
+
+}

+ 33 - 3
src/com/owncloud/android/utils/FileStorageUtils.java

@@ -2,7 +2,7 @@
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
- *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2016 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -53,23 +53,53 @@ public class FileStorageUtils {
     public static Integer mSortOrder = SORT_NAME;
     public static Boolean mSortAscending = true;
 
+    /**
+     * Takes a full path to owncloud file and removes beginning which is path to ownload data folder.
+     * If fullPath does not start with that folder, fullPath is returned as is.
+     */
+    public static final String removeDataFolderPath(String fullPath) {
+        File sdCard = Environment.getExternalStorageDirectory();
+        String dataFolderPath = sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/";
+        if(fullPath.indexOf(dataFolderPath) == 0) {
+            return fullPath.substring(dataFolderPath.length());
+        }
+        return fullPath;
+    }
     
+    /**
+     * Get local owncloud storage path for accountName.
+     */
     public static final String getSavePath(String accountName) {
         File sdCard = Environment.getExternalStorageDirectory();
         return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + Uri.encode(accountName, "@");
-        // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
+        // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names,
+        // that can be in the accountName since 0.1.190B
     }
 
+    /**
+     * Get local path where OCFile file is to be stored after upload. That is,
+     * corresponding local path (in local owncloud storage) to remote uploaded
+     * file.
+     */
     public static final String getDefaultSavePathFor(String accountName, OCFile file) {
         return getSavePath(accountName) + file.getRemotePath();
     }
 
+    /**
+     * Get absolute path to tmp folder inside datafolder in sd-card for given accountName.
+     */
     public static final String getTemporalPath(String accountName) {
         File sdCard = Environment.getExternalStorageDirectory();
         return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/tmp/" + Uri.encode(accountName, "@");
-            // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
+            // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names,
+            // that can be in the accountName since 0.1.190B
     }
 
+    /**
+     * Optimistic number of bytes available on sd-card. accountName is ignored.
+     * @param accountName not used. can thus be null.
+     * @return Optimistic number of available bytes (can be less)
+     */
     @SuppressLint("NewApi")
     public static final long getUsableSpace(String accountName) {
         File savePath = Environment.getExternalStorageDirectory();

+ 34 - 0
src/com/owncloud/android/utils/MimetypeIconUtil.java

@@ -1,3 +1,22 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
 package com.owncloud.android.utils;
 
 import android.webkit.MimeTypeMap;
@@ -83,6 +102,21 @@ public class MimetypeIconUtil {
         return R.drawable.ic_menu_archive;
     }
 
+    /**
+     * Returns a single MIME type of all the possible, by inspection of the file extension, and taking
+     * into account the MIME types known by ownCloud first.
+     *
+     * @param filename      Name of file
+     * @return              A single MIME type, "application/octet-stream" for unknown file extensions.
+     */
+    public static String getBestMimeTypeByFilename(String filename) {
+        List<String> candidates = determineMimeTypesByFilename(filename);
+        if (candidates == null || candidates.size() < 1) {
+            return "application/octet-stream";
+        }
+        return candidates.get(0);
+    }
+
     /**
      * determines the icon based on the mime type.
      *

+ 53 - 0
src/com/owncloud/android/utils/UploadUtils.java

@@ -0,0 +1,53 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author LukeOwncloud
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo.State;
+import android.os.BatteryManager;
+
+
+public class UploadUtils {
+
+    public static boolean isCharging(Context context) {
+        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        Intent batteryStatus = context.registerReceiver(null, ifilter);
+
+        int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+        return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
+    }
+
+    public static boolean isOnline(Context context) {
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
+    }
+
+    public static boolean isConnectedViaWiFi(Context context) {
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        return cm != null && cm.getActiveNetworkInfo() != null
+                && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI
+                && cm.getActiveNetworkInfo().getState() == State.CONNECTED;
+    }
+
+}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません